@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.
- package/LICENSE +21 -0
- package/changelog.md +133 -0
- package/dist/workspaces.es.js +5608 -0
- package/dist/workspaces.es.js.map +1 -0
- package/dist/workspaces.umd.js +5616 -0
- package/dist/workspaces.umd.js.map +1 -0
- package/dist/workspaces.umd.min.js +1 -0
- package/package.json +52 -0
- package/readme.md +7 -0
- package/rollup.config.js +54 -0
- package/src/builders/baseBuilder.ts +50 -0
- package/src/builders/parentBuilder.ts +62 -0
- package/src/builders/workspaceBuilder.ts +102 -0
- package/src/communication/bridge.ts +264 -0
- package/src/communication/constants.ts +202 -0
- package/src/communication/interop-transport.ts +175 -0
- package/src/controllers/base.ts +487 -0
- package/src/controllers/main.ts +541 -0
- package/src/controllers/shortcuts.ts +28 -0
- package/src/index.ts +23 -0
- package/src/main.ts +415 -0
- package/src/models/base/base.ts +402 -0
- package/src/models/column.ts +189 -0
- package/src/models/frame.ts +368 -0
- package/src/models/group.ts +237 -0
- package/src/models/row.ts +189 -0
- package/src/models/window.ts +309 -0
- package/src/models/workspace.ts +717 -0
- package/src/shared/decoders.ts +939 -0
- package/src/shared/ioc.ts +227 -0
- package/src/shared/privateDataManager.ts +110 -0
- package/src/shared/promisePlus.ts +34 -0
- package/src/shared/promiseWrapper.ts +11 -0
- package/src/types/builders.ts +10 -0
- package/src/types/controller.ts +79 -0
- package/src/types/glue.ts +37 -0
- package/src/types/ioc.ts +33 -0
- package/src/types/privateData.ts +92 -0
- package/src/types/protocol.ts +453 -0
- package/src/types/subscription.ts +31 -0
- package/temp.d.ts +49 -0
- package/tsconfig.json +10 -0
- 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
|
+
}
|