@openfin/core 29.72.18 → 30.73.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfin/core",
3
- "version": "29.72.18",
3
+ "version": "30.73.2",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./src/mock.js",
6
6
  "types": "./src/mock.d.ts",
@@ -0,0 +1,17 @@
1
+ declare type HandlerId = string;
2
+ export declare class PrivateChannelClient {
3
+ id: string;
4
+ client: OpenFin.ChannelClient;
5
+ listeners: Map<HandlerId, FDC3v2.Listener>;
6
+ constructor(client: OpenFin.ChannelClient, id: string);
7
+ broadcast(context: OpenFin.Context): Promise<void>;
8
+ getCurrentContext(contextType?: string): Promise<OpenFin.Context | null>;
9
+ addContextListener(contextType: string | null, handler: OpenFin.ContextHandler): Promise<FDC3v2.Listener>;
10
+ private createNonStandardUnsubscribeCb;
11
+ private createContextUnsubscribeCb;
12
+ onAddContextListener(handler: (contextType?: string) => void): FDC3v2.Listener;
13
+ onDisconnect(handler: () => void): FDC3v2.Listener;
14
+ onUnsubscribe(handler: (contextType?: string) => void): FDC3v2.Listener;
15
+ disconnect(): Promise<void>;
16
+ }
17
+ export {};
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrivateChannelClient = void 0;
4
+ const utils_1 = require("../utils");
5
+ class PrivateChannelClient {
6
+ constructor(client, id) {
7
+ this.id = id;
8
+ this.client = client;
9
+ this.listeners = new Map();
10
+ }
11
+ async broadcast(context) {
12
+ this.client.dispatch('broadcast', { context });
13
+ }
14
+ async getCurrentContext(contextType) {
15
+ return this.client.dispatch('getCurrentContext', { contextType });
16
+ }
17
+ async addContextListener(contextType, handler) {
18
+ if (typeof handler !== 'function') {
19
+ throw new Error("Non-function argument passed to the second parameter 'handler'. Be aware that the argument order does not match the FDC3 standard.");
20
+ }
21
+ let handlerId;
22
+ if (contextType) {
23
+ handlerId = `contextHandler:invoke-${this.id}-${contextType}-${utils_1.generateId()}`;
24
+ }
25
+ else {
26
+ handlerId = `contextHandler:invoke-${this.id}-${utils_1.generateId()}`;
27
+ }
28
+ this.client.register(handlerId, utils_1.wrapContextHandler(handler, handlerId));
29
+ const listener = { unsubscribe: await this.createContextUnsubscribeCb(handlerId) };
30
+ this.listeners.set(handlerId, listener);
31
+ await this.client.dispatch(`contextHandlerAdded`, { handlerId, contextType });
32
+ return listener;
33
+ }
34
+ createNonStandardUnsubscribeCb(handlerId) {
35
+ return async () => {
36
+ this.client.remove(handlerId);
37
+ this.listeners.delete(handlerId);
38
+ await this.client.dispatch('nonStandardHandlerRemoved', { handlerId });
39
+ };
40
+ }
41
+ createContextUnsubscribeCb(handlerId) {
42
+ return async () => {
43
+ this.client.remove(handlerId);
44
+ this.listeners.delete(handlerId);
45
+ await this.client.dispatch('contextHandlerRemoved', { handlerId });
46
+ };
47
+ }
48
+ onAddContextListener(handler) {
49
+ const handlerId = `onContextHandlerAdded:invoke-${this.id}-${utils_1.generateId()}`;
50
+ this.client.register(handlerId, handler);
51
+ const listener = { unsubscribe: this.createNonStandardUnsubscribeCb(handlerId) };
52
+ this.listeners.set(handlerId, listener);
53
+ this.client.dispatch(`onAddContextHandlerAdded`, { handlerId });
54
+ return listener;
55
+ }
56
+ onDisconnect(handler) {
57
+ const handlerId = `onDisconnect:invoke-${this.id}-${utils_1.generateId()}`;
58
+ this.client.register(handlerId, handler);
59
+ const listener = { unsubscribe: this.createNonStandardUnsubscribeCb(handlerId) };
60
+ this.listeners.set(handlerId, listener);
61
+ this.client.dispatch(`onDisconnectHandlerAdded`, { handlerId });
62
+ return listener;
63
+ }
64
+ onUnsubscribe(handler) {
65
+ const handlerId = `onUnsubscribe:invoke-${this.id}-${utils_1.generateId()}`;
66
+ this.client.register(handlerId, handler);
67
+ const listener = { unsubscribe: this.createNonStandardUnsubscribeCb(handlerId) };
68
+ this.listeners.set(handlerId, listener);
69
+ this.client.dispatch(`onUnsubscribeHandlerAdded`, { handlerId });
70
+ return listener;
71
+ }
72
+ async disconnect() {
73
+ const promises = [];
74
+ const listenerUnsubscribers = Array.from(this.listeners.values());
75
+ for (let i = 0; i < listenerUnsubscribers.length; i++) {
76
+ const listener = listenerUnsubscribers[i];
77
+ promises.push(listener.unsubscribe());
78
+ }
79
+ await Promise.all(promises);
80
+ await this.client.dispatch('clientDisconnecting');
81
+ await this.client.disconnect();
82
+ }
83
+ }
84
+ exports.PrivateChannelClient = PrivateChannelClient;
@@ -0,0 +1,40 @@
1
+ import { ChannelProvider } from '../../interappbus/channel/provider';
2
+ declare type HandlerId = string;
3
+ export declare class PrivateChannelProvider {
4
+ id: string;
5
+ private provider;
6
+ private clients;
7
+ private contextByContextType;
8
+ private lastContext;
9
+ constructor(provider: ChannelProvider, id: string);
10
+ private getClientState;
11
+ registerListeners(): void;
12
+ broadcast(payload: {
13
+ context: OpenFin.Context;
14
+ }, broadcasterClientIdentity: OpenFin.ClientIdentity): void;
15
+ getCurrentContext(payload: {
16
+ contextType?: string;
17
+ }, senderClientIdentity: OpenFin.ClientIdentity): OpenFin.Context | null;
18
+ contextHandlerAdded(payload: {
19
+ handlerId: HandlerId;
20
+ contextType?: string;
21
+ }, senderClientIdentity: OpenFin.ClientIdentity): void;
22
+ contextHandlerRemoved(payload: {
23
+ handlerId: HandlerId;
24
+ }, removingClientIdentity: OpenFin.ClientIdentity): Promise<void>;
25
+ nonStandardHandlerRemoved(payload: {
26
+ handlerId: HandlerId;
27
+ }, id: OpenFin.ClientIdentity): void;
28
+ onAddContextHandlerAdded(payload: {
29
+ handlerId: HandlerId;
30
+ }, senderClientIdentity: OpenFin.ClientIdentity): void;
31
+ onDisconnectHandlerAdded(payload: {
32
+ handlerId: HandlerId;
33
+ }, id: OpenFin.ClientIdentity): void;
34
+ onUnsubscribeHandlerAdded(payload: {
35
+ handlerId: HandlerId;
36
+ }, id: OpenFin.ClientIdentity): void;
37
+ clientDisconnecting(payload: {}, disconnectingClientIdentity: OpenFin.ClientIdentity): Promise<void>;
38
+ registerNewClient(clientIdentity: OpenFin.ClientIdentity): void;
39
+ }
40
+ export {};
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrivateChannelProvider = void 0;
4
+ const InteropBroker_1 = require("../InteropBroker");
5
+ class PrivateChannelProvider {
6
+ constructor(provider, id) {
7
+ this.provider = provider;
8
+ this.id = id;
9
+ this.clients = new Map();
10
+ this.registerListeners();
11
+ this.contextByContextType = new Map();
12
+ this.lastContext = undefined;
13
+ this.provider.onConnection((clientIdentity) => this.registerNewClient(clientIdentity));
14
+ this.provider.onDisconnection((clientIdentity) => this.clients.delete(clientIdentity.endpointId));
15
+ }
16
+ getClientState(id) {
17
+ return this.clients.get(id.endpointId);
18
+ }
19
+ registerListeners() {
20
+ this.provider.register('broadcast', this.broadcast.bind(this));
21
+ this.provider.register('getCurrentContext', this.getCurrentContext.bind(this));
22
+ this.provider.register('contextHandlerAdded', this.contextHandlerAdded.bind(this));
23
+ this.provider.register('contextHandlerRemoved', this.contextHandlerRemoved.bind(this));
24
+ this.provider.register('nonStandardHandlerRemoved', this.nonStandardHandlerRemoved.bind(this));
25
+ this.provider.register('onAddContextHandlerAdded', this.onAddContextHandlerAdded.bind(this));
26
+ this.provider.register('onDisconnectHandlerAdded', this.onDisconnectHandlerAdded.bind(this));
27
+ this.provider.register('onUnsubscribeHandlerAdded', this.onUnsubscribeHandlerAdded.bind(this));
28
+ this.provider.register('clientDisconnecting', this.clientDisconnecting.bind(this));
29
+ }
30
+ broadcast(payload, broadcasterClientIdentity) {
31
+ const { context } = payload;
32
+ const broadcasterClientState = this.getClientState(broadcasterClientIdentity);
33
+ if (!broadcasterClientState) {
34
+ throw new Error(`Client with Identity: ${broadcasterClientIdentity.uuid} ${broadcasterClientIdentity.name}, tried to call broadcast, is not connected to this Private Channel`);
35
+ }
36
+ const contextIntegrityCheckResult = InteropBroker_1.InteropBroker.checkContextIntegrity(context);
37
+ if (contextIntegrityCheckResult.isValid === false) {
38
+ throw new Error(`Failed to broadcast - bad Context. Reason: ${contextIntegrityCheckResult.reason}. Context: ${JSON.stringify(context)}`);
39
+ }
40
+ this.contextByContextType.set(context.type, context);
41
+ this.lastContext = context;
42
+ Array.from(this.clients.values()).forEach((currClientState) => {
43
+ const handlerIdsForContextType = currClientState.handlerIdsByContextTypes.get(context.type);
44
+ if (handlerIdsForContextType) {
45
+ handlerIdsForContextType.forEach((handlerId) => {
46
+ this.provider.dispatch(currClientState.clientIdentity, handlerId, context);
47
+ });
48
+ }
49
+ if (currClientState.globalHandler) {
50
+ this.provider.dispatch(currClientState.clientIdentity, currClientState.globalHandler, context);
51
+ }
52
+ });
53
+ }
54
+ getCurrentContext(payload, senderClientIdentity) {
55
+ const { contextType } = payload;
56
+ const clientState = this.getClientState(senderClientIdentity);
57
+ if (!clientState) {
58
+ throw new Error(`Client with Identity: ${senderClientIdentity.uuid} ${senderClientIdentity.name}, tried to call getCurrentContext, is not connected to this Private Channel`);
59
+ }
60
+ if (contextType !== undefined) {
61
+ const currentContext = this.contextByContextType.get(contextType);
62
+ if (currentContext)
63
+ return currentContext;
64
+ return null;
65
+ }
66
+ return this.lastContext ? this.lastContext : null;
67
+ }
68
+ contextHandlerAdded(payload, senderClientIdentity) {
69
+ const { handlerId, contextType } = payload;
70
+ const senderClientState = this.getClientState(senderClientIdentity);
71
+ if (!senderClientState) {
72
+ throw new Error(`Client with Identity: ${senderClientIdentity.uuid} ${senderClientIdentity.name}, tried to call addContextListener, is not connected to this Private Channel`);
73
+ }
74
+ if (contextType) {
75
+ const currentHandlersList = senderClientState.handlerIdsByContextTypes.get(contextType) || [];
76
+ senderClientState.handlerIdsByContextTypes.set(contextType, [...currentHandlersList, handlerId]);
77
+ const currContext = this.contextByContextType.get(contextType);
78
+ if (currContext) {
79
+ this.provider.dispatch(senderClientIdentity, handlerId, currContext);
80
+ }
81
+ }
82
+ else {
83
+ senderClientState.globalHandler = handlerId;
84
+ if (this.lastContext) {
85
+ this.provider.dispatch(senderClientIdentity, handlerId, this.lastContext);
86
+ }
87
+ }
88
+ Array.from(this.clients.values()).forEach((currClientState) => {
89
+ if (currClientState.clientIdentity.endpointId !== senderClientIdentity.endpointId &&
90
+ currClientState.onAddContextListenerHandlerId) {
91
+ this.provider.dispatch(currClientState.clientIdentity, currClientState.onAddContextListenerHandlerId, contextType);
92
+ }
93
+ });
94
+ }
95
+ async contextHandlerRemoved(payload, removingClientIdentity) {
96
+ // MC: Made this removal async to ensure that onUnsubscribe handlers are hit before anything else happens.
97
+ const { handlerId } = payload;
98
+ const removingClientState = this.getClientState(removingClientIdentity);
99
+ if (removingClientState) {
100
+ let contextType;
101
+ if (removingClientState.globalHandler === handlerId) {
102
+ removingClientState.globalHandler = undefined;
103
+ }
104
+ else {
105
+ for (const [currContextType, handlersIds] of removingClientState.handlerIdsByContextTypes) {
106
+ const index = handlersIds.indexOf(handlerId);
107
+ if (index > -1) {
108
+ handlersIds.splice(index, 1);
109
+ contextType = currContextType;
110
+ }
111
+ }
112
+ }
113
+ const dispatchesToSend = [];
114
+ const otherClientStates = Array.from(this.clients.values());
115
+ for (let i = 0; i < otherClientStates.length; i++) {
116
+ const otherClientState = otherClientStates[i];
117
+ if (otherClientState.clientIdentity.endpointId !== removingClientIdentity.endpointId &&
118
+ otherClientState.onUnsubscribeHandlerId) {
119
+ dispatchesToSend.push(this.provider.dispatch(otherClientState.clientIdentity, otherClientState.onUnsubscribeHandlerId, contextType));
120
+ }
121
+ }
122
+ try {
123
+ await Promise.all(dispatchesToSend);
124
+ }
125
+ catch (error) {
126
+ console.error(`Problem when attempting to dispatch to onUnsubscribeHandlers. Error: ${error} Removing Client: ${handlerId}. uuid: ${removingClientIdentity.uuid}. name: ${removingClientIdentity.name}. endpointId: ${removingClientIdentity.endpointId}`);
127
+ throw new Error(error);
128
+ }
129
+ }
130
+ else {
131
+ console.warn(`Trying to remove a handler from a client that isn't mapped. handlerId: ${handlerId}. uuid: ${removingClientIdentity.uuid}. name: ${removingClientIdentity.name}. endpointId: ${removingClientIdentity.endpointId}.`);
132
+ }
133
+ }
134
+ nonStandardHandlerRemoved(payload, id) {
135
+ const { handlerId } = payload;
136
+ const clientState = this.getClientState(id);
137
+ if (clientState) {
138
+ if (clientState.onDisconnectHandlerId === handlerId) {
139
+ clientState.onDisconnectHandlerId = undefined;
140
+ }
141
+ else if (clientState.onAddContextListenerHandlerId === handlerId) {
142
+ clientState.onAddContextListenerHandlerId = undefined;
143
+ }
144
+ else if (clientState.onUnsubscribeHandlerId === handlerId) {
145
+ clientState.onUnsubscribeHandlerId = undefined;
146
+ }
147
+ }
148
+ else {
149
+ console.warn(`Trying to remove a handler from a client that isn't mapped. handlerId: ${handlerId}. clientIdentity: ${id}`);
150
+ }
151
+ }
152
+ onAddContextHandlerAdded(payload, senderClientIdentity) {
153
+ const clientState = this.getClientState(senderClientIdentity);
154
+ const { handlerId } = payload;
155
+ if (!clientState) {
156
+ throw new Error(`Client with Identity: ${senderClientIdentity.uuid} ${senderClientIdentity.name}, tried to call onAddContextListener, is not connected to this Private Channel`);
157
+ }
158
+ clientState.onAddContextListenerHandlerId = handlerId;
159
+ // FDC3 Spec says that the added listener should fire for all previously-registered addContextListeners from the other client
160
+ Array.from(this.clients.values()).forEach((otherClientState) => {
161
+ if (otherClientState.clientIdentity.endpointId !== senderClientIdentity.endpointId) {
162
+ Array.from(otherClientState.handlerIdsByContextTypes.keys()).forEach((subscribedContextType) => {
163
+ this.provider.dispatch(senderClientIdentity, handlerId, subscribedContextType);
164
+ });
165
+ }
166
+ });
167
+ }
168
+ onDisconnectHandlerAdded(payload, id) {
169
+ const clientState = this.getClientState(id);
170
+ const { handlerId } = payload;
171
+ if (!clientState) {
172
+ throw new Error(`Client with Identity: ${id.uuid} ${id.name}, tried to call onDisconnect, is not connected to this Private Channel`);
173
+ }
174
+ clientState.onDisconnectHandlerId = handlerId;
175
+ }
176
+ onUnsubscribeHandlerAdded(payload, id) {
177
+ const clientState = this.getClientState(id);
178
+ const { handlerId } = payload;
179
+ if (!clientState) {
180
+ throw new Error(`Client with Identity: ${id.uuid} ${id.name}, tried to call onUnsubscribe, is not connected to this Private Channel`);
181
+ }
182
+ clientState.onUnsubscribeHandlerId = handlerId;
183
+ }
184
+ async clientDisconnecting(payload, disconnectingClientIdentity) {
185
+ const disconnectingClientState = this.getClientState(disconnectingClientIdentity);
186
+ if (!disconnectingClientState) {
187
+ throw new Error(`Client with Identity: ${disconnectingClientIdentity.uuid} ${disconnectingClientIdentity.name}, tried to call disconnect, is not connected to this Private Channel`);
188
+ }
189
+ disconnectingClientState.handlerIdsByContextTypes.clear();
190
+ this.clients.delete(disconnectingClientIdentity.endpointId);
191
+ const dispatchesToSend = [];
192
+ // TODO: call onDisconnect Handler of the other client only.
193
+ // CURRENTLY, just calling the onDisconnect handler for all the other clients. Once we limit it to just one other client, we can eliminate all the iteration code.
194
+ const otherClientStates = Array.from(this.clients.values());
195
+ for (let i = 0; i < otherClientStates.length; i++) {
196
+ const otherClientState = otherClientStates[i];
197
+ if (otherClientState.clientIdentity.endpointId !== disconnectingClientIdentity.endpointId &&
198
+ otherClientState.onDisconnectHandlerId) {
199
+ dispatchesToSend.push(this.provider.dispatch(otherClientState.clientIdentity, otherClientState.onDisconnectHandlerId));
200
+ }
201
+ }
202
+ try {
203
+ await Promise.all(dispatchesToSend);
204
+ }
205
+ catch (error) {
206
+ console.error(`Problem when attempting to dispatch to onDisconnectHandlers. Error: ${error} Disconnecting Client: uuid: ${disconnectingClientIdentity.uuid}. name: ${disconnectingClientIdentity.name}. endpointId: ${disconnectingClientIdentity.endpointId}`);
207
+ throw new Error(error);
208
+ }
209
+ }
210
+ registerNewClient(clientIdentity) {
211
+ if (!this.clients.has(clientIdentity.endpointId)) {
212
+ const clientSubscriptionState = {
213
+ clientIdentity,
214
+ handlerIdsByContextTypes: new Map(),
215
+ globalHandler: undefined,
216
+ onAddContextListenerHandlerId: undefined,
217
+ onUnsubscribeHandlerId: undefined,
218
+ onDisconnectHandlerId: undefined
219
+ };
220
+ this.clients.set(clientIdentity.endpointId, clientSubscriptionState);
221
+ }
222
+ }
223
+ }
224
+ exports.PrivateChannelProvider = PrivateChannelProvider;
@@ -102,6 +102,20 @@ import Transport from '../../../transport/transport';
102
102
  * @property { string } [version] The version number of the Intents schema being used.
