diff --git a/spec/openfinHandler.spec.ts b/spec/openfinHandler.spec.ts
index 3e80cfee9..b8a014b3a 100644
--- a/spec/openfinHandler.spec.ts
+++ b/spec/openfinHandler.spec.ts
@@ -35,10 +35,13 @@ jest.mock('../src/app/config-handler', () => ({
},
}));
+const mockSend = jest.fn();
jest.mock('../src/app/window-handler', () => {
return {
windowHandler: {
- getMainWebContents: jest.fn(),
+ getMainWebContents: jest.fn(() => ({
+ send: mockSend,
+ })),
},
};
});
@@ -163,17 +166,23 @@ describe('Openfin', () => {
expect(fireIntentSpy).toHaveBeenCalledTimes(1);
});
- it('should register an intent handler', async () => {
+ it('should register an intent handler and trigger intent handler on intent received', async () => {
+ const intentName = 'my-intent';
const connectSyncMock = await connectMock.Interop.connectSync();
- const intentHandlerRegistrationSpy = jest.spyOn(
- connectSyncMock,
- 'registerIntentHandler',
- );
await openfinHandler.connect();
- await openfinHandler.registerIntentHandler('my-intent');
+ await openfinHandler.registerIntentHandler(intentName);
+
+ expect(connectSyncMock.registerIntentHandler).toHaveBeenCalledTimes(1);
+
+ const intentHandler =
+ connectSyncMock.registerIntentHandler.mock.calls[0][0];
+ intentHandler('intent-data');
- expect(intentHandlerRegistrationSpy).toHaveBeenCalledTimes(1);
+ expect(mockSend).toHaveBeenCalledWith(
+ 'openfin-intent-received',
+ 'intent-data',
+ );
});
it('should join a context group', async () => {
@@ -253,4 +262,25 @@ describe('Openfin', () => {
expect(removeFromContextGroupSpy).toHaveBeenCalledTimes(1);
});
+
+ it('should trigger disconnection handler when disconnected', async () => {
+ const disconnectionEvent = {
+ type: 'type',
+ topic: 'topic',
+ brokerName: 'broken-name',
+ };
+
+ const connectSyncMock = await connectMock.Interop.connectSync();
+
+ await openfinHandler.connect();
+
+ const disconnectionHandler =
+ connectSyncMock.onDisconnection.mock.calls[0][0];
+ disconnectionHandler(disconnectionEvent);
+
+ expect(mockSend).toHaveBeenCalledWith(
+ 'openfin-disconnection',
+ disconnectionEvent,
+ );
+ });
});
diff --git a/src/app/openfin-handler.ts b/src/app/openfin-handler.ts
index 97417e7ce..15add8139 100644
--- a/src/app/openfin-handler.ts
+++ b/src/app/openfin-handler.ts
@@ -1,6 +1,6 @@
///
-import { connect } from '@openfin/node-adapter';
+import { connect, NodeFin } from '@openfin/node-adapter';
import { randomUUID, UUID } from 'crypto';
import { logger } from '../common/openfin-logger';
import { config, IConfig } from './config-handler';
@@ -13,33 +13,35 @@ export class OpenfinHandler {
private interopClient: OpenFin.InteropClient | undefined;
private intentHandlerSubscriptions: Map = new Map();
private isConnected: boolean = false;
- private fin: any;
+ private fin: NodeFin | undefined;
/**
* Connection to interop broker
*/
public async connect() {
+ this.reset();
+
const { openfin }: IConfig = config.getConfigFields(['openfin']);
if (!openfin) {
logger.error('openfin-handler: missing openfin params to connect.');
return { isConnected: false };
}
- logger.info('openfin-handler: connecting');
+ logger.info('openfin-handler: connecting...');
+
const parsedTimeoutValue = parseInt(openfin.connectionTimeout, 10);
const timeoutValue = isNaN(parsedTimeoutValue)
? TIMEOUT_THRESHOLD
: parsedTimeoutValue;
const connectionTimeoutPromise = new Promise((_, reject) =>
- setTimeout(() => {
- logger.error(
- `openfin-handler: Connection timeout after ${
- timeoutValue / 1000
- } seconds`,
- );
- return reject(
- new Error(`Connection timeout after ${timeoutValue / 1000} seconds`),
- );
- }, timeoutValue),
+ setTimeout(
+ () =>
+ reject(
+ new Error(
+ `connection timeout after ${timeoutValue / 1000} seconds`,
+ ),
+ ),
+ timeoutValue,
+ ),
);
const connectionPromise = (async () => {
@@ -49,28 +51,21 @@ export class OpenfinHandler {
uuid: openfin.uuid,
licenseKey: openfin.licenseKey,
runtime: {
- version: openfin.runtimeVersion,
+ version: openfin.runtimeVersion || 'stable',
},
});
}
logger.info(
- 'openfin-handler: connection established to Openfin runtime',
+ 'openfin-handler: connection established to openfin runtime',
);
logger.info(
- `openfin-handler: starting connection to interop broker using channel ${openfin.channelName}`,
+ `openfin-handler: starting connection to interop broker using channel ${openfin.channelName}...`,
);
this.interopClient = this.fin.Interop.connectSync(openfin.channelName);
this.isConnected = true;
-
- this.interopClient?.onDisconnection((event) => {
- const { brokerName } = event;
- logger.warn(
- `openfin-handler: Disconnected from Interop Broker ${brokerName}`,
- );
- this.clearSubscriptions();
- });
+ this.interopClient?.onDisconnection(this.disconnectionHandler);
return true;
} catch (error) {
@@ -87,7 +82,7 @@ export class OpenfinHandler {
return { isConnected };
} catch (error) {
logger.error(
- 'openfin-handler: error or timeout while connecting: ',
+ 'openfin-handler: error or timeout while connecting:',
error,
);
return { isConnected: false };
@@ -105,13 +100,13 @@ export class OpenfinHandler {
* Adds an intent handler for incoming intents
*/
public async registerIntentHandler(intentName: string): Promise {
- const unsubscriptionCallback =
- await this.interopClient?.registerIntentHandler(
- this.intentHandler,
- intentName,
- );
+ const subscription = await this.interopClient?.registerIntentHandler(
+ this.intentHandler,
+ intentName,
+ );
+
const uuid = randomUUID();
- this.intentHandlerSubscriptions.set(uuid, unsubscriptionCallback);
+ this.intentHandlerSubscriptions.set(uuid, subscription);
return uuid;
}
@@ -119,9 +114,9 @@ export class OpenfinHandler {
* Removes an intent handler for a given intent
*/
public async unregisterIntentHandler(uuid: UUID) {
- const unsubscriptionCallback = this.intentHandlerSubscriptions.get(uuid);
+ const subscription = this.intentHandlerSubscriptions.get(uuid);
- const response = await unsubscriptionCallback.unsubscribe();
+ const response = await subscription.unsubscribe();
this.intentHandlerSubscriptions.delete(uuid);
return response;
}
@@ -155,23 +150,21 @@ export class OpenfinHandler {
}
/**
- * Clears all openfin subscriptions
+ * Reset connection status, interop client, and existing subscriptions (if any).
*/
- public clearSubscriptions() {
+ public reset() {
this.isConnected = false;
this.interopClient = undefined;
- this.intentHandlerSubscriptions.forEach(
- (unsubscriptionCallback, intent) => {
- try {
- unsubscriptionCallback.unsubscribe();
- } catch (e) {
- logger.error(
- `openfin-handler: Error unsubscribing from intent ${intent}:`,
- e,
- );
- }
- },
- );
+ this.intentHandlerSubscriptions.forEach((subscriptions, intent) => {
+ try {
+ subscriptions.unsubscribe();
+ } catch (e) {
+ logger.error(
+ `openfin-handler: error unsubscribing from intent ${intent}:`,
+ e,
+ );
+ }
+ });
this.intentHandlerSubscriptions.clear();
}
@@ -227,10 +220,25 @@ export class OpenfinHandler {
};
}
+ /**
+ * Forward intent to main window when intent is received
+ */
private intentHandler = (intent: any) => {
logger.info('openfin-handler: intent received - ', intent);
- const mainWebContents = windowHandler.getMainWebContents();
- mainWebContents?.send('intent-received', intent);
+ windowHandler.getMainWebContents()?.send('openfin-intent-received', intent);
+ };
+
+ /**
+ * Forward disconnection event to main window when disconnected from Interop Broker
+ */
+ private disconnectionHandler = (
+ event: OpenFin.InteropBrokerDisconnectionEvent,
+ ) => {
+ logger.warn(
+ `openfin-handler: disconnected from interop broker ${event.brokerName}`,
+ );
+ windowHandler.getMainWebContents()?.send('openfin-disconnection', event);
+ this.reset();
};
}
diff --git a/src/app/window-utils.ts b/src/app/window-utils.ts
index 6f239e4f8..739c64580 100644
--- a/src/app/window-utils.ts
+++ b/src/app/window-utils.ts
@@ -476,7 +476,7 @@ export const sanitize = (windowName: string): void => {
// reset the badge count whenever an user refreshes the electron client
showBadgeCount(0);
// Clear all openfin subscriptions
- openfinHandler.clearSubscriptions();
+ openfinHandler.reset();
// Terminates the screen snippet process and screen share indicator frame on reload
if (!isMac || !isLinux) {
logger.info(
diff --git a/src/renderer/ssf-api.ts b/src/renderer/ssf-api.ts
index 6166461c0..ee252a9cb 100644
--- a/src/renderer/ssf-api.ts
+++ b/src/renderer/ssf-api.ts
@@ -67,14 +67,15 @@ export interface ILocalObject {
c9MessageCallback?: (status: IShellStatus) => void;
updateMyPresenceCallback?: (presence: EPresenceStatusCategory) => void;
phoneNumberCallback?: (arg: string) => void;
- intentsCallbacks: Map>;
+ openfinIntentCallbacks: Map>; // by intent name, then by callback id
+ openfinDisconnectionCallback?: (event?: any) => void;
writeImageToClipboard?: (blob: string) => void;
getHelpInfo?: () => Promise;
}
const local: ILocalObject = {
ipcRenderer,
- intentsCallbacks: new Map(),
+ openfinIntentCallbacks: new Map(),
};
const notificationActionCallbacks = new Map<
@@ -958,13 +959,17 @@ export class SSFApi {
/**
* Openfin Interop client initialization
*/
- public async openfinInit() {
+ public async openfinInit(options?: {
+ onDisconnection?: (event: any) => void;
+ }): Promise {
const connectionStatus = await local.ipcRenderer.invoke(
apiName.symphonyApi,
- {
- cmd: apiCmds.openfinConnect,
- },
+ { cmd: apiCmds.openfinConnect },
);
+
+ local.openfinIntentCallbacks.clear();
+ local.openfinDisconnectionCallback = options?.onDisconnection;
+
return connectionStatus;
}
@@ -1046,12 +1051,12 @@ export class SSFApi {
cmd: apiCmds.openfinRegisterIntentHandler,
intentName,
});
- if (local.intentsCallbacks.has(intentName)) {
- local.intentsCallbacks.get(intentName)?.set(uuid, intentHandler);
+ if (local.openfinIntentCallbacks.has(intentName)) {
+ local.openfinIntentCallbacks.get(intentName)?.set(uuid, intentHandler);
} else {
const innerMap = new Map();
innerMap.set(uuid, intentHandler);
- local.intentsCallbacks.set(intentName, innerMap);
+ local.openfinIntentCallbacks.set(intentName, innerMap);
}
return uuid;
}
@@ -1061,7 +1066,7 @@ export class SSFApi {
* @param UUID
*/
public async openfinUnregisterIntentHandler(callbackId: UUID): Promise {
- for (const innerMap of local.intentsCallbacks.values()) {
+ for (const innerMap of local.openfinIntentCallbacks.values()) {
if (innerMap.has(callbackId)) {
innerMap.delete(callbackId);
break;
@@ -1460,16 +1465,23 @@ local.ipcRenderer.on(
},
);
-local.ipcRenderer.on('intent-received', (_event: Event, intent: any) => {
- if (
- typeof intent.name === 'string' &&
- local.intentsCallbacks.has(intent.name)
- ) {
- const uuidCallbacks = local.intentsCallbacks.get(intent.name);
- uuidCallbacks?.forEach((callbacks, _uuid) => {
- callbacks(intent.context);
- });
- }
+local.ipcRenderer.on(
+ 'openfin-intent-received',
+ (_event: Event, intent: any) => {
+ if (
+ typeof intent.name === 'string' &&
+ local.openfinIntentCallbacks.has(intent.name)
+ ) {
+ const uuidCallbacks = local.openfinIntentCallbacks.get(intent.name);
+ uuidCallbacks?.forEach((callbacks, _uuid) => {
+ callbacks(intent.context);
+ });
+ }
+ },
+);
+
+local.ipcRenderer.on('openfin-disconnection', (_event: Event) => {
+ local.openfinDisconnectionCallback?.();
});
// Invoked whenever the app is reloaded/navigated
@@ -1480,7 +1492,7 @@ const sanitize = (): void => {
windowName: window.name,
});
}
- local.intentsCallbacks = new Map();
+ local.openfinIntentCallbacks = new Map();
};
// listens for the online/offline events and updates the main process