@raydiant/playlist-window-manager 0.0.0-test36 → 0.0.1-test2
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/BrowserWindowRenderer.js +10 -2
- package/build/PresentationWindow.d.ts +7 -0
- package/build/PresentationWindow.js +184 -9
- package/build/PresentationWindow.test.js +74 -2
- package/build/types.d.ts +14 -3
- package/package.json +3 -3
- package/src/BrowserWindowRenderer.ts +10 -3
- package/src/PresentationWindow.test.ts +103 -2
- package/src/PresentationWindow.ts +229 -10
- package/src/types.ts +15 -3
- package/tasks/npm-publish.sh +0 -0
|
@@ -86,6 +86,11 @@ var BrowserWindowRenderer = /** @class */ (function () {
|
|
|
86
86
|
// Create a hidden window to preload BrowserViews.
|
|
87
87
|
this.offscreenWindow = new electron_1.BrowserWindow({
|
|
88
88
|
show: false,
|
|
89
|
+
/**
|
|
90
|
+
* This option fixes window not being able to take fullscreen leaving
|
|
91
|
+
* 1px gap at the right and bottom of the screen.
|
|
92
|
+
*/
|
|
93
|
+
resizable: false,
|
|
89
94
|
frame: this.opts.frame,
|
|
90
95
|
hasShadow: this.opts.hasShadow,
|
|
91
96
|
webPreferences: {
|
|
@@ -124,6 +129,11 @@ var BrowserWindowRenderer = /** @class */ (function () {
|
|
|
124
129
|
})
|
|
125
130
|
: this.createOrGetPooledBrowserWindow(windowId, {
|
|
126
131
|
show: false,
|
|
132
|
+
/**
|
|
133
|
+
* This option fixes window not being able to take fullscreen leaving
|
|
134
|
+
* 1px gap at the right and bottom of the screen.
|
|
135
|
+
*/
|
|
136
|
+
resizable: false,
|
|
127
137
|
frame: this.opts.frame,
|
|
128
138
|
// NOTE: useWindowPool: true does not work with renderChildWindowsAsBrowserViews: false
|
|
129
139
|
// because the parent can't be changed after creating the BrowserWindow.
|
|
@@ -449,8 +459,6 @@ var BrowserWindowRenderer = /** @class */ (function () {
|
|
|
449
459
|
this.browserViewPool.add(browserView);
|
|
450
460
|
}
|
|
451
461
|
}
|
|
452
|
-
this.logger.info("Opening devTools", { windowId: windowId });
|
|
453
|
-
browserView.webContents.openDevTools();
|
|
454
462
|
return browserView;
|
|
455
463
|
};
|
|
456
464
|
BrowserWindowRenderer.prototype.destroyOrUnloadPooledBrowserView = function (windowId, browserView) {
|
|
@@ -68,6 +68,8 @@ export default class PresentationWindow {
|
|
|
68
68
|
private paymentSubscriptions;
|
|
69
69
|
private createObjectURLResolvers;
|
|
70
70
|
private createObjectURLTimeouts;
|
|
71
|
+
private interruptLocks;
|
|
72
|
+
private previouslyLocked;
|
|
71
73
|
constructor(options: PresentationWindowOptions);
|
|
72
74
|
load(): Promise<void>;
|
|
73
75
|
show(isFocused?: boolean): void;
|
|
@@ -147,4 +149,9 @@ export default class PresentationWindow {
|
|
|
147
149
|
private isValidOpenChildWindowPayload;
|
|
148
150
|
private shouldMute;
|
|
149
151
|
private createObjectURLForEmbeddedApp;
|
|
152
|
+
private createInterruptLock;
|
|
153
|
+
private renewInterruptLock;
|
|
154
|
+
private releaseInterruptLock;
|
|
155
|
+
private createLockTimeout;
|
|
156
|
+
private runInterruptCallback;
|
|
150
157
|
}
|
|
@@ -81,7 +81,7 @@ var PlaylistWindowManager_1 = __importDefault(require("./PlaylistWindowManager")
|
|
|
81
81
|
var WindowTimeoutError_1 = __importDefault(require("./WindowTimeoutError"));
|
|
82
82
|
var WindowLoadError_1 = __importDefault(require("./WindowLoadError"));
|
|
83
83
|
// TODO: Automatically inject latest version when building in CI.
|
|
84
|
-
var defaultAppRendererUrl = 'https://apps.raydiant.com/3.0
|
|
84
|
+
var defaultAppRendererUrl = 'https://apps.raydiant.com/3.1.0/index.html';
|
|
85
85
|
var isValidNumber = function (value) {
|
|
86
86
|
return typeof value === 'number' && !isNaN(value);
|
|
87
87
|
};
|
|
@@ -98,8 +98,27 @@ var isValidChargeRequest = function (value) {
|
|
|
98
98
|
return value &&
|
|
99
99
|
typeof value === 'object' &&
|
|
100
100
|
typeof value.requestId === 'string' &&
|
|
101
|
-
typeof value.
|
|
101
|
+
typeof value.subTotal === 'number' &&
|
|
102
|
+
typeof value.tip === 'number' &&
|
|
103
|
+
typeof value.tax === 'number' &&
|
|
104
|
+
typeof value.total === 'number';
|
|
102
105
|
};
|
|
106
|
+
var isValidAcquireLockRequest = function (value) {
|
|
107
|
+
return value && typeof value === 'object' && typeof value.requestId === 'string';
|
|
108
|
+
};
|
|
109
|
+
var isValidRenewLockRequest = function (value) {
|
|
110
|
+
return value &&
|
|
111
|
+
typeof value === 'object' &&
|
|
112
|
+
typeof value.requestId === 'string' &&
|
|
113
|
+
typeof value.lockId === 'string';
|
|
114
|
+
};
|
|
115
|
+
var isValidReleaseLockRequest = function (value) {
|
|
116
|
+
return value &&
|
|
117
|
+
typeof value === 'object' &&
|
|
118
|
+
typeof value.requestId === 'string' &&
|
|
119
|
+
typeof value.lockId === 'string';
|
|
120
|
+
};
|
|
121
|
+
var lockDuration = 1000 * 60; // 1 minute
|
|
103
122
|
var PresentationWindow = /** @class */ (function () {
|
|
104
123
|
function PresentationWindow(options) {
|
|
105
124
|
var _this = this;
|
|
@@ -131,8 +150,10 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
131
150
|
this.paymentSubscriptions = new Set();
|
|
132
151
|
this.createObjectURLResolvers = {};
|
|
133
152
|
this.createObjectURLTimeouts = {};
|
|
153
|
+
this.interruptLocks = {};
|
|
154
|
+
this.previouslyLocked = false;
|
|
134
155
|
this.handleEmbeddedMessage = function (type, payload) { return __awaiter(_this, void 0, void 0, function () {
|
|
135
|
-
var windowId, workerState;
|
|
156
|
+
var windowId, workerState, lock, lock, lock;
|
|
136
157
|
return __generator(this, function (_a) {
|
|
137
158
|
windowId = this.options.windowId;
|
|
138
159
|
switch (type) {
|
|
@@ -296,6 +317,48 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
296
317
|
}
|
|
297
318
|
break;
|
|
298
319
|
}
|
|
320
|
+
case 'runtime-acquire-interrupt-lock-request': {
|
|
321
|
+
if (isValidAcquireLockRequest(payload)) {
|
|
322
|
+
this.logInfo("Received 'runtime-acquire-interrupt-lock-request'");
|
|
323
|
+
lock = this.createInterruptLock();
|
|
324
|
+
this.sendMessageObject('runtime-acquire-interrupt-lock-response', {
|
|
325
|
+
requestId: payload.requestId,
|
|
326
|
+
lock: { id: lock.id, expiresAt: lock.expiresAt },
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
this.logWarning("Invalid payload for runtime-acquire-interrupt-lock-request message: ".concat(payload));
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 'runtime-renew-interrupt-lock-request': {
|
|
335
|
+
if (isValidRenewLockRequest(payload)) {
|
|
336
|
+
this.logInfo("Received 'runtime-renew-interrupt-lock-request'");
|
|
337
|
+
lock = this.renewInterruptLock(payload.lockId);
|
|
338
|
+
this.sendMessageObject('runtime-renew-interrupt-lock-response', {
|
|
339
|
+
requestId: payload.requestId,
|
|
340
|
+
lock: { id: lock.id, expiresAt: lock.expiresAt },
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
this.logWarning("Invalid payload for runtime-renew-interrupt-lock-request message: ".concat(payload));
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
case 'runtime-release-interrupt-lock-request': {
|
|
349
|
+
if (isValidReleaseLockRequest(payload)) {
|
|
350
|
+
this.logInfo("Received 'runtime-release-interrupt-lock-request'");
|
|
351
|
+
lock = this.releaseInterruptLock(payload.lockId);
|
|
352
|
+
this.sendMessageObject('runtime-release-interrupt-lock-response', {
|
|
353
|
+
requestId: payload.requestId,
|
|
354
|
+
lock: { id: lock.id, expiresAt: lock.expiresAt },
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
this.logWarning("Invalid payload for runtime-release-interrupt-lock-request message: ".concat(payload));
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
299
362
|
default: {
|
|
300
363
|
this.logWarning("Received unknown embedded message type: ".concat(type));
|
|
301
364
|
}
|
|
@@ -557,8 +620,29 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
557
620
|
return !!this.error;
|
|
558
621
|
};
|
|
559
622
|
PresentationWindow.prototype.isLocked = function () {
|
|
560
|
-
|
|
561
|
-
|
|
623
|
+
// Legacy check for a lock on the current window or child windows. These are set by enableInterruptLock
|
|
624
|
+
// and disableInterruptLock, which are only used by RaydiantKit apps (ie. Virtual Rooms).
|
|
625
|
+
if (this.isWindowLocked) {
|
|
626
|
+
this.logDebug("Window '".concat(this.options.windowId, "' is locked due to enableInterruptLock"));
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
if (Object.values(this.isChildWindowLocked).some(Boolean)) {
|
|
630
|
+
this.logDebug("Window '".concat(this.options.windowId, "' is locked due to enableInterruptLock on child windows"));
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
// Check interrupt locks on current window or child windows, used by embedded apps (apps created through
|
|
634
|
+
// the Developer Portal).
|
|
635
|
+
var now = +new Date();
|
|
636
|
+
var isExpired = function (lock) { return lock.expiresAt < now; };
|
|
637
|
+
if (!Object.values(this.interruptLocks).every(isExpired)) {
|
|
638
|
+
this.logDebug("Window '".concat(this.options.windowId, "' is locked due to interrupt lock"));
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
if (Object.values(this.windowManagers).some(function (wm) { return wm.isLocked(); })) {
|
|
642
|
+
this.logDebug("Window '".concat(this.options.windowId, "' is locked due to interrupt lock on child windows"));
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
645
|
+
return false;
|
|
562
646
|
};
|
|
563
647
|
PresentationWindow.prototype.setPresentation = function (presentation, playlistWindowManagerState, ruleEngineState) {
|
|
564
648
|
this.presentation = presentation;
|
|
@@ -952,8 +1036,24 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
952
1036
|
themeOverride: themeOverride,
|
|
953
1037
|
forceMute: forceMute,
|
|
954
1038
|
isPreview: isPreview,
|
|
955
|
-
onEnableInterruptLock: function () {
|
|
956
|
-
|
|
1039
|
+
onEnableInterruptLock: function () {
|
|
1040
|
+
// enableInterruptLock is only used by legacy RaydiantKit apps (ie. Virtual Rooms), while
|
|
1041
|
+
// runInterruptCallback is used by Developer Portal apps.
|
|
1042
|
+
if (_this.isEmbeddedApp()) {
|
|
1043
|
+
_this.runInterruptCallback();
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
_this.enableInteruptLock(windowName);
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
onDisableInterruptLock: function () {
|
|
1050
|
+
if (_this.isEmbeddedApp()) {
|
|
1051
|
+
_this.runInterruptCallback();
|
|
1052
|
+
}
|
|
1053
|
+
else {
|
|
1054
|
+
_this.disableInteruptLock(windowName);
|
|
1055
|
+
}
|
|
1056
|
+
},
|
|
957
1057
|
getTerminal: this.options.getTerminal,
|
|
958
1058
|
});
|
|
959
1059
|
// Hook up child window manager events to bubble up to the parent.
|
|
@@ -1015,6 +1115,8 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
1015
1115
|
this.logWarning("Failed to close child window manager for '".concat(windowName, "'."));
|
|
1016
1116
|
}
|
|
1017
1117
|
};
|
|
1118
|
+
// Legacy enable interrupt lock for RaydiantKit apps (ie. Virtual Rooms). This has been
|
|
1119
|
+
// replaced by createInterruptLock for embedded apps.
|
|
1018
1120
|
PresentationWindow.prototype.enableInteruptLock = function (windowName) {
|
|
1019
1121
|
if (windowName) {
|
|
1020
1122
|
this.isChildWindowLocked[windowName] = true;
|
|
@@ -1022,11 +1124,12 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
1022
1124
|
else {
|
|
1023
1125
|
this.isWindowLocked = true;
|
|
1024
1126
|
}
|
|
1025
|
-
// If the presentation is ready and locked, trigger the enable interrupt callback.
|
|
1026
1127
|
if (this.isReady() && this.isLocked() && this.enableInterruptLockCallback) {
|
|
1027
1128
|
this.enableInterruptLockCallback();
|
|
1028
1129
|
}
|
|
1029
1130
|
};
|
|
1131
|
+
// Legacy disable interrupt lock for RaydiantKit apps (ie. Virtual Rooms). This has been
|
|
1132
|
+
// replaced by releaseInterruptLock for embedded apps.
|
|
1030
1133
|
PresentationWindow.prototype.disableInteruptLock = function (windowName) {
|
|
1031
1134
|
if (windowName) {
|
|
1032
1135
|
this.isChildWindowLocked[windowName] = false;
|
|
@@ -1034,7 +1137,6 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
1034
1137
|
else {
|
|
1035
1138
|
this.isWindowLocked = false;
|
|
1036
1139
|
}
|
|
1037
|
-
// If the presentation is ready and unlocked, trigger the disable interrupt callback.
|
|
1038
1140
|
if (this.isReady() &&
|
|
1039
1141
|
!this.isLocked() &&
|
|
1040
1142
|
this.disableInterruptLockCallback) {
|
|
@@ -1200,6 +1302,79 @@ var PresentationWindow = /** @class */ (function () {
|
|
|
1200
1302
|
return '';
|
|
1201
1303
|
});
|
|
1202
1304
|
};
|
|
1305
|
+
PresentationWindow.prototype.createInterruptLock = function () {
|
|
1306
|
+
var lockId = (0, uuid_1.v4)();
|
|
1307
|
+
var lock = {
|
|
1308
|
+
id: lockId,
|
|
1309
|
+
expiresAt: +new Date() + lockDuration,
|
|
1310
|
+
timeout: this.createLockTimeout(lockId),
|
|
1311
|
+
};
|
|
1312
|
+
this.interruptLocks[lock.id] = lock;
|
|
1313
|
+
this.runInterruptCallback();
|
|
1314
|
+
return lock;
|
|
1315
|
+
};
|
|
1316
|
+
PresentationWindow.prototype.renewInterruptLock = function (id) {
|
|
1317
|
+
var lock = this.interruptLocks[id];
|
|
1318
|
+
if (lock) {
|
|
1319
|
+
// Reset expiration time.
|
|
1320
|
+
lock.expiresAt = +new Date() + lockDuration;
|
|
1321
|
+
// Reset timeout.
|
|
1322
|
+
if (lock.timeout) {
|
|
1323
|
+
clearTimeout(lock.timeout);
|
|
1324
|
+
}
|
|
1325
|
+
lock.timeout = this.createLockTimeout(id);
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
// If the lock doesn't exist it was already released, create a new lock with the same id.
|
|
1329
|
+
lock = {
|
|
1330
|
+
id: id,
|
|
1331
|
+
expiresAt: +new Date() + lockDuration,
|
|
1332
|
+
timeout: this.createLockTimeout(id),
|
|
1333
|
+
};
|
|
1334
|
+
this.interruptLocks[lock.id] = lock;
|
|
1335
|
+
}
|
|
1336
|
+
this.runInterruptCallback();
|
|
1337
|
+
return lock;
|
|
1338
|
+
};
|
|
1339
|
+
PresentationWindow.prototype.releaseInterruptLock = function (id) {
|
|
1340
|
+
var lock = this.interruptLocks[id];
|
|
1341
|
+
if (lock) {
|
|
1342
|
+
if (lock.timeout) {
|
|
1343
|
+
clearTimeout(lock.timeout);
|
|
1344
|
+
}
|
|
1345
|
+
delete this.interruptLocks[id];
|
|
1346
|
+
}
|
|
1347
|
+
else {
|
|
1348
|
+
// If the lock doesn't exist it was already released, return a fake lock object to
|
|
1349
|
+
// send a response back to the running app.
|
|
1350
|
+
lock = { id: id, expiresAt: 0, timeout: null };
|
|
1351
|
+
}
|
|
1352
|
+
this.runInterruptCallback();
|
|
1353
|
+
return lock;
|
|
1354
|
+
};
|
|
1355
|
+
PresentationWindow.prototype.createLockTimeout = function (id) {
|
|
1356
|
+
var _this = this;
|
|
1357
|
+
return setTimeout(function () {
|
|
1358
|
+
_this.logInfo("Interrupt lock expired, releasing: ".concat(id));
|
|
1359
|
+
_this.releaseInterruptLock(id);
|
|
1360
|
+
}, lockDuration);
|
|
1361
|
+
};
|
|
1362
|
+
PresentationWindow.prototype.runInterruptCallback = function () {
|
|
1363
|
+
var currentlyLocked = this.isLocked();
|
|
1364
|
+
// If locked and the lock state has changed, run the interrupt lock callback.
|
|
1365
|
+
// Or if not locked and the lock state has changed, run the disable interrupt lock callback.
|
|
1366
|
+
if (currentlyLocked &&
|
|
1367
|
+
this.previouslyLocked !== currentlyLocked &&
|
|
1368
|
+
this.enableInterruptLockCallback) {
|
|
1369
|
+
this.enableInterruptLockCallback();
|
|
1370
|
+
}
|
|
1371
|
+
else if (!currentlyLocked &&
|
|
1372
|
+
this.previouslyLocked !== currentlyLocked &&
|
|
1373
|
+
this.disableInterruptLockCallback) {
|
|
1374
|
+
this.disableInterruptLockCallback();
|
|
1375
|
+
}
|
|
1376
|
+
this.previouslyLocked = currentlyLocked;
|
|
1377
|
+
};
|
|
1203
1378
|
return PresentationWindow;
|
|
1204
1379
|
}());
|
|
1205
1380
|
exports.default = PresentationWindow;
|
|
@@ -260,11 +260,17 @@ describe('payments', function () {
|
|
|
260
260
|
_a.sent();
|
|
261
261
|
testRenderer.runReceiveMessageCallback('payments-charge-request', {
|
|
262
262
|
requestId: 'test',
|
|
263
|
-
|
|
263
|
+
subTotal: 100,
|
|
264
|
+
tip: 1,
|
|
265
|
+
tax: 10,
|
|
266
|
+
total: 111,
|
|
264
267
|
});
|
|
265
268
|
expect(callback).toHaveBeenCalledWith({
|
|
266
269
|
requestId: 'test',
|
|
267
|
-
|
|
270
|
+
subTotal: 100,
|
|
271
|
+
tip: 1,
|
|
272
|
+
tax: 10,
|
|
273
|
+
total: 111,
|
|
268
274
|
});
|
|
269
275
|
// Send charge response.
|
|
270
276
|
window.sendChargeResponse({
|
|
@@ -337,4 +343,70 @@ describe('payments', function () {
|
|
|
337
343
|
});
|
|
338
344
|
}); });
|
|
339
345
|
});
|
|
346
|
+
describe('interrupt locks', function () {
|
|
347
|
+
test('acquire, renew, release lock', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
348
|
+
var testRenderer, onEnableInterruptLock, onDisableInterruptLock, window, sendMessageObjectCall, lock, renewLockSendMessageObjectCall, renewedLock, releaseLockSendMessageObjectCall, releasedLock;
|
|
349
|
+
return __generator(this, function (_a) {
|
|
350
|
+
switch (_a.label) {
|
|
351
|
+
case 0:
|
|
352
|
+
testRenderer = (0, testUtils_1.createRenderer)();
|
|
353
|
+
onEnableInterruptLock = jest.fn();
|
|
354
|
+
onDisableInterruptLock = jest.fn();
|
|
355
|
+
window = (0, testUtils_1.createPresentationWindow)({
|
|
356
|
+
windowId: 'test-interrupt-lock-window-id',
|
|
357
|
+
presentation: (0, testUtils_1.createPresentation)(),
|
|
358
|
+
applicationVersion: (0, testUtils_1.createApplicationVersion)({
|
|
359
|
+
embeddedUrlFormat: 'http://test.local/:input',
|
|
360
|
+
}),
|
|
361
|
+
renderer: testRenderer,
|
|
362
|
+
});
|
|
363
|
+
window.onEnableInterruptLock(onEnableInterruptLock);
|
|
364
|
+
window.onDisableInterruptLock(onDisableInterruptLock);
|
|
365
|
+
return [4 /*yield*/, window.load()];
|
|
366
|
+
case 1:
|
|
367
|
+
_a.sent();
|
|
368
|
+
// Acquire lock.
|
|
369
|
+
testRenderer.runReceiveMessageCallback('runtime-acquire-interrupt-lock-request', { requestId: 'acquire-request' });
|
|
370
|
+
sendMessageObjectCall = testRenderer.sendMessageObject.mock.calls[0];
|
|
371
|
+
expect(sendMessageObjectCall[0]).toBe('test-interrupt-lock-window-id');
|
|
372
|
+
expect(sendMessageObjectCall[1]).toBe('runtime-acquire-interrupt-lock-response');
|
|
373
|
+
expect(sendMessageObjectCall[2].requestId).toBe('acquire-request');
|
|
374
|
+
lock = sendMessageObjectCall[2].lock;
|
|
375
|
+
expect(typeof lock.id).toBe('string');
|
|
376
|
+
expect(lock.id).toBeTruthy();
|
|
377
|
+
expect(typeof lock.expiresAt).toBe('number');
|
|
378
|
+
expect(lock.expiresAt).toBeTruthy();
|
|
379
|
+
expect(onEnableInterruptLock).toHaveBeenCalled();
|
|
380
|
+
// Wait 1ms to make sure we have a different expiresAt value.
|
|
381
|
+
return [4 /*yield*/, delay(1)];
|
|
382
|
+
case 2:
|
|
383
|
+
// Wait 1ms to make sure we have a different expiresAt value.
|
|
384
|
+
_a.sent();
|
|
385
|
+
// Renew lock.
|
|
386
|
+
testRenderer.runReceiveMessageCallback('runtime-renew-interrupt-lock-request', { requestId: 'renew-request', lockId: lock.id });
|
|
387
|
+
renewLockSendMessageObjectCall = testRenderer.sendMessageObject.mock.calls[1];
|
|
388
|
+
expect(renewLockSendMessageObjectCall[0]).toBe('test-interrupt-lock-window-id');
|
|
389
|
+
expect(renewLockSendMessageObjectCall[1]).toBe('runtime-renew-interrupt-lock-response');
|
|
390
|
+
expect(renewLockSendMessageObjectCall[2].requestId).toBe('renew-request');
|
|
391
|
+
renewedLock = renewLockSendMessageObjectCall[2].lock;
|
|
392
|
+
expect(renewedLock.id).toBe(lock.id);
|
|
393
|
+
expect(renewedLock.expiresAt).toBeGreaterThan(lock.expiresAt);
|
|
394
|
+
// Ensure onEnableInterruptLock is not called again.
|
|
395
|
+
expect(onEnableInterruptLock).toHaveBeenCalledTimes(1);
|
|
396
|
+
// Release lock.
|
|
397
|
+
testRenderer.runReceiveMessageCallback('runtime-release-interrupt-lock-request', { requestId: 'release-request', lockId: lock.id });
|
|
398
|
+
releaseLockSendMessageObjectCall = testRenderer.sendMessageObject.mock.calls[2];
|
|
399
|
+
expect(releaseLockSendMessageObjectCall[0]).toBe('test-interrupt-lock-window-id');
|
|
400
|
+
expect(releaseLockSendMessageObjectCall[1]).toBe('runtime-release-interrupt-lock-response');
|
|
401
|
+
expect(releaseLockSendMessageObjectCall[2].requestId).toBe('release-request');
|
|
402
|
+
releasedLock = releaseLockSendMessageObjectCall[2].lock;
|
|
403
|
+
expect(releasedLock.id).toBe(lock.id);
|
|
404
|
+
expect(releasedLock.expiresAt).toBe(renewedLock.expiresAt);
|
|
405
|
+
// Ensure onDisableInterruptLock is called.
|
|
406
|
+
expect(onDisableInterruptLock).toHaveBeenCalled();
|
|
407
|
+
return [2 /*return*/];
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}); });
|
|
411
|
+
});
|
|
340
412
|
// TODO: Backfill tests for all legacy and embedded apps.
|
package/build/types.d.ts
CHANGED
|
@@ -142,14 +142,20 @@ export interface AnalyticsEvent {
|
|
|
142
142
|
productCategory?: string | null;
|
|
143
143
|
}
|
|
144
144
|
export interface ChargeRequest {
|
|
145
|
+
orderId?: string;
|
|
145
146
|
requestId: string;
|
|
146
|
-
|
|
147
|
+
subTotal: number;
|
|
148
|
+
tip: number;
|
|
149
|
+
tax: number;
|
|
150
|
+
total: number;
|
|
147
151
|
items: Array<{
|
|
148
152
|
name: string;
|
|
149
|
-
price: number;
|
|
150
153
|
description?: string;
|
|
151
154
|
sku?: string;
|
|
152
|
-
|
|
155
|
+
price: number;
|
|
156
|
+
quantity: number;
|
|
157
|
+
tax: number;
|
|
158
|
+
total: number;
|
|
153
159
|
}>;
|
|
154
160
|
}
|
|
155
161
|
export interface Charge {
|
|
@@ -166,3 +172,8 @@ export interface ChargeResponse {
|
|
|
166
172
|
export interface Terminal {
|
|
167
173
|
status: string;
|
|
168
174
|
}
|
|
175
|
+
export interface InterruptLock {
|
|
176
|
+
id: string;
|
|
177
|
+
expiresAt: number;
|
|
178
|
+
timeout: ReturnType<typeof setTimeout> | null;
|
|
179
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raydiant/playlist-window-manager",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1-test2",
|
|
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.
|
|
15
|
+
"@raydiant/playlist-rule-engine": "3.3.0",
|
|
16
16
|
"raydiant-kit": "4.7.3",
|
|
17
17
|
"uuid": "^8.3.0",
|
|
18
18
|
"eventemitter3": "^4.0.7"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@types/jschannel": "^1.0.1",
|
|
23
23
|
"@types/jest": "^24.0.15",
|
|
24
24
|
"@types/uuid": "8.3.0",
|
|
25
|
-
"electron": "
|
|
25
|
+
"electron": "33.2.0",
|
|
26
26
|
"jest": "^24.8.0",
|
|
27
27
|
"ts-jest": "^24.0.2",
|
|
28
28
|
"typescript": "^4.1.2"
|
|
@@ -63,6 +63,11 @@ export default class BrowserWindowRenderer
|
|
|
63
63
|
// Create a hidden window to preload BrowserViews.
|
|
64
64
|
private offscreenWindow = new BrowserWindow({
|
|
65
65
|
show: false,
|
|
66
|
+
/**
|
|
67
|
+
* This option fixes window not being able to take fullscreen leaving
|
|
68
|
+
* 1px gap at the right and bottom of the screen.
|
|
69
|
+
*/
|
|
70
|
+
resizable: false,
|
|
66
71
|
frame: this.opts.frame,
|
|
67
72
|
hasShadow: this.opts.hasShadow,
|
|
68
73
|
webPreferences: {
|
|
@@ -110,6 +115,11 @@ export default class BrowserWindowRenderer
|
|
|
110
115
|
})
|
|
111
116
|
: this.createOrGetPooledBrowserWindow(windowId, {
|
|
112
117
|
show: false,
|
|
118
|
+
/**
|
|
119
|
+
* This option fixes window not being able to take fullscreen leaving
|
|
120
|
+
* 1px gap at the right and bottom of the screen.
|
|
121
|
+
*/
|
|
122
|
+
resizable: false,
|
|
113
123
|
frame: this.opts.frame,
|
|
114
124
|
// NOTE: useWindowPool: true does not work with renderChildWindowsAsBrowserViews: false
|
|
115
125
|
// because the parent can't be changed after creating the BrowserWindow.
|
|
@@ -519,9 +529,6 @@ export default class BrowserWindowRenderer
|
|
|
519
529
|
}
|
|
520
530
|
}
|
|
521
531
|
|
|
522
|
-
this.logger.info(`Opening devTools`, { windowId });
|
|
523
|
-
browserView.webContents.openDevTools();
|
|
524
|
-
|
|
525
532
|
return browserView;
|
|
526
533
|
}
|
|
527
534
|
|
|
@@ -238,12 +238,18 @@ describe('payments', () => {
|
|
|
238
238
|
|
|
239
239
|
testRenderer.runReceiveMessageCallback('payments-charge-request', {
|
|
240
240
|
requestId: 'test',
|
|
241
|
-
|
|
241
|
+
subTotal: 100,
|
|
242
|
+
tip: 1,
|
|
243
|
+
tax: 10,
|
|
244
|
+
total: 111,
|
|
242
245
|
});
|
|
243
246
|
|
|
244
247
|
expect(callback).toHaveBeenCalledWith({
|
|
245
248
|
requestId: 'test',
|
|
246
|
-
|
|
249
|
+
subTotal: 100,
|
|
250
|
+
tip: 1,
|
|
251
|
+
tax: 10,
|
|
252
|
+
total: 111,
|
|
247
253
|
});
|
|
248
254
|
|
|
249
255
|
// Send charge response.
|
|
@@ -330,4 +336,99 @@ describe('payments', () => {
|
|
|
330
336
|
});
|
|
331
337
|
});
|
|
332
338
|
|
|
339
|
+
describe('interrupt locks', () => {
|
|
340
|
+
test('acquire, renew, release lock', async () => {
|
|
341
|
+
const testRenderer = createRenderer();
|
|
342
|
+
|
|
343
|
+
const onEnableInterruptLock = jest.fn();
|
|
344
|
+
const onDisableInterruptLock = jest.fn();
|
|
345
|
+
|
|
346
|
+
const window = createPresentationWindow({
|
|
347
|
+
windowId: 'test-interrupt-lock-window-id',
|
|
348
|
+
presentation: createPresentation(),
|
|
349
|
+
applicationVersion: createApplicationVersion({
|
|
350
|
+
embeddedUrlFormat: 'http://test.local/:input',
|
|
351
|
+
}),
|
|
352
|
+
renderer: testRenderer,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
window.onEnableInterruptLock(onEnableInterruptLock);
|
|
356
|
+
window.onDisableInterruptLock(onDisableInterruptLock);
|
|
357
|
+
|
|
358
|
+
await window.load();
|
|
359
|
+
|
|
360
|
+
// Acquire lock.
|
|
361
|
+
testRenderer.runReceiveMessageCallback(
|
|
362
|
+
'runtime-acquire-interrupt-lock-request',
|
|
363
|
+
{ requestId: 'acquire-request' }
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// Ensure lock response is sent.
|
|
367
|
+
const sendMessageObjectCall = testRenderer.sendMessageObject.mock.calls[0];
|
|
368
|
+
expect(sendMessageObjectCall[0]).toBe('test-interrupt-lock-window-id');
|
|
369
|
+
expect(sendMessageObjectCall[1]).toBe(
|
|
370
|
+
'runtime-acquire-interrupt-lock-response'
|
|
371
|
+
);
|
|
372
|
+
expect(sendMessageObjectCall[2].requestId).toBe('acquire-request');
|
|
373
|
+
const lock = sendMessageObjectCall[2].lock;
|
|
374
|
+
expect(typeof lock.id).toBe('string');
|
|
375
|
+
expect(lock.id).toBeTruthy();
|
|
376
|
+
expect(typeof lock.expiresAt).toBe('number');
|
|
377
|
+
expect(lock.expiresAt).toBeTruthy();
|
|
378
|
+
|
|
379
|
+
expect(onEnableInterruptLock).toHaveBeenCalled();
|
|
380
|
+
|
|
381
|
+
// Wait 1ms to make sure we have a different expiresAt value.
|
|
382
|
+
await delay(1);
|
|
383
|
+
|
|
384
|
+
// Renew lock.
|
|
385
|
+
testRenderer.runReceiveMessageCallback(
|
|
386
|
+
'runtime-renew-interrupt-lock-request',
|
|
387
|
+
{ requestId: 'renew-request', lockId: lock.id }
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// Ensure renew lock response is sent.
|
|
391
|
+
const renewLockSendMessageObjectCall =
|
|
392
|
+
testRenderer.sendMessageObject.mock.calls[1];
|
|
393
|
+
expect(renewLockSendMessageObjectCall[0]).toBe(
|
|
394
|
+
'test-interrupt-lock-window-id'
|
|
395
|
+
);
|
|
396
|
+
expect(renewLockSendMessageObjectCall[1]).toBe(
|
|
397
|
+
'runtime-renew-interrupt-lock-response'
|
|
398
|
+
);
|
|
399
|
+
expect(renewLockSendMessageObjectCall[2].requestId).toBe('renew-request');
|
|
400
|
+
const renewedLock = renewLockSendMessageObjectCall[2].lock;
|
|
401
|
+
expect(renewedLock.id).toBe(lock.id);
|
|
402
|
+
expect(renewedLock.expiresAt).toBeGreaterThan(lock.expiresAt);
|
|
403
|
+
|
|
404
|
+
// Ensure onEnableInterruptLock is not called again.
|
|
405
|
+
expect(onEnableInterruptLock).toHaveBeenCalledTimes(1);
|
|
406
|
+
|
|
407
|
+
// Release lock.
|
|
408
|
+
testRenderer.runReceiveMessageCallback(
|
|
409
|
+
'runtime-release-interrupt-lock-request',
|
|
410
|
+
{ requestId: 'release-request', lockId: lock.id }
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
// Ensure release lock response is sent.
|
|
414
|
+
const releaseLockSendMessageObjectCall =
|
|
415
|
+
testRenderer.sendMessageObject.mock.calls[2];
|
|
416
|
+
expect(releaseLockSendMessageObjectCall[0]).toBe(
|
|
417
|
+
'test-interrupt-lock-window-id'
|
|
418
|
+
);
|
|
419
|
+
expect(releaseLockSendMessageObjectCall[1]).toBe(
|
|
420
|
+
'runtime-release-interrupt-lock-response'
|
|
421
|
+
);
|
|
422
|
+
expect(releaseLockSendMessageObjectCall[2].requestId).toBe(
|
|
423
|
+
'release-request'
|
|
424
|
+
);
|
|
425
|
+
const releasedLock = releaseLockSendMessageObjectCall[2].lock;
|
|
426
|
+
expect(releasedLock.id).toBe(lock.id);
|
|
427
|
+
expect(releasedLock.expiresAt).toBe(renewedLock.expiresAt);
|
|
428
|
+
|
|
429
|
+
// Ensure onDisableInterruptLock is called.
|
|
430
|
+
expect(onDisableInterruptLock).toHaveBeenCalled();
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
333
434
|
// TODO: Backfill tests for all legacy and embedded apps.
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
ChargeRequest,
|
|
25
25
|
ChargeResponse,
|
|
26
26
|
Terminal,
|
|
27
|
+
InterruptLock,
|
|
27
28
|
} from './types';
|
|
28
29
|
|
|
29
30
|
export interface PresentationWindowOptions {
|
|
@@ -61,7 +62,7 @@ export interface PresentationWindowOptions {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
// TODO: Automatically inject latest version when building in CI.
|
|
64
|
-
const defaultAppRendererUrl = 'https://apps.raydiant.com/3.0
|
|
65
|
+
const defaultAppRendererUrl = 'https://apps.raydiant.com/3.1.0/index.html';
|
|
65
66
|
|
|
66
67
|
const isValidNumber = (value: any) =>
|
|
67
68
|
typeof value === 'number' && !isNaN(value);
|
|
@@ -79,7 +80,33 @@ const isValidChargeRequest = (value: any): value is ChargeRequest =>
|
|
|
79
80
|
value &&
|
|
80
81
|
typeof value === 'object' &&
|
|
81
82
|
typeof value.requestId === 'string' &&
|
|
82
|
-
typeof value.
|
|
83
|
+
typeof value.subTotal === 'number' &&
|
|
84
|
+
typeof value.tip === 'number' &&
|
|
85
|
+
typeof value.tax === 'number' &&
|
|
86
|
+
typeof value.total === 'number';
|
|
87
|
+
|
|
88
|
+
const isValidAcquireLockRequest = (
|
|
89
|
+
value: any
|
|
90
|
+
): value is { requestId: string } =>
|
|
91
|
+
value && typeof value === 'object' && typeof value.requestId === 'string';
|
|
92
|
+
|
|
93
|
+
const isValidRenewLockRequest = (
|
|
94
|
+
value: any
|
|
95
|
+
): value is { requestId: string; lockId: string } =>
|
|
96
|
+
value &&
|
|
97
|
+
typeof value === 'object' &&
|
|
98
|
+
typeof value.requestId === 'string' &&
|
|
99
|
+
typeof value.lockId === 'string';
|
|
100
|
+
|
|
101
|
+
const isValidReleaseLockRequest = (
|
|
102
|
+
value: any
|
|
103
|
+
): value is { requestId: string; lockId: string } =>
|
|
104
|
+
value &&
|
|
105
|
+
typeof value === 'object' &&
|
|
106
|
+
typeof value.requestId === 'string' &&
|
|
107
|
+
typeof value.lockId === 'string';
|
|
108
|
+
|
|
109
|
+
const lockDuration = 1000 * 60; // 1 minute
|
|
83
110
|
|
|
84
111
|
export default class PresentationWindow {
|
|
85
112
|
private didReceiveReady: boolean = false;
|
|
@@ -124,6 +151,9 @@ export default class PresentationWindow {
|
|
|
124
151
|
ReturnType<typeof setTimeout>
|
|
125
152
|
> = {};
|
|
126
153
|
|
|
154
|
+
private interruptLocks: Record<string, InterruptLock> = {};
|
|
155
|
+
private previouslyLocked = false;
|
|
156
|
+
|
|
127
157
|
constructor(private options: PresentationWindowOptions) {
|
|
128
158
|
this.builderState = options.builderState;
|
|
129
159
|
this.presentation = options.presentation;
|
|
@@ -335,10 +365,39 @@ export default class PresentationWindow {
|
|
|
335
365
|
}
|
|
336
366
|
|
|
337
367
|
public isLocked() {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
368
|
+
// Legacy check for a lock on the current window or child windows. These are set by enableInterruptLock
|
|
369
|
+
// and disableInterruptLock, which are only used by RaydiantKit apps (ie. Virtual Rooms).
|
|
370
|
+
if (this.isWindowLocked) {
|
|
371
|
+
this.logDebug(
|
|
372
|
+
`Window '${this.options.windowId}' is locked due to enableInterruptLock`
|
|
373
|
+
);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
if (Object.values(this.isChildWindowLocked).some(Boolean)) {
|
|
377
|
+
this.logDebug(
|
|
378
|
+
`Window '${this.options.windowId}' is locked due to enableInterruptLock on child windows`
|
|
379
|
+
);
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Check interrupt locks on current window or child windows, used by embedded apps (apps created through
|
|
384
|
+
// the Developer Portal).
|
|
385
|
+
const now = +new Date();
|
|
386
|
+
const isExpired = (lock: InterruptLock) => lock.expiresAt < now;
|
|
387
|
+
if (!Object.values(this.interruptLocks).every(isExpired)) {
|
|
388
|
+
this.logDebug(
|
|
389
|
+
`Window '${this.options.windowId}' is locked due to interrupt lock`
|
|
390
|
+
);
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
if (Object.values(this.windowManagers).some((wm) => wm.isLocked())) {
|
|
394
|
+
this.logDebug(
|
|
395
|
+
`Window '${this.options.windowId}' is locked due to interrupt lock on child windows`
|
|
396
|
+
);
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return false;
|
|
342
401
|
}
|
|
343
402
|
|
|
344
403
|
public setPresentation(
|
|
@@ -783,8 +842,22 @@ export default class PresentationWindow {
|
|
|
783
842
|
themeOverride,
|
|
784
843
|
forceMute,
|
|
785
844
|
isPreview,
|
|
786
|
-
onEnableInterruptLock: () =>
|
|
787
|
-
|
|
845
|
+
onEnableInterruptLock: () => {
|
|
846
|
+
// enableInterruptLock is only used by legacy RaydiantKit apps (ie. Virtual Rooms), while
|
|
847
|
+
// runInterruptCallback is used by Developer Portal apps.
|
|
848
|
+
if (this.isEmbeddedApp()) {
|
|
849
|
+
this.runInterruptCallback();
|
|
850
|
+
} else {
|
|
851
|
+
this.enableInteruptLock(windowName);
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
onDisableInterruptLock: () => {
|
|
855
|
+
if (this.isEmbeddedApp()) {
|
|
856
|
+
this.runInterruptCallback();
|
|
857
|
+
} else {
|
|
858
|
+
this.disableInteruptLock(windowName);
|
|
859
|
+
}
|
|
860
|
+
},
|
|
788
861
|
getTerminal: this.options.getTerminal,
|
|
789
862
|
});
|
|
790
863
|
|
|
@@ -847,6 +920,8 @@ export default class PresentationWindow {
|
|
|
847
920
|
}
|
|
848
921
|
}
|
|
849
922
|
|
|
923
|
+
// Legacy enable interrupt lock for RaydiantKit apps (ie. Virtual Rooms). This has been
|
|
924
|
+
// replaced by createInterruptLock for embedded apps.
|
|
850
925
|
private enableInteruptLock(windowName?: string) {
|
|
851
926
|
if (windowName) {
|
|
852
927
|
this.isChildWindowLocked[windowName] = true;
|
|
@@ -854,12 +929,13 @@ export default class PresentationWindow {
|
|
|
854
929
|
this.isWindowLocked = true;
|
|
855
930
|
}
|
|
856
931
|
|
|
857
|
-
// If the presentation is ready and locked, trigger the enable interrupt callback.
|
|
858
932
|
if (this.isReady() && this.isLocked() && this.enableInterruptLockCallback) {
|
|
859
933
|
this.enableInterruptLockCallback();
|
|
860
934
|
}
|
|
861
935
|
}
|
|
862
936
|
|
|
937
|
+
// Legacy disable interrupt lock for RaydiantKit apps (ie. Virtual Rooms). This has been
|
|
938
|
+
// replaced by releaseInterruptLock for embedded apps.
|
|
863
939
|
private disableInteruptLock(windowName?: string) {
|
|
864
940
|
if (windowName) {
|
|
865
941
|
this.isChildWindowLocked[windowName] = false;
|
|
@@ -867,7 +943,6 @@ export default class PresentationWindow {
|
|
|
867
943
|
this.isWindowLocked = false;
|
|
868
944
|
}
|
|
869
945
|
|
|
870
|
-
// If the presentation is ready and unlocked, trigger the disable interrupt callback.
|
|
871
946
|
if (
|
|
872
947
|
this.isReady() &&
|
|
873
948
|
!this.isLocked() &&
|
|
@@ -1105,6 +1180,61 @@ export default class PresentationWindow {
|
|
|
1105
1180
|
break;
|
|
1106
1181
|
}
|
|
1107
1182
|
|
|
1183
|
+
case 'runtime-acquire-interrupt-lock-request': {
|
|
1184
|
+
if (isValidAcquireLockRequest(payload)) {
|
|
1185
|
+
this.logInfo(`Received 'runtime-acquire-interrupt-lock-request'`);
|
|
1186
|
+
|
|
1187
|
+
const lock = this.createInterruptLock();
|
|
1188
|
+
|
|
1189
|
+
this.sendMessageObject('runtime-acquire-interrupt-lock-response', {
|
|
1190
|
+
requestId: payload.requestId,
|
|
1191
|
+
lock: { id: lock.id, expiresAt: lock.expiresAt },
|
|
1192
|
+
});
|
|
1193
|
+
} else {
|
|
1194
|
+
this.logWarning(
|
|
1195
|
+
`Invalid payload for runtime-acquire-interrupt-lock-request message: ${payload}`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
case 'runtime-renew-interrupt-lock-request': {
|
|
1203
|
+
if (isValidRenewLockRequest(payload)) {
|
|
1204
|
+
this.logInfo(`Received 'runtime-renew-interrupt-lock-request'`);
|
|
1205
|
+
|
|
1206
|
+
const lock = this.renewInterruptLock(payload.lockId);
|
|
1207
|
+
|
|
1208
|
+
this.sendMessageObject('runtime-renew-interrupt-lock-response', {
|
|
1209
|
+
requestId: payload.requestId,
|
|
1210
|
+
lock: { id: lock.id, expiresAt: lock.expiresAt },
|
|
1211
|
+
});
|
|
1212
|
+
} else {
|
|
1213
|
+
this.logWarning(
|
|
1214
|
+
`Invalid payload for runtime-renew-interrupt-lock-request message: ${payload}`
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
break;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
case 'runtime-release-interrupt-lock-request': {
|
|
1221
|
+
if (isValidReleaseLockRequest(payload)) {
|
|
1222
|
+
this.logInfo(`Received 'runtime-release-interrupt-lock-request'`);
|
|
1223
|
+
|
|
1224
|
+
const lock = this.releaseInterruptLock(payload.lockId);
|
|
1225
|
+
|
|
1226
|
+
this.sendMessageObject('runtime-release-interrupt-lock-response', {
|
|
1227
|
+
requestId: payload.requestId,
|
|
1228
|
+
lock: { id: lock.id, expiresAt: lock.expiresAt },
|
|
1229
|
+
});
|
|
1230
|
+
} else {
|
|
1231
|
+
this.logWarning(
|
|
1232
|
+
`Invalid payload for runtime-release-interrupt-lock-request message: ${payload}`
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1108
1238
|
default: {
|
|
1109
1239
|
this.logWarning(`Received unknown embedded message type: ${type}`);
|
|
1110
1240
|
}
|
|
@@ -1407,6 +1537,95 @@ export default class PresentationWindow {
|
|
|
1407
1537
|
return '';
|
|
1408
1538
|
});
|
|
1409
1539
|
}
|
|
1540
|
+
|
|
1541
|
+
private createInterruptLock() {
|
|
1542
|
+
const lockId = uuidv4();
|
|
1543
|
+
const lock = {
|
|
1544
|
+
id: lockId,
|
|
1545
|
+
expiresAt: +new Date() + lockDuration,
|
|
1546
|
+
timeout: this.createLockTimeout(lockId),
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
this.interruptLocks[lock.id] = lock;
|
|
1550
|
+
|
|
1551
|
+
this.runInterruptCallback();
|
|
1552
|
+
|
|
1553
|
+
return lock;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
private renewInterruptLock(id: string) {
|
|
1557
|
+
let lock = this.interruptLocks[id];
|
|
1558
|
+
if (lock) {
|
|
1559
|
+
// Reset expiration time.
|
|
1560
|
+
lock.expiresAt = +new Date() + lockDuration;
|
|
1561
|
+
|
|
1562
|
+
// Reset timeout.
|
|
1563
|
+
if (lock.timeout) {
|
|
1564
|
+
clearTimeout(lock.timeout);
|
|
1565
|
+
}
|
|
1566
|
+
lock.timeout = this.createLockTimeout(id);
|
|
1567
|
+
} else {
|
|
1568
|
+
// If the lock doesn't exist it was already released, create a new lock with the same id.
|
|
1569
|
+
lock = {
|
|
1570
|
+
id,
|
|
1571
|
+
expiresAt: +new Date() + lockDuration,
|
|
1572
|
+
timeout: this.createLockTimeout(id),
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
this.interruptLocks[lock.id] = lock;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
this.runInterruptCallback();
|
|
1579
|
+
|
|
1580
|
+
return lock;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
private releaseInterruptLock(id: string) {
|
|
1584
|
+
let lock = this.interruptLocks[id];
|
|
1585
|
+
if (lock) {
|
|
1586
|
+
if (lock.timeout) {
|
|
1587
|
+
clearTimeout(lock.timeout);
|
|
1588
|
+
}
|
|
1589
|
+
delete this.interruptLocks[id];
|
|
1590
|
+
} else {
|
|
1591
|
+
// If the lock doesn't exist it was already released, return a fake lock object to
|
|
1592
|
+
// send a response back to the running app.
|
|
1593
|
+
lock = { id, expiresAt: 0, timeout: null };
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
this.runInterruptCallback();
|
|
1597
|
+
|
|
1598
|
+
return lock;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
private createLockTimeout(id: string) {
|
|
1602
|
+
return setTimeout(() => {
|
|
1603
|
+
this.logInfo(`Interrupt lock expired, releasing: ${id}`);
|
|
1604
|
+
this.releaseInterruptLock(id);
|
|
1605
|
+
}, lockDuration);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
private runInterruptCallback() {
|
|
1609
|
+
const currentlyLocked = this.isLocked();
|
|
1610
|
+
|
|
1611
|
+
// If locked and the lock state has changed, run the interrupt lock callback.
|
|
1612
|
+
// Or if not locked and the lock state has changed, run the disable interrupt lock callback.
|
|
1613
|
+
if (
|
|
1614
|
+
currentlyLocked &&
|
|
1615
|
+
this.previouslyLocked !== currentlyLocked &&
|
|
1616
|
+
this.enableInterruptLockCallback
|
|
1617
|
+
) {
|
|
1618
|
+
this.enableInterruptLockCallback();
|
|
1619
|
+
} else if (
|
|
1620
|
+
!currentlyLocked &&
|
|
1621
|
+
this.previouslyLocked !== currentlyLocked &&
|
|
1622
|
+
this.disableInterruptLockCallback
|
|
1623
|
+
) {
|
|
1624
|
+
this.disableInterruptLockCallback();
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
this.previouslyLocked = currentlyLocked;
|
|
1628
|
+
}
|
|
1410
1629
|
}
|
|
1411
1630
|
|
|
1412
1631
|
const isValidCreateObjectURLResultPayload = (
|
package/src/types.ts
CHANGED
|
@@ -176,14 +176,20 @@ export interface AnalyticsEvent {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
export interface ChargeRequest {
|
|
179
|
+
orderId?: string;
|
|
179
180
|
requestId: string;
|
|
180
|
-
|
|
181
|
+
subTotal: number;
|
|
182
|
+
tip: number;
|
|
183
|
+
tax: number;
|
|
184
|
+
total: number;
|
|
181
185
|
items: Array<{
|
|
182
186
|
name: string;
|
|
183
|
-
price: number;
|
|
184
187
|
description?: string;
|
|
185
188
|
sku?: string;
|
|
186
|
-
|
|
189
|
+
price: number;
|
|
190
|
+
quantity: number;
|
|
191
|
+
tax: number;
|
|
192
|
+
total: number;
|
|
187
193
|
}>;
|
|
188
194
|
}
|
|
189
195
|
|
|
@@ -203,3 +209,9 @@ export interface ChargeResponse {
|
|
|
203
209
|
export interface Terminal {
|
|
204
210
|
status: string;
|
|
205
211
|
}
|
|
212
|
+
|
|
213
|
+
export interface InterruptLock {
|
|
214
|
+
id: string;
|
|
215
|
+
expiresAt: number;
|
|
216
|
+
timeout: ReturnType<typeof setTimeout> | null;
|
|
217
|
+
}
|
package/tasks/npm-publish.sh
CHANGED
|
File without changes
|