@ixon-cdk/iframe-adapter 1.2.0-next.4
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/README.md +3 -0
- package/index.js +450 -0
- package/package.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# IXON Component Development Kit IFrame Adapter
|
|
2
|
+
|
|
3
|
+
This library contains an iframe adapter for the IXON Component Development Kit (CDK). It creates a bridge between a component that mounts an `<iframe>` element and the application that is loaded inside of it. This allows the embedded application to use the same ComponentContext API's that are provided to the component.
|
package/index.js
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
const EVENT_NS = 'cdk:cmpctx:';
|
|
2
|
+
|
|
3
|
+
const EVENTS = {
|
|
4
|
+
// Lifecycle
|
|
5
|
+
Create: `${EVENT_NS}create`,
|
|
6
|
+
Created: `${EVENT_NS}created`,
|
|
7
|
+
Destroy: `${EVENT_NS}destroy`,
|
|
8
|
+
|
|
9
|
+
// ResourceDataClient
|
|
10
|
+
ResourceDataClientCreate: `${EVENT_NS}rdc:create`,
|
|
11
|
+
ResourceDataClientDestroy: `${EVENT_NS}rdc:destroy`,
|
|
12
|
+
ResourceDataClientQuery: `${EVENT_NS}rdc:query`,
|
|
13
|
+
ResourceDataClientQueryResult: `${EVENT_NS}rdc:queryresult`,
|
|
14
|
+
ResourceDataClientRender: `${EVENT_NS}rdc:render`,
|
|
15
|
+
ResourceDataClientRenderResult: `${EVENT_NS}rdc:renderresult`,
|
|
16
|
+
|
|
17
|
+
// VPN
|
|
18
|
+
ShowVpnStatusDetails: `${EVENT_NS}showvpnstatusdetails`,
|
|
19
|
+
ToggleVpn: `${EVENT_NS}togglevpn`,
|
|
20
|
+
VpnClientStatusChange: `${EVENT_NS}vpnclientstatuschange`,
|
|
21
|
+
|
|
22
|
+
// Miscellaneous
|
|
23
|
+
NavigateByUrl: `${EVENT_NS}navigatebyurl`,
|
|
24
|
+
SetTimeRange: `${EVENT_NS}settimerange`,
|
|
25
|
+
TimeRangeChange: `${EVENT_NS}timerangechange`,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
class NotImplementedError extends Error {
|
|
29
|
+
constructor() {
|
|
30
|
+
super('Method is not implemented');
|
|
31
|
+
this.name = 'NotImplementedError';
|
|
32
|
+
this.message = 'Method is not implemented';
|
|
33
|
+
this.stack = 'NotImplementedError: Method is not implemented';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class ComponentContext {
|
|
38
|
+
ontimerangechange = null;
|
|
39
|
+
onvpnclientstatuschange = null;
|
|
40
|
+
|
|
41
|
+
constructor(serializableContext) {
|
|
42
|
+
this._getApiLinks = () => new Map(serializableContext._apiLinkEntries);
|
|
43
|
+
this.appData = serializableContext.appData;
|
|
44
|
+
this.componentBaseUrl = serializableContext.componentBaseUrl;
|
|
45
|
+
this.destroyed = serializableContext.destroyed;
|
|
46
|
+
this.mode = serializableContext.mode;
|
|
47
|
+
this.inputs = serializableContext.inputs;
|
|
48
|
+
this.inputMap = new Map(Object.entries(this.inputs));
|
|
49
|
+
this.timeRange = serializableContext.timeRange;
|
|
50
|
+
this.timeRangeIsAdjustable = serializableContext.timeRangeIsAdjustable;
|
|
51
|
+
this.vpnClientStatus = serializableContext.vpnClientStatus;
|
|
52
|
+
this._messageHandler = this._messageHandler.bind(this);
|
|
53
|
+
window.addEventListener('message', this._messageHandler, false);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_messageHandler(event) {
|
|
57
|
+
if (event.origin !== window.origin) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (event.source !== window.parent) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
switch (event.data.type) {
|
|
66
|
+
case EVENTS.TimeRangeChange:
|
|
67
|
+
if (this.ontimerangechange) {
|
|
68
|
+
this.ontimerangechange(...event.data.payload);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case EVENTS.VpnClientStatusChange:
|
|
72
|
+
if (this.onvpnclientstatuschange) {
|
|
73
|
+
this.onvpnclientstatuschange(...event.data.payload);
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
createBackendComponentClient() {
|
|
80
|
+
throw new NotImplementedError();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
createLoggingDataClient() {
|
|
84
|
+
throw new NotImplementedError();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
createResourceDataClient() {
|
|
88
|
+
const clientId = crypto.randomUUID();
|
|
89
|
+
window.parent.postMessage(
|
|
90
|
+
{
|
|
91
|
+
type: EVENTS.ResourceDataClientCreate,
|
|
92
|
+
payload: { clientId },
|
|
93
|
+
},
|
|
94
|
+
window.origin,
|
|
95
|
+
);
|
|
96
|
+
return new ResourceDataClient(clientId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
createTooltip() {
|
|
100
|
+
throw new NotImplementedError();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
destroy() {
|
|
104
|
+
window.parent.postMessage({ type: EVENTS.Destroy }, window.origin);
|
|
105
|
+
window.removeEventListener('message', this._messageHandler, false);
|
|
106
|
+
this.destroyed = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getApiUrl(rel, params) {
|
|
110
|
+
const link = this._getApiLinks().get(rel);
|
|
111
|
+
if (!link) {
|
|
112
|
+
throw new Error('IxApiError: entry not found');
|
|
113
|
+
}
|
|
114
|
+
// NOTE:
|
|
115
|
+
// This implementation part is an exact copy of the shared `getLinkUrl` utility
|
|
116
|
+
// function which can be found in `@lib/ixon/ixapi/ixapi.utils.ts
|
|
117
|
+
if (params) {
|
|
118
|
+
return Object.keys(params)
|
|
119
|
+
.sort()
|
|
120
|
+
.reduce((href, key) => {
|
|
121
|
+
if (href.includes(`{${key}}`)) {
|
|
122
|
+
return href.replace(`{${key}}`, params[key]);
|
|
123
|
+
}
|
|
124
|
+
const url = new URL(href);
|
|
125
|
+
url.searchParams.set(key, params[key]);
|
|
126
|
+
return decodeURIComponent(url.href);
|
|
127
|
+
}, link.href);
|
|
128
|
+
}
|
|
129
|
+
return link.href;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
navigateByUrl(url) {
|
|
133
|
+
window.parent.postMessage(
|
|
134
|
+
{ type: EVENTS.NavigateByUrl, payload: { url } },
|
|
135
|
+
window.origin,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
saveAsFile() {
|
|
140
|
+
throw new NotImplementedError();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setTimeRange(timeRange) {
|
|
144
|
+
window.parent.postMessage(
|
|
145
|
+
{
|
|
146
|
+
type: EVENTS.SetTimeRange,
|
|
147
|
+
payload: { timeRange },
|
|
148
|
+
},
|
|
149
|
+
window.origin,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
showVpnStatusDetails(agentId) {
|
|
154
|
+
window.parent.postMessage(
|
|
155
|
+
{
|
|
156
|
+
type: EVENTS.ShowVpnStatusDetails,
|
|
157
|
+
payload: { agentId },
|
|
158
|
+
},
|
|
159
|
+
window.origin,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
testVpnAccess() {
|
|
164
|
+
throw new NotImplementedError();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
toggleVpn(agentId) {
|
|
168
|
+
window.parent.postMessage(
|
|
169
|
+
{ type: EVENTS.ToggleVpn, payload: { agentId } },
|
|
170
|
+
window.origin,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
translate() {
|
|
175
|
+
throw new NotImplementedError();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
class ResourceDataClient {
|
|
180
|
+
constructor(id) {
|
|
181
|
+
this.id = id;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
query(query, listener) {
|
|
185
|
+
const queryId = crypto.randomUUID();
|
|
186
|
+
|
|
187
|
+
window.addEventListener('message', _queryHandler, false);
|
|
188
|
+
|
|
189
|
+
window.parent.postMessage(
|
|
190
|
+
{
|
|
191
|
+
type: EVENTS.ResourceDataClientQuery,
|
|
192
|
+
payload: { clientId: this.id, queryId, query },
|
|
193
|
+
},
|
|
194
|
+
window.origin,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
return () => {
|
|
198
|
+
window.removeEventListener('message', _queryHandler, false);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
function _queryHandler(event) {
|
|
202
|
+
if (event.origin !== window.origin) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (event.source !== window.parent) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (
|
|
211
|
+
event.data.type === EVENTS.ResourceDataClientQueryResult &&
|
|
212
|
+
event.data.payload.queryId === queryId
|
|
213
|
+
) {
|
|
214
|
+
listener(event.data.payload.results);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
render(query, listener) {
|
|
220
|
+
const queryId = crypto.randomUUID();
|
|
221
|
+
|
|
222
|
+
window.addEventListener('message', _queryHandler, false);
|
|
223
|
+
|
|
224
|
+
window.parent.postMessage(
|
|
225
|
+
{
|
|
226
|
+
type: EVENTS.ResourceDataClientRender,
|
|
227
|
+
payload: { clientId: this.id, queryId, query },
|
|
228
|
+
},
|
|
229
|
+
window.origin,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return () => {
|
|
233
|
+
window.removeEventListener('message', _queryHandler, false);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
function _queryHandler(event) {
|
|
237
|
+
if (event.origin !== window.origin) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (event.source !== window.parent) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (
|
|
246
|
+
event.data.type === EVENTS.ResourceDataClientRenderResult &&
|
|
247
|
+
event.data.payload.queryId === queryId
|
|
248
|
+
) {
|
|
249
|
+
listener(event.data.payload.results);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
destroy() {
|
|
255
|
+
window.parent.postMessage(
|
|
256
|
+
{
|
|
257
|
+
type: EVENTS.ResourceDataClientDestroy,
|
|
258
|
+
payload: { clientId: this.id },
|
|
259
|
+
},
|
|
260
|
+
window.origin,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function getComponentContext(options) {
|
|
266
|
+
const hasInputMigrations = !!(options && options.migrateInputs);
|
|
267
|
+
|
|
268
|
+
if (!hasInputMigrations && getComponentContext.__cache__) {
|
|
269
|
+
return Promise.resolve(getComponentContext.__cache__);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return new Promise(resolve => {
|
|
273
|
+
window.addEventListener(
|
|
274
|
+
'message',
|
|
275
|
+
function messageHandler(event) {
|
|
276
|
+
if (event.origin !== window.origin) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (event.source !== window.parent) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (event.data && event.data.type === EVENTS.Created) {
|
|
285
|
+
let serializableContext = event.data.payload;
|
|
286
|
+
|
|
287
|
+
if (hasInputMigrations) {
|
|
288
|
+
serializableContext.inputs = options.migrateInputs(
|
|
289
|
+
serializableContext.inputs,
|
|
290
|
+
null,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const context = new ComponentContext(serializableContext);
|
|
295
|
+
resolve(context);
|
|
296
|
+
|
|
297
|
+
// memoize
|
|
298
|
+
if (!hasInputMigrations) {
|
|
299
|
+
getComponentContext.__cache__ = context;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
window.removeEventListener('message', messageHandler, false);
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
false,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// dispatch "create" event
|
|
309
|
+
window.parent.postMessage({ type: EVENTS.Create }, window.origin);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function connect({ iframe, context }) {
|
|
314
|
+
window.addEventListener('message', _adapterMessageHandler, false);
|
|
315
|
+
|
|
316
|
+
// Broadcasts time range changes to connected iframe
|
|
317
|
+
context.ontimerangechange = (...args) => {
|
|
318
|
+
iframe.contentWindow.postMessage({
|
|
319
|
+
type: EVENTS.TimeRangeChange,
|
|
320
|
+
payload: args,
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// Broadcasts VPN Client status changes to connected iframe
|
|
325
|
+
context.onvpnclientstatuschange = (...args) => {
|
|
326
|
+
iframe.contentWindow.postMessage({
|
|
327
|
+
type: EVENTS.VpnClientStatusChange,
|
|
328
|
+
payload: args,
|
|
329
|
+
});
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
destroy() {
|
|
334
|
+
window.removeEventListener('message', _adapterMessageHandler, false);
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Returns a serializable object for the passed CompontContext instance
|
|
340
|
+
*/
|
|
341
|
+
function _getSerializableContext(context) {
|
|
342
|
+
return {
|
|
343
|
+
_apiLinkEntries: [...context._getApiLinks().entries()],
|
|
344
|
+
appData: context.appData,
|
|
345
|
+
componentBaseUrl: context.componentBaseUrl,
|
|
346
|
+
destroyed: context.destroyed,
|
|
347
|
+
mode: context.mode,
|
|
348
|
+
inputs: context.inputs,
|
|
349
|
+
timeRange: context.timeRange,
|
|
350
|
+
timeRangeIsAdjustable: context.timeRangeIsAdjustable,
|
|
351
|
+
vpnClientStatus: context.vpnClientStatus,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function _adapterMessageHandler(event) {
|
|
356
|
+
_adapterMessageHandler._clients =
|
|
357
|
+
_adapterMessageHandler._clients || new Map([]);
|
|
358
|
+
|
|
359
|
+
if (event.origin !== window.origin) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (event.source !== iframe.contentWindow) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
switch (event.data.type) {
|
|
368
|
+
/**
|
|
369
|
+
* Lifecycle
|
|
370
|
+
*/
|
|
371
|
+
case EVENTS.Create:
|
|
372
|
+
iframe.contentWindow.postMessage({
|
|
373
|
+
type: EVENTS.Created,
|
|
374
|
+
payload: _getSerializableContext(context),
|
|
375
|
+
});
|
|
376
|
+
break;
|
|
377
|
+
case EVENTS.Destroy:
|
|
378
|
+
context.destroy();
|
|
379
|
+
break;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* ResourceDataClient
|
|
383
|
+
*/
|
|
384
|
+
case EVENTS.ResourceDataClientCreate: {
|
|
385
|
+
const { clientId } = event.data.payload;
|
|
386
|
+
const client = context.createResourceDataClient(clientId);
|
|
387
|
+
_adapterMessageHandler._clients.set(clientId, client);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
case EVENTS.ResourceDataClientDestroy: {
|
|
391
|
+
const { clientId } = event.data.payload;
|
|
392
|
+
if (_adapterMessageHandler._clients.has(clientId)) {
|
|
393
|
+
_adapterMessageHandler._clients.get(clientId).destroy();
|
|
394
|
+
_adapterMessageHandler._clients.delete(clientId);
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case EVENTS.ResourceDataClientQuery: {
|
|
399
|
+
const { clientId, queryId, query } = event.data.payload;
|
|
400
|
+
let client = _adapterMessageHandler._clients.get(clientId);
|
|
401
|
+
if (client && !client._destroyed) {
|
|
402
|
+
client.query(query, results => {
|
|
403
|
+
if (client && !client._destroyed) {
|
|
404
|
+
iframe.contentWindow.postMessage({
|
|
405
|
+
type: EVENTS.ResourceDataClientQueryResult,
|
|
406
|
+
payload: { queryId, results },
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case EVENTS.ResourceDataClientRender: {
|
|
414
|
+
const { clientId, queryId, query } = event.data.payload;
|
|
415
|
+
let client = _adapterMessageHandler._clients.get(clientId);
|
|
416
|
+
if (client && !client._destroyed) {
|
|
417
|
+
client.render(query, results => {
|
|
418
|
+
if (client && !client._destroyed) {
|
|
419
|
+
iframe.contentWindow.postMessage({
|
|
420
|
+
type: EVENTS.ResourceDataClientRenderResult,
|
|
421
|
+
payload: { queryId, results },
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* VPN
|
|
431
|
+
*/
|
|
432
|
+
case EVENTS.ShowVpnStatusDetails:
|
|
433
|
+
context.showVpnStatusDetails(event.data.payload.agentId);
|
|
434
|
+
break;
|
|
435
|
+
case EVENTS.ToggleVpn:
|
|
436
|
+
context.toggleVpn(event.data.payload.agentId);
|
|
437
|
+
break;
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Miscellaneous
|
|
441
|
+
*/
|
|
442
|
+
case EVENTS.NavigateByUrl:
|
|
443
|
+
context.navigateByUrl(event.data.payload.url);
|
|
444
|
+
break;
|
|
445
|
+
case EVENTS.SetTimeRange:
|
|
446
|
+
context.setTimeRange(event.data.payload.timeRange);
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ixon-cdk/iframe-adapter",
|
|
3
|
+
"version": "1.2.0-next.4",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"files": [
|
|
9
|
+
"!index.spec.js"
|
|
10
|
+
],
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"@ixon-cdk/types": "^1.2.0-next.4"
|
|
13
|
+
}
|
|
14
|
+
}
|