Skip to content

Commit

Permalink
Merge pull request getstation#376 from viktor44/feature/notifications
Browse files Browse the repository at this point in the history
Gmail Notifications
  • Loading branch information
viktor44 authored Jul 12, 2024
2 parents 99fe06d + 754cb27 commit 8d377c1
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 8 deletions.
Binary file modified .yarn/install-state.gz
Binary file not shown.
5 changes: 5 additions & 0 deletions packages/app/manifests/definitions/140.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@
"theme_color": "#0070C9",
"scope": "https://outlook.live.com",
"bx_legacy_service_id": "outlook",
"extended_scopes": [
"https://*.live.com",
"https://*.office.com",
"https://*.microsoft.com"
],
"recommendedPosition": "12"
}
4 changes: 2 additions & 2 deletions packages/app/src/app-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,9 @@ export class BrowserXAppWorker {
this.mainWindowManager.on('leave-full-screen', () => this.dispatch(setFullScreenState(false)));
this.mainWindowManager.on('swipe-left', () => this.dispatch(executeWebviewMethodForCurrentTab('go-back')));
this.mainWindowManager.on('swipe-right', () => this.dispatch(executeWebviewMethodForCurrentTab('go-forward')));
this.mainWindowManager.on('new-notification', (notificationId: string, props: NotificationProps, options: NotificationOptions) =>
this.mainWindowManager.on('new-notification', (notificationId: string, props: NotificationProps, options: NotificationOptions) => {
this.dispatch(notificationCenter.newNotification(undefined, undefined, notificationId, props, options))
);
});
}

private initSDK() {
Expand Down
5 changes: 4 additions & 1 deletion packages/app/src/applications/Application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,11 @@ class ApplicationImpl extends React.PureComponent {
}

async handleDomReady() {
const js = await injectJS(this.props.legacyServiceId);

const webviewInjectJS = require(`!!raw-loader!../static/preload/webview-inject.js`).default
this.webView.view.executeJavaScript(webviewInjectJS); //`(function(){\n${bxNotifJS}\n})()`);

const js = await injectJS(this.props.legacyServiceId);
if (js && this.webView && this.webView.view) {
this.webView.view.executeJavaScript(js);
// const webContents = remote.webContents.fromId(this.webView.view.getWebContentsId());
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/notification-center/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ function* interceptNotificationEventsFromWebContents({ webcontentsId, tabId }: {
step: RequestForApplicationNotificationsStep.ASK,
}));
}

yield put(newNotification(applicationId, tabId, props.id, props));
});

Expand Down
1 change: 1 addition & 0 deletions packages/app/src/notification-center/webview-preload.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* tslint:disable:function-name */
//import { ipcRenderer } from 'electron';
import * as shortid from 'shortid';
import { EventTarget } from 'event-target-shim';

Expand Down
6 changes: 5 additions & 1 deletion packages/app/src/services/services/os-notification/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Notification, webContents } from 'electron';
import log from 'electron-log';