103
103
  * @property { Function } getResult {@link getResult Function} that returns a promise that will resolve to either `Context` data returned by the application that resolves the raised intent or a `Channel` established and returned by the app resolving the intent.
104
104
  */
105
+ /**
106
+ * @typedef { object } PrivateChannel
107
+ * @summary Object representing a private context channel, which is intended to support secure communication between applications, and extends the Channel interface with event handlers which provide information on the connection state of both parties, ensuring that desktop agents do not need to queue or retain messages that are broadcast before a context listener is added and that applications are able to stop broadcasting messages when the other party has disconnected.
108
+ * @property { string } id Private Channel Id
109
+ * @property { string } type Channel Type
110
+ * @property { DisplayMetadata } [displayMetadata]
111
+ * @property { function } broadcast
112
+ * @property { function } getCurrentContext
113
+ * @property { function } addContextListener
114
+ * @property { function } onAddContextListener
115
+ * @property { function } onUnsubscribe
116
+ * @property { function } onDisconnect
117
+ * @property { function } disconnect
118
+ */
105
119
  /**
106
120
  * @class
107
121
  * @alias fdc3v2
@@ -229,7 +243,14 @@ export default class Fdc3Module2 extends Base implements FDC3v2.DesktopAgent {
229
243
  * @tutorial fdc3.getOrCreateChannel
230
244
  */
