@raydiant/playlist-window-manager 0.15.2 → 0.16.0

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.
@@ -1,6 +1,6 @@
1
1
  import * as RE from '@raydiant/playlist-rule-engine';
2
2
  import EventEmitter from 'eventemitter3';
3
- import { PresentationWindowRenderer, Device, WindowBounds, Theme, Logger } from './types';
3
+ import { PresentationWindowRenderer, Device, WindowBounds, Theme, Logger, WorkerState } from './types';
4
4
  import PlaylistWindowManagerState from './PlaylistWindowManagerState';
5
5
  export interface PlaylistWindowManagerOptions {
6
6
  ruleEngine: RE.RuleEngine;
@@ -40,6 +40,7 @@ export default class PlaylistWindowManager extends EventEmitter {
40
40
  private themeOverride?;
41
41
  private forceMute;
42
42
  private isFocused;
43
+ private workerState;
43
44
  constructor(options: PlaylistWindowManagerOptions);
44
45
  private currentPresentationId;
45
46
  private nextPresentationId;
@@ -73,6 +74,7 @@ export default class PlaylistWindowManager extends EventEmitter {
73
74
  hide(): Promise<void>;
74
75
  isReady(): boolean;
75
76
  isLocked(): boolean;
77
+ runWorkerSubscriptions(state: WorkerState): void;
76
78
  private createWindow;
77
79
  private loadCurrentWindow;
78
80
  private shouldPreload;
@@ -93,6 +93,7 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
93
93
  _this.lastCompleteAt = null;
94
94
  _this.forceMute = false;
95
95
  _this.isFocused = false;
96
+ _this.workerState = {};
96
97
  _this.handleEnableInterruptLock = function () {
97
98
  var onEnableInterruptLock = _this.options.onEnableInterruptLock;
98
99
  if (onEnableInterruptLock) {
@@ -619,6 +620,18 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
619
620
  }
620
621
  return this.currentWindow.isLocked();
621
622
  };
623
+ PlaylistWindowManager.prototype.runWorkerSubscriptions = function (state) {
624
+ // Persist worker state so new windows can be initialized with it.
625
+ this.workerState = state;
626
+ // Run worker subcriptions on the current window.
627
+ if (this.currentWindow) {
628
+ this.currentWindow.runWorkerSubscriptions(state);
629
+ }
630
+ // Run worker subscriptions on the next window.
631
+ if (this.nextWindow) {
632
+ this.nextWindow.runWorkerSubscriptions(state);
633
+ }
634
+ };
622
635
  PlaylistWindowManager.prototype.createWindow = function (pr) {
623
636
  var _a = this.options, readyTimeoutDurationSeconds = _a.readyTimeoutDurationSeconds, appRendererUrl = _a.appRendererUrl, backgroundColor = _a.backgroundColor, device = _a.device, logger = _a.logger, windowName = _a.windowName, parentWindowId = _a.parentWindowId, isPreview = _a.isPreview;
624
637
  if (!this.ruleEngine) {
@@ -660,7 +673,8 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
660
673
  playlistWindowManagerState: this.state,
661
674
  ruleEngineState: this.ruleEngine.state,
662
675
  globalRule: this.ruleEngine.globalRule,
663
- isPreview: isPreview
676
+ isPreview: isPreview,
677
+ workerState: this.workerState
664
678
  });
665
679
  };
666
680
  PlaylistWindowManager.prototype.loadCurrentWindow = function () {
@@ -1,7 +1,7 @@
1
1
  import * as RE from '@raydiant/playlist-rule-engine';
2
2
  import PlaylistWindowManagerState from './PlaylistWindowManagerState';
3
3
  import PlaylistWindowManager from './PlaylistWindowManager';
4
- import { Presentation, ApplicationVersion, Theme, Device, PresentationWindowRenderer, WindowBounds, Logger, BuilderState } from './types';
4
+ import { Presentation, ApplicationVersion, Theme, Device, PresentationWindowRenderer, WindowBounds, Logger, BuilderState, WorkerState } from './types';
5
5
  export interface PresentationWindowOptions {
6
6
  windowManager?: PlaylistWindowManager;
7
7
  windowId: string;
@@ -27,6 +27,7 @@ export interface PresentationWindowOptions {
27
27
  backgroundColor?: string;
28
28
  isPreview?: boolean;
29
29
  forceMute?: boolean;
30
+ workerState: WorkerState;
30
31
  onPresentationProperties?: (properties: any[], string: {
31
32
  [key: string]: string;
32
33
  }) => void;
@@ -55,6 +56,8 @@ export default class PresentationWindow {
55
56
  private theme;
56
57
  private forceMute;
57
58
  private error;
59
+ private workerState;
60
+ private workerSubscriptions;
58
61
  constructor(options: PresentationWindowOptions);
59
62
  load(): Promise<void>;
60
63
  show(isFocused?: boolean): void;
@@ -84,8 +87,9 @@ export default class PresentationWindow {
84
87
  private isEmbeddedApp;
85
88
  private allChildWindowManagersReady;
86
89
  private checkIsReadyCallback;
90
+ private appendWindowIdToHash;
87
91
  private loadEmbeddedApp;
88
- private loadRaydiantApp;
92
+ private loadLegacyApp;
89
93
  private createAndLoadWindow;
90
94
  private loadWindow;
91
95
  private sendMessage;
@@ -96,7 +100,8 @@ export default class PresentationWindow {
96
100
  private closeChildWindow;
97
101
  private enableInteruptLock;
98
102
  private disableInteruptLock;
99
- private handleMessage;
103
+ private handleEmbeddedMessage;
104
+ private handleLegacyMessage;
100
105
  private handleError;
101
106
  private logDebug;
102
107
  private logInfo;
@@ -117,6 +122,7 @@ export default class PresentationWindow {
117
122
  playlistName: string | undefined;
118
123
  rule: RE.RuleOperatorType | RE.RuleGroupType | RE.RuleScheduleType | null | undefined;
119
124
  };
125
+ runWorkerSubscriptions(state: WorkerState): void;
120
126
  private createRuleEngine;
121
127
  private isValidOpenChildWindowPayload;
122
128
  private shouldMute;
@@ -63,7 +63,7 @@ var PlaylistWindowManager_1 = __importDefault(require("./PlaylistWindowManager")
63
63
  var WindowTimeoutError_1 = __importDefault(require("./WindowTimeoutError"));
64
64
  var WindowLoadError_1 = __importDefault(require("./WindowLoadError"));
65
65
  // TODO: Automatically inject latest version when building in CI.
66
- var defaultAppRendererUrl = 'https://apps.raydiant.com/1.4.7/index.html';
66
+ var defaultAppRendererUrl = 'https://apps.raydiant.com/2.0.0/index.html';
67
67
  var isValidNumber = function (value) {
68
68
  return typeof value === 'number' && !isNaN(value);
69
69
  };
@@ -89,7 +89,43 @@ var PresentationWindow = /** @class */ (function () {
89
89
  this.disableInterruptLockCallback = null;
90
90
  this.forceMute = false;
91
91
  this.error = null;
92
- this.handleMessage = function (type, payload) { return __awaiter(_this, void 0, void 0, function () {
92
+ this.workerState = {};
93
+ this.workerSubscriptions = new Set();
94
+ this.handleEmbeddedMessage = function (type, payload) { return __awaiter(_this, void 0, void 0, function () {
95
+ var windowId, workerState;
96
+ return __generator(this, function (_a) {
97
+ windowId = this.options.windowId;
98
+ if (type === 'worker-subscribe') {
99
+ if (typeof payload === 'string') {
100
+ this.logInfo("Received 'worker-subscribe' from window '" + windowId + "'");
101
+ // The payload is the worker name.
102
+ this.workerSubscriptions.add(payload);
103
+ workerState = this.workerState[payload];
104
+ if (workerState !== undefined) {
105
+ this.sendMessage('worker-state', {
106
+ name: payload,
107
+ state: workerState
108
+ });
109
+ }
110
+ }
111
+ else {
112
+ this.logWarning("Invalid payload for worker-subscribe message: " + payload);
113
+ }
114
+ }
115
+ else if (type === 'worker-unsubscribe') {
116
+ if (typeof payload === 'string') {
117
+ this.logInfo("Received 'worker-unsubscribe' from window '" + windowId + "'");
118
+ // The payload is the worker name.
119
+ this.workerSubscriptions.delete(payload);
120
+ }
121
+ else {
122
+ this.logWarning("Invalid payload for worker-unsubscribe message: " + payload);
123
+ }
124
+ }
125
+ return [2 /*return*/];
126
+ });
127
+ }); };
128
+ this.handleLegacyMessage = function (type, payload) { return __awaiter(_this, void 0, void 0, function () {
93
129
  var _a, windowId, onPresentationProperties;
94
130
  return __generator(this, function (_b) {
95
131
  _a = this.options, windowId = _a.windowId, onPresentationProperties = _a.onPresentationProperties;
@@ -168,12 +204,13 @@ var PresentationWindow = /** @class */ (function () {
168
204
  this.ruleEngineState = options.ruleEngineState;
169
205
  this.theme = options.theme;
170
206
  this.forceMute = options.forceMute || false;
207
+ this.workerState = options.workerState;
171
208
  }
172
209
  PresentationWindow.prototype.load = function () {
173
210
  if (this.isEmbeddedApp()) {
174
211
  return this.loadEmbeddedApp();
175
212
  }
176
- return this.loadRaydiantApp();
213
+ return this.loadLegacyApp();
177
214
  };
178
215
  PresentationWindow.prototype.show = function (isFocused) {
179
216
  var _this = this;
@@ -225,6 +262,8 @@ var PresentationWindow = /** @class */ (function () {
225
262
  this.errorCallback = null;
226
263
  this.enableInterruptLockCallback = null;
227
264
  this.disableInterruptLockCallback = null;
265
+ // Clean up subscriptions
266
+ this.workerSubscriptions.clear();
228
267
  // Clear timers
229
268
  if (this.readyTimeout) {
230
269
  clearTimeout(this.readyTimeout);
@@ -379,7 +418,7 @@ var PresentationWindow = /** @class */ (function () {
379
418
  PresentationWindow.prototype.isEmbeddedApp = function () {
380
419
  var _a = this.options, applicationVersion = _a.applicationVersion, isPreview = _a.isPreview;
381
420
  // If the application has a source url, always use it for rendering the preview.
382
- // This fixes an issue with Hoopla which is an embedded app but uses a Raydiant app
421
+ // This fixes an issue with Hoopla which is an embedded app but uses a legacy app
383
422
  // for it's preview.
384
423
  if (isPreview && applicationVersion.sourceUrl) {
385
424
  return false;
@@ -408,6 +447,13 @@ var PresentationWindow = /** @class */ (function () {
408
447
  }
409
448
  }
410
449
  };
450
+ PresentationWindow.prototype.appendWindowIdToHash = function (urlStr, windowId) {
451
+ var url = new URL(urlStr);
452
+ var hashParams = new URLSearchParams(url.hash.slice(1));
453
+ hashParams.set('__raydiantWindowId', windowId);
454
+ url.hash = hashParams.toString();
455
+ return url.toString();
456
+ };
411
457
  PresentationWindow.prototype.loadEmbeddedApp = function () {
412
458
  return __awaiter(this, void 0, void 0, function () {
413
459
  var _a, windowId, applicationVersion, url;
@@ -420,16 +466,11 @@ var PresentationWindow = /** @class */ (function () {
420
466
  }
421
467
  url = embeddedApps_1.default(this.presentation, applicationVersion);
422
468
  this.logInfo("Create window '" + windowId + "' for embedded app url '" + url + "'");
423
- // Embedded apps should not have a backgroundColor bexcause some sites expect
424
- // the browser default of white will be applied. We also don't attach the message
425
- // listeners for embedded apps. We could eventually allow embedded apps to send
426
- // lifecycle events but we should consider namespacing our message events.
427
- return [4 /*yield*/, this.createAndLoadWindow(url, '#ffffff')];
469
+ return [4 /*yield*/, this.createAndLoadWindow(this.appendWindowIdToHash(url, windowId),
470
+ // Embedded apps should not have a backgroundColor because some sites expect
471
+ // the browser default of white will be applied.
472
+ '#ffffff', this.handleEmbeddedMessage)];
428
473
  case 1:
429
- // Embedded apps should not have a backgroundColor bexcause some sites expect
430
- // the browser default of white will be applied. We also don't attach the message
431
- // listeners for embedded apps. We could eventually allow embedded apps to send
432
- // lifecycle events but we should consider namespacing our message events.
433
474
  _b.sent();
434
475
  this.didEmbeddedAppLoad = true;
435
476
  this.checkIsReadyCallback();
@@ -438,17 +479,16 @@ var PresentationWindow = /** @class */ (function () {
438
479
  });
439
480
  });
440
481
  };
441
- PresentationWindow.prototype.loadRaydiantApp = function () {
482
+ PresentationWindow.prototype.loadLegacyApp = function () {
442
483
  return __awaiter(this, void 0, void 0, function () {
443
- var _a, windowId, _b, appRendererUrl, backgroundColor, url;
484
+ var _a, windowId, _b, appRendererUrl, backgroundColor;
444
485
  return __generator(this, function (_c) {
445
486
  switch (_c.label) {
446
487
  case 0:
447
488
  _a = this.options, windowId = _a.windowId, _b = _a.appRendererUrl, appRendererUrl = _b === void 0 ? defaultAppRendererUrl : _b, backgroundColor = _a.backgroundColor;
448
- url = appRendererUrl + "#windowId=" + windowId;
449
489
  this.logInfo("Create window '" + windowId + "'");
450
- return [4 /*yield*/, this.createAndLoadWindow(url, backgroundColor || '#000000', // Default to black bg for Raydiant apps
451
- this.handleMessage)];
490
+ return [4 /*yield*/, this.createAndLoadWindow(this.appendWindowIdToHash(appRendererUrl, windowId), backgroundColor || '#000000', // Default to black bg for Raydiant apps
491
+ this.handleLegacyMessage)];
452
492
  case 1:
453
493
  _c.sent();
454
494
  return [4 /*yield*/, this.waitForPesentationReady()];
@@ -766,6 +806,24 @@ var PresentationWindow = /** @class */ (function () {
766
806
  PresentationWindow.prototype.metadata = function () {
767
807
  return this.getLogContext();
768
808
  };
809
+ PresentationWindow.prototype.runWorkerSubscriptions = function (state) {
810
+ // For each worker subscription, compare the previous state to the new state.
811
+ // If the state has changed, send the new state to the subscription.
812
+ for (var _i = 0, _a = Array.from(this.workerSubscriptions); _i < _a.length; _i++) {
813
+ var workerName = _a[_i];
814
+ var workerState = state[workerName];
815
+ var previousWorkerState = this.workerState[workerName];
816
+ if (workerState !== undefined &&
817
+ !fast_deep_equal_1.default(workerState, previousWorkerState)) {
818
+ this.sendMessage('worker-state', {
819
+ name: workerName,
820
+ state: workerState
821
+ });
822
+ }
823
+ }
824
+ // Persist the new state so we can compare if the previous values have changed..
825
+ this.workerState = state;
826
+ };
769
827
  PresentationWindow.prototype.createRuleEngine = function (playlistId) {
770
828
  var globalRule = this.options.globalRule;
771
829
  return new RE.RuleEngine(playlistId, this.ruleEngineState, globalRule
package/build/preload.js CHANGED
@@ -1,20 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- var electron_1 = require("electron"); // eslint-disable-line
4
- var sendMessage = function (windowId, type, payload) {
5
- electron_1.ipcRenderer.send.apply(electron_1.ipcRenderer, [windowId, type].concat(payload));
3
+ var electron_1 = require("electron");
4
+ var internals = {
5
+ sendMessage: function (windowId, type, payload) {
6
+ electron_1.ipcRenderer.send.apply(electron_1.ipcRenderer, [windowId, type].concat(payload));
7
+ }
6
8
  };
7
- var messageHandler = function (_, message) {
8
- var receiveMessage = window.receiveMessage;
9
- if (typeof receiveMessage === 'function') {
10
- receiveMessage(message);
9
+ // Set __RAYDIANT_INTERNALS on window so it is accessible in to the running application.
10
+ window.__RAYDIANT_INTERNALS = internals;
11
+ // Forward messages from the main process to the app (render process) via receiveMessage.
12
+ // __RAYDIANT_INTERNALS.receiveMessage is required to be set by the app with the Raydiant SDK.
13
+ electron_1.ipcRenderer.on('electron-main', function (_, message) {
14
+ if (typeof internals.receiveMessage === 'function') {
15
+ internals.receiveMessage(message);
11
16
  }
12
17
  else {
13
- throw new Error('receiveMessage is not defined');
18
+ console.error('Failed to handle message from main process, receiveMessage is not defined');
14
19
  }
15
- };
16
- // Add sendMessage and messageHandler to window so that app renderer knows we're
17
- // running in an Electron environment.
18
- window.sendMessage = sendMessage;
19
- window.messageHandler = messageHandler;
20
- electron_1.ipcRenderer.on('electron-main', messageHandler);
20
+ });
package/build/types.d.ts CHANGED
@@ -71,6 +71,9 @@ export interface WindowBounds {
71
71
  width: number;
72
72
  height: number;
73
73
  }
74
+ export interface WorkerState {
75
+ [key: string]: any;
76
+ }
74
77
  export interface CreateWindowOptions {
75
78
  windowId: string;
76
79
  parentWindowId?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raydiant/playlist-window-manager",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "description": "Window manager for playlist playback.",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
@@ -6,7 +6,8 @@ import {
6
6
  Device,
7
7
  WindowBounds,
8
8
  Theme,
9
- Logger
9
+ Logger,
10
+ WorkerState
10
11
  } from './types';
11
12
  import PresentationWindow from './PresentationWindow';
12
13
  import PlaylistWindowManagerState from './PlaylistWindowManagerState';
@@ -99,6 +100,8 @@ export default class PlaylistWindowManager extends EventEmitter {
99
100
  private forceMute: boolean = false;
100
101
  private isFocused: boolean = false;
101
102
 
103
+ private workerState: WorkerState = {};
104
+
102
105
  constructor(private options: PlaylistWindowManagerOptions) {
103
106
  super();
104
107
 
@@ -421,6 +424,21 @@ export default class PlaylistWindowManager extends EventEmitter {
421
424
  return this.currentWindow.isLocked();
422
425
  }
423
426
 
427
+ public runWorkerSubscriptions(state: WorkerState) {
428
+ // Persist worker state so new windows can be initialized with it.
429
+ this.workerState = state;
430
+
431
+ // Run worker subcriptions on the current window.
432
+ if (this.currentWindow) {
433
+ this.currentWindow.runWorkerSubscriptions(state);
434
+ }
435
+
436
+ // Run worker subscriptions on the next window.
437
+ if (this.nextWindow) {
438
+ this.nextWindow.runWorkerSubscriptions(state);
439
+ }
440
+ }
441
+
424
442
  private createWindow(pr: RE.NextPresentationID): PresentationWindow {
425
443
  const {
426
444
  readyTimeoutDurationSeconds,
@@ -483,7 +501,8 @@ export default class PlaylistWindowManager extends EventEmitter {
483
501
  playlistWindowManagerState: this.state,
484
502
  ruleEngineState: this.ruleEngine.state,
485
503
  globalRule: this.ruleEngine.globalRule,
486
- isPreview
504
+ isPreview,
505
+ workerState: this.workerState
487
506
  });
488
507
  }
489
508
 
@@ -14,7 +14,8 @@ import {
14
14
  WindowBounds,
15
15
  Logger,
16
16
  OpenChildWindowOptions,
17
- BuilderState
17
+ BuilderState,
18
+ WorkerState
18
19
  } from './types';
19
20
 
20
21
  export interface PresentationWindowOptions {
@@ -42,6 +43,7 @@ export interface PresentationWindowOptions {
42
43
  backgroundColor?: string;
43
44
  isPreview?: boolean;
44
45
  forceMute?: boolean;
46
+ workerState: WorkerState;
45
47
  onPresentationProperties?: (
46
48
  properties: any[],
47
49
  string: { [key: string]: string }
@@ -49,7 +51,7 @@ export interface PresentationWindowOptions {
49
51
  }
50
52
 
51
53
  // TODO: Automatically inject latest version when building in CI.
52
- const defaultAppRendererUrl = 'https://apps.raydiant.com/1.4.7/index.html';
54
+ const defaultAppRendererUrl = 'https://apps.raydiant.com/2.0.0/index.html';
53
55
 
54
56
  const isValidNumber = (value: any) =>
55
57
  typeof value === 'number' && !isNaN(value);
@@ -82,6 +84,9 @@ export default class PresentationWindow {
82
84
  private forceMute: boolean = false;
83
85
  private error: Error | null = null;
84
86
 
87
+ private workerState: WorkerState = {};
88
+ private workerSubscriptions = new Set<string>();
89
+
85
90
  constructor(private options: PresentationWindowOptions) {
86
91
  this.builderState = options.builderState;
87
92
  this.presentation = options.presentation;
@@ -89,13 +94,14 @@ export default class PresentationWindow {
89
94
  this.ruleEngineState = options.ruleEngineState;
90
95
  this.theme = options.theme;
91
96
  this.forceMute = options.forceMute || false;
97
+ this.workerState = options.workerState;
92
98
  }
93
99
 
94
100
  public load(): Promise<void> {
95
101
  if (this.isEmbeddedApp()) {
96
102
  return this.loadEmbeddedApp();
97
103
  }
98
- return this.loadRaydiantApp();
104
+ return this.loadLegacyApp();
99
105
  }
100
106
 
101
107
  public show(isFocused: boolean = true): void {
@@ -154,6 +160,9 @@ export default class PresentationWindow {
154
160
  this.enableInterruptLockCallback = null;
155
161
  this.disableInterruptLockCallback = null;
156
162
 
163
+ // Clean up subscriptions
164
+ this.workerSubscriptions.clear();
165
+
157
166
  // Clear timers
158
167
  if (this.readyTimeout) {
159
168
  clearTimeout(this.readyTimeout);
@@ -329,7 +338,7 @@ export default class PresentationWindow {
329
338
  const { applicationVersion, isPreview } = this.options;
330
339
 
331
340
  // If the application has a source url, always use it for rendering the preview.
332
- // This fixes an issue with Hoopla which is an embedded app but uses a Raydiant app
341
+ // This fixes an issue with Hoopla which is an embedded app but uses a legacy app
333
342
  // for it's preview.
334
343
  if (isPreview && applicationVersion.sourceUrl) {
335
344
  return false;
@@ -365,6 +374,15 @@ export default class PresentationWindow {
365
374
  }
366
375
  }
367
376
 
377
+ private appendWindowIdToHash(urlStr: string, windowId: string) {
378
+ const url = new URL(urlStr);
379
+ const hashParams = new URLSearchParams(url.hash.slice(1));
380
+ hashParams.set('__raydiantWindowId', windowId);
381
+ url.hash = hashParams.toString();
382
+
383
+ return url.toString();
384
+ }
385
+
368
386
  private async loadEmbeddedApp(): Promise<void> {
369
387
  const { windowId, applicationVersion } = this.options;
370
388
 
@@ -375,34 +393,35 @@ export default class PresentationWindow {
375
393
  }
376
394
 
377
395
  const url = createEmbeddedApp(this.presentation, applicationVersion);
378
-
379
396
  this.logInfo(`Create window '${windowId}' for embedded app url '${url}'`);
380
397
 
381
- // Embedded apps should not have a backgroundColor bexcause some sites expect
382
- // the browser default of white will be applied. We also don't attach the message
383
- // listeners for embedded apps. We could eventually allow embedded apps to send
384
- // lifecycle events but we should consider namespacing our message events.
385
- await this.createAndLoadWindow(url, '#ffffff');
398
+ await this.createAndLoadWindow(
399
+ this.appendWindowIdToHash(url, windowId),
400
+ // Embedded apps should not have a backgroundColor because some sites expect
401
+ // the browser default of white will be applied.
402
+ '#ffffff',
403
+ this.handleEmbeddedMessage
404
+ );
386
405
  this.didEmbeddedAppLoad = true;
387
406
 
388
407
  this.checkIsReadyCallback();
389
408
  }
390
409
 
391
- private async loadRaydiantApp(): Promise<void> {
410
+ private async loadLegacyApp(): Promise<void> {
392
411
  const {
393
412
  windowId,
394
413
  appRendererUrl = defaultAppRendererUrl,
395
414
  backgroundColor
396
415
  } = this.options;
397
416
 
398
- let url = `${appRendererUrl}#windowId=${windowId}`;
399
-
400
417
  this.logInfo(`Create window '${windowId}'`);
418
+
401
419
  await this.createAndLoadWindow(
402
- url,
420
+ this.appendWindowIdToHash(appRendererUrl, windowId),
403
421
  backgroundColor || '#000000', // Default to black bg for Raydiant apps
404
- this.handleMessage
422
+ this.handleLegacyMessage
405
423
  );
424
+
406
425
  await this.waitForPesentationReady();
407
426
  }
408
427
 
@@ -709,7 +728,45 @@ export default class PresentationWindow {
709
728
  }
710
729
  }
711
730
 
712
- private handleMessage = async (type: string, payload?: any) => {
731
+ private handleEmbeddedMessage = async (type: string, payload?: any) => {
732
+ const { windowId } = this.options;
733
+
734
+ if (type === 'worker-subscribe') {
735
+ if (typeof payload === 'string') {
736
+ this.logInfo(`Received 'worker-subscribe' from window '${windowId}'`);
737
+
738
+ // The payload is the worker name.
739
+ this.workerSubscriptions.add(payload);
740
+
741
+ // Send the current state to the worker if it exists as soon
742
+ // as the app subscribes.
743
+ const workerState = this.workerState[payload];
744
+ if (workerState !== undefined) {
745
+ this.sendMessage('worker-state', {
746
+ name: payload,
747
+ state: workerState
748
+ });
749
+ }
750
+ } else {
751
+ this.logWarning(
752
+ `Invalid payload for worker-subscribe message: ${payload}`
753
+ );
754
+ }
755
+ } else if (type === 'worker-unsubscribe') {
756
+ if (typeof payload === 'string') {
757
+ this.logInfo(`Received 'worker-unsubscribe' from window '${windowId}'`);
758
+
759
+ // The payload is the worker name.
760
+ this.workerSubscriptions.delete(payload);
761
+ } else {
762
+ this.logWarning(
763
+ `Invalid payload for worker-unsubscribe message: ${payload}`
764
+ );
765
+ }
766
+ }
767
+ };
768
+
769
+ private handleLegacyMessage = async (type: string, payload?: any) => {
713
770
  const { windowId, onPresentationProperties } = this.options;
714
771
 
715
772
  if (type === 'loaded') {
@@ -874,6 +931,28 @@ export default class PresentationWindow {
874
931
  return this.getLogContext();
875
932
  }
876
933
 
934
+ public runWorkerSubscriptions(state: WorkerState) {
935
+ // For each worker subscription, compare the previous state to the new state.
936
+ // If the state has changed, send the new state to the subscription.
937
+ for (const workerName of Array.from(this.workerSubscriptions)) {
938
+ const workerState = state[workerName];
939
+ const previousWorkerState = this.workerState[workerName];
940
+
941
+ if (
942
+ workerState !== undefined &&
943
+ !deepEqual(workerState, previousWorkerState)
944
+ ) {
945
+ this.sendMessage('worker-state', {
946
+ name: workerName,
947
+ state: workerState
948
+ });
949
+ }
950
+ }
951
+
952
+ // Persist the new state so we can compare if the previous values have changed..
953
+ this.workerState = state;
954
+ }
955
+
877
956
  private createRuleEngine(playlistId: string) {
878
957
  const { globalRule } = this.options;
879
958
  return new RE.RuleEngine(
package/src/preload.ts CHANGED
@@ -1,21 +1,27 @@
1
- import { ipcRenderer, IpcRendererEvent } from 'electron'; // eslint-disable-line
1
+ import { ipcRenderer, IpcRendererEvent } from 'electron';
2
2
 
3
- const sendMessage = (windowId: string, type: string, payload?: any) => {
4
- ipcRenderer.send(windowId, type, ...payload);
5
- };
3
+ interface RaydiantInternals {
4
+ sendMessage: (windowId: string, type: string, payload?: any) => void;
5
+ receiveMessage?: (message: string) => void;
6
+ }
6
7
 
7
- const messageHandler = (_: IpcRendererEvent, message: string) => {
8
- const receiveMessage = (window as any).receiveMessage;
9
- if (typeof receiveMessage === 'function') {
10
- receiveMessage(message);
11
- } else {
12
- throw new Error('receiveMessage is not defined');
8
+ const internals: RaydiantInternals = {
9
+ sendMessage: (windowId: string, type: string, payload?: any) => {
10
+ ipcRenderer.send(windowId, type, ...payload);
13
11
  }
14
12
  };
15
13
 
16
- // Add sendMessage and messageHandler to window so that app renderer knows we're
17
- // running in an Electron environment.
18
- (window as any).sendMessage = sendMessage;
19
- (window as any).messageHandler = messageHandler;
14
+ // Set __RAYDIANT_INTERNALS on window so it is accessible in to the running application.
15
+ (window as any).__RAYDIANT_INTERNALS = internals;
20
16
 
21
- ipcRenderer.on('electron-main', messageHandler);
17
+ // Forward messages from the main process to the app (render process) via receiveMessage.
18
+ // __RAYDIANT_INTERNALS.receiveMessage is required to be set by the app with the Raydiant SDK.
19
+ ipcRenderer.on('electron-main', (_: IpcRendererEvent, message: string) => {
20
+ if (typeof internals.receiveMessage === 'function') {
21
+ internals.receiveMessage(message);
22
+ } else {
23
+ console.error(
24
+ 'Failed to handle message from main process, receiveMessage is not defined'
25
+ );
26
+ }
27
+ });
package/src/types.ts CHANGED
@@ -70,6 +70,10 @@ export interface WindowBounds {
70
70
  height: number;
71
71
  }
72
72
 
73
+ export interface WorkerState {
74
+ [key: string]: any;
75
+ }
76
+
73
77
  export interface CreateWindowOptions {
74
78
  windowId: string;
75
79
  parentWindowId?: string;