@raydiant/playlist-window-manager 0.15.1 → 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.
- package/build/PlaylistWindowManager.d.ts +3 -1
- package/build/PlaylistWindowManager.js +17 -4
- package/build/PresentationWindow.d.ts +9 -3
- package/build/PresentationWindow.js +76 -18
- package/build/preload.js +14 -14
- package/build/types.d.ts +3 -0
- package/package.json +2 -2
- package/src/PlaylistWindowManager.ts +27 -5
- package/src/PresentationWindow.ts +95 -16
- package/src/preload.ts +21 -15
- package/src/types.ts +4 -0
|
@@ -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) {
|
|
@@ -464,10 +465,9 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
|
|
|
464
465
|
PlaylistWindowManager.prototype.reinitializeRuleEngineState = function (newRulesDataStoreObj) {
|
|
465
466
|
var _this = this;
|
|
466
467
|
var logger = this.options.logger;
|
|
467
|
-
logger.
|
|
468
|
+
logger.debug('Dynamic Rule engine state change event received!');
|
|
468
469
|
if (this.ruleEngine) {
|
|
469
|
-
logger.
|
|
470
|
-
logger.info("New Rule Data Store data received: ", newRulesDataStoreObj);
|
|
470
|
+
logger.debug("OLD Rule Data Store: " + JSON.stringify(this.ruleEngine.state) + " \n NEW Rule Data Store: " + JSON.stringify(newRulesDataStoreObj));
|
|
471
471
|
this.ruleEngine.state.rulesDataStore = newRulesDataStoreObj;
|
|
472
472
|
}
|
|
473
473
|
else {
|
|
@@ -620,6 +620,18 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
|
|
|
620
620
|
}
|
|
621
621
|
return this.currentWindow.isLocked();
|
|
622
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
|
+
};
|
|
623
635
|
PlaylistWindowManager.prototype.createWindow = function (pr) {
|
|
624
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;
|
|
625
637
|
if (!this.ruleEngine) {
|
|
@@ -661,7 +673,8 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
|
|
|
661
673
|
playlistWindowManagerState: this.state,
|
|
662
674
|
ruleEngineState: this.ruleEngine.state,
|
|
663
675
|
globalRule: this.ruleEngine.globalRule,
|
|
664
|
-
isPreview: isPreview
|
|
676
|
+
isPreview: isPreview,
|
|
677
|
+
workerState: this.workerState
|
|
665
678
|
});
|
|
666
679
|
};
|
|
667
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
|
|
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
|
|
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/
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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.
|
|
482
|
+
PresentationWindow.prototype.loadLegacyApp = function () {
|
|
442
483
|
return __awaiter(this, void 0, void 0, function () {
|
|
443
|
-
var _a, windowId, _b, appRendererUrl, backgroundColor
|
|
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(
|
|
451
|
-
this.
|
|
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");
|
|
4
|
-
var
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raydiant/playlist-window-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Window manager for playlist playback.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"fast-deep-equal": "^3.1.3",
|
|
15
|
-
"@raydiant/playlist-rule-engine": "3.2.
|
|
15
|
+
"@raydiant/playlist-rule-engine": "3.2.2",
|
|
16
16
|
"raydiant-kit": "4.7.3",
|
|
17
17
|
"uuid": "^8.3.0",
|
|
18
18
|
"eventemitter3": "^4.0.7"
|
|
@@ -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
|
|
|
@@ -270,10 +273,13 @@ export default class PlaylistWindowManager extends EventEmitter {
|
|
|
270
273
|
public reinitializeRuleEngineState(newRulesDataStoreObj: RE.RulesDataStore) {
|
|
271
274
|
const { logger } = this.options;
|
|
272
275
|
|
|
273
|
-
logger.
|
|
276
|
+
logger.debug('Dynamic Rule engine state change event received!');
|
|
274
277
|
if (this.ruleEngine) {
|
|
275
|
-
logger.
|
|
276
|
-
|
|
278
|
+
logger.debug(
|
|
279
|
+
`OLD Rule Data Store: ${JSON.stringify(
|
|
280
|
+
this.ruleEngine.state
|
|
281
|
+
)} \n NEW Rule Data Store: ${JSON.stringify(newRulesDataStoreObj)}`
|
|
282
|
+
);
|
|
277
283
|
this.ruleEngine.state.rulesDataStore = newRulesDataStoreObj;
|
|
278
284
|
} else {
|
|
279
285
|
logger.warn(`No Rule engine found for reinitiazling the state!`);
|
|
@@ -418,6 +424,21 @@ export default class PlaylistWindowManager extends EventEmitter {
|
|
|
418
424
|
return this.currentWindow.isLocked();
|
|
419
425
|
}
|
|
420
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
|
+
|
|
421
442
|
private createWindow(pr: RE.NextPresentationID): PresentationWindow {
|
|
422
443
|
const {
|
|
423
444
|
readyTimeoutDurationSeconds,
|
|
@@ -480,7 +501,8 @@ export default class PlaylistWindowManager extends EventEmitter {
|
|
|
480
501
|
playlistWindowManagerState: this.state,
|
|
481
502
|
ruleEngineState: this.ruleEngine.state,
|
|
482
503
|
globalRule: this.ruleEngine.globalRule,
|
|
483
|
-
isPreview
|
|
504
|
+
isPreview,
|
|
505
|
+
workerState: this.workerState
|
|
484
506
|
});
|
|
485
507
|
}
|
|
486
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/
|
|
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.
|
|
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
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
|
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
|
-
|
|
420
|
+
this.appendWindowIdToHash(appRendererUrl, windowId),
|
|
403
421
|
backgroundColor || '#000000', // Default to black bg for Raydiant apps
|
|
404
|
-
this.
|
|
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
|
|
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';
|
|
1
|
+
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
interface RaydiantInternals {
|
|
4
|
+
sendMessage: (windowId: string, type: string, payload?: any) => void;
|
|
5
|
+
receiveMessage?: (message: string) => void;
|
|
6
|
+
}
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
//
|
|
17
|
-
|
|
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
|
-
|
|
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
|
+
});
|