231
245
  getOrCreateChannel(channelId: string): Promise<FDC3.Channel>;
232
- createPrivateChannel(): Promise<FDC3v2.PrivateChannel | void>;
246
+ /**
247
+ * Returns a Channel with an auto-generated identity that is intended for private communication between applications. Primarily used to create channels that will be returned to other applications via an IntentResolution for a raised intent.
248
+ * @returns { Promise<PrivateChannel> }
249
+ * @tutorial fdc3v2.createPrivateChannel
250
+ */
251
+ createPrivateChannel(): Promise<FDC3v2.PrivateChannel>;
252
+ connectPrivateChannel(channelId: string): Promise<FDC3v2.PrivateChannel>;
253
+ private buildPrivateChannelObject;
233
254
  /**
234
255
  * Retrieves a list of the User Channels available for the app to join.
235
256
  * @returns { Promise<Channel[]>}
@@ -5,6 +5,8 @@ const utils_1 = require("../utils");
5
5
  const InteropClient_1 = require("../InteropClient");
6
6
  const utils_2 = require("./utils");
7
7
  const fdc3_1_2_1 = require("./fdc3-1.2");
8
+ const PrivateChannelClient_1 = require("./PrivateChannelClient");
9
+ const PrivateChannelProvider_1 = require("./PrivateChannelProvider");
8
10
  /**
9
11
  * @typedef { object } AppIdentifier
10
12
  * @summary Identifies an application, or instance of an application, and is used to target FDC3 API calls at specific applications.
@@ -107,6 +109,20 @@ const fdc3_1_2_1 = require("./fdc3-1.2");
107
109
  * @property { string } [version] The version number of the Intents schema being used.
108
110
  * @property { Function } getResult {@link getResult Function} that returns a promise that will resolve to either `Context` data returned by the application that resolves the raised intent or a `Channel` established and returned by the app resolving the intent.
109
111
  */
