fix: ELECTRON-1022 (Fix issues with notification and origin) (#673)

* ELECTRON-1022 - Fix issues with notification and origin

* Typescript - Add Notification APIs to window.ssf object

* Typescript - Fix unit test

* Typescript - Fix notification bg color issue
This commit is contained in:
Kiran Niranjan 2019-06-10 22:14:44 +05:30 committed by Vishwas Shashidhar
parent 10d3d49c44
commit 1e953b0092
12 changed files with 160 additions and 13 deletions

View File

@ -215,7 +215,27 @@
method: 'notification',
};
window.postMessage({ method: 'notification', data: notf }, '*');
if (window.ssf) {
const ssfNotificationHandler = new window.ssf.Notification(notf.title, notf);
const onclick = (event) => {
event.target.close();
alert('notification clicked: ' + event.target.data.title);
};
ssfNotificationHandler.addEventListener('click', onclick);
const onclose = () => {
alert('notification closed');
};
ssfNotificationHandler.addEventListener('close', onclose);
const onerror = (event) => {
alert('error=' + event.result);
};
ssfNotificationHandler.addEventListener('error', onerror);
} else {
window.postMessage({ method: 'notification', data: notf }, '*');
}
});
/**

View File

@ -62,6 +62,7 @@ jest.mock('../src/common/logger', () => {
return {
logger: {
setLoggerWindow: jest.fn(),
error: jest.fn(),
},
};
});

View File

@ -126,7 +126,8 @@ export const handleChildWindow = (webContents: WebContents): void => {
const childWebContents: WebContents = newWinOptions.webContents;
// Event needed to hide native menu bar
childWebContents.once('did-start-loading', () => {
const browserWin = BrowserWindow.fromWebContents(childWebContents);
const browserWin = BrowserWindow.fromWebContents(childWebContents) as ICustomBrowserWindow;
browserWin.origin = config.getGlobalConfigFields([ 'url' ]).url;
if (isWindowsOS && browserWin && !browserWin.isDestroyed()) {
browserWin.setMenuBarVisibility(false);
}

View File

@ -26,6 +26,7 @@ import {
*/
ipcMain.on(apiName.symphonyApi, (event: Electron.Event, arg: IApiArgs) => {
if (!isValidWindow(BrowserWindow.fromWebContents(event.sender))) {
logger.error(`main-api-handler: invalid window try to perform action, ignoring action`, arg.cmd);
return;
}

View File

@ -36,6 +36,7 @@ interface ICustomBrowserWindowConstructorOpts extends Electron.BrowserWindowCons
export interface ICustomBrowserWindow extends Electron.BrowserWindow {
winName: string;
notificationObj?: object;
origin?: string;
}
// Default window width & height
@ -234,6 +235,8 @@ export class WindowHandler {
this.mainWindow.loadURL(this.url);
// check for build expiry in case of test builds
this.checkExpiry(this.mainWindow);
// need this for postMessage origin
this.mainWindow.origin = this.globalConfig.url;
this.mainWindow.webContents.on('did-finish-load', async () => {
logger.info(`window-handler: main window web contents finished loading!`);
// early exit if the window has already been destroyed

View File

@ -223,7 +223,7 @@ export const isValidWindow = (browserWin: Electron.BrowserWindow): boolean => {
}
if (!result) {
logger.warn('window-utils: invalid window try to perform action, ignoring action');
logger.warn(`window-utils: invalid window try to perform action, ignoring action`);
}
return result;

View File

@ -112,6 +112,11 @@ export interface INotificationData {
displayTime: number;
}
export enum NotificationActions {
notificationClicked = 'notification-clicked',
notificationClosed = 'notification-closed',
}
/**
* Screen sharing Indicator
*/

View File

@ -1,8 +1,7 @@
import { ipcRenderer, remote } from 'electron';
import { remote } from 'electron';
import {
apiCmds,
apiName,
IBoundsChange,
ILogMsg, INotificationData,
IScreenSharingIndicator, IScreenSharingIndicatorOptions,
@ -60,7 +59,9 @@ export class AppBridge {
constructor() {
// starts with corporate pod and
// will be updated with the global config url
this.origin = ipcRenderer.sendSync(apiName.symphonyApi, { cmd: apiCmds.getConfigUrl });
const currentWindow = remote.getCurrentWindow();
// @ts-ignore
this.origin = currentWindow.origin || '';
if (ssInstance && typeof ssInstance.setBroadcastMessage === 'function') {
ssInstance.setBroadcastMessage(this.broadcastMessage);
}

View File

@ -11,6 +11,7 @@ interface IState {
company: string;
body: string;
image: string;
icon: string;
id: number;
color: string;
}
@ -31,6 +32,7 @@ export default class NotificationComp extends React.Component<{}, IState> {
company: 'Symphony',
body: '',
image: '',
icon: '',
id: 0,
color: '',
};
@ -49,11 +51,12 @@ export default class NotificationComp extends React.Component<{}, IState> {
* Renders the custom title bar
*/
public render(): JSX.Element {
const { title, company, body, image, id, color } = this.state;
const isLightTheme = color ? color.match(whiteColorRegExp) : true;
const { title, company, body, image, icon, id, color } = this.state;
const colorHex = color ? '#' + color : undefined;
const isLightTheme = colorHex ? colorHex.match(whiteColorRegExp) : true;
const theme = classNames({ light: isLightTheme, dark: !isLightTheme });
const bgColor = { backgroundColor: color || '#ffffff' };
const bgColor = { backgroundColor: colorHex || '#ffffff' };
return (
<div className='container' style={bgColor} onClick={this.eventHandlers.onClick(id)}>
@ -62,11 +65,11 @@ export default class NotificationComp extends React.Component<{}, IState> {
</div>
<div className='header'>
<span className={`title ${theme}`}>{title}</span>
<span className='company' style={{color: color || '#4a4a4a'}}>{company}</span>
<span className='company' style={{color: colorHex || '#4a4a4a'}}>{company}</span>
<span className={`message ${theme}`}>{body}</span>
</div>
<div className='user-profile-pic-container'>
<img src={image} className='user-profile-pic' alt='user profile picture'/>
<img src={image || icon} className='user-profile-pic' alt='user profile picture'/>
</div>
<div className='close' title={i18n.t('Close')()} onClick={this.eventHandlers.onClose(id)}>
<svg fill='#000000' height='16' viewBox='0 0 24 24' width='16' xmlns='http://www.w3.org/2000/svg'>

View File

@ -0,0 +1,89 @@
import { remote } from 'electron';
import { INotificationData, NotificationActions } from '../common/api-interface';
const notification = remote.require('../renderer/notification').notification;
let latestID = 0;
/**
* Implementation for notifications interface,
* this class is to mock the Window.Notification interface
*/
export default class SSFNotificationHandler {
public _data: INotificationData;
private readonly id: number;
private readonly eventHandlers = {
onClick: (event: NotificationActions, _data: INotificationData) => this.notificationClicked(event),
};
private notificationClickCallback: (({ target }) => {}) | undefined;
private notificationCloseCallback: (({ target }) => {}) | undefined;
constructor(title, options) {
this.id = latestID;
latestID++;
this._data = { ...title, ...options };
notification.showNotification(this._data, this.eventHandlers.onClick);
}
/**
* Closes notification
*/
public close(): void {
notification.hideNotification(this.id);
}
/**
* Always allow showing notifications.
* @return {string} 'granted'
*/
static get permission(): string {
return 'granted';
}
/**
* Returns data object passed in via constructor options
*/
get data(): INotificationData {
return this._data;
}
/**
* Adds event listeners for 'click', 'close', 'show', 'error' events
*
* @param {String} event event to listen for
* @param {func} cb callback invoked when event occurs
*/
public addEventListener(event: string, cb: () => {}): void {
if (event && typeof cb === 'function') {
switch (event) {
case 'click':
this.notificationClickCallback = cb;
break;
case 'close':
this.notificationCloseCallback = cb;
break;
}
}
}
/**
* Handles the callback based on the event name
*
* @param event {NotificationActions}
*/
private notificationClicked(event: NotificationActions): void {
switch (event) {
case NotificationActions.notificationClicked:
if (this.notificationClickCallback && typeof this.notificationClickCallback === 'function') {
this.notificationClickCallback({ target: this });
}
break;
case NotificationActions.notificationClosed:
if (this.notificationCloseCallback && typeof this.notificationCloseCallback === 'function') {
this.notificationCloseCallback({ target: this });
}
}
}
}

View File

@ -3,7 +3,7 @@ import { app, ipcMain } from 'electron';
import { config } from '../app/config-handler';
import { createComponentWindow, windowExists } from '../app/window-utils';
import { AnimationQueue } from '../common/animation-queue';
import { apiName, INotificationData } from '../common/api-interface';
import { apiName, INotificationData, NotificationActions } from '../common/api-interface';
import { logger } from '../common/logger';
import NotificationHandler from './notification-handler';
@ -60,6 +60,8 @@ class Notification extends NotificationHandler {
constructor(opts) {
super(opts);
ipcMain.on('close-notification', (_event, windowId) => {
// removes the event listeners on the client side
this.notificationClosed(windowId);
this.hideNotification(windowId);
});
@ -160,6 +162,7 @@ class Notification extends NotificationHandler {
*/
public setNotificationContent(notificationWindow: ICustomBrowserWindow, data: INotificationData): void {
notificationWindow.clientId = data.id;
notificationWindow.notificationData = data;
const displayTime = data.displayTime ? data.displayTime : notificationSettings.displayTime;
let timeoutId;
@ -218,12 +221,29 @@ class Notification extends NotificationHandler {
const data = browserWindow.notificationData;
const callback = this.notificationCallbacks[ clientId ];
if (typeof callback === 'function') {
this.notificationCallbacks[ clientId ]('notification-clicked', data);
callback(NotificationActions.notificationClicked, data);
}
this.hideNotification(clientId);
}
}
/**
* Handles notification close which updates client
* to remove event listeners
*
* @param clientId {number}
*/
public notificationClosed(clientId): void {
const browserWindow = this.getNotificationWindow(clientId);
if (browserWindow && windowExists(browserWindow) && browserWindow.notificationData) {
const data = browserWindow.notificationData;
const callback = this.notificationCallbacks[ clientId ];
if (typeof callback === 'function') {
callback(NotificationActions.notificationClosed, data);
}
}
}
/**
* Returns the notification based on the client id
*

View File

@ -18,6 +18,7 @@ import {
import { i18n, LocaleType } from '../common/i18n-preload';
import { throttle } from '../common/utils';
import { getSource } from './desktop-capturer';
import SSFNotificationHandler from './notification-ssf-hendler';
import { ScreenSnippetBcHandler } from './screen-snippet-bc-handler';
let isAltKey: boolean = false;
@ -127,6 +128,8 @@ export class SSFApi {
public SearchUtils: any = swiftSearchUtils; // tslint:disable-line
public Notification = SSFNotificationHandler; // tslint:disable-line
/**
* Implements equivalent of desktopCapturer.getSources - that works in
* a sandboxed renderer process.