@interopio/workspaces-api 3.0.1

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/changelog.md +133 -0
  3. package/dist/workspaces.es.js +5608 -0
  4. package/dist/workspaces.es.js.map +1 -0
  5. package/dist/workspaces.umd.js +5616 -0
  6. package/dist/workspaces.umd.js.map +1 -0
  7. package/dist/workspaces.umd.min.js +1 -0
  8. package/package.json +52 -0
  9. package/readme.md +7 -0
  10. package/rollup.config.js +54 -0
  11. package/src/builders/baseBuilder.ts +50 -0
  12. package/src/builders/parentBuilder.ts +62 -0
  13. package/src/builders/workspaceBuilder.ts +102 -0
  14. package/src/communication/bridge.ts +264 -0
  15. package/src/communication/constants.ts +202 -0
  16. package/src/communication/interop-transport.ts +175 -0
  17. package/src/controllers/base.ts +487 -0
  18. package/src/controllers/main.ts +541 -0
  19. package/src/controllers/shortcuts.ts +28 -0
  20. package/src/index.ts +23 -0
  21. package/src/main.ts +415 -0
  22. package/src/models/base/base.ts +402 -0
  23. package/src/models/column.ts +189 -0
  24. package/src/models/frame.ts +368 -0
  25. package/src/models/group.ts +237 -0
  26. package/src/models/row.ts +189 -0
  27. package/src/models/window.ts +309 -0
  28. package/src/models/workspace.ts +717 -0
  29. package/src/shared/decoders.ts +939 -0
  30. package/src/shared/ioc.ts +227 -0
  31. package/src/shared/privateDataManager.ts +110 -0
  32. package/src/shared/promisePlus.ts +34 -0
  33. package/src/shared/promiseWrapper.ts +11 -0
  34. package/src/types/builders.ts +10 -0
  35. package/src/types/controller.ts +79 -0
  36. package/src/types/glue.ts +37 -0
  37. package/src/types/ioc.ts +33 -0
  38. package/src/types/privateData.ts +92 -0
  39. package/src/types/protocol.ts +453 -0
  40. package/src/types/subscription.ts +31 -0
  41. package/temp.d.ts +49 -0
  42. package/tsconfig.json +10 -0
  43. package/workspaces.d.ts +1986 -0