112
+ /**
113
+ * @typedef { object } PrivateChannel
114
+ * @summary Object representing a private context channel, which is intended to support secure communication between applications, and extends the Channel interface with event handlers which provide information on the connection state of both parties, ensuring that desktop agents do not need to queue or retain messages that are broadcast before a context listener is added and that applications are able to stop broadcasting messages when the other party has disconnected.
115
+ * @property { string } id Private Channel Id
116
+ * @property { string } type Channel Type
117
+ * @property { DisplayMetadata } [displayMetadata]
118
+ * @property { function } broadcast
119
+ * @property { function } getCurrentContext
120
+ * @property { function } addContextListener
121
+ * @property { function } onAddContextListener
122
+ * @property { function } onUnsubscribe
123
+ * @property { function } onDisconnect
124
+ * @property { function } disconnect
125
+ */
110
126
  /**
111
127
  * @class
112
128
  * @alias fdc3v2
@@ -366,9 +382,80 @@ class Fdc3Module2 extends base_1.Base {
366
382
  async getOrCreateChannel(channelId) {
367
383
  return this.fdc3Module.getOrCreateChannel(channelId);
368
384
  }
369
- // eslint-disable-next-line class-methods-use-this
385
+ /**
386
+ * Returns a Channel with an auto-generated identity that is intended for private communication between applications. Primarily used to create channels that will be returned to other applications via an IntentResolution for a raised intent.
387
+ * @returns { Promise<PrivateChannel> }
388
+ * @tutorial fdc3v2.createPrivateChannel
389
+ */
370
390
  async createPrivateChannel() {
371
- // private channels will be implemented in another PR
391
+ const channelId = utils_1.generateId();
392
+ const channelProvider = await this.fin.InterApplicationBus.Channel.create(channelId);
393
+ const newPrivateChannelProvider = new PrivateChannelProvider_1.PrivateChannelProvider(channelProvider, channelId);
394
+ const channelClient = await this.fin.InterApplicationBus.Channel.connect(channelId);
395
+ const newPrivateChannelClient = new PrivateChannelClient_1.PrivateChannelClient(channelClient, channelId);
396
+ return this.buildPrivateChannelObject(newPrivateChannelClient);
397
+ }
398
+ // TODO: need to remove this method and not expose it. Added temporarily for the purposes of testing.
399
+ // eslint-disable-next-line class-methods-use-this
400
+ async connectPrivateChannel(channelId) {
401
+ try {
402
+ const channelClient = await fin.InterApplicationBus.Channel.connect(channelId);
403
+ const privateChannelClient = new PrivateChannelClient_1.PrivateChannelClient(channelClient, channelId);
404
+ return this.buildPrivateChannelObject(privateChannelClient);
405
+ }
406
+ catch (error) {
407
+ throw new Error(`Private Channel with id: ${channelId} doesn't exist`);
408
+ }
409
+ }
410
+ // eslint-disable-next-line class-methods-use-this
411
+ buildPrivateChannelObject(privateChannelClient) {
412
+ let clientDisconnected = false;
413
+ const checkIfClientDisconnected = () => {
414
+ if (clientDisconnected) {
415
+ throw new Error('Private Channel Client has been disconnected from the Private Channel');
416
+ }
417
+ };
418
+ return {
419
+ id: privateChannelClient.id,
420
+ type: 'private',
421
+ broadcast: async (context) => {
422
+ checkIfClientDisconnected();
423
+ return privateChannelClient.broadcast(context);
424
+ },
425
+ getCurrentContext: async (contextType) => {
426
+ checkIfClientDisconnected();
427
+ return privateChannelClient.getCurrentContext(contextType);
428
+ },
429
+ addContextListener: async (contextType, handler) => {
430
+ checkIfClientDisconnected();
431
+ let handlerInUse = handler;
432
+ let contextTypeInUse = contextType;
433
+ if (typeof contextType === 'function') {
434
+ console.warn('addContextListener(handler) has been deprecated. Please use addContextListener(null, handler)');
435
+ handlerInUse = contextType;
436
+ contextTypeInUse = null;
437
+ }
438
+ const listener = privateChannelClient.addContextListener(contextTypeInUse, handlerInUse);
439
+ return listener;
440
+ },
441
+ onAddContextListener: (handler) => {
442
+ checkIfClientDisconnected();
443
+ return privateChannelClient.onAddContextListener(handler);
444
+ },
445
+ disconnect: async () => {
446
+ checkIfClientDisconnected();
447
+ clientDisconnected = true;
448
+ return privateChannelClient.disconnect();
449
+ },
450
+ onDisconnect: (handler) => {
451
+ checkIfClientDisconnected();
452
+ return privateChannelClient.onDisconnect(handler);
453
+ },
454
+ onUnsubscribe: (handler) => {
455
+ checkIfClientDisconnected();
456
+ return privateChannelClient.onUnsubscribe(handler);
457
+ }
458
+ };
372
459
  }
