@raydiant/playlist-window-manager 0.26.2 → 0.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -154,4 +154,5 @@ export default class PresentationWindow {
154
154
  private releaseInterruptLock;
155
155
  private createLockTimeout;
156
156
  private runInterruptCallback;
157
+ private getRenderMode;
157
158
  }
@@ -659,17 +659,30 @@ var PresentationWindow = /** @class */ (function () {
659
659
  this.forceMute = forceMute;
660
660
  };
661
661
  PresentationWindow.prototype.reload = function () {
662
+ var _a;
662
663
  return __awaiter(this, void 0, void 0, function () {
663
- var _a, applicationVersion, windowId, updatedInputs, url;
664
- return __generator(this, function (_b) {
665
- switch (_b.label) {
664
+ var _b, applicationVersion, windowId, updatedInputs, url;
665
+ return __generator(this, function (_c) {
666
+ switch (_c.label) {
666
667
  case 0:
667
- _a = this.options, applicationVersion = _a.applicationVersion, windowId = _a.windowId;
668
+ _b = this.options, applicationVersion = _b.applicationVersion, windowId = _b.windowId;
668
669
  if (!this.isEmbeddedApp()) return [3 /*break*/, 4];
669
670
  if (!applicationVersion.embeddedUrlFormat) return [3 /*break*/, 3];
670
671
  if (!this.previewSubscriptions.has('inputs')) return [3 /*break*/, 1];
671
672
  this.logInfo("Reload window '".concat(windowId, "' for embedded app preview state"));
672
673
  updatedInputs = transformInputValues(this.presentation.applicationVariables);
674
+ // Add metadata to inputs if they exist in the URL template. These need to be
675
+ // consistent with metadata keys in embeddedApps.ts.
676
+ if (applicationVersion.embeddedUrlFormat.includes(':$renderMode')) {
677
+ updatedInputs['$renderMode'] = this.getRenderMode();
678
+ }
679
+ if (applicationVersion.embeddedUrlFormat.includes(':$token')) {
680
+ updatedInputs['$token'] = this.presentation.token;
681
+ }
682
+ if (applicationVersion.embeddedUrlFormat.includes(':$deviceId')) {
683
+ updatedInputs['$deviceId'] = (_a = this.options.device) === null || _a === void 0 ? void 0 : _a.id;
684
+ }
685
+ // Send the updated inputs to the embedded app if they have changed.
673
686
  if (!(0, fast_deep_equal_1.default)(updatedInputs, this.previewState.inputs)) {
674
687
  this.sendMessageObject('preview-inputs', updatedInputs);
675
688
  }
@@ -680,13 +693,13 @@ var PresentationWindow = /** @class */ (function () {
680
693
  this.logInfo("Reload window '".concat(windowId, "' for embedded app url '").concat(url, "'"));
681
694
  return [4 /*yield*/, this.loadWindow(url)];
682
695
  case 2:
683
- _b.sent();
684
- _b.label = 3;
696
+ _c.sent();
697
+ _c.label = 3;
685
698
  case 3: return [3 /*break*/, 5];
686
699
  case 4:
687
700
  this.logInfo("Reload window '".concat(windowId, "'"));
688
701
  this.sendRenderMessage();
689
- _b.label = 5;
702
+ _c.label = 5;
690
703
  case 5: return [2 /*return*/];
691
704
  }
692
705
  });
@@ -1144,10 +1157,13 @@ var PresentationWindow = /** @class */ (function () {
1144
1157
  }
1145
1158
  };
1146
1159
  PresentationWindow.prototype.getEmbeddedAppUrl = function () {
1147
- var _a = this.options, windowId = _a.windowId, appRendererOfflineSupport = _a.appRendererOfflineSupport;
1160
+ var _a = this.options, windowId = _a.windowId, appRendererOfflineSupport = _a.appRendererOfflineSupport, device = _a.device;
1148
1161
  var metadata = {
1149
- renderMode: this.options.isPreview ? 'preview' : 'live',
1162
+ renderMode: this.getRenderMode(),
1150
1163
  };
1164
+ if (device) {
1165
+ metadata.deviceId = device.id;
1166
+ }
1151
1167
  var url = (0, embeddedApps_1.default)(this.presentation, this.options.applicationVersion, metadata);
1152
1168
  return this.getFullAppRendererUrl({
1153
1169
  appRendererUrl: url,
@@ -1375,6 +1391,9 @@ var PresentationWindow = /** @class */ (function () {
1375
1391
  }
1376
1392
  this.previouslyLocked = currentlyLocked;
1377
1393
  };
1394
+ PresentationWindow.prototype.getRenderMode = function () {
1395
+ return this.options.isPreview ? 'preview' : 'live';
1396
+ };
1378
1397
  return PresentationWindow;
1379
1398
  }());
1380
1399
  exports.default = PresentationWindow;
@@ -1405,7 +1424,12 @@ var transformInputValues = function (inputs) {
1405
1424
  transformed[key] = [];
1406
1425
  for (var _c = 0, value_1 = value; _c < value_1.length; _c++) {
1407
1426
  var item = value_1[_c];
1408
- transformed[key].push(transformInputValues(item));
1427
+ if (typeof item === 'object' && item !== null) {
1428
+ transformed[key].push(transformInputValues(item));
1429
+ }
1430
+ else {
1431
+ transformed[key].push(item);
1432
+ }
1409
1433
  }
1410
1434
  }
1411
1435
  else {
@@ -111,12 +111,21 @@ describe('embedded apps', function () {
111
111
  window = (0, testUtils_1.createPresentationWindow)({
112
112
  windowId: 'test-reload-message',
113
113
  presentation: (0, testUtils_1.createPresentation)({
114
- applicationVariables: { input: 'test' },
114
+ applicationVariables: {
115
+ input: 'test',
116
+ multi: [],
117
+ },
118
+ token: 'token',
115
119
  }),
116
120
  applicationVersion: (0, testUtils_1.createApplicationVersion)({
117
- embeddedUrlFormat: 'http://test.local/:input',
118
- presentationProperties: [{ name: 'input', type: 'string' }],
121
+ embeddedUrlFormat: 'http://test.local/:input?renderMode=:$renderMode&token=:$token&deviceId=:$deviceId',
122
+ presentationProperties: [
123
+ { name: 'input', type: 'string' },
124
+ { name: 'multi', type: 'select' },
125
+ ],
119
126
  }),
127
+ isPreview: true,
128
+ device: { id: 'device-id', name: 'device-name' },
120
129
  renderer: testRenderer,
121
130
  });
122
131
  return [4 /*yield*/, window.load()];
@@ -124,13 +133,25 @@ describe('embedded apps', function () {
124
133
  _a.sent();
125
134
  // Subscribe to receive input updates via message.
126
135
  testRenderer.runReceiveMessageCallback('preview-subscribe', 'inputs');
127
- window.setPresentation((0, testUtils_1.createPresentation)({ applicationVariables: { input: 'updated' } }), (0, testUtils_1.createPlaylistWindowManagerState)(), (0, testUtils_1.createRuleEngineState)());
136
+ window.setPresentation((0, testUtils_1.createPresentation)({
137
+ applicationVariables: {
138
+ input: 'updated',
139
+ multi: ['a', 'b', 'c'],
140
+ },
141
+ token: 'token',
142
+ }), (0, testUtils_1.createPlaylistWindowManagerState)(), (0, testUtils_1.createRuleEngineState)());
128
143
  return [4 /*yield*/, window.reload()];
129
144
  case 2:
130
145
  _a.sent();
131
146
  // loadWindow should only be called once for the initial load.
132
147
  expect(testRenderer.loadWindow).toHaveBeenCalledTimes(1);
133
- expect(testRenderer.sendMessageObject).toHaveBeenCalledWith('test-reload-message', 'preview-inputs', { input: 'updated' });
148
+ expect(testRenderer.sendMessageObject).toHaveBeenCalledWith('test-reload-message', 'preview-inputs', {
149
+ $renderMode: 'preview',
150
+ $token: 'token',
151
+ $deviceId: 'device-id',
152
+ input: 'updated',
153
+ multi: ['a', 'b', 'c'],
154
+ });
134
155
  // sendMessageObject should not be called again for the same inputs.
135
156
  return [4 /*yield*/, window.reload()];
136
157
  case 3:
@@ -13,6 +13,7 @@ function createEmbeddedApp(presentation, applicationVersion, metadata) {
13
13
  } while (match !== null);
14
14
  // replace
15
15
  var url = applicationVersion.embeddedUrlFormat || '';
16
+ var queryIndex = url.indexOf('?');
16
17
  matches.forEach(function (m) {
17
18
  var queryParamVariableName = m[1]; // Where the value of your input will be injected. E.g. ":location"
18
19
  var queryParamName = m[2]; // The query param name. E.g. "location"
@@ -20,6 +21,8 @@ function createEmbeddedApp(presentation, applicationVersion, metadata) {
20
21
  var shouldEncodeValue = true;
21
22
  var value = '';
22
23
  if (variableName.charAt(0) === '$') {
24
+ var variableIndex = url.indexOf(queryParamVariableName);
25
+ var isPartOfQuery = queryIndex != -1 && variableIndex > queryIndex;
23
26
  var metadataKey = variableName.slice(1);
24
27
  if (metadataKey === 'renderMode') {
25
28
  value = metadata.renderMode;
@@ -27,6 +30,18 @@ function createEmbeddedApp(presentation, applicationVersion, metadata) {
27
30
  else if (metadataKey === 'token') {
28
31
  value = presentation.token;
29
32
  }
33
+ else if (metadataKey === 'deviceId') {
34
+ if (metadata.deviceId) {
35
+ value = metadata.deviceId;
36
+ }
37
+ else {
38
+ // If :$deviceId is part of the URL path, avoid creating a broken URL if metadata.deviceId is not defined.
39
+ // This will use the variable name as the value instead of an empty string.
40
+ if (!isPartOfQuery) {
41
+ value = queryParamVariableName;
42
+ }
43
+ }
44
+ }
30
45
  }
31
46
  else {
32
47
  // Don't replace variables that are numbers to allow setting
@@ -128,3 +128,24 @@ test('Replaces $token with presentation token', function () {
128
128
  });
129
129
  expect((0, embeddedApps_1.default)(presentation, applicationVersion, createMetadata())).toBe('https://example.com?token=abc');
130
130
  });
131
+ test('Replaces $deviceId with metadata deviceId when provided', function () {
132
+ var presentation = createPresentation();
133
+ var applicationVersion = createApplicationVersion({
134
+ embeddedUrlFormat: 'https://example.com/:$deviceId/content',
135
+ });
136
+ expect((0, embeddedApps_1.default)(presentation, applicationVersion, createMetadata({ deviceId: 'device-id' }))).toBe('https://example.com/device-id/content');
137
+ });
138
+ test('Do not replace $deviceId when in the URI with metadata not defined', function () {
139
+ var presentation = createPresentation();
140
+ var applicationVersion = createApplicationVersion({
141
+ embeddedUrlFormat: 'https://example.com/:$deviceId/content',
142
+ });
143
+ expect((0, embeddedApps_1.default)(presentation, applicationVersion, createMetadata())).toBe("https://example.com/".concat(encodeURIComponent(':$deviceId'), "/content"));
144
+ });
145
+ test('Replaces $deviceId with empty string when in the query string and metadata not defined', function () {
146
+ var presentation = createPresentation();
147
+ var applicationVersion = createApplicationVersion({
148
+ embeddedUrlFormat: 'https://example.com/content?device=:$deviceId',
149
+ });
150
+ expect((0, embeddedApps_1.default)(presentation, applicationVersion, createMetadata())).toBe("https://example.com/content?device=");
151
+ });
@@ -99,6 +99,8 @@ function createPresentationWindow(overrides) {
99
99
  unMuteWindow: function () { },
100
100
  },
101
101
  getTerminal: overrides.getTerminal,
102
+ isPreview: overrides.isPreview,
103
+ device: overrides.device,
102
104
  });
103
105
  }
104
106
  exports.createPresentationWindow = createPresentationWindow;
@@ -113,6 +115,7 @@ function createPresentation(overrides) {
113
115
  applicationVariables: (_e = overrides.applicationVariables) !== null && _e !== void 0 ? _e : {},
114
116
  duration: (_f = overrides.duration) !== null && _f !== void 0 ? _f : 1,
115
117
  r: createPresentationRelationships(overrides.r),
118
+ token: overrides.token,
116
119
  };
117
120
  }
118
121
  exports.createPresentation = createPresentation;
package/build/types.d.ts CHANGED
@@ -135,6 +135,7 @@ export interface InputState {
135
135
  }
136
136
  export interface EmbeddedAppMetadata {
137
137
  renderMode: 'preview' | 'live';
138
+ deviceId?: string;
138
139
  }
139
140
  export interface AnalyticsEvent {
140
141
  type: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raydiant/playlist-window-manager",
3
- "version": "0.26.2",
3
+ "version": "0.27.1",
4
4
  "description": "Window manager for playlist playback.",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
@@ -81,12 +81,22 @@ describe('embedded apps', () => {
81
81
  const window = createPresentationWindow({
82
82
  windowId: 'test-reload-message',
83
83
  presentation: createPresentation({
84
- applicationVariables: { input: 'test' },
84
+ applicationVariables: {
85
+ input: 'test',
86
+ multi: [],
87
+ },
88
+ token: 'token',
85
89
  }),
86
90
  applicationVersion: createApplicationVersion({
87
- embeddedUrlFormat: 'http://test.local/:input',
88
- presentationProperties: [{ name: 'input', type: 'string' }],
91
+ embeddedUrlFormat:
92
+ 'http://test.local/:input?renderMode=:$renderMode&token=:$token&deviceId=:$deviceId',
93
+ presentationProperties: [
94
+ { name: 'input', type: 'string' },
95
+ { name: 'multi', type: 'select' },
96
+ ],
89
97
  }),
98
+ isPreview: true,
99
+ device: { id: 'device-id', name: 'device-name' },
90
100
  renderer: testRenderer,
91
101
  });
92
102
 
@@ -96,7 +106,13 @@ describe('embedded apps', () => {
96
106
  testRenderer.runReceiveMessageCallback('preview-subscribe', 'inputs');
97
107
 
98
108
  window.setPresentation(
99
- createPresentation({ applicationVariables: { input: 'updated' } }),
109
+ createPresentation({
110
+ applicationVariables: {
111
+ input: 'updated',
112
+ multi: ['a', 'b', 'c'],
113
+ },
114
+ token: 'token',
115
+ }),
100
116
  createPlaylistWindowManagerState(),
101
117
  createRuleEngineState()
102
118
  );
@@ -109,7 +125,13 @@ describe('embedded apps', () => {
109
125
  expect(testRenderer.sendMessageObject).toHaveBeenCalledWith(
110
126
  'test-reload-message',
111
127
  'preview-inputs',
112
- { input: 'updated' }
128
+ {
129
+ $renderMode: 'preview',
130
+ $token: 'token',
131
+ $deviceId: 'device-id',
132
+ input: 'updated',
133
+ multi: ['a', 'b', 'c'],
134
+ }
113
135
  );
114
136
 
115
137
  // sendMessageObject should not be called again for the same inputs.
@@ -427,6 +427,8 @@ export default class PresentationWindow {
427
427
 
428
428
  if (this.isEmbeddedApp()) {
429
429
  if (applicationVersion.embeddedUrlFormat) {
430
+ // If the embedded app has subscribed to inputs event, send it the updated inputs
431
+ // with postMessage. Otherwise, reload the window with the new inputs.
430
432
  if (this.previewSubscriptions.has('inputs')) {
431
433
  this.logInfo(
432
434
  `Reload window '${windowId}' for embedded app preview state`
@@ -436,6 +438,21 @@ export default class PresentationWindow {
436
438
  this.presentation.applicationVariables
437
439
  );
438
440
 
441
+ // Add metadata to inputs if they exist in the URL template. These need to be
442
+ // consistent with metadata keys in embeddedApps.ts.
443
+ if (applicationVersion.embeddedUrlFormat.includes(':$renderMode')) {
444
+ updatedInputs['$renderMode'] = this.getRenderMode();
445
+ }
446
+
447
+ if (applicationVersion.embeddedUrlFormat.includes(':$token')) {
448
+ updatedInputs['$token'] = this.presentation.token;
449
+ }
450
+
451
+ if (applicationVersion.embeddedUrlFormat.includes(':$deviceId')) {
452
+ updatedInputs['$deviceId'] = this.options.device?.id;
453
+ }
454
+
455
+ // Send the updated inputs to the embedded app if they have changed.
439
456
  if (!deepEqual(updatedInputs, this.previewState.inputs)) {
440
457
  this.sendMessageObject('preview-inputs', updatedInputs);
441
458
  }
@@ -953,12 +970,16 @@ export default class PresentationWindow {
953
970
  }
954
971
 
955
972
  private getEmbeddedAppUrl() {
956
- const { windowId, appRendererOfflineSupport } = this.options;
973
+ const { windowId, appRendererOfflineSupport, device } = this.options;
957
974
 
958
975
  const metadata: EmbeddedAppMetadata = {
959
- renderMode: this.options.isPreview ? 'preview' : 'live',
976
+ renderMode: this.getRenderMode(),
960
977
  };
961
978
 
979
+ if (device) {
980
+ metadata.deviceId = device.id;
981
+ }
982
+
962
983
  const url = createEmbeddedApp(
963
984
  this.presentation,
964
985
  this.options.applicationVersion,
@@ -1626,6 +1647,10 @@ export default class PresentationWindow {
1626
1647
 
1627
1648
  this.previouslyLocked = currentlyLocked;
1628
1649
  }
1650
+
1651
+ private getRenderMode() {
1652
+ return this.options.isPreview ? 'preview' : 'live';
1653
+ }
1629
1654
  }
1630
1655
 
1631
1656
  const isValidCreateObjectURLResultPayload = (
@@ -1657,7 +1682,11 @@ const transformInputValues = (inputs: Record<string, any>) => {
1657
1682
  // Value is an array input, traverse each item in the array for file input values.
1658
1683
  transformed[key] = [];
1659
1684
  for (const item of value) {
1660
- transformed[key].push(transformInputValues(item));
1685
+ if (typeof item === 'object' && item !== null) {
1686
+ transformed[key].push(transformInputValues(item));
1687
+ } else {
1688
+ transformed[key].push(item);
1689
+ }
1661
1690
  }
1662
1691
  } else {
1663
1692
  transformed[key] = value;
@@ -240,3 +240,40 @@ test('Replaces $token with presentation token', () => {
240
240
  createEmbeddedApp(presentation, applicationVersion, createMetadata())
241
241
  ).toBe('https://example.com?token=abc');
242
242
  });
243
+
244
+ test('Replaces $deviceId with metadata deviceId when provided', () => {
245
+ const presentation = createPresentation();
246
+ const applicationVersion = createApplicationVersion({
247
+ embeddedUrlFormat: 'https://example.com/:$deviceId/content',
248
+ });
249
+
250
+ expect(
251
+ createEmbeddedApp(
252
+ presentation,
253
+ applicationVersion,
254
+ createMetadata({ deviceId: 'device-id' })
255
+ )
256
+ ).toBe('https://example.com/device-id/content');
257
+ });
258
+
259
+ test('Do not replace $deviceId when in the URI with metadata not defined', () => {
260
+ const presentation = createPresentation();
261
+ const applicationVersion = createApplicationVersion({
262
+ embeddedUrlFormat: 'https://example.com/:$deviceId/content',
263
+ });
264
+
265
+ expect(
266
+ createEmbeddedApp(presentation, applicationVersion, createMetadata())
267
+ ).toBe(`https://example.com/${encodeURIComponent(':$deviceId')}/content`);
268
+ });
269
+
270
+ test('Replaces $deviceId with empty string when in the query string and metadata not defined', () => {
271
+ const presentation = createPresentation();
272
+ const applicationVersion = createApplicationVersion({
273
+ embeddedUrlFormat: 'https://example.com/content?device=:$deviceId',
274
+ });
275
+
276
+ expect(
277
+ createEmbeddedApp(presentation, applicationVersion, createMetadata())
278
+ ).toBe(`https://example.com/content?device=`);
279
+ });
@@ -19,6 +19,7 @@ export default function createEmbeddedApp(
19
19
 
20
20
  // replace
21
21
  let url = applicationVersion.embeddedUrlFormat || '';
22
+ const queryIndex = url.indexOf('?');
22
23
  matches.forEach((m) => {
23
24
  const queryParamVariableName = m[1]; // Where the value of your input will be injected. E.g. ":location"
24
25
  const queryParamName = m[2]; // The query param name. E.g. "location"
@@ -29,11 +30,24 @@ export default function createEmbeddedApp(
29
30
  let value: any = '';
30
31
 
31
32
  if (variableName.charAt(0) === '$') {
33
+ const variableIndex = url.indexOf(queryParamVariableName);
34
+ const isPartOfQuery = queryIndex != -1 && variableIndex > queryIndex;
35
+
32
36
  const metadataKey = variableName.slice(1);
33
37
  if (metadataKey === 'renderMode') {
34
38
  value = metadata.renderMode;
35
39
  } else if (metadataKey === 'token') {
36
40
  value = presentation.token;
41
+ } else if (metadataKey === 'deviceId') {
42
+ if (metadata.deviceId) {
43
+ value = metadata.deviceId;
44
+ } else {
45
+ // If :$deviceId is part of the URL path, avoid creating a broken URL if metadata.deviceId is not defined.
46
+ // This will use the variable name as the value instead of an empty string.
47
+ if (!isPartOfQuery) {
48
+ value = queryParamVariableName;
49
+ }
50
+ }
37
51
  }
38
52
  } else {
39
53
  // Don't replace variables that are numbers to allow setting
package/src/testUtils.ts CHANGED
@@ -39,6 +39,8 @@ export function createPresentationWindow(
39
39
  unMuteWindow() {},
40
40
  },
41
41
  getTerminal: overrides.getTerminal,
42
+ isPreview: overrides.isPreview,
43
+ device: overrides.device,
42
44
  });
43
45
  }
44
46
 
@@ -54,6 +56,7 @@ export function createPresentation(
54
56
  applicationVariables: overrides.applicationVariables ?? {},
55
57
  duration: overrides.duration ?? 1,
56
58
  r: createPresentationRelationships(overrides.r),
59
+ token: overrides.token,
57
60
  };
58
61
  }
59
62
 
package/src/types.ts CHANGED
@@ -167,6 +167,7 @@ export interface InputState {
167
167
 
168
168
  export interface EmbeddedAppMetadata {
169
169
  renderMode: 'preview' | 'live';
170
+ deviceId?: string;
170
171
  }
171
172
 
172
173
  export interface AnalyticsEvent {