@raydiant/playlist-window-manager 0.20.0 → 0.21.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/build/BrowserViewRenderer.d.ts +3 -2
- package/build/BrowserViewRenderer.js +47 -23
- package/build/BrowserWindowRenderer.d.ts +3 -2
- package/build/BrowserWindowRenderer.js +66 -42
- package/build/IFrameRenderer.d.ts +3 -2
- package/build/IFrameRenderer.js +38 -28
- package/build/PlaylistWindowManager.js +14 -11
- package/build/PlaylistWindowManagerState.js +3 -3
- package/build/PresentationWindow.d.ts +9 -2
- package/build/PresentationWindow.js +241 -86
- package/build/PresentationWindow.test.d.ts +1 -0
- package/build/PresentationWindow.test.js +241 -0
- package/build/WindowLoadError.js +3 -1
- package/build/WindowTimeoutError.js +3 -1
- package/build/WindowUnresponsiveError.js +3 -1
- package/build/embeddedApps.js +7 -0
- package/build/embeddedApps.test.js +42 -32
- package/build/index.js +19 -6
- package/build/testUtils.d.ts +43 -0
- package/build/testUtils.js +208 -0
- package/build/types.d.ts +7 -2
- package/package.json +2 -2
- package/src/BrowserViewRenderer.ts +18 -7
- package/src/BrowserWindowRenderer.ts +29 -17
- package/src/IFrameRenderer.ts +27 -10
- package/src/PresentationWindow.test.ts +221 -0
- package/src/PresentationWindow.ts +232 -59
- package/src/embeddedApps.test.ts +39 -20
- package/src/embeddedApps.ts +13 -4
- package/src/testUtils.ts +159 -0
- package/src/types.ts +14 -3
- package/tasks/npm-publish.sh +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as RE from '@raydiant/playlist-rule-engine';
|
|
2
2
|
import deepEqual from 'fast-deep-equal';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
4
|
import createEmbeddedApp from './embeddedApps';
|
|
4
5
|
import PlaylistWindowManagerState from './PlaylistWindowManagerState';
|
|
5
6
|
import PlaylistWindowManager from './PlaylistWindowManager';
|
|
@@ -17,7 +18,8 @@ import {
|
|
|
17
18
|
BuilderState,
|
|
18
19
|
WorkerState,
|
|
19
20
|
EmbeddedAppMetadata,
|
|
20
|
-
AnalyticsEvent
|
|
21
|
+
AnalyticsEvent,
|
|
22
|
+
PreviewState,
|
|
21
23
|
} from './types';
|
|
22
24
|
|
|
23
25
|
export interface PresentationWindowOptions {
|
|
@@ -53,7 +55,7 @@ export interface PresentationWindowOptions {
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
// TODO: Automatically inject latest version when building in CI.
|
|
56
|
-
const defaultAppRendererUrl = 'https://apps.raydiant.com/3.0.
|
|
58
|
+
const defaultAppRendererUrl = 'https://apps.raydiant.com/3.0.1/index.html';
|
|
57
59
|
|
|
58
60
|
const isValidNumber = (value: any) =>
|
|
59
61
|
typeof value === 'number' && !isNaN(value);
|
|
@@ -74,21 +76,18 @@ export default class PresentationWindow {
|
|
|
74
76
|
private completeCallback: (() => void) | null = null;
|
|
75
77
|
private errorCallback: ((err: Error) => void) | null = null;
|
|
76
78
|
private readyTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
77
|
-
// private completeThrottleTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
78
79
|
private windowManagers: { [windowId: string]: PlaylistWindowManager } = {};
|
|
79
80
|
private focusedWindowManager: PlaylistWindowManager | null = null;
|
|
80
81
|
private didEmbeddedAppLoad: boolean = false;
|
|
81
82
|
private lastPlayedAt: Date | null = null;
|
|
82
|
-
private configurableDurationTimeout: ReturnType<
|
|
83
|
-
|
|
84
|
-
> | null = null;
|
|
83
|
+
private configurableDurationTimeout: ReturnType<typeof setTimeout> | null =
|
|
84
|
+
null;
|
|
85
85
|
private isWindowLocked: boolean = false;
|
|
86
86
|
private isChildWindowLocked: { [windowId: string]: boolean } = {};
|
|
87
87
|
private enableInterruptLockCallback: (() => void) | null = null;
|
|
88
88
|
private disableInterruptLockCallback: (() => void) | null = null;
|
|
89
|
-
private analyticsEventCallback:
|
|
90
|
-
|
|
91
|
-
| null = null;
|
|
89
|
+
private analyticsEventCallback: ((event: AnalyticsEvent) => void) | null =
|
|
90
|
+
null;
|
|
92
91
|
|
|
93
92
|
private builderState: BuilderState | undefined;
|
|
94
93
|
private presentation: Presentation;
|
|
@@ -101,6 +100,14 @@ export default class PresentationWindow {
|
|
|
101
100
|
private workerState: WorkerState = {};
|
|
102
101
|
private workerSubscriptions = new Set<string>();
|
|
103
102
|
private runtimeSubscriptions = new Set<string>();
|
|
103
|
+
private previewSubscriptions = new Set<string>();
|
|
104
|
+
private previewState: PreviewState = { inputs: {} };
|
|
105
|
+
|
|
106
|
+
private createObjectURLResolvers: Record<string, (url: string) => void> = {};
|
|
107
|
+
private createObjectURLTimeouts: Record<
|
|
108
|
+
string,
|
|
109
|
+
ReturnType<typeof setTimeout>
|
|
110
|
+
> = {};
|
|
104
111
|
|
|
105
112
|
constructor(private options: PresentationWindowOptions) {
|
|
106
113
|
this.builderState = options.builderState;
|
|
@@ -138,7 +145,7 @@ export default class PresentationWindow {
|
|
|
138
145
|
|
|
139
146
|
// Show child windows after main window, setting focus if necessary.
|
|
140
147
|
let isChildWindowFocused = false;
|
|
141
|
-
Object.values(this.windowManagers).map(wm => {
|
|
148
|
+
Object.values(this.windowManagers).map((wm) => {
|
|
142
149
|
if (
|
|
143
150
|
isFocused &&
|
|
144
151
|
this.focusedWindowManager &&
|
|
@@ -161,7 +168,7 @@ export default class PresentationWindow {
|
|
|
161
168
|
public hide(): void {
|
|
162
169
|
const { windowId, renderer } = this.options;
|
|
163
170
|
// Hide child windows before main window.
|
|
164
|
-
Object.values(this.windowManagers).map(wm => wm.hide());
|
|
171
|
+
Object.values(this.windowManagers).map((wm) => wm.hide());
|
|
165
172
|
renderer.hideWindow(windowId);
|
|
166
173
|
}
|
|
167
174
|
|
|
@@ -189,7 +196,7 @@ export default class PresentationWindow {
|
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
// Remove any child window managers.
|
|
192
|
-
Object.values(this.windowManagers).forEach(wm => {
|
|
199
|
+
Object.values(this.windowManagers).forEach((wm) => {
|
|
193
200
|
wm.destroy();
|
|
194
201
|
});
|
|
195
202
|
|
|
@@ -197,7 +204,7 @@ export default class PresentationWindow {
|
|
|
197
204
|
try {
|
|
198
205
|
await renderer.removeWindow(windowId);
|
|
199
206
|
this.logInfo(`Removed window '${windowId}'`);
|
|
200
|
-
} catch (err) {
|
|
207
|
+
} catch (err: any) {
|
|
201
208
|
this.logError(
|
|
202
209
|
`Failed to remove window '${windowId}' with '${err.message}'`
|
|
203
210
|
);
|
|
@@ -208,7 +215,7 @@ export default class PresentationWindow {
|
|
|
208
215
|
public reinitializePresentationWindowRuleEngineState(
|
|
209
216
|
newRulesDataStoreObj: RE.RulesDataStore
|
|
210
217
|
) {
|
|
211
|
-
Object.values(this.windowManagers).forEach(wm => {
|
|
218
|
+
Object.values(this.windowManagers).forEach((wm) => {
|
|
212
219
|
wm.reinitializeRuleEngineState(newRulesDataStoreObj);
|
|
213
220
|
});
|
|
214
221
|
}
|
|
@@ -229,10 +236,10 @@ export default class PresentationWindow {
|
|
|
229
236
|
}
|
|
230
237
|
|
|
231
238
|
if (!this.isEmbeddedApp()) {
|
|
232
|
-
this.
|
|
239
|
+
this.sendMessageString('play');
|
|
233
240
|
|
|
234
241
|
// Play any child window managers.
|
|
235
|
-
Object.values(this.windowManagers).forEach(wm => {
|
|
242
|
+
Object.values(this.windowManagers).forEach((wm) => {
|
|
236
243
|
// We only want to trigger play on child windows the first play of the main window or if
|
|
237
244
|
// the child window is paused.
|
|
238
245
|
if (!wm.isPlaying() || wm.isPaused()) {
|
|
@@ -241,7 +248,7 @@ export default class PresentationWindow {
|
|
|
241
248
|
});
|
|
242
249
|
} else if (this.runtimeSubscriptions.has('play')) {
|
|
243
250
|
// If the embedded app has subscribed to the play event, send it a play event.
|
|
244
|
-
this.
|
|
251
|
+
this.sendMessageString('play');
|
|
245
252
|
}
|
|
246
253
|
|
|
247
254
|
this.lastPlayedAt = new Date();
|
|
@@ -249,12 +256,12 @@ export default class PresentationWindow {
|
|
|
249
256
|
|
|
250
257
|
public pause() {
|
|
251
258
|
// Pause all child window managers.
|
|
252
|
-
Object.values(this.windowManagers).forEach(wm => wm.pause());
|
|
259
|
+
Object.values(this.windowManagers).forEach((wm) => wm.pause());
|
|
253
260
|
}
|
|
254
261
|
|
|
255
262
|
public resume() {
|
|
256
263
|
// Resume all child window managers.
|
|
257
|
-
Object.values(this.windowManagers).forEach(wm => wm.resume());
|
|
264
|
+
Object.values(this.windowManagers).forEach((wm) => wm.resume());
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
public reset(): void {
|
|
@@ -342,12 +349,28 @@ export default class PresentationWindow {
|
|
|
342
349
|
|
|
343
350
|
if (this.isEmbeddedApp()) {
|
|
344
351
|
if (applicationVersion.embeddedUrlFormat) {
|
|
345
|
-
|
|
352
|
+
if (this.previewSubscriptions.has('inputs')) {
|
|
353
|
+
this.logInfo(
|
|
354
|
+
`Reload window '${windowId}' for embedded app preview state`
|
|
355
|
+
);
|
|
346
356
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
357
|
+
const updatedInputs = transformInputValues(
|
|
358
|
+
this.presentation.applicationVariables
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
if (!deepEqual(updatedInputs, this.previewState.inputs)) {
|
|
362
|
+
this.sendMessageObject('preview-inputs', updatedInputs);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.previewState.inputs = updatedInputs;
|
|
366
|
+
} else {
|
|
367
|
+
const url = this.getEmbeddedAppUrl();
|
|
368
|
+
|
|
369
|
+
this.logInfo(
|
|
370
|
+
`Reload window '${windowId}' for embedded app url '${url}'`
|
|
371
|
+
);
|
|
372
|
+
await this.loadWindow(url);
|
|
373
|
+
}
|
|
351
374
|
}
|
|
352
375
|
} else {
|
|
353
376
|
this.logInfo(`Reload window '${windowId}'`);
|
|
@@ -359,10 +382,18 @@ export default class PresentationWindow {
|
|
|
359
382
|
// Do no support conditional properties for embedded apps. We will likely
|
|
360
383
|
// change the way conditional properties work.
|
|
361
384
|
if (!this.isEmbeddedApp()) {
|
|
362
|
-
this.
|
|
385
|
+
this.sendMessageString('run_get_properties', builderState);
|
|
363
386
|
}
|
|
364
387
|
}
|
|
365
388
|
|
|
389
|
+
public async createObjectURL(blob: Blob): Promise<string> {
|
|
390
|
+
if (this.isEmbeddedApp()) {
|
|
391
|
+
return this.createObjectURLForEmbeddedApp(blob);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return URL.createObjectURL(blob);
|
|
395
|
+
}
|
|
396
|
+
|
|
366
397
|
private isEmbeddedApp() {
|
|
367
398
|
const { applicationVersion, isPreview } = this.options;
|
|
368
399
|
|
|
@@ -382,7 +413,7 @@ export default class PresentationWindow {
|
|
|
382
413
|
return true;
|
|
383
414
|
}
|
|
384
415
|
|
|
385
|
-
return Object.values(this.windowManagers).every(wm => wm.isReady());
|
|
416
|
+
return Object.values(this.windowManagers).every((wm) => wm.isReady());
|
|
386
417
|
}
|
|
387
418
|
|
|
388
419
|
private checkIsReadyCallback() {
|
|
@@ -440,7 +471,7 @@ export default class PresentationWindow {
|
|
|
440
471
|
const {
|
|
441
472
|
windowId,
|
|
442
473
|
appRendererUrl = defaultAppRendererUrl,
|
|
443
|
-
backgroundColor
|
|
474
|
+
backgroundColor,
|
|
444
475
|
} = this.options;
|
|
445
476
|
|
|
446
477
|
this.logInfo(`Create window '${windowId}'`);
|
|
@@ -467,7 +498,7 @@ export default class PresentationWindow {
|
|
|
467
498
|
bounds,
|
|
468
499
|
backgroundColor,
|
|
469
500
|
receiveMessageCallback,
|
|
470
|
-
errorCallback: this.handleError
|
|
501
|
+
errorCallback: this.handleError,
|
|
471
502
|
});
|
|
472
503
|
|
|
473
504
|
// Mute new window. This ensures preloaded windows are muted by default and
|
|
@@ -486,25 +517,36 @@ export default class PresentationWindow {
|
|
|
486
517
|
try {
|
|
487
518
|
await renderer.loadWindow(windowId, url);
|
|
488
519
|
this.logInfo(`Loaded window '${windowId}'`);
|
|
489
|
-
} catch (err) {
|
|
520
|
+
} catch (err: any) {
|
|
490
521
|
this.handleError(
|
|
491
522
|
new WindowLoadError(`Failed to load window with '${err.message}'`)
|
|
492
523
|
);
|
|
493
524
|
}
|
|
494
525
|
}
|
|
495
526
|
|
|
496
|
-
|
|
527
|
+
// Send a JSON stringified message to the renderer. This is the legacy way of sending
|
|
528
|
+
// messages and should be deprecated in favor of sendMessageObject.
|
|
529
|
+
private sendMessageString(type: string, payload?: any) {
|
|
497
530
|
const { windowId, renderer } = this.options;
|
|
498
531
|
const message = JSON.stringify({ type, payload, windowId });
|
|
499
532
|
|
|
500
533
|
this.logInfo(`Sending '${type}' to window '${windowId}'`);
|
|
501
|
-
renderer.
|
|
534
|
+
renderer.sendMessageString(windowId, message);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Send a message object to the renderer. This is the preferred way of sending messages
|
|
538
|
+
// going forward since it allows for more complex payloads like ArrayBuffers (for File Inputs).
|
|
539
|
+
private sendMessageObject(type: string, payload?: any) {
|
|
540
|
+
const { windowId, renderer } = this.options;
|
|
541
|
+
|
|
542
|
+
this.logInfo(`Sending '${type}' to window '${windowId}'`);
|
|
543
|
+
renderer.sendMessageObject(windowId, type, payload);
|
|
502
544
|
}
|
|
503
545
|
|
|
504
546
|
private waitForPesentationReady(): Promise<void> {
|
|
505
547
|
const { windowId, readyTimeoutDurationSeconds = 30 } = this.options;
|
|
506
548
|
|
|
507
|
-
return new Promise<void>(resolve => {
|
|
549
|
+
return new Promise<void>((resolve) => {
|
|
508
550
|
if (this.didReceiveReady) {
|
|
509
551
|
resolve();
|
|
510
552
|
} else {
|
|
@@ -564,7 +606,7 @@ export default class PresentationWindow {
|
|
|
564
606
|
// IFrames do not have a mute/unmute API so we are overriding any mute properties
|
|
565
607
|
// to support muting MZ zones when using the IFrameRenderer (SecondScreen).
|
|
566
608
|
if (this.forceMute) {
|
|
567
|
-
this.
|
|
609
|
+
this.sendMessageString('render', {
|
|
568
610
|
builderState: this.builderState,
|
|
569
611
|
presentation: {
|
|
570
612
|
...this.presentation,
|
|
@@ -573,22 +615,22 @@ export default class PresentationWindow {
|
|
|
573
615
|
// TODO: We need a better way to identify mute properties (ie. PropType.mute())
|
|
574
616
|
// Force mute the presentation, existing apps use either mute or muted.
|
|
575
617
|
mute: true,
|
|
576
|
-
muted: true
|
|
577
|
-
}
|
|
618
|
+
muted: true,
|
|
619
|
+
},
|
|
578
620
|
},
|
|
579
621
|
theme: this.buildTheme(),
|
|
580
622
|
applicationVersion,
|
|
581
623
|
device,
|
|
582
|
-
isPreview
|
|
624
|
+
isPreview,
|
|
583
625
|
});
|
|
584
626
|
} else {
|
|
585
|
-
this.
|
|
627
|
+
this.sendMessageString('render', {
|
|
586
628
|
builderState: this.builderState,
|
|
587
629
|
presentation: this.presentation,
|
|
588
630
|
theme: this.buildTheme(),
|
|
589
631
|
applicationVersion,
|
|
590
632
|
device,
|
|
591
|
-
isPreview
|
|
633
|
+
isPreview,
|
|
592
634
|
});
|
|
593
635
|
}
|
|
594
636
|
}
|
|
@@ -608,7 +650,7 @@ export default class PresentationWindow {
|
|
|
608
650
|
readyTimeoutDurationSeconds,
|
|
609
651
|
appRendererUrl,
|
|
610
652
|
logger,
|
|
611
|
-
isPreview
|
|
653
|
+
isPreview,
|
|
612
654
|
} = this.options;
|
|
613
655
|
const parentWindowManager = this.options.windowManager;
|
|
614
656
|
|
|
@@ -664,31 +706,31 @@ export default class PresentationWindow {
|
|
|
664
706
|
x: bounds.x,
|
|
665
707
|
y: bounds.y,
|
|
666
708
|
width: bounds.width,
|
|
667
|
-
height: bounds.height
|
|
709
|
+
height: bounds.height,
|
|
668
710
|
},
|
|
669
711
|
backgroundColor: 'transparent',
|
|
670
712
|
themeOverride,
|
|
671
713
|
forceMute,
|
|
672
714
|
isPreview,
|
|
673
715
|
onEnableInterruptLock: () => this.enableInteruptLock(windowName),
|
|
674
|
-
onDisableInterruptLock: () => this.disableInteruptLock(windowName)
|
|
716
|
+
onDisableInterruptLock: () => this.disableInteruptLock(windowName),
|
|
675
717
|
});
|
|
676
718
|
|
|
677
719
|
// Hook up child window manager events to bubble up to the parent.
|
|
678
720
|
if (parentWindowManager) {
|
|
679
|
-
windowManager.on('window_preload_error', metadata => {
|
|
721
|
+
windowManager.on('window_preload_error', (metadata) => {
|
|
680
722
|
parentWindowManager.emit('window_preload_error', metadata);
|
|
681
723
|
});
|
|
682
|
-
windowManager.on('window_play', metadata => {
|
|
724
|
+
windowManager.on('window_play', (metadata) => {
|
|
683
725
|
parentWindowManager.emit('window_play', metadata);
|
|
684
726
|
});
|
|
685
|
-
windowManager.on('window_end_success', metadata => {
|
|
727
|
+
windowManager.on('window_end_success', (metadata) => {
|
|
686
728
|
parentWindowManager.emit('window_end_success', metadata);
|
|
687
729
|
});
|
|
688
|
-
windowManager.on('window_end_error', metadata => {
|
|
730
|
+
windowManager.on('window_end_error', (metadata) => {
|
|
689
731
|
parentWindowManager.emit('window_end_error', metadata);
|
|
690
732
|
});
|
|
691
|
-
windowManager.on('window_analytics_event', metadata => {
|
|
733
|
+
windowManager.on('window_analytics_event', (metadata) => {
|
|
692
734
|
parentWindowManager.emit('window_analytics_event', metadata);
|
|
693
735
|
});
|
|
694
736
|
}
|
|
@@ -764,7 +806,7 @@ export default class PresentationWindow {
|
|
|
764
806
|
const { windowId } = this.options;
|
|
765
807
|
|
|
766
808
|
const metadata: EmbeddedAppMetadata = {
|
|
767
|
-
renderMode: this.options.isPreview ? 'preview' : 'live'
|
|
809
|
+
renderMode: this.options.isPreview ? 'preview' : 'live',
|
|
768
810
|
};
|
|
769
811
|
|
|
770
812
|
const url = createEmbeddedApp(
|
|
@@ -809,7 +851,7 @@ export default class PresentationWindow {
|
|
|
809
851
|
|
|
810
852
|
// Immediately send play if the app if already playing.
|
|
811
853
|
if (this.isPlaying()) {
|
|
812
|
-
this.
|
|
854
|
+
this.sendMessageString('play');
|
|
813
855
|
}
|
|
814
856
|
} else {
|
|
815
857
|
this.logWarning(
|
|
@@ -845,9 +887,9 @@ export default class PresentationWindow {
|
|
|
845
887
|
// as the app subscribes.
|
|
846
888
|
const workerState = this.workerState[payload];
|
|
847
889
|
if (workerState !== undefined) {
|
|
848
|
-
this.
|
|
890
|
+
this.sendMessageString('worker-state', {
|
|
849
891
|
name: payload,
|
|
850
|
-
state: workerState
|
|
892
|
+
state: workerState,
|
|
851
893
|
});
|
|
852
894
|
}
|
|
853
895
|
} else {
|
|
@@ -881,6 +923,56 @@ export default class PresentationWindow {
|
|
|
881
923
|
break;
|
|
882
924
|
}
|
|
883
925
|
|
|
926
|
+
case 'create-object-url-result': {
|
|
927
|
+
if (isValidCreateObjectURLResultPayload(payload)) {
|
|
928
|
+
// Resolve promise with the local blob URL.
|
|
929
|
+
if (this.createObjectURLResolvers[payload.requestId]) {
|
|
930
|
+
this.createObjectURLResolvers[payload.requestId](payload.url);
|
|
931
|
+
delete this.createObjectURLResolvers[payload.requestId];
|
|
932
|
+
}
|
|
933
|
+
// Clear the timeout for the request.
|
|
934
|
+
if (this.createObjectURLTimeouts[payload.requestId]) {
|
|
935
|
+
clearTimeout(this.createObjectURLTimeouts[payload.requestId]);
|
|
936
|
+
delete this.createObjectURLTimeouts[payload.requestId];
|
|
937
|
+
}
|
|
938
|
+
} else {
|
|
939
|
+
this.logWarning(
|
|
940
|
+
`Invalid payload for create-object-url-result message: ${payload}`
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
break;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
case 'preview-subscribe': {
|
|
947
|
+
if (typeof payload === 'string') {
|
|
948
|
+
this.logInfo(
|
|
949
|
+
`Received 'preview-subscribe' from window '${windowId}'`
|
|
950
|
+
);
|
|
951
|
+
// The payload is the event name (ex. 'inputs').
|
|
952
|
+
this.previewSubscriptions.add(payload);
|
|
953
|
+
} else {
|
|
954
|
+
this.logWarning(
|
|
955
|
+
`Invalid payload for preview-subscribe message: ${payload}`
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
case 'preview-unsubscribe': {
|
|
962
|
+
if (typeof payload === 'string') {
|
|
963
|
+
this.logInfo(
|
|
964
|
+
`Received 'preview-unsubscribe' from window '${windowId}'`
|
|
965
|
+
);
|
|
966
|
+
// The payload is the event name (ex. 'inputs').
|
|
967
|
+
this.previewSubscriptions.delete(payload);
|
|
968
|
+
} else {
|
|
969
|
+
this.logWarning(
|
|
970
|
+
`Invalid payload for preview-subscribe message: ${payload}`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
|
|
884
976
|
default: {
|
|
885
977
|
this.logWarning(`Received unknown embedded message type: ${type}`);
|
|
886
978
|
}
|
|
@@ -933,7 +1025,7 @@ export default class PresentationWindow {
|
|
|
933
1025
|
x: Math.round(payload.x),
|
|
934
1026
|
y: Math.round(payload.y),
|
|
935
1027
|
width: Math.round(payload.width),
|
|
936
|
-
height: Math.round(payload.height)
|
|
1028
|
+
height: Math.round(payload.height),
|
|
937
1029
|
},
|
|
938
1030
|
payload.isMuted,
|
|
939
1031
|
payload.isFocused,
|
|
@@ -1037,7 +1129,7 @@ export default class PresentationWindow {
|
|
|
1037
1129
|
publishedAt,
|
|
1038
1130
|
playlistId,
|
|
1039
1131
|
playlistName,
|
|
1040
|
-
rule
|
|
1132
|
+
rule,
|
|
1041
1133
|
} = this.options;
|
|
1042
1134
|
|
|
1043
1135
|
return {
|
|
@@ -1052,7 +1144,7 @@ export default class PresentationWindow {
|
|
|
1052
1144
|
publishedAt,
|
|
1053
1145
|
playlistId,
|
|
1054
1146
|
playlistName,
|
|
1055
|
-
rule
|
|
1147
|
+
rule,
|
|
1056
1148
|
};
|
|
1057
1149
|
}
|
|
1058
1150
|
|
|
@@ -1071,9 +1163,9 @@ export default class PresentationWindow {
|
|
|
1071
1163
|
workerState !== undefined &&
|
|
1072
1164
|
!deepEqual(workerState, previousWorkerState)
|
|
1073
1165
|
) {
|
|
1074
|
-
this.
|
|
1166
|
+
this.sendMessageString('worker-state', {
|
|
1075
1167
|
name: workerName,
|
|
1076
|
-
state: workerState
|
|
1168
|
+
state: workerState,
|
|
1077
1169
|
});
|
|
1078
1170
|
}
|
|
1079
1171
|
}
|
|
@@ -1097,15 +1189,15 @@ export default class PresentationWindow {
|
|
|
1097
1189
|
{
|
|
1098
1190
|
type: 'equals',
|
|
1099
1191
|
left: { var: ['target', 'hasPlaylists'] },
|
|
1100
|
-
right: { const: false }
|
|
1101
|
-
}
|
|
1102
|
-
]
|
|
1192
|
+
right: { const: false },
|
|
1193
|
+
},
|
|
1194
|
+
],
|
|
1103
1195
|
}
|
|
1104
1196
|
: {
|
|
1105
1197
|
// A global rule was not provided so we don't need to combine the rule with an AND.
|
|
1106
1198
|
type: 'equals',
|
|
1107
1199
|
left: { var: ['target', 'hasPlaylists'] },
|
|
1108
|
-
right: { const: false }
|
|
1200
|
+
right: { const: false },
|
|
1109
1201
|
}
|
|
1110
1202
|
);
|
|
1111
1203
|
}
|
|
@@ -1141,4 +1233,85 @@ export default class PresentationWindow {
|
|
|
1141
1233
|
appVars.muteZone1 === true && appVars.muteZone2 === true;
|
|
1142
1234
|
return isMutedMultizone;
|
|
1143
1235
|
}
|
|
1236
|
+
|
|
1237
|
+
private createObjectURLForEmbeddedApp(blob: Blob): Promise<string> {
|
|
1238
|
+
return new Promise((resolve, reject) => {
|
|
1239
|
+
// Handle multiple concurrent createObjectURL calls by creating a request id for each
|
|
1240
|
+
// call. We send the request id along with the message to the running app and the app
|
|
1241
|
+
// sends the request id back with the result so we can resolve the correct promise.
|
|
1242
|
+
const requestId = uuidv4();
|
|
1243
|
+
this.createObjectURLResolvers[requestId] = resolve;
|
|
1244
|
+
|
|
1245
|
+
// Start a timer to reject the promise if the app doesn't respond with the create object
|
|
1246
|
+
// url result within 1 second. This should be more or less instant.
|
|
1247
|
+
this.createObjectURLTimeouts[requestId] = setTimeout(() => {
|
|
1248
|
+
const error = new Error(
|
|
1249
|
+
`Timed out waiting for the create-object-url-result message for request id ${requestId}`
|
|
1250
|
+
);
|
|
1251
|
+
reject(error);
|
|
1252
|
+
delete this.createObjectURLResolvers[requestId];
|
|
1253
|
+
delete this.createObjectURLTimeouts[requestId];
|
|
1254
|
+
}, 1000);
|
|
1255
|
+
|
|
1256
|
+
// Create a FileReader object to read the file data as an ArrayBuffer.
|
|
1257
|
+
const reader = new FileReader();
|
|
1258
|
+
|
|
1259
|
+
reader.addEventListener(
|
|
1260
|
+
'load',
|
|
1261
|
+
() => {
|
|
1262
|
+
const buffer = reader.result;
|
|
1263
|
+
|
|
1264
|
+
// Check if buffer is an ArrayBuffer
|
|
1265
|
+
if (buffer instanceof ArrayBuffer) {
|
|
1266
|
+
// Send the ArrayBuffer to the running app to create the local file blob URL.
|
|
1267
|
+
this.sendMessageObject('create-object-url', { requestId, buffer });
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1270
|
+
false
|
|
1271
|
+
);
|
|
1272
|
+
|
|
1273
|
+
reader.readAsArrayBuffer(blob);
|
|
1274
|
+
|
|
1275
|
+
return '';
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1144
1278
|
}
|
|
1279
|
+
|
|
1280
|
+
const isValidCreateObjectURLResultPayload = (
|
|
1281
|
+
payload: any
|
|
1282
|
+
): payload is { requestId: string; url: string } => {
|
|
1283
|
+
return (
|
|
1284
|
+
// Ensure payload is an object.
|
|
1285
|
+
typeof payload === 'object' &&
|
|
1286
|
+
payload !== null &&
|
|
1287
|
+
// Ensure requestId is a string.
|
|
1288
|
+
'requestId' in payload &&
|
|
1289
|
+
typeof payload['requestId'] === 'string' &&
|
|
1290
|
+
// Ensure payload.file is an object.
|
|
1291
|
+
'url' in payload &&
|
|
1292
|
+
typeof payload['url'] === 'string'
|
|
1293
|
+
);
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
const transformInputValues = (inputs: Record<string, any>) => {
|
|
1297
|
+
const transformed: Record<string, any> = {};
|
|
1298
|
+
|
|
1299
|
+
// Recursively traverse inputs, transforming any file input values to remove internal
|
|
1300
|
+
// properties like file_upload_id.
|
|
1301
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
1302
|
+
if (typeof value === 'object' && value !== null && 'url' in value) {
|
|
1303
|
+
// Value is a file input, strip out the file_upload_id property.
|
|
1304
|
+
transformed[key] = { url: value.url };
|
|
1305
|
+
} else if (Array.isArray(value)) {
|
|
1306
|
+
// Value is an array input, traverse each item in the array for file input values.
|
|
1307
|
+
transformed[key] = [];
|
|
1308
|
+
for (const item of value) {
|
|
1309
|
+
transformed[key].push(transformInputValues(item));
|
|
1310
|
+
}
|
|
1311
|
+
} else {
|
|
1312
|
+
transformed[key] = value;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
return transformed;
|
|
1317
|
+
};
|