373
460
  /**
374
461
  * Retrieves a list of the User Channels available for the app to join.
@@ -9,17 +9,17 @@ export declare class TabDragController {
9
9
  constructor(viewOverlay: ViewOverlay);
10
10
  private dropZonePreview?;
11
11
  /**
12
- * When a tab is dragged from a stack greater than one, it's view will need to be hidden and the
13
- * view next in the stack shown.
14
12
  *
15
- * Conversely, when a view is the last view in a window, it will need to remain visible.
13
+ * When a tab is dragged out of a stack, it will need to be hidden from the stack.
16
14
  *
17
- * This function implements this logic accordingly
18
- * @param movingView The view which is currently being dragged
19
- * @param currentView The current active view of the tabstack
20
- * @param isOnlyViewInWindow Indicates whether the moving view is the only in the platform window.
15
+ * Additionally, if there is a new view to show in the stack, it will be shown at the position specified by
16
+ * containerBounds
17
+ *
18
+ * @param draggingView The view which is currently being dragged
19
+ * @param containerBounds The bounds of the container of the view to be shown in the stack
20
+ * @param nextView The view which has become active after dragging the draggingView out.
21
21
  */
22
- handleTabStackActiveView: (movingView: View, currentView: View, isOnlyViewInWindow: boolean) => Promise<void>;
22
+ handleTabStackActiveView: (draggingView: View, containerBounds?: OpenFin.Bounds, nextView?: View) => Promise<void>;
23
23
  /**
24
24
  * Extracts the border and backgroundColor css values from the drop zone preview,
25
25
  * and sets the viewOverlay to match them.
@@ -38,6 +38,8 @@ export declare class TabDragController {
38
38
  * Disables the click through setting on every view in the platform.
39
39
  */
