@raydiant/playlist-window-manager 0.0.0-test-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.
- package/.eslintrc +6 -0
- package/README.md +16 -0
- package/babel.config.js +13 -0
- package/build/BrowserViewRenderer.d.ts +19 -0
- package/build/BrowserViewRenderer.js +214 -0
- package/build/BrowserWindowRenderer.d.ts +47 -0
- package/build/BrowserWindowRenderer.js +528 -0
- package/build/IFrameRenderer.d.ts +20 -0
- package/build/IFrameRenderer.js +226 -0
- package/build/PlaylistWindowManager.d.ts +106 -0
- package/build/PlaylistWindowManager.js +1018 -0
- package/build/PlaylistWindowManagerState.d.ts +28 -0
- package/build/PlaylistWindowManagerState.js +60 -0
- package/build/PresentationWindow.d.ts +142 -0
- package/build/PresentationWindow.js +1140 -0
- package/build/PresentationWindow.test.d.ts +1 -0
- package/build/PresentationWindow.test.js +241 -0
- package/build/WindowLoadError.d.ts +3 -0
- package/build/WindowLoadError.js +29 -0
- package/build/WindowTimeoutError.d.ts +3 -0
- package/build/WindowTimeoutError.js +29 -0
- package/build/WindowUnresponsiveError.d.ts +3 -0
- package/build/WindowUnresponsiveError.js +29 -0
- package/build/embeddedApps.d.ts +2 -0
- package/build/embeddedApps.js +59 -0
- package/build/embeddedApps.test.d.ts +1 -0
- package/build/embeddedApps.test.js +123 -0
- package/build/index.d.ts +10 -0
- package/build/index.js +30 -0
- package/build/preload.d.ts +1 -0
- package/build/preload.js +20 -0
- package/build/testUtils.d.ts +43 -0
- package/build/testUtils.js +208 -0
- package/build/types.d.ts +142 -0
- package/build/types.js +2 -0
- package/jest.config.js +6 -0
- package/package.json +30 -0
- package/src/BrowserViewRenderer.ts +182 -0
- package/src/BrowserWindowRenderer.ts +624 -0
- package/src/IFrameRenderer.ts +251 -0
- package/src/PlaylistWindowManager.ts +1042 -0
- package/src/PlaylistWindowManagerState.ts +70 -0
- package/src/PresentationWindow.test.ts +221 -0
- package/src/PresentationWindow.ts +1327 -0
- package/src/WindowLoadError.ts +8 -0
- package/src/WindowTimeoutError.ts +8 -0
- package/src/WindowUnresponsiveError.ts +8 -0
- package/src/embeddedApps.test.ts +231 -0
- package/src/embeddedApps.ts +74 -0
- package/src/index.ts +13 -0
- package/src/preload.ts +27 -0
- package/src/testUtils.ts +159 -0
- package/src/types.ts +175 -0
- package/tasks/npm-publish.sh +13 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export default class WindowTimeoutError extends Error {
|
|
2
|
+
constructor(message: string) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'WindowTimeoutError';
|
|
5
|
+
// Set prototype for instanceof WindowTimeoutError checks.
|
|
6
|
+
Object.setPrototypeOf(this, WindowTimeoutError.prototype);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export default class WindowUnresponsiveError extends Error {
|
|
2
|
+
constructor(message: string) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'WindowUnresponsiveError';
|
|
5
|
+
// Set prototype for instanceof WindowUnresponsiveError checks.
|
|
6
|
+
Object.setPrototypeOf(this, WindowUnresponsiveError.prototype);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import createEmbeddedApp from './embeddedApps';
|
|
2
|
+
import { Presentation, ApplicationVersion, EmbeddedAppMetadata } from './types';
|
|
3
|
+
|
|
4
|
+
const createPresentation = (
|
|
5
|
+
overrides: Partial<Presentation> = {}
|
|
6
|
+
): Presentation => ({
|
|
7
|
+
id: 'id',
|
|
8
|
+
name: 'name',
|
|
9
|
+
applicationDeploymentId: 'applicationDeploymentId',
|
|
10
|
+
themeId: null,
|
|
11
|
+
applicationVariables: {},
|
|
12
|
+
duration: 0,
|
|
13
|
+
r: {
|
|
14
|
+
resource: {
|
|
15
|
+
updatedAt: '',
|
|
16
|
+
createdAt: '',
|
|
17
|
+
},
|
|
18
|
+
presentationsPlaylists: [],
|
|
19
|
+
},
|
|
20
|
+
...overrides,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const createApplicationVersion = (
|
|
24
|
+
overrides: Partial<ApplicationVersion> = {}
|
|
25
|
+
): ApplicationVersion => ({
|
|
26
|
+
id: 'id',
|
|
27
|
+
name: 'name',
|
|
28
|
+
applicationId: 'applicationId',
|
|
29
|
+
embeddedUrlFormat: null,
|
|
30
|
+
configurableDuration: false,
|
|
31
|
+
sourceUrl: null,
|
|
32
|
+
presentationProperties: [],
|
|
33
|
+
...overrides,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const createMetadata = (
|
|
37
|
+
overrides: Partial<EmbeddedAppMetadata> = {}
|
|
38
|
+
): EmbeddedAppMetadata => ({
|
|
39
|
+
renderMode: 'live',
|
|
40
|
+
...overrides,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('Replaces variable in query parameteers', () => {
|
|
44
|
+
expect(
|
|
45
|
+
createEmbeddedApp(
|
|
46
|
+
createPresentation({ applicationVariables: { foo: 'bar' } }),
|
|
47
|
+
createApplicationVersion({
|
|
48
|
+
embeddedUrlFormat: 'https://example.com?foo=:foo',
|
|
49
|
+
}),
|
|
50
|
+
createMetadata()
|
|
51
|
+
)
|
|
52
|
+
).toBe('https://example.com?foo=bar');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('Replaces variable in path', () => {
|
|
56
|
+
expect(
|
|
57
|
+
createEmbeddedApp(
|
|
58
|
+
createPresentation({ applicationVariables: { foo: 'bar' } }),
|
|
59
|
+
createApplicationVersion({
|
|
60
|
+
embeddedUrlFormat: 'https://example.com/:foo',
|
|
61
|
+
}),
|
|
62
|
+
createMetadata()
|
|
63
|
+
)
|
|
64
|
+
).toBe('https://example.com/bar');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('Replaces undefined variable with empty string', () => {
|
|
68
|
+
expect(
|
|
69
|
+
createEmbeddedApp(
|
|
70
|
+
createPresentation({ applicationVariables: {} }),
|
|
71
|
+
createApplicationVersion({
|
|
72
|
+
embeddedUrlFormat: 'https://example.com/:foo',
|
|
73
|
+
}),
|
|
74
|
+
createMetadata()
|
|
75
|
+
)
|
|
76
|
+
).toBe('https://example.com/');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('Replaces variable with underscore', () => {
|
|
80
|
+
expect(
|
|
81
|
+
createEmbeddedApp(
|
|
82
|
+
createPresentation({ applicationVariables: { foo_1: 'bar' } }),
|
|
83
|
+
createApplicationVersion({
|
|
84
|
+
embeddedUrlFormat: 'https://example.com/:foo_1',
|
|
85
|
+
}),
|
|
86
|
+
createMetadata()
|
|
87
|
+
)
|
|
88
|
+
).toBe('https://example.com/bar');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('Replaces variable with hyphen', () => {
|
|
92
|
+
expect(
|
|
93
|
+
createEmbeddedApp(
|
|
94
|
+
createPresentation({ applicationVariables: { 'foo-1': 'bar' } }),
|
|
95
|
+
createApplicationVersion({
|
|
96
|
+
embeddedUrlFormat: 'https://example.com/:foo-1',
|
|
97
|
+
}),
|
|
98
|
+
createMetadata()
|
|
99
|
+
)
|
|
100
|
+
).toBe('https://example.com/bar');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('Does not replace variable that is a number', () => {
|
|
104
|
+
expect(
|
|
105
|
+
createEmbeddedApp(
|
|
106
|
+
createPresentation({ applicationVariables: { foo: 'bar' } }),
|
|
107
|
+
createApplicationVersion({
|
|
108
|
+
embeddedUrlFormat: 'http://localhost:8080?foo=:foo',
|
|
109
|
+
}),
|
|
110
|
+
createMetadata()
|
|
111
|
+
)
|
|
112
|
+
).toBe('http://localhost:8080?foo=bar');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('Encodes a variables containing special characters', () => {
|
|
116
|
+
const fooValue = 'Lorem Ipsum. !@#$%^&*()-. More Text';
|
|
117
|
+
const fooValueEncoded =
|
|
118
|
+
'Lorem%20Ipsum.%20!%40%23%24%25%5E%26*()-.%20More%20Text';
|
|
119
|
+
|
|
120
|
+
expect(
|
|
121
|
+
createEmbeddedApp(
|
|
122
|
+
createPresentation({ applicationVariables: { foo: fooValue } }),
|
|
123
|
+
createApplicationVersion({
|
|
124
|
+
embeddedUrlFormat: `http://localhost:8080?foo=:foo`,
|
|
125
|
+
}),
|
|
126
|
+
createMetadata()
|
|
127
|
+
)
|
|
128
|
+
).toBe(`http://localhost:8080?foo=${fooValueEncoded}`);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('Does not encode value if presentation property disables encoding', () => {
|
|
132
|
+
expect(
|
|
133
|
+
createEmbeddedApp(
|
|
134
|
+
createPresentation({
|
|
135
|
+
applicationVariables: { url: 'https://raydiant.com' },
|
|
136
|
+
}),
|
|
137
|
+
createApplicationVersion({
|
|
138
|
+
embeddedUrlFormat: `:url`,
|
|
139
|
+
presentationProperties: [
|
|
140
|
+
{ type: 'string', name: 'url', encode: false },
|
|
141
|
+
],
|
|
142
|
+
}),
|
|
143
|
+
createMetadata()
|
|
144
|
+
)
|
|
145
|
+
).toBe(`https://raydiant.com`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('Encodes a variables containing an array', () => {
|
|
149
|
+
const fooValue = ['option-a', 'option-b', 'option-c'];
|
|
150
|
+
const fooValueEncoded =
|
|
151
|
+
'%5B%22option-a%22%2C%22option-b%22%2C%22option-c%22%5D';
|
|
152
|
+
|
|
153
|
+
expect(
|
|
154
|
+
createEmbeddedApp(
|
|
155
|
+
createPresentation({ applicationVariables: { foo: fooValue } }),
|
|
156
|
+
createApplicationVersion({
|
|
157
|
+
embeddedUrlFormat: `http://localhost:8080?foo=:foo`,
|
|
158
|
+
}),
|
|
159
|
+
createMetadata()
|
|
160
|
+
)
|
|
161
|
+
).toBe(`http://localhost:8080?foo=${fooValueEncoded}`);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('Replaces $renderMode with metadata', () => {
|
|
165
|
+
const presentation = createPresentation();
|
|
166
|
+
const applicationVersion = createApplicationVersion({
|
|
167
|
+
embeddedUrlFormat: 'https://example.com?renderMode=:$renderMode',
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(
|
|
171
|
+
createEmbeddedApp(
|
|
172
|
+
presentation,
|
|
173
|
+
applicationVersion,
|
|
174
|
+
createMetadata({ renderMode: 'preview' })
|
|
175
|
+
)
|
|
176
|
+
).toBe('https://example.com?renderMode=preview');
|
|
177
|
+
|
|
178
|
+
expect(
|
|
179
|
+
createEmbeddedApp(
|
|
180
|
+
presentation,
|
|
181
|
+
applicationVersion,
|
|
182
|
+
createMetadata({ renderMode: 'live' })
|
|
183
|
+
)
|
|
184
|
+
).toBe('https://example.com?renderMode=live');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('Replaces undefined metadata with empty string', () => {
|
|
188
|
+
const presentation = createPresentation();
|
|
189
|
+
const applicationVersion = createApplicationVersion({
|
|
190
|
+
embeddedUrlFormat: 'https://example.com?invalid=:$invalid',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(
|
|
194
|
+
createEmbeddedApp(presentation, applicationVersion, createMetadata())
|
|
195
|
+
).toBe('https://example.com?invalid=');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('Replace protocol, ip and port', () => {
|
|
199
|
+
expect(
|
|
200
|
+
createEmbeddedApp(
|
|
201
|
+
createPresentation({
|
|
202
|
+
applicationVariables: {
|
|
203
|
+
protocol: 'http',
|
|
204
|
+
ip: '192.168.1.0',
|
|
205
|
+
port: 3000,
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
208
|
+
createApplicationVersion({
|
|
209
|
+
embeddedUrlFormat: `:protocol://:ip::port`,
|
|
210
|
+
}),
|
|
211
|
+
createMetadata()
|
|
212
|
+
)
|
|
213
|
+
).toBe(`http://192.168.1.0:3000`);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('File input uses url as value', () => {
|
|
217
|
+
expect(
|
|
218
|
+
createEmbeddedApp(
|
|
219
|
+
createPresentation({
|
|
220
|
+
applicationVariables: { file: { url: 'https://image.png' } },
|
|
221
|
+
}),
|
|
222
|
+
createApplicationVersion({
|
|
223
|
+
embeddedUrlFormat: `https://example.come?image=:file`,
|
|
224
|
+
presentationProperties: [{ type: 'file', name: 'file' }],
|
|
225
|
+
}),
|
|
226
|
+
createMetadata()
|
|
227
|
+
)
|
|
228
|
+
).toBe(
|
|
229
|
+
`https://example.come?image=${encodeURIComponent('https://image.png')}`
|
|
230
|
+
);
|
|
231
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Presentation, ApplicationVersion, EmbeddedAppMetadata } from './types';
|
|
2
|
+
|
|
3
|
+
export default function createEmbeddedApp(
|
|
4
|
+
presentation: Presentation,
|
|
5
|
+
applicationVersion: ApplicationVersion,
|
|
6
|
+
metadata: EmbeddedAppMetadata
|
|
7
|
+
) {
|
|
8
|
+
// build matches
|
|
9
|
+
const parameterRegex = /(:([\w-\$]+))(.?$|[^\w-\$])/gm;
|
|
10
|
+
const matches = [];
|
|
11
|
+
let match = null;
|
|
12
|
+
do {
|
|
13
|
+
match = parameterRegex.exec(applicationVersion.embeddedUrlFormat || '');
|
|
14
|
+
|
|
15
|
+
if (match) {
|
|
16
|
+
matches.push(match);
|
|
17
|
+
}
|
|
18
|
+
} while (match !== null);
|
|
19
|
+
|
|
20
|
+
// replace
|
|
21
|
+
let url = applicationVersion.embeddedUrlFormat || '';
|
|
22
|
+
matches.forEach((m) => {
|
|
23
|
+
const queryParamVariableName = m[1]; // Where the value of your input will be injected. E.g. ":location"
|
|
24
|
+
const queryParamName = m[2]; // The query param name. E.g. "location"
|
|
25
|
+
|
|
26
|
+
const [, variableName] = queryParamVariableName.split(':');
|
|
27
|
+
|
|
28
|
+
let shouldEncodeValue = true;
|
|
29
|
+
let value: any = '';
|
|
30
|
+
|
|
31
|
+
if (variableName.charAt(0) === '$') {
|
|
32
|
+
const metadataKey = variableName.slice(1);
|
|
33
|
+
if (metadataKey === 'renderMode') {
|
|
34
|
+
value = metadata.renderMode;
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
// Don't replace variables that are numbers to allow setting
|
|
38
|
+
// port numbers in the embedded app url.
|
|
39
|
+
if (!isNaN(Number(variableName))) return;
|
|
40
|
+
|
|
41
|
+
const presentationProperty =
|
|
42
|
+
applicationVersion.presentationProperties.find(
|
|
43
|
+
(p) => p.name === variableName
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// If the presentation property is not found (this shouldn't happen), default to encoding the value.
|
|
47
|
+
// Otherwise, only skip encoding if the presentation property's encode property is false.
|
|
48
|
+
shouldEncodeValue =
|
|
49
|
+
!presentationProperty || presentationProperty.encode !== false;
|
|
50
|
+
|
|
51
|
+
value = presentation.applicationVariables[queryParamName];
|
|
52
|
+
if (value === undefined) value = '';
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
value = JSON.stringify(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Presentation property specific handling.
|
|
58
|
+
if (presentationProperty) {
|
|
59
|
+
// If the presentation property is a file, use the url property as the value.
|
|
60
|
+
if (presentationProperty.type === 'file' && value && value.url) {
|
|
61
|
+
value = value.url;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (shouldEncodeValue) {
|
|
67
|
+
value = encodeURIComponent(value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
url = url.replace(queryParamVariableName, value);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return url;
|
|
74
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import PlaylistWindowManager from './PlaylistWindowManager';
|
|
2
|
+
export * from './PlaylistWindowManager';
|
|
3
|
+
export { PlaylistWindowManager };
|
|
4
|
+
|
|
5
|
+
import PlaylistWindowManagerState from './PlaylistWindowManagerState';
|
|
6
|
+
export * from './PlaylistWindowManagerState';
|
|
7
|
+
export { PlaylistWindowManagerState };
|
|
8
|
+
|
|
9
|
+
import PresentationWindow from './PresentationWindow';
|
|
10
|
+
export * from './PresentationWindow';
|
|
11
|
+
export { PresentationWindow };
|
|
12
|
+
|
|
13
|
+
export * from './types';
|
package/src/preload.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
|
2
|
+
|
|
3
|
+
interface RaydiantInternals {
|
|
4
|
+
sendMessage: (windowId: string, type: string, payload?: any) => void;
|
|
5
|
+
receiveMessage?: (message: string) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const internals: RaydiantInternals = {
|
|
9
|
+
sendMessage: (windowId: string, type: string, payload?: any) => {
|
|
10
|
+
ipcRenderer.send(windowId, type, payload);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Set __RAYDIANT_INTERNALS on window so it is accessible in to the running application.
|
|
15
|
+
(window as any).__RAYDIANT_INTERNALS = internals;
|
|
16
|
+
|
|
17
|
+
// Forward messages from the main process to the app (render process) via receiveMessage.
|
|
18
|
+
// __RAYDIANT_INTERNALS.receiveMessage is required to be set by the app with the Raydiant SDK.
|
|
19
|
+
ipcRenderer.on('electron-main', (_: IpcRendererEvent, message: string) => {
|
|
20
|
+
if (typeof internals.receiveMessage === 'function') {
|
|
21
|
+
internals.receiveMessage(message);
|
|
22
|
+
} else {
|
|
23
|
+
console.error(
|
|
24
|
+
'Failed to handle message from main process, receiveMessage is not defined'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
});
|
package/src/testUtils.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as re from '@raydiant/playlist-rule-engine';
|
|
2
|
+
import PresentationWindow, {
|
|
3
|
+
PresentationWindowOptions,
|
|
4
|
+
} from './PresentationWindow';
|
|
5
|
+
import PlaylistWindowManagerState, {
|
|
6
|
+
PlaylistWindowManagerStateOptions,
|
|
7
|
+
} from './PlaylistWindowManagerState';
|
|
8
|
+
import {
|
|
9
|
+
Presentation,
|
|
10
|
+
ApplicationVersion,
|
|
11
|
+
Logger,
|
|
12
|
+
CreateWindowOptions,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
export function createPresentationWindow(
|
|
16
|
+
overrides: Partial<PresentationWindowOptions> = {}
|
|
17
|
+
): PresentationWindow {
|
|
18
|
+
return new PresentationWindow({
|
|
19
|
+
windowId: overrides.windowId ?? 'test',
|
|
20
|
+
presentation: createPresentation(overrides.presentation),
|
|
21
|
+
applicationVersion: createApplicationVersion(overrides.applicationVersion),
|
|
22
|
+
publishedAt: overrides.publishedAt ?? 'publishedAt',
|
|
23
|
+
playlistWindowManagerState:
|
|
24
|
+
overrides.playlistWindowManagerState ??
|
|
25
|
+
createPlaylistWindowManagerState(),
|
|
26
|
+
ruleEngineState: overrides.ruleEngineState ?? createRuleEngineState(),
|
|
27
|
+
logger: createLogger(),
|
|
28
|
+
workerState: overrides.workerState ?? {},
|
|
29
|
+
renderer: overrides.renderer ?? {
|
|
30
|
+
createWindow() {},
|
|
31
|
+
async loadWindow() {},
|
|
32
|
+
updateWindowBounds() {},
|
|
33
|
+
showWindow() {},
|
|
34
|
+
hideWindow() {},
|
|
35
|
+
async removeWindow() {},
|
|
36
|
+
sendMessageString() {},
|
|
37
|
+
sendMessageObject() {},
|
|
38
|
+
muteWindow() {},
|
|
39
|
+
unMuteWindow() {},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createPresentation(
|
|
45
|
+
overrides: Partial<Presentation> = {}
|
|
46
|
+
): Presentation {
|
|
47
|
+
return {
|
|
48
|
+
id: overrides.id ?? 'id',
|
|
49
|
+
name: overrides.name ?? 'name',
|
|
50
|
+
applicationDeploymentId:
|
|
51
|
+
overrides.applicationDeploymentId ?? 'applicationDeploymentId',
|
|
52
|
+
themeId: overrides.themeId ?? null,
|
|
53
|
+
applicationVariables: overrides.applicationVariables ?? {},
|
|
54
|
+
duration: overrides.duration ?? 1,
|
|
55
|
+
r: createPresentationRelationships(overrides.r),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createPresentationRelationships(
|
|
60
|
+
overrides: Partial<Presentation['r']> = {}
|
|
61
|
+
): Presentation['r'] {
|
|
62
|
+
return {
|
|
63
|
+
resource: overrides.resource ?? {
|
|
64
|
+
updatedAt: 'updatedAt',
|
|
65
|
+
createdAt: 'createdAt',
|
|
66
|
+
},
|
|
67
|
+
presentationsPlaylists: overrides.presentationsPlaylists ?? [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createApplicationVersion(
|
|
72
|
+
overrides: Partial<ApplicationVersion> = {}
|
|
73
|
+
): ApplicationVersion {
|
|
74
|
+
return {
|
|
75
|
+
id: overrides.id ?? 'id',
|
|
76
|
+
name: overrides.name ?? 'name',
|
|
77
|
+
applicationId: overrides.applicationId ?? 'applicationId',
|
|
78
|
+
embeddedUrlFormat: overrides.embeddedUrlFormat ?? null,
|
|
79
|
+
configurableDuration: overrides.configurableDuration ?? false,
|
|
80
|
+
sourceUrl: overrides.sourceUrl ?? null,
|
|
81
|
+
presentationProperties: overrides.presentationProperties ?? [],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createPlaylistWindowManagerState(
|
|
86
|
+
overrides: Partial<PlaylistWindowManagerStateOptions> = {}
|
|
87
|
+
): PlaylistWindowManagerState {
|
|
88
|
+
return new PlaylistWindowManagerState({
|
|
89
|
+
presentations: overrides.presentations ?? {},
|
|
90
|
+
applicationVersions: overrides.applicationVersions ?? {},
|
|
91
|
+
themes: overrides.themes ?? {},
|
|
92
|
+
publishedAt: overrides.publishedAt ?? 'publishedAt',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createRuleEngineState(
|
|
97
|
+
playlists: {
|
|
98
|
+
[index: string]: re.Playlist;
|
|
99
|
+
} = {},
|
|
100
|
+
presentations: {
|
|
101
|
+
[index: string]: re.Presentation;
|
|
102
|
+
} = {},
|
|
103
|
+
rulesDataStore: re.RulesDataStore = {},
|
|
104
|
+
isMultitimezoneEnabled: boolean = false
|
|
105
|
+
): re.State {
|
|
106
|
+
return new re.State(
|
|
107
|
+
playlists,
|
|
108
|
+
presentations,
|
|
109
|
+
rulesDataStore,
|
|
110
|
+
isMultitimezoneEnabled
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function createLogger(overrides: Partial<Logger> = {}) {
|
|
115
|
+
const noop = () => {};
|
|
116
|
+
return {
|
|
117
|
+
debug: overrides.debug ?? noop,
|
|
118
|
+
info: overrides.info ?? noop,
|
|
119
|
+
warn: overrides.warn ?? noop,
|
|
120
|
+
error: overrides.error ?? noop,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createRenderer() {
|
|
125
|
+
let createWindowOpts: CreateWindowOptions | null = null;
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
createWindow: jest.fn((opts: CreateWindowOptions) => {
|
|
129
|
+
createWindowOpts = opts;
|
|
130
|
+
}),
|
|
131
|
+
loadWindow: jest.fn(),
|
|
132
|
+
updateWindowBounds: jest.fn(),
|
|
133
|
+
showWindow: jest.fn(),
|
|
134
|
+
hideWindow: jest.fn(),
|
|
135
|
+
removeWindow: jest.fn(),
|
|
136
|
+
sendMessageString: jest.fn(),
|
|
137
|
+
sendMessageObject: jest.fn(),
|
|
138
|
+
muteWindow: jest.fn(),
|
|
139
|
+
unMuteWindow: jest.fn(),
|
|
140
|
+
runReceiveMessageCallback(type: string, payload: any) {
|
|
141
|
+
if (createWindowOpts?.receiveMessageCallback) {
|
|
142
|
+
createWindowOpts.receiveMessageCallback(type, payload);
|
|
143
|
+
} else {
|
|
144
|
+
throw new Error(
|
|
145
|
+
'No receiveMessageCallback defined, make sure to call window.load first'
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
runErrorCallback(error: Error) {
|
|
150
|
+
if (createWindowOpts?.errorCallback) {
|
|
151
|
+
createWindowOpts.errorCallback(error);
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error(
|
|
154
|
+
'No errorCallback defined, make sure to call window.load first'
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export interface Playlist {
|
|
2
|
+
id: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface Presentation {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
applicationDeploymentId: string;
|
|
9
|
+
themeId: string | null;
|
|
10
|
+
applicationVariables: { [key: string]: any };
|
|
11
|
+
duration: number;
|
|
12
|
+
r: {
|
|
13
|
+
resource: {
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
};
|
|
17
|
+
presentationsPlaylists: Array<{ playlistId: string }>;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ApplicationVersion {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
applicationId: string;
|
|
25
|
+
embeddedUrlFormat: string | null;
|
|
26
|
+
configurableDuration: boolean;
|
|
27
|
+
sourceUrl: string | null;
|
|
28
|
+
presentationProperties: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
type: string;
|
|
31
|
+
encode?: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Font {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
weight: string;
|
|
39
|
+
fileUpload: {
|
|
40
|
+
url: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface Theme {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
backgroundColor: string;
|
|
47
|
+
backgroundImage: string;
|
|
48
|
+
backgroundImagePortrait: string;
|
|
49
|
+
headingTextColor: string;
|
|
50
|
+
headingFont: string;
|
|
51
|
+
heading2TextColor: string;
|
|
52
|
+
heading2Font: string;
|
|
53
|
+
bodyTextColor: string;
|
|
54
|
+
bodyFont: string;
|
|
55
|
+
borderColor: string;
|
|
56
|
+
headingFontNew: Font | null;
|
|
57
|
+
heading2FontNew: Font | null;
|
|
58
|
+
bodyFontNew: Font | null;
|
|
59
|
+
backgroundImageFileUpload: {
|
|
60
|
+
url: string;
|
|
61
|
+
} | null;
|
|
62
|
+
backgroundScale: 'fit' | 'fill' | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Device {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string | null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface WindowBounds {
|
|
71
|
+
x: number;
|
|
72
|
+
y: number;
|
|
73
|
+
width: number;
|
|
74
|
+
height: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface WorkerState {
|
|
78
|
+
[key: string]: any;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface PreviewState {
|
|
82
|
+
inputs: Record<string, any>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface CreateWindowOptions {
|
|
86
|
+
windowId: string;
|
|
87
|
+
parentWindowId?: string;
|
|
88
|
+
bounds?: WindowBounds;
|
|
89
|
+
backgroundColor?: string;
|
|
90
|
+
|
|
91
|
+
// Called when attaching a message listener to the inner window.
|
|
92
|
+
// The callback function will receive the message type (ie. presentation_ready)
|
|
93
|
+
// and a message payload if provided.
|
|
94
|
+
receiveMessageCallback?: (type: string, payload?: any) => void;
|
|
95
|
+
|
|
96
|
+
// Called when a window is unresponsive or crashed to move on to the next presentation.
|
|
97
|
+
errorCallback: (err: any) => void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface Logger {
|
|
101
|
+
debug(message: any, context?: { [key: string]: any }): void;
|
|
102
|
+
info(message: any, context?: { [key: string]: any }): void;
|
|
103
|
+
warn(message: any, context?: { [key: string]: any }): void;
|
|
104
|
+
error(message: any, context?: { [key: string]: any }): void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface PresentationWindowRenderer {
|
|
108
|
+
// Called when a new window needs to be created. It will be called once
|
|
109
|
+
// for the current window and again for the next window. A created window
|
|
110
|
+
// is hidden until the manager says it's active.
|
|
111
|
+
createWindow(options: CreateWindowOptions): void;
|
|
112
|
+
|
|
113
|
+
// Called when a window should be loaded with a URL, returns a promise that
|
|
114
|
+
// resolves when the window has finished loading.
|
|
115
|
+
loadWindow(windowId: string, url: string): Promise<void>;
|
|
116
|
+
|
|
117
|
+
// Called when a window's bounds beed to be updated.
|
|
118
|
+
updateWindowBounds(windowId: string, bounds: WindowBounds): void;
|
|
119
|
+
|
|
120
|
+
// Called when a window should be shown.
|
|
121
|
+
showWindow(windowId: string, isFocused: boolean): void;
|
|
122
|
+
|
|
123
|
+
// Called when a window should be hidden.
|
|
124
|
+
hideWindow(windowId: string): void;
|
|
125
|
+
|
|
126
|
+
// Called when a window should be destoyed, returns a promise that resolves
|
|
127
|
+
// when the window is finished unloading.
|
|
128
|
+
removeWindow(windowId: string): Promise<void>;
|
|
129
|
+
|
|
130
|
+
// Called when sending a message string to the inner window.
|
|
131
|
+
sendMessageString(windowId: string, message: string): void;
|
|
132
|
+
|
|
133
|
+
// Called when sending a message object to the inner window.
|
|
134
|
+
sendMessageObject(windowId: string, type: string, payload?: any): void;
|
|
135
|
+
|
|
136
|
+
// Called when muting a window.
|
|
137
|
+
muteWindow(windowId: string): void;
|
|
138
|
+
|
|
139
|
+
// Called when unmuting a window.
|
|
140
|
+
unMuteWindow(windowId: string): void;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface OpenChildWindowOptions {
|
|
144
|
+
windowId: string;
|
|
145
|
+
playlistId: string;
|
|
146
|
+
x: number;
|
|
147
|
+
y: number;
|
|
148
|
+
width: number;
|
|
149
|
+
height: number;
|
|
150
|
+
isFocused: boolean;
|
|
151
|
+
isMuted: boolean;
|
|
152
|
+
theme?: Theme;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type Path = Array<string | number>;
|
|
156
|
+
|
|
157
|
+
export interface BuilderState {
|
|
158
|
+
inputs: InputState[];
|
|
159
|
+
profileId?: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface InputState {
|
|
163
|
+
path: Path;
|
|
164
|
+
state: any;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface EmbeddedAppMetadata {
|
|
168
|
+
renderMode: 'preview' | 'live';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface AnalyticsEvent {
|
|
172
|
+
type: string;
|
|
173
|
+
source: string;
|
|
174
|
+
productCategory?: string | null;
|
|
175
|
+
}
|