@@ -0,0 +1,264 @@
1
+ import { CallbackRegistry, UnsubscribeFunction } from "callback-registry";
2
+ import { SubscriptionConfig, ActiveSubscription, WorkspaceEventType, WorkspaceEventAction, WorkspacePayload, WorkspaceEventScope, PendingSubscription } from "../types/subscription";
3
+ import { ClientOperations, CLIENT_OPERATIONS, STREAMS } from "./constants";
4
+ import { OPERATIONS } from "./constants";
5
+ import { eventTypeDecoder, streamRequestArgumentsDecoder, workspaceEventActionDecoder } from "../shared/decoders";
6
+ import { IOConnectWorkspaces } from "../../workspaces";
7
+ import { InteropTransport } from "./interop-transport";
8
+ import { PromiseWrapper } from "../shared/promiseWrapper";
9
+ import { Instance } from "../types/glue";
10
+
11
+ export class Bridge {
12
+
13
+ private readonly activeSubscriptions: ActiveSubscription[] = [];
14
+ private readonly pendingSubScriptions: PendingSubscription[] = [];
15
+
16
+ constructor(
17
+ private readonly transport: InteropTransport,
18
+ private readonly registry: CallbackRegistry
19
+ ) { }
20
+
21
+ public async createCoreEventSubscription(): Promise<void> {
22
+ await this.transport.coreSubscriptionReady(this.handleCoreEvent.bind(this));
23
+ }
24
+
25
+ public handleCoreSubscription(config: SubscriptionConfig): UnsubscribeFunction {
26
+ const registryKey = `${config.eventType}-${config.action}`;
27
+ const scope = config.scope;
28
+ const scopeId = config.scopeId;
29
+
30
+ return this.registry.add(registryKey, (args) => {
31
+ const scopeConfig = {
32
+ type: scope,
33
+ id: scopeId
34
+ };
35
+
36
+ const receivedIds = {
37
+ frame: args.frameSummary?.id || args.windowSummary?.config.frameId,
38
+ workspace: args.workspaceSummary?.id || args.windowSummary?.config.workspaceId,
39
+ container: args.containerSummary?.itemId,
40
+ window: args.windowSummary?.itemId
41
+ };
42
+
43
+ const shouldInvokeCallback = this.checkScopeMatch(scopeConfig, receivedIds);
44
+
45
+ if (!shouldInvokeCallback) {
46
+ return;
47
+ }
48
+
49
+ config.callback(args);
50
+ });
51
+ }
52
+
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ public async send<T>(operationName: string, operationArgs?: any): Promise<T> {
55
+
56
+ const operationDefinition = Object.values(OPERATIONS).find((operation) => operation.name === operationName);
57
+
58
+ if (!operationDefinition) {
59
+ throw new Error(`Cannot find definition for operation name: ${operationName}`);
60
+ }
61
+
62
+ if (operationDefinition.argsDecoder) {
63
+ try {
64
+ operationDefinition.argsDecoder.runWithException(operationArgs);
65
+ } catch (error) {
66
+ throw new Error(`Unexpected internal outgoing validation error: ${error.message}, for input: ${JSON.stringify(error.input)}, for operation ${operationName}`);
67
+ }
68
+ }
69
+
70
+ try {
71
+ const operationResult = await this.transport.transmitControl(operationDefinition.name, operationArgs);
72
+
73
+ operationDefinition.resultDecoder.runWithException(operationResult);
74
+
75
+ return operationResult;
76
+ } catch (error) {
77
+ if (error.kind) {
78
+ throw new Error(`Unexpected internal incoming validation error: ${error.message}, for input: ${JSON.stringify(error.input)}, for operation ${operationName}`);
79
+ }
80
+ throw new Error(error.message);
81
+ }
82
+ }
83
+
84
+ public async subscribe(config: SubscriptionConfig): Promise<IOConnectWorkspaces.Unsubscribe> {
85
+ const pendingSub = this.getPendingSubscription(config);
86
+ if (pendingSub) {
87
+ await pendingSub.promise;
88
+ }
89
+
90
+ let activeSub = this.getActiveSubscription(config);
91
+ const registryKey = this.getRegistryKey(config);
92
+
93
+ if (!activeSub) {
94
+ const pendingPromise = new PromiseWrapper<void>();
95
+ const pendingSubscription = {
96
+ streamType: config.eventType,
97
+ level: config.scope,
98
+ levelId: config.scopeId,
99
+ promise: pendingPromise.promise
100
+ };
101
+ this.pendingSubScriptions.push(pendingSubscription);
102
+ try {
103
+ const stream = STREAMS[config.eventType];
104
+ const gdSub = await this.transport.subscribe(stream.name, this.getBranchKey(config), config.eventType);
105
+
106
+ gdSub.onData((streamData) => {
107
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
+ const data = streamData.data as { action: string; payload: any };
109
+
110
+ // important to decode without exception, because we do not want to throw an exception here
111
+ const requestedArgumentsResult = streamRequestArgumentsDecoder.run(streamData.requestArguments);
112
+ const actionResult = workspaceEventActionDecoder.run(data.action);
113
+
114
+ if (!requestedArgumentsResult.ok || !actionResult.ok) {
115
+ return;
116
+ }
117
+
118
+ const streamType = requestedArgumentsResult.result.type;
119
+ const branch = requestedArgumentsResult.result.branch;
120
+
121
+ const keyToExecute = `${streamType}-${branch}-${actionResult.result}`;
122
+ const validatedPayload = STREAMS[streamType].payloadDecoder.run(data.payload);
123
+ if (!validatedPayload.ok) {
124
+ return;
125
+ }
126
+
127
+ this.registry.execute(keyToExecute, validatedPayload.result);
128
+ });
129
+
130
+ activeSub = {
131
+ streamType: config.eventType,
132
+ level: config.scope,
133
+ levelId: config.scopeId,
134
+ callbacksCount: 0,
135
+ gdSub
136
+ };
137
+
138
+ this.activeSubscriptions.push(activeSub);
139
+
140
+ pendingPromise.resolve();
141
+ } catch (error) {
142
+ pendingPromise.reject(error);
143
+
144
+ throw error;
145
+ } finally {
146
+ this.removePendingSubscription(pendingSubscription);
147
+ }
148
+
149
+ }
150
+
151
+ const unsubscribe = this.registry.add(registryKey, config.callback);
152
+
153
+ ++activeSub.callbacksCount;
154
+
155
+ return (): void => {
156
+ unsubscribe();
157
+
158
+ --activeSub.callbacksCount;
159
+
160
+ if (activeSub.callbacksCount === 0) {
161
+ activeSub.gdSub.close();
162
+ this.activeSubscriptions.splice(this.activeSubscriptions.indexOf(activeSub), 1);
163
+ }
164
+ };
165
+ }
166
+
167
+ public onOperation(callback: (operation: ClientOperations, caller: Instance) => void) {
168
+
169
+ const wrappedCallback = (payload: ClientOperations, caller: Instance) => {
170
+ const operationName = payload.operation;
171
+ const operationArgs = payload.data;
172
+ const operationDefinition = Object.values(CLIENT_OPERATIONS).find((operation) => operation.name === operationName);
173
+
174
+ if (!operationDefinition) {
175
+ throw new Error(`Cannot find definition for operation name: ${operationName}`);
176
+ }
177
+
178
+ if (operationDefinition.argsDecoder) {
179
+ try {
180
+ operationDefinition.argsDecoder.runWithException(operationArgs);
181
+ } catch (error) {
182
+ throw new Error(`Unexpected internal outgoing validation error: ${error.message}, for input: ${JSON.stringify(error.input)}, for operation ${operationName}`);
183
+ }
184
+ }
185
+
186
+ callback(payload, caller);
187
+ };
188
+
189
+ return this.transport.onInternalMethodInvoked("control", wrappedCallback);
190
+ }
191
+
192
+ private checkScopeMatch(scope: { type: WorkspaceEventScope; id?: string }, receivedIds: { frame: string; workspace: string; container: string; window: string }): boolean {
193
+
194
+ if (scope.type === "global") {
195
+ return true;
196
+ }
197
+
198
+ if (scope.type === "frame" && scope.id === receivedIds.frame) {
199
+ return true;
200
+ }
201
+
202
+ if (scope.type === "workspace" && scope.id === receivedIds.workspace) {
203
+ return true;
204
+ }
205
+
206
+ if (scope.type === "container" && scope.id === receivedIds.container) {
207
+ return true;
208
+ }
209
+
210
+ if (scope.type === "window" && scope.id === receivedIds.window) {
211
+ return true;
212
+ }
213
+
214
+ return false;
215
+ }
216
+
217
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
218
+ private handleCoreEvent(args: any): void {
219
+ const data = args.data;
220
+ try {
221
+ const verifiedAction: WorkspaceEventAction = workspaceEventActionDecoder.runWithException(data.action);
222
+ const verifiedType: WorkspaceEventType = eventTypeDecoder.runWithException(data.type);
223
+ const verifiedPayload: WorkspacePayload = STREAMS[verifiedType].payloadDecoder.runWithException(data.payload);
224
+
225
+ const registryKey = `${verifiedType}-${verifiedAction}`;
226
+
227
+ this.registry.execute(registryKey, verifiedPayload);
228
+ } catch (error) {
229
+ console.warn(`Cannot handle event with data ${JSON.stringify(data)}, because of validation error: ${error.message}`);
230
+ }
231
+ }
232
+
233
+ private getBranchKey(config: SubscriptionConfig): string {
234
+ return config.scope === "global" ? config.scope : `${config.scope}_${config.scopeId}`;
235
+ }
236
+
237
+ private getRegistryKey(config: SubscriptionConfig): string {
238
+ return `${config.eventType}-${this.getBranchKey(config)}-${config.action}`;
239
+ }
240
+
241
+ private getActiveSubscription(config: SubscriptionConfig): ActiveSubscription {
242
+ return this.activeSubscriptions
243
+ .find((activeSub) => activeSub.streamType === config.eventType &&
244
+ activeSub.level === config.scope &&
245
+ activeSub.levelId === config.scopeId
246
+ );
247
+ }
248
+
249
+ private getPendingSubscription(config: SubscriptionConfig): PendingSubscription {
250
+ return this.pendingSubScriptions
251
+ .find((activeSub) => activeSub.streamType === config.eventType &&
252
+ activeSub.level === config.scope &&
253
+ activeSub.levelId === config.scopeId
254
+ );
255
+ }
256
+
257
+ private removePendingSubscription(pendingSubscription: PendingSubscription): void {
258
+ const index = this.pendingSubScriptions.indexOf(pendingSubscription);
259
+
260
+ if (index >= 0) {
261
+ this.pendingSubScriptions.splice(index, 1);
262
+ }
263
+ }
264
+ }
@@ -0,0 +1,202 @@
1
+ import {
2
+ isWindowInSwimlaneResultDecoder,
3
+ frameSummaryDecoder,
4
+ workspaceSnapshotResultDecoder,
5
+ frameSummariesResultDecoder,
6
+ workspaceCreateConfigDecoder,
7
+ getFrameSummaryConfigDecoder,
8
+ layoutSummariesDecoder,
9
+ openWorkspaceConfigDecoder,
10
+ workspaceSummariesResultDecoder,
11
+ voidResultDecoder,
12
+ exportedLayoutsResultDecoder,
13
+ workspaceLayoutDecoder,
14
+ deleteLayoutConfigDecoder,
15
+ simpleItemConfigDecoder,
16
+ resizeItemConfigDecoder,
17
+ moveFrameConfigDecoder,
18
+ frameSnapshotResultDecoder,
19
+ simpleWindowOperationSuccessResultDecoder,
20
+ setItemTitleConfigDecoder,
21
+ moveWindowConfigDecoder,
22
+ addWindowConfigDecoder,
23
+ addContainerConfigDecoder,
24
+ addItemResultDecoder,
25
+ bundleWorkspaceConfigDecoder,
26
+ workspaceStreamDataDecoder,
27
+ frameStreamDataDecoder,
28
+ containerStreamDataDecoder,
29
+ windowStreamDataDecoder,
30
+ workspaceLayoutSaveConfigDecoder,
31
+ pingResultDecoder,
32
+ frameStateConfigDecoder,
33
+ frameStateResultDecoder,
34
+ workspacesImportLayoutDecoder,
35
+ workspaceSelectorDecoder,
36
+ lockWorkspaceDecoder,
37
+ lockWindowDecoder,
38
+ lockContainerDecoder,
39
+ frameBoundsResultDecoder,
40
+ getWorkspaceIconResultDecoder,
41
+ pinWorkspaceDecoder,
42
+ setWorkspaceIconDecoder,
43
+ frameSummaryResultDecoder,
44
+ emptyFrameDefinitionDecoder,
45
+ frameInitProtocolConfigDecoder,
46
+ shortcutClickedDataDecoder,
47
+ shortcutConfigDecoder,
48
+ frameSnapshotConfigDecoder,
49
+ setMaximizationBoundaryConfigDecoder,
50
+ loadingAnimationConfigDecoder,
51
+ getPlatformFrameIdResultDecoder,
52
+ workspacesImportLayoutsDecoder,
53
+ bundleItemConfigDecoder,
54
+ operationCheckConfigDecoder,
55
+ operationCheckResultDecoder
56
+ } from "../shared/decoders";
57
+ import { ControlOperation, StreamOperation } from "../types/protocol";
58
+ import { WorkspaceEventType } from "../types/subscription";
59
+
60
+ type OperationsTypes = "isWindowInWorkspace" |
61
+ "createWorkspace" |
62
+ "getAllFramesSummaries" |
63
+ "getFrameSummary" |
64
+ "getAllWorkspacesSummaries" |
65
+ "getWorkspaceSnapshot" |
66
+ "getAllLayoutsSummaries" |
67
+ "openWorkspace" |
68
+ "deleteLayout" |
69
+ "saveLayout" |
70
+ "importLayout" |
71
+ "importLayouts" |
72
+ "exportAllLayouts" |
73
+ "restoreItem" |
74
+ "maximizeItem" |
75
+ "focusItem" |
76
+ "closeItem" |
77
+ "resizeItem" |
78
+ "setMaximizationBoundary" |
79
+ "moveFrame" |
80
+ "getFrameSnapshot" |
81
+ "forceLoadWindow" |
82
+ "ejectWindow" |
83
+ "setItemTitle" |
84
+ "moveWindowTo" |
85
+ "addWindow" |
86
+ "addContainer" |
87
+ "bundleWorkspace" |
88
+ "bundleItem" |
89
+ "ping" |
90
+ "changeFrameState" |
91
+ "getFrameState" |
92
+ "getFrameBounds" |
93
+ "hibernateWorkspace" |
94
+ "resumeWorkspace" |
95
+ "lockWorkspace" |
96
+ "lockWindow" |
97
+ "lockContainer" |
98
+ "pinWorkspace" |
99
+ "unpinWorkspace" |
100
+ "getWorkspaceIcon" |
101
+ "setWorkspaceIcon" |
102
+ "createFrame" |
103
+ "registerShortcut" |
104
+ "unregisterShortcut" |
105
+ "initFrame" |
106
+ "showLoadingAnimation" |
107
+ "hideLoadingAnimation" |
108
+ "getPlatformFrameId" |
109
+ "operationCheck";
110
+ type OutgoingMethodTypes = "control" | "frameStream" | "workspaceStream" | "containerStream" | "windowStream";
111
+ export type IncomingMethodTypes = "control";
112
+
113
+ export const webPlatformMethodName = "T42.Web.Platform.Control";
114
+ export const webPlatformWspStreamName = "T42.Web.Platform.WSP.Stream";
115
+
116
+ export const OUTGOING_METHODS: { [key in OutgoingMethodTypes]: { name: string; isStream: boolean } } = {
117
+ control: { name: "T42.Workspaces.Control", isStream: false },
118
+ frameStream: { name: "T42.Workspaces.Stream.Frame", isStream: true },
119
+ workspaceStream: { name: "T42.Workspaces.Stream.Workspace", isStream: true },
120
+ containerStream: { name: "T42.Workspaces.Stream.Container", isStream: true },
121
+ windowStream: { name: "T42.Workspaces.Stream.Window", isStream: true }
122
+ };
123
+
124
+ export const INCOMING_METHODS: { [key in IncomingMethodTypes]: { name: string; isStream: boolean } } = {
125
+ control: { name: "T42.Workspaces.Client.Control", isStream: false },
126
+ };
127
+
128
+ export const STREAMS: { [key in WorkspaceEventType]: StreamOperation } = {
129
+ frame: { name: "T42.Workspaces.Stream.Frame", payloadDecoder: frameStreamDataDecoder },
130
+ workspace: { name: "T42.Workspaces.Stream.Workspace", payloadDecoder: workspaceStreamDataDecoder },
131
+ container: { name: "T42.Workspaces.Stream.Container", payloadDecoder: containerStreamDataDecoder },
132
+ window: { name: "T42.Workspaces.Stream.Window", payloadDecoder: windowStreamDataDecoder }
133
+ };
134
+
135
+ interface ShortcutClicked {
136
+ operation: "shortcutClicked",
137
+ data: {
138
+ shortcut: string;
139
+ frameId: string;
140
+ }
141
+ }
142
+
143
+ export type ClientOperations = ShortcutClicked;
144
+
145
+ export type OnOperationsTypes = "shortcutClicked";
146
+
147
+ export const CLIENT_OPERATIONS: { [key in OnOperationsTypes]: ControlOperation } = {
148
+ shortcutClicked: { name: "shortcutClicked", argsDecoder: shortcutClickedDataDecoder, resultDecoder: voidResultDecoder },
149
+ };
150
+
151
+ export const OPERATIONS: { [key in OperationsTypes]: ControlOperation } = {
152
+ ping: { name: "ping", resultDecoder: pingResultDecoder },
153
+ isWindowInWorkspace: { name: "isWindowInWorkspace", argsDecoder: simpleItemConfigDecoder, resultDecoder: isWindowInSwimlaneResultDecoder },
154
+ createWorkspace: { name: "createWorkspace", resultDecoder: workspaceSnapshotResultDecoder, argsDecoder: workspaceCreateConfigDecoder },
155
+ createFrame: { name: "createFrame", resultDecoder: frameSummaryResultDecoder, argsDecoder: emptyFrameDefinitionDecoder },
156
+ initFrame: { name: "initFrame", resultDecoder: voidResultDecoder, argsDecoder: frameInitProtocolConfigDecoder },
157
+ getAllFramesSummaries: { name: "getAllFramesSummaries", resultDecoder: frameSummariesResultDecoder },
158
+ getFrameSummary: { name: "getFrameSummary", resultDecoder: frameSummaryDecoder, argsDecoder: getFrameSummaryConfigDecoder },
159
+ getAllWorkspacesSummaries: { name: "getAllWorkspacesSummaries", resultDecoder: workspaceSummariesResultDecoder },
160
+ getWorkspaceSnapshot: { name: "getWorkspaceSnapshot", resultDecoder: workspaceSnapshotResultDecoder, argsDecoder: simpleItemConfigDecoder },
161
+ getAllLayoutsSummaries: { name: "getAllLayoutsSummaries", resultDecoder: layoutSummariesDecoder },
162
+ openWorkspace: { name: "openWorkspace", argsDecoder: openWorkspaceConfigDecoder, resultDecoder: workspaceSnapshotResultDecoder },
163
+ deleteLayout: { name: "deleteLayout", resultDecoder: voidResultDecoder, argsDecoder: deleteLayoutConfigDecoder },
164
+ saveLayout: { name: "saveLayout", resultDecoder: workspaceLayoutDecoder, argsDecoder: workspaceLayoutSaveConfigDecoder },
165
+ importLayout: { name: "importLayout", resultDecoder: voidResultDecoder, argsDecoder: workspacesImportLayoutDecoder },
166
+ importLayouts: { name: "importLayouts", resultDecoder: voidResultDecoder, argsDecoder: workspacesImportLayoutsDecoder },
167
+ exportAllLayouts: { name: "exportAllLayouts", resultDecoder: exportedLayoutsResultDecoder },
168
+ restoreItem: { name: "restoreItem", argsDecoder: simpleItemConfigDecoder, resultDecoder: voidResultDecoder },
169
+ maximizeItem: { name: "maximizeItem", argsDecoder: simpleItemConfigDecoder, resultDecoder: voidResultDecoder },
170
+ focusItem: { name: "focusItem", argsDecoder: simpleItemConfigDecoder, resultDecoder: voidResultDecoder },
171
+ closeItem: { name: "closeItem", argsDecoder: simpleItemConfigDecoder, resultDecoder: voidResultDecoder },
172
+ resizeItem: { name: "resizeItem", argsDecoder: resizeItemConfigDecoder, resultDecoder: voidResultDecoder },
173
+ setMaximizationBoundary: { name: "setMaximizationBoundary", argsDecoder: setMaximizationBoundaryConfigDecoder, resultDecoder: voidResultDecoder },
174
+ changeFrameState: { name: "changeFrameState", argsDecoder: frameStateConfigDecoder, resultDecoder: voidResultDecoder },
175
+ getFrameState: { name: "getFrameState", argsDecoder: simpleItemConfigDecoder, resultDecoder: frameStateResultDecoder },
176
+ getFrameBounds: { name: "getFrameBounds", argsDecoder: simpleItemConfigDecoder, resultDecoder: frameBoundsResultDecoder },
177
+ moveFrame: { name: "moveFrame", argsDecoder: moveFrameConfigDecoder, resultDecoder: voidResultDecoder },
178
+ getFrameSnapshot: { name: "getFrameSnapshot", argsDecoder: frameSnapshotConfigDecoder, resultDecoder: frameSnapshotResultDecoder },
179
+ forceLoadWindow: { name: "forceLoadWindow", argsDecoder: simpleItemConfigDecoder, resultDecoder: simpleWindowOperationSuccessResultDecoder },
180
+ ejectWindow: { name: "ejectWindow", argsDecoder: simpleItemConfigDecoder, resultDecoder: simpleWindowOperationSuccessResultDecoder },
181
+ setItemTitle: { name: "setItemTitle", argsDecoder: setItemTitleConfigDecoder, resultDecoder: voidResultDecoder },
182
+ moveWindowTo: { name: "moveWindowTo", argsDecoder: moveWindowConfigDecoder, resultDecoder: voidResultDecoder },
183
+ addWindow: { name: "addWindow", argsDecoder: addWindowConfigDecoder, resultDecoder: addItemResultDecoder },
184
+ addContainer: { name: "addContainer", argsDecoder: addContainerConfigDecoder, resultDecoder: addItemResultDecoder },
185
+ bundleWorkspace: { name: "bundleWorkspace", argsDecoder: bundleWorkspaceConfigDecoder, resultDecoder: voidResultDecoder },
186
+ bundleItem: { name: "bundleItem", argsDecoder: bundleItemConfigDecoder, resultDecoder: voidResultDecoder },
187
+ hibernateWorkspace: { name: "hibernateWorkspace", argsDecoder: workspaceSelectorDecoder, resultDecoder: voidResultDecoder },
188
+ resumeWorkspace: { name: "resumeWorkspace", argsDecoder: workspaceSelectorDecoder, resultDecoder: voidResultDecoder },
189
+ lockWorkspace: { name: "lockWorkspace", argsDecoder: lockWorkspaceDecoder, resultDecoder: voidResultDecoder },
190
+ lockWindow: { name: "lockWindow", argsDecoder: lockWindowDecoder, resultDecoder: voidResultDecoder },
191
+ lockContainer: { name: "lockContainer", argsDecoder: lockContainerDecoder, resultDecoder: voidResultDecoder },
192
+ pinWorkspace: { name: "pinWorkspace", argsDecoder: pinWorkspaceDecoder, resultDecoder: voidResultDecoder },
193
+ unpinWorkspace: { name: "unpinWorkspace", argsDecoder: workspaceSelectorDecoder, resultDecoder: voidResultDecoder },
194
+ getWorkspaceIcon: { name: "getWorkspaceIcon", argsDecoder: workspaceSelectorDecoder, resultDecoder: getWorkspaceIconResultDecoder },
195
+ setWorkspaceIcon: { name: "setWorkspaceIcon", argsDecoder: setWorkspaceIconDecoder, resultDecoder: voidResultDecoder },
196
+ registerShortcut: { name: "registerShortcut", argsDecoder: shortcutConfigDecoder, resultDecoder: voidResultDecoder },
197
+ unregisterShortcut: { name: "unregisterShortcut", argsDecoder: shortcutConfigDecoder, resultDecoder: voidResultDecoder },
198
+ showLoadingAnimation: { name: "showLoadingAnimation", argsDecoder: loadingAnimationConfigDecoder, resultDecoder: voidResultDecoder },
199
+ hideLoadingAnimation: { name: "hideLoadingAnimation", argsDecoder: loadingAnimationConfigDecoder, resultDecoder: voidResultDecoder },
200
+ getPlatformFrameId: { name: "getPlatformFrameId", resultDecoder: getPlatformFrameIdResultDecoder },
201
+ operationCheck: { name: "operationCheck", argsDecoder: operationCheckConfigDecoder, resultDecoder: operationCheckResultDecoder },
202
+ };
@@ -0,0 +1,175 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { ClientOperations, IncomingMethodTypes, INCOMING_METHODS, OUTGOING_METHODS, webPlatformMethodName, webPlatformWspStreamName } from "./constants";
3
+ import { promisePlus } from "../shared/promisePlus";
4
+ import { WorkspaceEventType } from "../types/subscription";
5
+ import { InteropAPI, Subscription, InvocationResult, Instance } from "../types/glue";
6
+ import { CallbackRegistry } from "callback-registry";
7
+
8
+ export class InteropTransport {
9
+
10
+ private readonly defaultTransportTimeout: number = 30000;
11
+ private coreEventMethodInitiated = false;
12
+ private corePlatformSubPromise: Promise<Subscription>;
13
+
14
+ constructor(private readonly agm: InteropAPI,
15
+ private readonly registry: CallbackRegistry) { }
16
+
17
+ public async initiate(actualWindowId: string): Promise<void> {
18
+
19
+ const desktopGlobal = (window as any).glue42gd || (window as any).iodesktop;
20
+
21
+ if (desktopGlobal) {
22
+ await Promise.all(
23
+ Object.values(OUTGOING_METHODS).map((method) => {
24
+ return this.verifyMethodLive(method.name);
25
+ })
26
+ );
27
+
28
+ await Promise.all(
29
+ Object.keys(INCOMING_METHODS).map((method: IncomingMethodTypes) => {
30
+ return this.registerMethod(method);
31
+ })
32
+ );
33
+
34
+ return;
35
+ }
36
+
37
+ const browserGlobal = (window as any).glue42core || (window as any).iobrowser;
38
+
39
+ const systemId = browserGlobal.communicationId;
40
+
41
+ await Promise.all([
42
+ this.verifyMethodLive(webPlatformMethodName, systemId),
43
+ this.verifyMethodLive(webPlatformWspStreamName, systemId)
44
+ ]);
45
+
46
+ await this.transmitControl("frameHello", { windowId: actualWindowId });
47
+ }
48
+
49
+ public coreSubscriptionReady(eventCallback: (args?: any) => void): Promise<Subscription> {
50
+ if (!this.coreEventMethodInitiated) {
51
+ this.subscribePlatform(eventCallback);
52
+ }
53
+ return this.corePlatformSubPromise;
54
+ }
55
+
56
+ public subscribePlatform(eventCallback: (args?: any) => void): void {
57
+ this.coreEventMethodInitiated = true;
58
+
59
+ const browserGlobal = (window as any).glue42core || (window as any).iobrowser;
60
+
61
+ const systemId = browserGlobal.communicationId;
62
+
63
+ this.corePlatformSubPromise = this.agm.subscribe(webPlatformWspStreamName, systemId ? { target: { instance: systemId } } : undefined);
64
+
65
+ this.corePlatformSubPromise
66
+ .then((sub) => {
67
+ sub.onData((data) => eventCallback(data.data));
68
+ });
69
+ }
70
+
71
+ public async subscribe(streamName: string, streamBranch: string, streamType: WorkspaceEventType): Promise<Subscription> {
72
+
73
+ const subscriptionArgs = {
74
+ branch: streamBranch,
75
+ type: streamType
76
+ };
77
+
78
+ let subscription: Subscription;
79
+
80
+ try {
81
+ subscription = await this.agm.subscribe(streamName, { arguments: subscriptionArgs });
82
+ } catch (error) {
83
+ const message = `Internal subscription error! Error details: stream - ${streamName}, branch: ${streamBranch}. Internal message: ${error.message}`;
84
+ throw new Error(message);
85
+ }
86
+
87
+ return subscription;
88
+ }
89
+
90
+ public async transmitControl(operation: string, operationArguments: any): Promise<any> {
91
+
92
+ const desktopGlobal = (window as any).glue42gd || (window as any).iodesktop;
93
+ const browserGlobal = (window as any).glue42core || (window as any).iobrowser;
94
+
95
+ const invocationArguments = desktopGlobal ? { operation, operationArguments } : { operation, domain: "workspaces", data: operationArguments };
96
+ const methodName = desktopGlobal ? OUTGOING_METHODS.control.name : webPlatformMethodName;
97
+
98
+ const platformTarget = desktopGlobal ? undefined : browserGlobal.communicationId;
99
+
100
+ let invocationResult: InvocationResult<any>;
101
+ const baseErrorMessage = `Internal Workspaces Communication Error. Attempted operation: ${JSON.stringify(invocationArguments)}. `;
102
+
103
+ // using the 0 index of the errors and values collections, because we expect only one server and
104
+ // this is to safeguard in case in future we decide to deprecate the default returned/message properties in favor of using only collections
105
+ try {
106
+ invocationResult = await this.agm.invoke(methodName, invocationArguments, platformTarget ? { instance: platformTarget } : "best", { methodResponseTimeoutMs: this.defaultTransportTimeout });
107
+
108
+ if (!invocationResult) {
109
+ throw new Error("Received unsupported result from GD - empty result");
110
+ }
111
+
112
+ if (!Array.isArray(invocationResult.all_return_values) || invocationResult.all_return_values.length === 0) {
113
+ throw new Error("Received unsupported result from GD - empty values collection");
114
+ }
115
+
116
+ } catch (error) {
117
+ if (error && error.all_errors && error.all_errors.length) {
118
+ // IMPORTANT: Do NOT change the `Inner message:` string, because it is used by other programs to extract the inner message of a communication error
119
+ const invocationErrorMessage = error.all_errors[0].message;
120
+ throw new Error(`${baseErrorMessage} -> Inner message: ${invocationErrorMessage}`);
121
+
122
+ }
123
+ // IMPORTANT: Do NOT change the `Inner message:` string, because it is used by other programs to extract the inner message of a communication error
124
+ throw new Error(`${baseErrorMessage} -> Inner message: ${error.message}`);
125
+ }
126
+
127
+ return invocationResult.all_return_values[0].returned;
128
+ }
129
+
130
+ public onInternalMethodInvoked(key: IncomingMethodTypes, callback: (operation: ClientOperations, caller: Instance) => void) {
131
+ return this.registry.add(key, callback);
132
+ }
133
+
134
+ private verifyMethodLive(name: string, systemId?: string): Promise<void> {
135
+ return promisePlus(() => {
136
+ return new Promise((resolve) => {
137
+ const hasMethod = this.agm.methods().some((method) => {
138
+ const nameMatch = method.name === name;
139
+
140
+ const serverMatch = systemId ?
141
+ method.getServers().some((server) => server.instance === systemId) :
142
+ true;
143
+
144
+ return nameMatch && serverMatch;
145
+ });
146
+
147
+ if (hasMethod) {
148
+ resolve();
149
+ return;
150
+ }
151
+
152
+ const unSub = this.agm.serverMethodAdded((data) => {
153
+ const method = data.method;
154
+ const server = data.server;
155
+
156
+ const serverMatch = systemId ?
157
+ server.instance === systemId :
158
+ true;
159
+
160
+ if (method.name === name && serverMatch) {
161
+ unSub();
162
+ resolve();
163
+ }
164
+ });
165
+ });
166
+ }, 15000, "Timeout waiting for the Workspaces communication channels");
167
+ }
168
+
169
+ private registerMethod(key: IncomingMethodTypes) {
170
+ const method = INCOMING_METHODS[key];
171
+ return this.agm.register(method.name, (args, caller) => {
172
+ this.registry.execute(key, args, caller);
173
+ });
174
+ }
175
+ }