40
40
  endDrag: () => Promise<void>;
41
+ private disposeObserve?;
42
+ disposeOverlayObserver: () => void;
41
43
  /**
42
44
  * Observes a golden-layout drop zone preview in order to render a BrowserView
43
45
  * overlay whenever a tab is dragged over a droppable region.
@@ -11,29 +11,28 @@ class TabDragController {
11
11
  constructor(viewOverlay) {
12
12
  this.viewOverlay = viewOverlay;
13
13
  /**
14
- * When a tab is dragged from a stack greater than one, it's view will need to be hidden and the
15
- * view next in the stack shown.
16
14
  *
17
- * Conversely, when a view is the last view in a window, it will need to remain visible.
15
+ * When a tab is dragged out of a stack, it will need to be hidden from the stack.
18
16
  *
19
- * This function implements this logic accordingly
20
- * @param movingView The view which is currently being dragged
21
- * @param currentView The current active view of the tabstack
22
- * @param isOnlyViewInWindow Indicates whether the moving view is the only in the platform window.
17
+ * Additionally, if there is a new view to show in the stack, it will be shown at the position specified by
18
+ * containerBounds
19
+ *
20
+ * @param draggingView The view which is currently being dragged
21
+ * @param containerBounds The bounds of the container of the view to be shown in the stack
22
+ * @param nextView The view which has become active after dragging the draggingView out.
23
23
  */
