@raydiant/playlist-window-manager 0.22.0-rc-electron-upgrade-26-03 → 0.22.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.
@@ -76,10 +76,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
76
76
  var path = __importStar(require("path"));
77
77
  var electron_1 = require("electron");
78
78
  var WindowUnresponsiveError_1 = __importDefault(require("./WindowUnresponsiveError"));
79
- var globalWebPreferences = {
80
- nodeIntegration: true,
81
- contextIsolation: false,
82
- };
83
79
  var BrowserWindowRenderer = /** @class */ (function () {
84
80
  function BrowserWindowRenderer(opts) {
85
81
  this.opts = opts;
@@ -92,7 +88,9 @@ var BrowserWindowRenderer = /** @class */ (function () {
92
88
  show: false,
93
89
  frame: this.opts.frame,
94
90
  hasShadow: this.opts.hasShadow,
95
- webPreferences: __assign({ preload: path.join(__dirname, 'preload.js') }, globalWebPreferences),
91
+ webPreferences: {
92
+ preload: path.join(__dirname, 'preload.js'),
93
+ },
96
94
  });
97
95
  this.proxySettings = null;
98
96
  this.offscreenWindow.setBounds(opts.bounds);
@@ -118,7 +116,9 @@ var BrowserWindowRenderer = /** @class */ (function () {
118
116
  // render all child windows as browser views.
119
117
  view: parentWindowId && this.opts.renderChildWindowsAsBrowserViews
120
118
  ? this.createOrGetPooledBrowserView(windowId, {
121
- webPreferences: __assign({ preload: path.join(__dirname, 'preload.js') }, globalWebPreferences),
119
+ webPreferences: {
120
+ preload: path.join(__dirname, 'preload.js'),
121
+ },
122
122
  })
123
123
  : this.createOrGetPooledBrowserWindow(windowId, {
124
124
  show: false,
@@ -130,7 +130,9 @@ var BrowserWindowRenderer = /** @class */ (function () {
130
130
  : undefined,
131
131
  transparent: backgroundColor === 'transparent',
132
132
  hasShadow: this.opts.hasShadow,
133
- webPreferences: __assign({ preload: path.join(__dirname, 'preload.js') }, globalWebPreferences),
133
+ webPreferences: {
134
+ preload: path.join(__dirname, 'preload.js'),
135
+ },
134
136
  }),
135
137
  // If we are creating a child window and renderChildWindowsAsBrowserViews is false that means
136
138
  // we are rendering child windows with BrowserWindow. BrowserWindows are positioned absolutely so
@@ -157,10 +159,7 @@ var BrowserWindowRenderer = /** @class */ (function () {
157
159
  },
158
160
  };
159
161
  // Attached crash handlers to skip presentation on error.
160
- window.view.webContents.on(
161
- // FIXME
162
- // @ts-ignore
163
- 'render-process-gone', window.renderProcessGoneCallback);
162
+ window.view.webContents.on('render-process-gone', window.renderProcessGoneCallback);
164
163
  window.view.webContents.on('unresponsive', window.unresponsiveCallback);
165
164
  if (window.view instanceof electron_1.BrowserView) {
166
165
  this.moveWindowOffscreen(window);
@@ -266,10 +265,7 @@ var BrowserWindowRenderer = /** @class */ (function () {
266
265
  throw new Error("Failed to remove window, missing window id '".concat(windowId, "'"));
267
266
  }
268
267
  // Remove crash handlers
269
- window.view.webContents.removeListener(
270
- // FIXME
271
- // @ts-ignore
272
- 'render-process-gone', window.renderProcessGoneCallback);
268
+ window.view.webContents.removeListener('render-process-gone', window.renderProcessGoneCallback);
273
269
  window.view.webContents.removeListener('unresponsive', window.unresponsiveCallback);
274
270
  // Remove message handler
275
271
  electron_1.ipcMain.removeListener(windowId, window.receiveMessageCallback);
@@ -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, WorkerState } from './types';
3
+ import { PresentationWindowRenderer, Device, WindowBounds, Theme, Logger, WorkerState, Charge, ChargeResponse, Terminal } from './types';
4
4
  import PlaylistWindowManagerState from './PlaylistWindowManagerState';
5
5
  export interface PlaylistWindowManagerOptions {
6
6
  ruleEngine: RE.RuleEngine;
@@ -18,6 +18,7 @@ export interface PlaylistWindowManagerOptions {
18
18
  isPreview?: boolean;
19
19
  onEnableInterruptLock?: () => void;
20
20
  onDisableInterruptLock?: () => void;
21
+ getTerminal?: () => Terminal | null;
21
22
  }
22
23
  export default class PlaylistWindowManager extends EventEmitter {
23
24
  private options;
@@ -75,6 +76,9 @@ export default class PlaylistWindowManager extends EventEmitter {
75
76
  isReady(): boolean;
76
77
  isLocked(): boolean;
77
78
  runWorkerSubscriptions(state: WorkerState): void;
79
+ sendChargeResponse(response: ChargeResponse): void;
80
+ sendCharge(charge: Charge): void;
81
+ sendTerminal(terminal: Terminal | null): void;
78
82
  private createWindow;
79
83
  private loadCurrentWindow;
80
84
  private shouldPreload;
@@ -90,6 +94,7 @@ export default class PlaylistWindowManager extends EventEmitter {
90
94
  private handleEnableInterruptLock;
91
95
  private handleDisableInterruptLock;
92
96
  private handleAnalyticsEvent;
97
+ private handleChargeRequest;
93
98
  private handleCurrentPresentationComplete;
94
99
  private handleCurrentPresentationError;
95
100
  private handleNextPresentationError;
@@ -117,6 +117,11 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
117
117
  // send analytics events since the next window is not visible.
118
118
  _this.emitCurrentWindowEvent('window_analytics_event', event);
119
119
  };
120
+ _this.handleChargeRequest = function (request) {
121
+ // Emit the event on the current window. Only the current window should
122
+ // send charge requests since the next window is not visible.
123
+ _this.emitCurrentWindowEvent('window_charge_request', request);
124
+ };
120
125
  _this.handleCurrentPresentationComplete = function () { return __awaiter(_this, void 0, void 0, function () {
121
126
  var logger, isThrottled, now, timeSinceLastComplete, delay, _a, next, preview, nextPreviewPresentationId;
122
127
  var _this = this;
@@ -640,6 +645,37 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
640
645
  this.nextWindow.runWorkerSubscriptions(state);
641
646
  }
642
647
  };
648
+ PlaylistWindowManager.prototype.sendChargeResponse = function (response) {
649
+ // Send charge response to the current window. The window will check if
650
+ // has a matching request id and if so, send the response to the app.
651
+ if (this.currentWindow) {
652
+ this.currentWindow.sendChargeResponse(response);
653
+ }
654
+ // Send charge response to the next window.
655
+ if (this.nextWindow) {
656
+ this.nextWindow.sendChargeResponse(response);
657
+ }
658
+ };
659
+ PlaylistWindowManager.prototype.sendCharge = function (charge) {
660
+ // Send charge to the current window.
661
+ if (this.currentWindow) {
662
+ this.currentWindow.sendCharge(charge);
663
+ }
664
+ // Send charge to the next window.
665
+ if (this.nextWindow) {
666
+ this.nextWindow.sendCharge(charge);
667
+ }
668
+ };
669
+ PlaylistWindowManager.prototype.sendTerminal = function (terminal) {
670
+ // Send terminal to the current window.
671
+ if (this.currentWindow) {
672
+ this.currentWindow.sendTerminal(terminal);
673
+ }
674
+ // Send terminal to the next window.
675
+ if (this.nextWindow) {
676
+ this.nextWindow.sendTerminal(terminal);
677
+ }
678
+ };
643
679
  PlaylistWindowManager.prototype.createWindow = function (pr) {
644
680
  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;
645
681
  if (!this.ruleEngine) {
@@ -682,7 +718,8 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
682
718
  ruleEngineState: this.ruleEngine.state,
683
719
  globalRule: this.ruleEngine.globalRule,
684
720
  isPreview: isPreview,
685
- workerState: this.workerState
721
+ workerState: this.workerState,
722
+ getTerminal: this.options.getTerminal,
686
723
  });
687
724
  };
688
725
  PlaylistWindowManager.prototype.loadCurrentWindow = function () {
@@ -702,6 +739,7 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
702
739
  this.currentWindow.onEnableInterruptLock(this.handleEnableInterruptLock);
703
740
  this.currentWindow.onDisableInterruptLock(this.handleDisableInterruptLock);
704
741
  this.currentWindow.onAnalyticsEvent(this.handleAnalyticsEvent);
742
+ this.currentWindow.onChargeRequest(this.handleChargeRequest);
705
743
  return [4 /*yield*/, this.currentWindow.load()];
706
744
  case 1:
707
745
  _a.sent();
@@ -906,6 +944,7 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
906
944
  this.currentWindow.onEnableInterruptLock(this.handleEnableInterruptLock);
907
945
  this.currentWindow.onDisableInterruptLock(this.handleDisableInterruptLock);
908
946
  this.currentWindow.onAnalyticsEvent(this.handleAnalyticsEvent);
947
+ this.currentWindow.onChargeRequest(this.handleChargeRequest);
909
948
  }
910
949
  // Play new current window.
911
950
  this.playCurrentWindow();
@@ -997,12 +1036,12 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
997
1036
  };
998
1037
  PlaylistWindowManager.prototype.emitCurrentWindowErrorEvent = function (event, error) {
999
1038
  return this.emitCurrentWindowEvent(event, {
1000
- errorMessage: this.getErrorMessage(error)
1039
+ errorMessage: this.getErrorMessage(error),
1001
1040
  });
1002
1041
  };
1003
1042
  PlaylistWindowManager.prototype.emitNextWindowErrorEvent = function (event, error) {
1004
1043
  return this.emitNextWindowEvent(event, {
1005
- errorMessage: this.getErrorMessage(error)
1044
+ errorMessage: this.getErrorMessage(error),
1006
1045
  });
1007
1046
  };
1008
1047
  PlaylistWindowManager.prototype.getErrorMessage = function (error) {
@@ -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, WorkerState, AnalyticsEvent } from './types';
4
+ import { Presentation, ApplicationVersion, Theme, Device, PresentationWindowRenderer, WindowBounds, Logger, BuilderState, WorkerState, AnalyticsEvent, Charge, ChargeRequest, ChargeResponse, Terminal } from './types';
5
5
  export interface PresentationWindowOptions {
6
6
  windowManager?: PlaylistWindowManager;
7
7
  windowId: string;
@@ -31,6 +31,7 @@ export interface PresentationWindowOptions {
31
31
  onPresentationProperties?: (properties: any[], string: {
32
32
  [key: string]: string;
33
33
  }) => void;
34
+ getTerminal?: () => Terminal | null;
34
35
  }
35
36
  export default class PresentationWindow {
36
37
  private options;
@@ -50,6 +51,7 @@ export default class PresentationWindow {
50
51
  private enableInterruptLockCallback;
51
52
  private disableInterruptLockCallback;
52
53
  private analyticsEventCallback;
54
+ private chargeRequestCallback;
53
55
  private builderState;
54
56
  private presentation;
55
57
  private playlistWindowManagerState;
@@ -62,6 +64,7 @@ export default class PresentationWindow {
62
64
  private runtimeSubscriptions;
63
65
  private previewSubscriptions;
64
66
  private previewState;
67
+ private paymentSubscriptions;
65
68
  private createObjectURLResolvers;
66
69
  private createObjectURLTimeouts;
67
70
  constructor(options: PresentationWindowOptions);
@@ -79,6 +82,7 @@ export default class PresentationWindow {
79
82
  onEnableInterruptLock(callback: (() => void) | null): void;
80
83
  onDisableInterruptLock(callback: (() => void) | null): void;
81
84
  onAnalyticsEvent(callback: ((event: AnalyticsEvent) => void) | null): void;
85
+ onChargeRequest(callback: ((request: ChargeRequest) => void) | null): void;
82
86
  isReady(): boolean;
83
87
  isComplete(): boolean;
84
88
  isPlaying(): boolean;
@@ -92,6 +96,9 @@ export default class PresentationWindow {
92
96
  reload(): Promise<void>;
93
97
  runGetProperties(builderState?: BuilderState): void;
94
98
  createObjectURL(blob: Blob): Promise<string>;
99
+ sendChargeResponse(response: ChargeResponse): void;
100
+ sendCharge(charge: Charge): void;
101
+ sendTerminal(terminal: Terminal | null): void;
95
102
  private isEmbeddedApp;
96
103
  private allChildWindowManagersReady;
97
104
  private checkIsReadyCallback;
@@ -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.0/index.html';
84
+ var defaultAppRendererUrl = 'https://apps.raydiant.com/3.0.4/index.html';
85
85
  var isValidNumber = function (value) {
86
86
  return typeof value === 'number' && !isNaN(value);
87
87
  };
@@ -94,6 +94,18 @@ var isValidAnalyticsEvent = function (value) {
94
94
  value.productCategory === undefined ||
95
95
  value.productCategory === null);
96
96
  };
97
+ var isValidChargeRequest = function (value) {
98
+ return value &&
99
+ typeof value === 'object' &&
100
+ typeof value.requestId === 'string' &&
101
+ typeof value.amount === 'number' &&
102
+ (value.tags === undefined ||
103
+ value.tags === null ||
104
+ typeof value.tags === 'object') &&
105
+ (value.idempotencyId === undefined ||
106
+ value.idempotencyId === null ||
107
+ typeof value.idempotencyId === 'string');
108
+ };
97
109
  var PresentationWindow = /** @class */ (function () {
98
110
  function PresentationWindow(options) {
99
111
  var _this = this;
@@ -114,6 +126,7 @@ var PresentationWindow = /** @class */ (function () {
114
126
  this.enableInterruptLockCallback = null;
115
127
  this.disableInterruptLockCallback = null;
116
128
  this.analyticsEventCallback = null;
129
+ this.chargeRequestCallback = null;
117
130
  this.forceMute = false;
118
131
  this.error = null;
119
132
  this.workerState = {};
@@ -121,6 +134,7 @@ var PresentationWindow = /** @class */ (function () {
121
134
  this.runtimeSubscriptions = new Set();
122
135
  this.previewSubscriptions = new Set();
123
136
  this.previewState = { inputs: {} };
137
+ this.paymentSubscriptions = new Set();
124
138
  this.createObjectURLResolvers = {};
125
139
  this.createObjectURLTimeouts = {};
126
140
  this.handleEmbeddedMessage = function (type, payload) { return __awaiter(_this, void 0, void 0, function () {
@@ -200,8 +214,13 @@ var PresentationWindow = /** @class */ (function () {
200
214
  break;
201
215
  }
202
216
  case 'analytics-event': {
203
- if (this.analyticsEventCallback && isValidAnalyticsEvent(payload)) {
204
- this.analyticsEventCallback(payload);
217
+ if (isValidAnalyticsEvent(payload)) {
218
+ if (this.analyticsEventCallback) {
219
+ this.analyticsEventCallback(payload);
220
+ }
221
+ }
222
+ else {
223
+ this.logWarning("Invalid payload for analytics-event message: ".concat(payload));
205
224
  }
206
225
  break;
207
226
  }
@@ -245,6 +264,44 @@ var PresentationWindow = /** @class */ (function () {
245
264
  }
246
265
  break;
247
266
  }
267
+ case 'payments-charge-request': {
268
+ if (isValidChargeRequest(payload)) {
269
+ if (this.chargeRequestCallback) {
270
+ this.chargeRequestCallback(payload);
271
+ }
272
+ }
273
+ else {
274
+ this.logWarning("Invalid payload for payments-charge-request message: ".concat(payload));
275
+ }
276
+ break;
277
+ }
278
+ case 'payments-subscribe': {
279
+ if (typeof payload === 'string') {
280
+ this.logInfo("Received 'worker-subscribe' from window '".concat(windowId, "'"));
281
+ // The payload is the payment subscription type (ex. 'charge', 'terminal')
282
+ this.paymentSubscriptions.add(payload);
283
+ // Immediately send terminal when subscribing.
284
+ if (payload === 'terminal' && this.options.getTerminal) {
285
+ this.sendMessageObject('payments-terminal-event', {
286
+ terminal: this.options.getTerminal(),
287
+ });
288
+ }
289
+ }
290
+ else {
291
+ this.logWarning("Invalid payload for payments-subscribe message: ".concat(payload));
292
+ }
293
+ break;
294
+ }
295
+ case 'payments-unsubscribe': {
296
+ if (typeof payload === 'string') {
297
+ this.logInfo("Received 'payments-unsubscribe' from window '".concat(windowId, "'"));
298
+ this.paymentSubscriptions.delete(payload);
299
+ }
300
+ else {
301
+ this.logWarning("Invalid payload for payments-unsubscribe message: ".concat(payload));
302
+ }
303
+ break;
304
+ }
248
305
  default: {
249
306
  this.logWarning("Received unknown embedded message type: ".concat(type));
250
307
  }
@@ -480,6 +537,9 @@ var PresentationWindow = /** @class */ (function () {
480
537
  PresentationWindow.prototype.onAnalyticsEvent = function (callback) {
481
538
  this.analyticsEventCallback = callback;
482
539
  };
540
+ PresentationWindow.prototype.onChargeRequest = function (callback) {
541
+ this.chargeRequestCallback = callback;
542
+ };
483
543
  PresentationWindow.prototype.isReady = function () {
484
544
  // TODO: To support the 'ready' event in embedded apps, they will need to tell the
485
545
  // runtime that it should wait for ready instead of DOMLoaded. This could be a "wait
@@ -571,6 +631,39 @@ var PresentationWindow = /** @class */ (function () {
571
631
  });
572
632
  });
573
633
  };
634
+ PresentationWindow.prototype.sendChargeResponse = function (response) {
635
+ // TODO: Instead of sending the charge to all windows, we should only send it to the app
636
+ // that requested the charge. We probably don't want to use the windowId to avoid missing
637
+ // the events in case the window was closed/restarted. Since we are only sending the charge
638
+ // id and status this seems ok for now but need to be mindful of sending any sensitive data.
639
+ this.sendMessageObject('payments-charge-response', {
640
+ requestId: response.requestId,
641
+ charge: response.charge,
642
+ error: response.error,
643
+ });
644
+ // Forward the event to the child window managers.
645
+ Object.values(this.windowManagers).forEach(function (wm) {
646
+ return wm.sendChargeResponse(response);
647
+ });
648
+ };
649
+ PresentationWindow.prototype.sendCharge = function (charge) {
650
+ // TODO: Instead of sending the charge to all windows, we should only send it to the app
651
+ // that requested the charge.
652
+ if (this.paymentSubscriptions.has('charge')) {
653
+ this.sendMessageObject('payments-charge-event', { charge: charge });
654
+ }
655
+ // Forward the event to the child window managers.
656
+ Object.values(this.windowManagers).forEach(function (wm) { return wm.sendCharge(charge); });
657
+ };
658
+ PresentationWindow.prototype.sendTerminal = function (terminal) {
659
+ if (this.paymentSubscriptions.has('terminal')) {
660
+ this.sendMessageObject('payments-terminal-event', { terminal: terminal });
661
+ }
662
+ // Forward the event to the child window managers.
663
+ Object.values(this.windowManagers).forEach(function (wm) {
664
+ return wm.sendTerminal(terminal);
665
+ });
666
+ };
574
667
  PresentationWindow.prototype.isEmbeddedApp = function () {
575
668
  var _a = this.options, applicationVersion = _a.applicationVersion, isPreview = _a.isPreview;
576
669
  // If the application has a source url, always use it for rendering the preview.
@@ -859,6 +952,7 @@ var PresentationWindow = /** @class */ (function () {
859
952
  isPreview: isPreview,
860
953
  onEnableInterruptLock: function () { return _this.enableInteruptLock(windowName); },
861
954
  onDisableInterruptLock: function () { return _this.disableInteruptLock(windowName); },
955
+ getTerminal: this.options.getTerminal,
862
956
  });
863
957
  // Hook up child window manager events to bubble up to the parent.
864
958
  if (parentWindowManager) {
@@ -877,6 +971,9 @@ var PresentationWindow = /** @class */ (function () {
877
971
  windowManager.on('window_analytics_event', function (metadata) {
878
972
  parentWindowManager.emit('window_analytics_event', metadata);
879
973
  });
974
+ windowManager.on('window_charge_request', function (metadata) {
975
+ parentWindowManager.emit('window_charge_request', metadata);
976
+ });
880
977
  }
881
978
  this.windowManagers[windowName] = windowManager;
882
979
  windowManager.setRenderer(renderer);
@@ -238,4 +238,103 @@ describe('embedded apps', function () {
238
238
  });
239
239
  }); });
240
240
  });
241
+ describe('payments', function () {
242
+ test('charge request', function () { return __awaiter(void 0, void 0, void 0, function () {
243
+ var testRenderer, window, callback;
244
+ return __generator(this, function (_a) {
245
+ switch (_a.label) {
246
+ case 0:
247
+ testRenderer = (0, testUtils_1.createRenderer)();
248
+ window = (0, testUtils_1.createPresentationWindow)({
249
+ windowId: 'test-charge-request',
250
+ presentation: (0, testUtils_1.createPresentation)(),
251
+ applicationVersion: (0, testUtils_1.createApplicationVersion)({
252
+ embeddedUrlFormat: 'http://test.local/:input',
253
+ }),
254
+ renderer: testRenderer,
255
+ });
256
+ callback = jest.fn();
257
+ window.onChargeRequest(callback);
258
+ return [4 /*yield*/, window.load()];
259
+ case 1:
260
+ _a.sent();
261
+ testRenderer.runReceiveMessageCallback('payments-charge-request', {
262
+ requestId: 'test',
263
+ amount: 100,
264
+ });
265
+ expect(callback).toHaveBeenCalledWith({
266
+ requestId: 'test',
267
+ amount: 100,
268
+ });
269
+ // Send charge response.
270
+ window.sendChargeResponse({
271
+ requestId: 'test',
272
+ charge: { id: '1', status: 'pending' },
273
+ });
274
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledWith('test-charge-request', 'payments-charge-response', { requestId: 'test', charge: { id: '1', status: 'pending' } });
275
+ return [2 /*return*/];
276
+ }
277
+ });
278
+ }); });
279
+ test('subscribe to charge', function () { return __awaiter(void 0, void 0, void 0, function () {
280
+ var testRenderer, window;
281
+ return __generator(this, function (_a) {
282
+ switch (_a.label) {
283
+ case 0:
284
+ testRenderer = (0, testUtils_1.createRenderer)();
285
+ window = (0, testUtils_1.createPresentationWindow)({
286
+ windowId: 'test-payments-subscribe-charge',
287
+ presentation: (0, testUtils_1.createPresentation)(),
288
+ applicationVersion: (0, testUtils_1.createApplicationVersion)({
289
+ embeddedUrlFormat: 'http://test.local/:input',
290
+ }),
291
+ renderer: testRenderer,
292
+ });
293
+ return [4 /*yield*/, window.load()];
294
+ case 1:
295
+ _a.sent();
296
+ testRenderer.runReceiveMessageCallback('payments-subscribe', 'charge');
297
+ window.sendCharge({ id: 'test', status: 'success' });
298
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledWith('test-payments-subscribe-charge', 'payments-charge-event', { charge: { id: 'test', status: 'success' } });
299
+ // Unsubscribe should not send a message.
300
+ testRenderer.runReceiveMessageCallback('payments-unsubscribe', 'charge');
301
+ window.sendCharge({ id: 'test2', status: 'success' });
302
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledTimes(1);
303
+ return [2 /*return*/];
304
+ }
305
+ });
306
+ }); });
307
+ test('subscribe to terminal', function () { return __awaiter(void 0, void 0, void 0, function () {
308
+ var testRenderer, window;
309
+ return __generator(this, function (_a) {
310
+ switch (_a.label) {
311
+ case 0:
312
+ testRenderer = (0, testUtils_1.createRenderer)();
313
+ window = (0, testUtils_1.createPresentationWindow)({
314
+ windowId: 'test-payments-subscribe-terminal',
315
+ presentation: (0, testUtils_1.createPresentation)(),
316
+ applicationVersion: (0, testUtils_1.createApplicationVersion)({
317
+ embeddedUrlFormat: 'http://test.local/:input',
318
+ }),
319
+ renderer: testRenderer,
320
+ getTerminal: function () { return null; },
321
+ });
322
+ return [4 /*yield*/, window.load()];
323
+ case 1:
324
+ _a.sent();
325
+ testRenderer.runReceiveMessageCallback('payments-subscribe', 'terminal');
326
+ // Terminal subscriptions should receive the current terminal immediately after subscribing.
327
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledWith('test-payments-subscribe-terminal', 'payments-terminal-event', { terminal: null });
328
+ // Send terminal update event.
329
+ window.sendTerminal({ status: 'open' });
330
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledWith('test-payments-subscribe-terminal', 'payments-terminal-event', { terminal: { status: 'open' } });
331
+ // Unsubscribe should not send a message.
332
+ testRenderer.runReceiveMessageCallback('payments-unsubscribe', 'terminal');
333
+ window.sendTerminal({ status: 'closed' });
334
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledTimes(2);
335
+ return [2 /*return*/];
336
+ }
337
+ });
338
+ }); });
339
+ });
241
340
  // TODO: Backfill tests for all legacy and embedded apps.
@@ -98,6 +98,7 @@ function createPresentationWindow(overrides) {
98
98
  muteWindow: function () { },
99
99
  unMuteWindow: function () { },
100
100
  },
101
+ getTerminal: overrides.getTerminal,
101
102
  });
102
103
  }
103
104
  exports.createPresentationWindow = createPresentationWindow;
package/build/types.d.ts CHANGED
@@ -140,3 +140,23 @@ export interface AnalyticsEvent {
140
140
  source: string;
141
141
  productCategory?: string | null;
142
142
  }
143
+ export interface ChargeRequest {
144
+ requestId: string;
145
+ amount: number;
146
+ tags?: Record<string, undefined | null | boolean | number | string>;
147
+ idempotencyId?: string;
148
+ }
149
+ export interface Charge {
150
+ id: string;
151
+ status: string;
152
+ }
153
+ export interface ChargeResponse {
154
+ requestId: string;
155
+ charge?: Charge;
156
+ error?: {
157
+ message: string;
158
+ };
159
+ }
160
+ export interface Terminal {
161
+ status: string;
162
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raydiant/playlist-window-manager",
3
- "version": "0.22.0-rc-electron-upgrade-26-03",
3
+ "version": "0.22.0",
4
4
  "description": "Window manager for playlist playback.",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
@@ -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": "26.2.0",
25
+ "electron": "10.1.5",
26
26
  "jest": "^24.8.0",
27
27
  "ts-jest": "^24.0.2",
28
28
  "typescript": "^4.1.2"
@@ -46,11 +46,6 @@ interface BrowserWindowRendererOptions {
46
46
  windowCrashHandler?: () => void;
47
47
  }
48
48
 
49
- const globalWebPreferences = {
50
- // nodeIntegration: true,
51
- contextIsolation: false,
52
- };
53
-
54
49
  export default class BrowserWindowRenderer
55
50
  implements PresentationWindowRenderer
56
51
  {
@@ -69,7 +64,6 @@ export default class BrowserWindowRenderer
69
64
  hasShadow: this.opts.hasShadow,
70
65
  webPreferences: {
71
66
  preload: path.join(__dirname, 'preload.js'),
72
- ...globalWebPreferences,
73
67
  },
74
68
  });
75
69
 
@@ -107,7 +101,6 @@ export default class BrowserWindowRenderer
107
101
  ? this.createOrGetPooledBrowserView(windowId, {
108
102
  webPreferences: {
109
103
  preload: path.join(__dirname, 'preload.js'),
110
- ...globalWebPreferences,
111
104
  },
112
105
  })
113
106
  : this.createOrGetPooledBrowserWindow(windowId, {
@@ -122,7 +115,6 @@ export default class BrowserWindowRenderer
122
115
  hasShadow: this.opts.hasShadow,
123
116
  webPreferences: {
124
117
  preload: path.join(__dirname, 'preload.js'),
125
- ...globalWebPreferences,
126
118
  },
127
119
  }),
128
120
  // If we are creating a child window and renderChildWindowsAsBrowserViews is false that means
@@ -177,12 +169,12 @@ export default class BrowserWindowRenderer
177
169
 
178
170
  // Attached crash handlers to skip presentation on error.
179
171
  window.view.webContents.on(
180
- // FIXME
181
- // @ts-ignore
182
172
  'render-process-gone',
183
173
  window.renderProcessGoneCallback
184
174
  );
175
+
185
176
  window.view.webContents.on('unresponsive', window.unresponsiveCallback);
177
+
186
178
  if (window.view instanceof BrowserView) {
187
179
  this.moveWindowOffscreen(window);
188
180
  }
@@ -190,6 +182,7 @@ export default class BrowserWindowRenderer
190
182
  this.windows[windowId] = window;
191
183
  // Listen for messages from window.
192
184
  ipcMain.on(windowId, window.receiveMessageCallback);
185
+
193
186
  if (backgroundColor) {
194
187
  // To make a transparent window you need to set it's value to #00000000
195
188
  // Electron issue: https://github.com/electron/electron/issues/20357
@@ -286,8 +279,6 @@ export default class BrowserWindowRenderer
286
279
 
287
280
  // Remove crash handlers
288
281
  window.view.webContents.removeListener(
289
- // FIXME
290
- // @ts-ignore
291
282
  'render-process-gone',
292
283
  window.renderProcessGoneCallback
293
284
  );
@@ -8,7 +8,11 @@ import {
8
8
  Theme,
9
9
  Logger,
10
10
  WorkerState,
11
- AnalyticsEvent
11
+ AnalyticsEvent,
12
+ Charge,
13
+ ChargeRequest,
14
+ ChargeResponse,
15
+ Terminal,
12
16
  } from './types';
13
17
  import PresentationWindow from './PresentationWindow';
14
18
  import PlaylistWindowManagerState from './PlaylistWindowManagerState';
@@ -67,6 +71,9 @@ export interface PlaylistWindowManagerOptions {
67
71
 
68
72
  // Called when the current window wants to disable the interrupt lock
69
73
  onDisableInterruptLock?: () => void;
74
+
75
+ // Called when a window needs the current terminal object.
76
+ getTerminal?: () => Terminal | null;
70
77
  }
71
78
 
72
79
  // Wait at least 3 seconds before restarting or moving onto the next window
@@ -440,6 +447,43 @@ export default class PlaylistWindowManager extends EventEmitter {
440
447
  }
441
448
  }
442
449
 
450
+ public sendChargeResponse(response: ChargeResponse) {
451
+ // Send charge response to the current window. The window will check if
452
+ // has a matching request id and if so, send the response to the app.
453
+ if (this.currentWindow) {
454
+ this.currentWindow.sendChargeResponse(response);
455
+ }
456
+
457
+ // Send charge response to the next window.
458
+ if (this.nextWindow) {
459
+ this.nextWindow.sendChargeResponse(response);
460
+ }
461
+ }
462
+
463
+ public sendCharge(charge: Charge) {
464
+ // Send charge to the current window.
465
+ if (this.currentWindow) {
466
+ this.currentWindow.sendCharge(charge);
467
+ }
468
+
469
+ // Send charge to the next window.
470
+ if (this.nextWindow) {
471
+ this.nextWindow.sendCharge(charge);
472
+ }
473
+ }
474
+
475
+ public sendTerminal(terminal: Terminal | null) {
476
+ // Send terminal to the current window.
477
+ if (this.currentWindow) {
478
+ this.currentWindow.sendTerminal(terminal);
479
+ }
480
+
481
+ // Send terminal to the next window.
482
+ if (this.nextWindow) {
483
+ this.nextWindow.sendTerminal(terminal);
484
+ }
485
+ }
486
+
443
487
  private createWindow(pr: RE.NextPresentationID): PresentationWindow {
444
488
  const {
445
489
  readyTimeoutDurationSeconds,
@@ -449,7 +493,7 @@ export default class PlaylistWindowManager extends EventEmitter {
449
493
  logger,
450
494
  windowName,
451
495
  parentWindowId,
452
- isPreview
496
+ isPreview,
453
497
  } = this.options;
454
498
 
455
499
  if (!this.ruleEngine) {
@@ -503,7 +547,8 @@ export default class PlaylistWindowManager extends EventEmitter {
503
547
  ruleEngineState: this.ruleEngine.state,
504
548
  globalRule: this.ruleEngine.globalRule,
505
549
  isPreview,
506
- workerState: this.workerState
550
+ workerState: this.workerState,
551
+ getTerminal: this.options.getTerminal,
507
552
  });
508
553
  }
509
554
 
@@ -523,6 +568,7 @@ export default class PlaylistWindowManager extends EventEmitter {
523
568
  this.currentWindow.onEnableInterruptLock(this.handleEnableInterruptLock);
524
569
  this.currentWindow.onDisableInterruptLock(this.handleDisableInterruptLock);
525
570
  this.currentWindow.onAnalyticsEvent(this.handleAnalyticsEvent);
571
+ this.currentWindow.onChargeRequest(this.handleChargeRequest);
526
572
 
527
573
  await this.currentWindow.load();
528
574
  }
@@ -695,6 +741,7 @@ export default class PlaylistWindowManager extends EventEmitter {
695
741
  this.handleDisableInterruptLock
696
742
  );
697
743
  this.currentWindow.onAnalyticsEvent(this.handleAnalyticsEvent);
744
+ this.currentWindow.onChargeRequest(this.handleChargeRequest);
698
745
  }
699
746
 
700
747
  // Play new current window.
@@ -764,6 +811,12 @@ export default class PlaylistWindowManager extends EventEmitter {
764
811
  this.emitCurrentWindowEvent('window_analytics_event', event);
765
812
  };
766
813
 
814
+ private handleChargeRequest = (request: ChargeRequest) => {
815
+ // Emit the event on the current window. Only the current window should
816
+ // send charge requests since the next window is not visible.
817
+ this.emitCurrentWindowEvent('window_charge_request', request);
818
+ };
819
+
767
820
  private handleCurrentPresentationComplete = async () => {
768
821
  const { logger } = this.options;
769
822
 
@@ -1005,7 +1058,7 @@ export default class PlaylistWindowManager extends EventEmitter {
1005
1058
  if (window) {
1006
1059
  this.emit(event, {
1007
1060
  ...window.metadata(),
1008
- data
1061
+ data,
1009
1062
  });
1010
1063
  } else {
1011
1064
  logger.warn(`Attempted to emit ${label} window event but window not set`);
@@ -1022,13 +1075,13 @@ export default class PlaylistWindowManager extends EventEmitter {
1022
1075
 
1023
1076
  private emitCurrentWindowErrorEvent(event: string, error: any) {
1024
1077
  return this.emitCurrentWindowEvent(event, {
1025
- errorMessage: this.getErrorMessage(error)
1078
+ errorMessage: this.getErrorMessage(error),
1026
1079
  });
1027
1080
  }
1028
1081
 
1029
1082
  private emitNextWindowErrorEvent(event: string, error: any) {
1030
1083
  return this.emitNextWindowEvent(event, {
1031
- errorMessage: this.getErrorMessage(error)
1084
+ errorMessage: this.getErrorMessage(error),
1032
1085
  });
1033
1086
  }
1034
1087
 
@@ -218,4 +218,116 @@ describe('embedded apps', () => {
218
218
  });
219
219
  });
220
220
 
221
+ describe('payments', () => {
222
+ test('charge request', async () => {
223
+ const testRenderer = createRenderer();
224
+
225
+ const window = createPresentationWindow({
226
+ windowId: 'test-charge-request',
227
+ presentation: createPresentation(),
228
+ applicationVersion: createApplicationVersion({
229
+ embeddedUrlFormat: 'http://test.local/:input',
230
+ }),
231
+ renderer: testRenderer,
232
+ });
233
+
234
+ const callback = jest.fn();
235
+ window.onChargeRequest(callback);
236
+
237
+ await window.load();
238
+
239
+ testRenderer.runReceiveMessageCallback('payments-charge-request', {
240
+ requestId: 'test',
241
+ amount: 100,
242
+ });
243
+
244
+ expect(callback).toHaveBeenCalledWith({
245
+ requestId: 'test',
246
+ amount: 100,
247
+ });
248
+
249
+ // Send charge response.
250
+
251
+ window.sendChargeResponse({
252
+ requestId: 'test',
253
+ charge: { id: '1', status: 'pending' },
254
+ });
255
+
256
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledWith(
257
+ 'test-charge-request',
258
+ 'payments-charge-response',
259
+ { requestId: 'test', charge: { id: '1', status: 'pending' } }
260
+ );
261
+ });
262
+
263
+ test('subscribe to charge', async () => {
264
+ const testRenderer = createRenderer();
265
+
266
+ const window = createPresentationWindow({
267
+ windowId: 'test-payments-subscribe-charge',
268
+ presentation: createPresentation(),
269
+ applicationVersion: createApplicationVersion({
270
+ embeddedUrlFormat: 'http://test.local/:input',
271
+ }),
272
+ renderer: testRenderer,
273
+ });
274
+
275
+ await window.load();
276
+
277
+ testRenderer.runReceiveMessageCallback('payments-subscribe', 'charge');
278
+
279
+ window.sendCharge({ id: 'test', status: 'success' });
280
+
281
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledWith(
282
+ 'test-payments-subscribe-charge',
283
+ 'payments-charge-event',
284
+ { charge: { id: 'test', status: 'success' } }
285
+ );
286
+
287
+ // Unsubscribe should not send a message.
288
+ testRenderer.runReceiveMessageCallback('payments-unsubscribe', 'charge');
289
+ window.sendCharge({ id: 'test2', status: 'success' });
290
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledTimes(1);
291
+ });
292
+
293
+ test('subscribe to terminal', async () => {
294
+ const testRenderer = createRenderer();
295
+
296
+ const window = createPresentationWindow({
297
+ windowId: 'test-payments-subscribe-terminal',
298
+ presentation: createPresentation(),
299
+ applicationVersion: createApplicationVersion({
300
+ embeddedUrlFormat: 'http://test.local/:input',
301
+ }),
302
+ renderer: testRenderer,
303
+ getTerminal: () => null,
304
+ });
305
+
306
+ await window.load();
307
+
308
+ testRenderer.runReceiveMessageCallback('payments-subscribe', 'terminal');
309
+
310
+ // Terminal subscriptions should receive the current terminal immediately after subscribing.
311
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledWith(
312
+ 'test-payments-subscribe-terminal',
313
+ 'payments-terminal-event',
314
+ { terminal: null }
315
+ );
316
+
317
+ // Send terminal update event.
318
+ window.sendTerminal({ status: 'open' });
319
+
320
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledWith(
321
+ 'test-payments-subscribe-terminal',
322
+ 'payments-terminal-event',
323
+ { terminal: { status: 'open' } }
324
+ );
325
+
326
+ // Unsubscribe should not send a message.
327
+ testRenderer.runReceiveMessageCallback('payments-unsubscribe', 'terminal');
328
+ window.sendTerminal({ status: 'closed' });
329
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledTimes(2);
330
+ });
331
+ });
332
+
221
333
  // TODO: Backfill tests for all legacy and embedded apps.
@@ -20,6 +20,10 @@ import {
20
20
  EmbeddedAppMetadata,
21
21
  AnalyticsEvent,
22
22
  PreviewState,
23
+ Charge,
24
+ ChargeRequest,
25
+ ChargeResponse,
26
+ Terminal,
23
27
  } from './types';
24
28
 
25
29
  export interface PresentationWindowOptions {
@@ -52,10 +56,11 @@ export interface PresentationWindowOptions {
52
56
  properties: any[],
53
57
  string: { [key: string]: string }
54
58
  ) => void;
59
+ getTerminal?: () => Terminal | null;
55
60
  }
56
61
 
57
62
  // TODO: Automatically inject latest version when building in CI.
58
- const defaultAppRendererUrl = 'https://apps.raydiant.com/3.0.0/index.html';
63
+ const defaultAppRendererUrl = 'https://apps.raydiant.com/3.0.4/index.html';
59
64
 
60
65
  const isValidNumber = (value: any) =>
61
66
  typeof value === 'number' && !isNaN(value);
@@ -69,6 +74,18 @@ const isValidAnalyticsEvent = (value: any): value is AnalyticsEvent =>
69
74
  value.productCategory === undefined ||
70
75
  value.productCategory === null);
71
76
 
77
+ const isValidChargeRequest = (value: any): value is ChargeRequest =>
78
+ value &&
79
+ typeof value === 'object' &&
80
+ typeof value.requestId === 'string' &&
81
+ typeof value.amount === 'number' &&
82
+ (value.tags === undefined ||
83
+ value.tags === null ||
84
+ typeof value.tags === 'object') &&
85
+ (value.idempotencyId === undefined ||
86
+ value.idempotencyId === null ||
87
+ typeof value.idempotencyId === 'string');
88
+
72
89
  export default class PresentationWindow {
73
90
  private didReceiveReady: boolean = false;
74
91
  private readyCallback: (() => void) | null = null;
@@ -88,6 +105,8 @@ export default class PresentationWindow {
88
105
  private disableInterruptLockCallback: (() => void) | null = null;
89
106
  private analyticsEventCallback: ((event: AnalyticsEvent) => void) | null =
90
107
  null;
108
+ private chargeRequestCallback: ((request: ChargeRequest) => void) | null =
109
+ null;
91
110
 
92
111
  private builderState: BuilderState | undefined;
93
112
  private presentation: Presentation;
@@ -102,6 +121,7 @@ export default class PresentationWindow {
102
121
  private runtimeSubscriptions = new Set<string>();
103
122
  private previewSubscriptions = new Set<string>();
104
123
  private previewState: PreviewState = { inputs: {} };
124
+ private paymentSubscriptions = new Set<string>();
105
125
 
106
126
  private createObjectURLResolvers: Record<string, (url: string) => void> = {};
107
127
  private createObjectURLTimeouts: Record<
@@ -288,6 +308,10 @@ export default class PresentationWindow {
288
308
  this.analyticsEventCallback = callback;
289
309
  }
290
310
 
311
+ public onChargeRequest(callback: ((request: ChargeRequest) => void) | null) {
312
+ this.chargeRequestCallback = callback;
313
+ }
314
+
291
315
  public isReady() {
292
316
  // TODO: To support the 'ready' event in embedded apps, they will need to tell the
293
317
  // runtime that it should wait for ready instead of DOMLoaded. This could be a "wait
@@ -394,6 +418,42 @@ export default class PresentationWindow {
394
418
  return URL.createObjectURL(blob);
395
419
  }
396
420
 
421
+ public sendChargeResponse(response: ChargeResponse) {
422
+ // TODO: Instead of sending the charge to all windows, we should only send it to the app
423
+ // that requested the charge. We probably don't want to use the windowId to avoid missing
424
+ // the events in case the window was closed/restarted. Since we are only sending the charge
425
+ // id and status this seems ok for now but need to be mindful of sending any sensitive data.
426
+ this.sendMessageObject('payments-charge-response', {
427
+ requestId: response.requestId,
428
+ charge: response.charge,
429
+ error: response.error,
430
+ });
431
+ // Forward the event to the child window managers.
432
+ Object.values(this.windowManagers).forEach((wm) =>
433
+ wm.sendChargeResponse(response)
434
+ );
435
+ }
436
+
437
+ public sendCharge(charge: Charge) {
438
+ // TODO: Instead of sending the charge to all windows, we should only send it to the app
439
+ // that requested the charge.
440
+ if (this.paymentSubscriptions.has('charge')) {
441
+ this.sendMessageObject('payments-charge-event', { charge });
442
+ }
443
+ // Forward the event to the child window managers.
444
+ Object.values(this.windowManagers).forEach((wm) => wm.sendCharge(charge));
445
+ }
446
+
447
+ public sendTerminal(terminal: Terminal | null) {
448
+ if (this.paymentSubscriptions.has('terminal')) {
449
+ this.sendMessageObject('payments-terminal-event', { terminal });
450
+ }
451
+ // Forward the event to the child window managers.
452
+ Object.values(this.windowManagers).forEach((wm) =>
453
+ wm.sendTerminal(terminal)
454
+ );
455
+ }
456
+
397
457
  private isEmbeddedApp() {
398
458
  const { applicationVersion, isPreview } = this.options;
399
459
 
@@ -714,6 +774,7 @@ export default class PresentationWindow {
714
774
  isPreview,
715
775
  onEnableInterruptLock: () => this.enableInteruptLock(windowName),
716
776
  onDisableInterruptLock: () => this.disableInteruptLock(windowName),
777
+ getTerminal: this.options.getTerminal,
717
778
  });
718
779
 
719
780
  // Hook up child window manager events to bubble up to the parent.
@@ -733,6 +794,9 @@ export default class PresentationWindow {
733
794
  windowManager.on('window_analytics_event', (metadata) => {
734
795
  parentWindowManager.emit('window_analytics_event', metadata);
735
796
  });
797
+ windowManager.on('window_charge_request', (metadata) => {
798
+ parentWindowManager.emit('window_charge_request', metadata);
799
+ });
736
800
  }
737
801
 
738
802
  this.windowManagers[windowName] = windowManager;
@@ -917,8 +981,14 @@ export default class PresentationWindow {
917
981
  }
918
982
 
919
983
  case 'analytics-event': {
920
- if (this.analyticsEventCallback && isValidAnalyticsEvent(payload)) {
921
- this.analyticsEventCallback(payload);
984
+ if (isValidAnalyticsEvent(payload)) {
985
+ if (this.analyticsEventCallback) {
986
+ this.analyticsEventCallback(payload);
987
+ }
988
+ } else {
989
+ this.logWarning(
990
+ `Invalid payload for analytics-event message: ${payload}`
991
+ );
922
992
  }
923
993
  break;
924
994
  }
@@ -973,6 +1043,53 @@ export default class PresentationWindow {
973
1043
  break;
974
1044
  }
975
1045
 
1046
+ case 'payments-charge-request': {
1047
+ if (isValidChargeRequest(payload)) {
1048
+ if (this.chargeRequestCallback) {
1049
+ this.chargeRequestCallback(payload);
1050
+ }
1051
+ } else {
1052
+ this.logWarning(
1053
+ `Invalid payload for payments-charge-request message: ${payload}`
1054
+ );
1055
+ }
1056
+ break;
1057
+ }
1058
+
1059
+ case 'payments-subscribe': {
1060
+ if (typeof payload === 'string') {
1061
+ this.logInfo(`Received 'worker-subscribe' from window '${windowId}'`);
1062
+ // The payload is the payment subscription type (ex. 'charge', 'terminal')
1063
+ this.paymentSubscriptions.add(payload);
1064
+
1065
+ // Immediately send terminal when subscribing.
1066
+ if (payload === 'terminal' && this.options.getTerminal) {
1067
+ this.sendMessageObject('payments-terminal-event', {
1068
+ terminal: this.options.getTerminal(),
1069
+ });
1070
+ }
1071
+ } else {
1072
+ this.logWarning(
1073
+ `Invalid payload for payments-subscribe message: ${payload}`
1074
+ );
1075
+ }
1076
+ break;
1077
+ }
1078
+
1079
+ case 'payments-unsubscribe': {
1080
+ if (typeof payload === 'string') {
1081
+ this.logInfo(
1082
+ `Received 'payments-unsubscribe' from window '${windowId}'`
1083
+ );
1084
+ this.paymentSubscriptions.delete(payload);
1085
+ } else {
1086
+ this.logWarning(
1087
+ `Invalid payload for payments-unsubscribe message: ${payload}`
1088
+ );
1089
+ }
1090
+ break;
1091
+ }
1092
+
976
1093
  default: {
977
1094
  this.logWarning(`Received unknown embedded message type: ${type}`);
978
1095
  }
package/src/testUtils.ts CHANGED
@@ -38,6 +38,7 @@ export function createPresentationWindow(
38
38
  muteWindow() {},
39
39
  unMuteWindow() {},
40
40
  },
41
+ getTerminal: overrides.getTerminal,
41
42
  });
42
43
  }
43
44
 
package/src/types.ts CHANGED
@@ -173,3 +173,27 @@ export interface AnalyticsEvent {
173
173
  source: string;
174
174
  productCategory?: string | null;
175
175
  }
176
+
177
+ export interface ChargeRequest {
178
+ requestId: string;
179
+ amount: number;
180
+ tags?: Record<string, undefined | null | boolean | number | string>;
181
+ idempotencyId?: string;
182
+ }
183
+
184
+ export interface Charge {
185
+ id: string;
186
+ status: string;
187
+ }
188
+
189
+ export interface ChargeResponse {
190
+ requestId: string;
191
+ charge?: Charge;
192
+ error?: {
193
+ message: string;
194
+ };
195
+ }
196
+
197
+ export interface Terminal {
198
+ status: string;
199
+ }