import { ServiceSubscription } from '../../lib/class';
import { RPC } from '../../lib/types';
Expand All @@ -8,6 +9,9 @@ import { getDoNotDisturb, asNativeImage } from './utils';
export class OSNotificationServiceImpl extends OSNotificationService implements RPC.Interface<OSNotificationService> {

async show(param: IOSNotificationServiceShowParam) {

log.info(`>>> OSNotificationServiceImpl.show ${JSON.stringify(param)}`);

const notificationOptions: Electron.NotificationConstructorOptions = {
title: param.title,
actions: [],
Expand All @@ -16,7 +20,6 @@ export class OSNotificationServiceImpl extends OSNotificationService implements
};

if (param.imageURL) {

notificationOptions.icon = await asNativeImage(param.imageURL);
}
if (param.body) {
Expand All @@ -25,6 +28,7 @@ export class OSNotificationServiceImpl extends OSNotificationService implements

const notification = new Notification(notificationOptions);
notification.show();

return new OSNotificationImpl(notification);
}

Expand Down
13 changes: 10 additions & 3 deletions packages/app/src/services/services/os-notification/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import * as memoize from 'memoizee';

export const asNativeImage = memoize((url: string): Promise<Electron.NativeImage> => {
return new Promise((resolve, reject) => {
if (url.indexOf('data:') === 0) {
if (url.startsWith('data:')) {
resolve(nativeImage.createFromDataURL(url));
return;
}

if (url.indexOf('http:') === 0 || url.indexOf('https:') === 0) {
if (url.startsWith('http:') || url.startsWith('https:')) {
fetch(url)
.then((res: any) => res.buffer())
.then((buffer: Buffer) => {
Expand All @@ -19,9 +19,16 @@ export const asNativeImage = memoize((url: string): Promise<Electron.NativeImage
return;
}

if (url.startsWith('blob:http')) {
//FIXME: implement blob:https:// ( Telegram notifications )
resolve(nativeImage.createEmpty());
return;
}

try {
resolve(nativeImage.createFromPath(url));
} catch (e) {
}
catch (e) {
reject(new Error(`Unknow schema for ${url}`));
}
});
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export const enhanceSession = (session: Session) => {

if (responseHeaders) {
delete responseHeaders['content-security-policy']; //vk: causes "This document requires 'TrustedHTML' assignment." error. Does not allow us to modify page CSS.
delete responseHeaders['content-security-policy-report-only'];
}

callback({
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/static/preload/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ require('./window-open');
})();

require('../../plugins/webview-preload');
require('../../notification-center/webview-preload');
//require('../../notification-center/webview-preload');
require('../../dialogs/webview-preload');
require('../../ui/webview-preload');
require('./autologin');
Expand Down
216 changes: 216 additions & 0 deletions packages/app/src/static/preload/webview-inject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@

console.log('>>>>>> WebView inject start');

// nanoid (copy from https://github.com/ai/nanoid/blob/main/non-secure/index.js)
const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';

const nanoid = (size = 21) => {
let id = ''
// A compact alternative for `for (var i = 0; i < step; i++)`.
let i = size
while (i--) {
// `| 0` is more compact and faster than `Math.floor()`.
id += urlAlphabet[(Math.random() * 64) | 0]
}
return id
}
// nanoid

////////////////////////////

const onReady = (documentObject) => {
const isReady = (x) => x === 'complete';
return new Promise((resolve) => {
if (isReady(documentObject.readyState)) {
resolve();
} else {
documentObject.addEventListener('readystatechange', (e) => {
if (isReady(e.target.readyState)) {
resolve();
}
}, false);
}
});
};

const recursiveOverride = (document, window, action) => {
const recursiveOverrideInt = (windowObject, documentObject) => {
action(windowObject);
onReady(document).then(() => {
const iframes = documentObject.getElementsByTagName('iframe');
for (const iframe of iframes) {
try {
recursiveOverrideInt(iframe.contentWindow, iframe.contentDocument);
}
catch (e) {
// contentDocument can be inaccessible depending on CORS
// we just ignores it because we can't do anything about it
}
}
})
};

recursiveOverrideInt(window, document);
};

// Notifications

const GRANTED = 'granted';

const getDefaultProperties = (title) => ({
actions: [],
badge: '',
body: '',
data: null,
dir: 'auto',
lang: '',
tag: '',
icon: '',
image: '',
requireInteraction: false,
silent: false,
timestamp: (new Date()).getTime(),
title,
vibrate: [],
});

class BxNotification {
constructor(title, options = {}) {
// Chrome, Safari, etc. does not throw when title is empty string
if (!title && title !== '') {
throw new Error('Title is required');
}
this.id = `notif/${nanoid()}`;

// default properties
const properties = Object.assign({ }, getDefaultProperties(title), options || {});

Object.keys(properties).forEach(key => {
Object.defineProperty(this, key, {
value: properties[key],
writable: false,
});
});

this._registerIPC();

console.log(
'>>>>>> New notification 1', (new Date()).toLocaleTimeString(), JSON.stringify({
id: this.id,
timestamp: this.timestamp,
title: this.title,
body: this.body,
icon: this.icon,
})
);

let fixedIconUrl = this.icon;
if (typeof fixedIconUrl === 'string') {
//vk: I have no idea why but...
if (fixedIconUrl.startsWith('//')) { // Gmail
fixedIconUrl = 'https:' + this.icon;
}
}

window.bxApi.notificationCenter.sendNotification(this.id, {
timestamp: this.timestamp,
title: this.title,
body: this.body,
icon: fixedIconUrl,
});
}

_registerIPC() {
window.bxApi.notificationCenter.addNotificationClickListener(this._handleNotificationClickIPC);
}

_unregisterIPC() {
try {
console.log('unregisterIPC');
window.bxApi.notificationCenter.removeNotificationClickListener(this._handleNotificationClickIPC);
} catch (error) {
console.log('ERROR unregisterIPC', error);
}
}

_handleNotificationClickIPC(_e /*: Event */, notificationId /*: string */) {
if (this.id !== notificationId) {
return;
}
this.dispatchEvent(new MouseEvent('click'));
this._unregisterIPC();
}

close() {
console.log('close');
try {
window.bxApi.notificationCenter.closeNotification(this.id);
} catch (error) {
console.log('ERROR close', error);
}
}

get onclick() {
console.log('get onclick');
return null;
}
set onclick(value) {
console.log('set onclick');
}

get onclose() {
console.log('get onclose');
return null;
}
set onclose(value) {
console.log('set onclose');
}

get onerror() {
console.log('get onerror');
return null;
}
set onerror(value) {
console.log('set onerror');
}

get onshow() {
console.log('get onshow');
return null;
}
set onshow(value) {
console.log('set onshow');
}

dispatchEvent(event) {
console.log('dispatchEvent', event);
}

addEventListener(type, listener, options) {
console.log('addEventListener', type);
}

removeEventListener(type, listener, options) {
console.log('removeEventListener', type);
}

requestPermission(deprecatedCallback) {
const request = Promise.resolve(GRANTED);
if (deprecatedCallback) {
request.then(deprecatedCallback);
}
return request;
}
}

const overrideNotifications = () => {
BxNotification.permission = GRANTED;

// window.Notification = BxNotification;
recursiveOverride(document, window, (windowObject) => { windowObject.Notification = BxNotification });

console.log('>>>>>> Notification override. Done');
}

overrideNotifications();

0 comments on commit 8d377c1

Please sign in to comment.