24
- this.handleTabStackActiveView = async (movingView, currentView, isOnlyViewInWindow) => {
24
+ this.handleTabStackActiveView = async (draggingView, containerBounds, nextView) => {
25
25
  if (this.dropZonePreview) {
26
- const hideMovingViewIfPossible = async () => {
27
- if (!isOnlyViewInWindow) {
28
- await movingView.hide();
29
- }
30
- };
31
- const showCurrentView = async () => {
32
- if (currentView.identity.name !== movingView.identity.name) {
33
- await currentView.show();
34
- }
35
- };
36
- await Promise.all([hideMovingViewIfPossible(), showCurrentView()]);
26
+ if (nextView && containerBounds) {
27
+ // Due to https://github.com/electron/electron/issues/20064,
28
+ // setBounds does not work in certain scenarios.
29
+ // Therefore we call it twice before and after showing to ensure as much
30
+ // visual fidelity as possible (although flicker can still occur).
31
+ await (nextView === null || nextView === void 0 ? void 0 : nextView.setBounds(containerBounds));
32
+ await (nextView === null || nextView === void 0 ? void 0 : nextView.show());
33
+ await (nextView === null || nextView === void 0 ? void 0 : nextView.setBounds(containerBounds));
34
+ }
35
+ await draggingView.hide();
37
36
  }
38
37
  };
39
38
  /**
@@ -63,6 +62,12 @@ class TabDragController {
63
62
  this.endDrag = async () => {
64
63
  await this.viewOverlay.setIgnoreViewMouseEvents(false);
65
64
  };
65
+ this.disposeOverlayObserver = () => {
66
+ if (this.disposeObserve) {
67
+ this.disposeObserve();
68
+ }
69
+ this.dropZonePreview = undefined;
70
+ };
66
71
  /**
67
72
  * Observes a golden-layout drop zone preview in order to render a BrowserView
68
73
  * overlay whenever a tab is dragged over a droppable region.
@@ -74,7 +79,7 @@ class TabDragController {
74
79
  this.dropZonePreview = dropZonePreview;
75
80
  let lastBounds;
76
81
  dropZonePreview.style.visibility = 'hidden';
77
- dropZonePreview.addEventListener('drop-area-highlighted', async (e) => {
82
+ const onDropAreaHighlighted = async (e) => {
78
83
  try {
79
84
  const { bounds } = e.detail;
80
85
  if (!lastBounds || !bounds_observer_1.isDomRectEqual(lastBounds, bounds)) {
@@ -85,8 +90,8 @@ class TabDragController {
85
90
  catch (error) {
86
91
  console.warn('Unexpected error encountered rendering tab drag preview.', error);
87
92
  }
88
- });
89
- dropZonePreview.addEventListener('drop-area-hidden', async () => {
93
+ };
94
+ const onDropAreaHidden = async () => {
90
95
  try {
91
96
  lastBounds = undefined;
92
97
  await this.viewOverlay.detachOverlay();
@@ -94,7 +99,16 @@ class TabDragController {
94
99
  catch (error) {
95
100
  console.warn('Unexpected error encountered hiding tab drag preview.', error);
96
101
  }
97
- });
102
+ };
103
+ dropZonePreview.addEventListener('drop-area-highlighted', onDropAreaHighlighted);
104
+ dropZonePreview.addEventListener('drop-area-hidden', onDropAreaHidden);
105
+ this.disposeObserve = () => {
106
+ dropZonePreview.removeEventListener('drop-area-highlighted', onDropAreaHighlighted);
107
+ dropZonePreview.removeEventListener('drop-area-hidden', onDropAreaHidden);
108
+ };
109
+ }
110
+ else {
111
+ console.warn('Tried to observe a drop zone overlay without disposing the previous.');
98
112
  }
99
113
  };
100
114
  }