@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.
Files changed (55) hide show
  1. package/.eslintrc +6 -0
  2. package/README.md +16 -0
  3. package/babel.config.js +13 -0
  4. package/build/BrowserViewRenderer.d.ts +19 -0
  5. package/build/BrowserViewRenderer.js +214 -0
  6. package/build/BrowserWindowRenderer.d.ts +47 -0
  7. package/build/BrowserWindowRenderer.js +528 -0
  8. package/build/IFrameRenderer.d.ts +20 -0
  9. package/build/IFrameRenderer.js +226 -0
  10. package/build/PlaylistWindowManager.d.ts +106 -0
  11. package/build/PlaylistWindowManager.js +1018 -0
  12. package/build/PlaylistWindowManagerState.d.ts +28 -0
  13. package/build/PlaylistWindowManagerState.js +60 -0
  14. package/build/PresentationWindow.d.ts +142 -0
  15. package/build/PresentationWindow.js +1140 -0
  16. package/build/PresentationWindow.test.d.ts +1 -0
  17. package/build/PresentationWindow.test.js +241 -0
  18. package/build/WindowLoadError.d.ts +3 -0
  19. package/build/WindowLoadError.js +29 -0
  20. package/build/WindowTimeoutError.d.ts +3 -0
  21. package/build/WindowTimeoutError.js +29 -0
  22. package/build/WindowUnresponsiveError.d.ts +3 -0
  23. package/build/WindowUnresponsiveError.js +29 -0
  24. package/build/embeddedApps.d.ts +2 -0
  25. package/build/embeddedApps.js +59 -0
  26. package/build/embeddedApps.test.d.ts +1 -0
  27. package/build/embeddedApps.test.js +123 -0
  28. package/build/index.d.ts +10 -0
  29. package/build/index.js +30 -0
  30. package/build/preload.d.ts +1 -0
  31. package/build/preload.js +20 -0
  32. package/build/testUtils.d.ts +43 -0
  33. package/build/testUtils.js +208 -0
  34. package/build/types.d.ts +142 -0
  35. package/build/types.js +2 -0
  36. package/jest.config.js +6 -0
  37. package/package.json +30 -0
  38. package/src/BrowserViewRenderer.ts +182 -0
  39. package/src/BrowserWindowRenderer.ts +624 -0
  40. package/src/IFrameRenderer.ts +251 -0
  41. package/src/PlaylistWindowManager.ts +1042 -0
  42. package/src/PlaylistWindowManagerState.ts +70 -0
  43. package/src/PresentationWindow.test.ts +221 -0
  44. package/src/PresentationWindow.ts +1327 -0
  45. package/src/WindowLoadError.ts +8 -0
  46. package/src/WindowTimeoutError.ts +8 -0
  47. package/src/WindowUnresponsiveError.ts +8 -0
  48. package/src/embeddedApps.test.ts +231 -0
  49. package/src/embeddedApps.ts +74 -0
  50. package/src/index.ts +13 -0
  51. package/src/preload.ts +27 -0
  52. package/src/testUtils.ts +159 -0
  53. package/src/types.ts +175 -0
  54. package/tasks/npm-publish.sh +13 -0
  55. package/tsconfig.json +7 -0
@@ -0,0 +1,251 @@
1
+ import {
2
+ PresentationWindowRenderer,
3
+ CreateWindowOptions,
4
+ WindowBounds,
5
+ } from './types';
6
+
7
+ interface IFrameRendererWindow {
8
+ frame: HTMLIFrameElement;
9
+ receiveMessageCallback?: (type: string, payload?: any) => void;
10
+ }
11
+
12
+ const namespace = 'raydiant';
13
+
14
+ export default class IFrameRenderer implements PresentationWindowRenderer {
15
+ private windows: {
16
+ [windowId: string]: IFrameRendererWindow;
17
+ } = {};
18
+
19
+ constructor(private container: HTMLElement) {
20
+ // Post message is a single channel for all window so we need to
21
+ // attach a global message handler that forwards the event to the
22
+ // correct window.
23
+ window.addEventListener('message', this.globalMessageHandler, false);
24
+ }
25
+
26
+ public async createWindow({
27
+ windowId,
28
+ bounds,
29
+ backgroundColor = 'black',
30
+ receiveMessageCallback,
31
+ }: CreateWindowOptions) {
32
+ const frame = document.createElement('iframe');
33
+ this.windows[windowId] = { frame, receiveMessageCallback };
34
+
35
+ frame.setAttribute('data-windowId', windowId);
36
+
37
+ // Set the allowed permissions for the iframe.
38
+ // * allows any loaded domain to have access, which is required for
39
+ // third-party sites loaded into the iframe.
40
+ frame.allow = 'microphone *; camera *; autoplay';
41
+
42
+ // Default styles.
43
+ frame.style.position = 'absolute';
44
+ frame.style.zIndex = '0';
45
+ frame.style.border = 'none';
46
+
47
+ // Position will be 0,0 for the main window.
48
+ if (bounds) {
49
+ frame.style.top = `${bounds.y}px`;
50
+ frame.style.left = `${bounds.x}px`;
51
+ frame.style.width = `${bounds.width}px`;
52
+ frame.style.height = `${bounds.height}px`;
53
+ } else {
54
+ // If bounds assume fullscreen.
55
+ frame.style.top = '0px';
56
+ frame.style.left = '0px';
57
+ frame.style.width = '100%';
58
+ frame.style.height = '100%';
59
+ }
60
+
61
+ if (backgroundColor) {
62
+ frame.style.backgroundColor = backgroundColor;
63
+ }
64
+
65
+ // Set initial visibility and send to background.
66
+ frame.style.zIndex = '0';
67
+ frame.style.visibility = 'hidden';
68
+
69
+ this.container.appendChild(frame);
70
+ }
71
+
72
+ public loadWindow(windowId: string, url: string) {
73
+ const window = this.getWindow(windowId);
74
+ if (!window) {
75
+ throw new Error(
76
+ `Failed to update window bounds, missing window id '${windowId}'`
77
+ );
78
+ }
79
+
80
+ // Return a promise that resolves then the window has successfully loaded.
81
+ return new Promise<void>((resolve, reject) => {
82
+ window.frame.onload = () => {
83
+ resolve();
84
+ };
85
+
86
+ window.frame.onerror = () => {
87
+ reject(new Error(`Error loading window for '${url}'`));
88
+ };
89
+
90
+ window.frame.src = url;
91
+ });
92
+ }
93
+
94
+ public updateWindowBounds(windowId: string, bounds: WindowBounds) {
95
+ const window = this.getWindow(windowId);
96
+ if (!window) {
97
+ throw new Error(
98
+ `Failed to update window bounds, missing window id '${windowId}'`
99
+ );
100
+ }
101
+
102
+ window.frame.style.top = `${bounds.y}px`;
103
+ window.frame.style.left = `${bounds.x}px`;
104
+ window.frame.style.width = `${bounds.width}px`;
105
+ window.frame.style.height = `${bounds.height}px`;
106
+ }
107
+
108
+ public showWindow(windowId: string, isFocused: boolean) {
109
+ const window = this.getWindow(windowId);
110
+ if (!window) {
111
+ throw new Error(`Failed to show window, missing window id '${windowId}'`);
112
+ }
113
+
114
+ window.frame.style.zIndex = '9999';
115
+ window.frame.style.visibility = 'visible';
116
+
117
+ if (isFocused) {
118
+ window.frame.focus();
119
+ }
120
+ }
121
+
122
+ public hideWindow(windowId: string) {
123
+ const window = this.getWindow(windowId);
124
+ if (!window) {
125
+ throw new Error(`Failed to hide window, missing window id '${windowId}'`);
126
+ }
127
+
128
+ window.frame.style.zIndex = '0';
129
+ window.frame.style.visibility = 'hidden';
130
+ }
131
+
132
+ public async removeWindow(windowId: string) {
133
+ const window = this.getWindow(windowId);
134
+ if (!window) {
135
+ throw new Error(
136
+ `Failed to remove window, missing window id '${windowId}'`
137
+ );
138
+ }
139
+
140
+ this.container.removeChild(window.frame);
141
+ delete this.windows[windowId];
142
+ }
143
+
144
+ public sendMessageString(windowId: string, message: string) {
145
+ const window = this.getWindow(windowId);
146
+ if (!window) {
147
+ throw new Error(
148
+ `Failed to send message '${message}', missing window id '${windowId}'`
149
+ );
150
+ }
151
+
152
+ if (!window.frame.contentWindow) {
153
+ throw new Error(
154
+ `Failed to send message '${message}', contentWindow missing for '${windowId}'`
155
+ );
156
+ }
157
+
158
+ window.frame.contentWindow.postMessage(`${namespace}:${message}`, '*');
159
+ }
160
+
161
+ public sendMessageObject(windowId: string, type: string, payload?: any) {
162
+ const window = this.getWindow(windowId);
163
+ if (!window) {
164
+ throw new Error(
165
+ `Failed to send message '${type}', missing window id '${windowId}'`
166
+ );
167
+ }
168
+
169
+ if (!window.frame.contentWindow) {
170
+ throw new Error(
171
+ `Failed to send message '${type}', contentWindow missing for '${windowId}'`
172
+ );
173
+ }
174
+
175
+ window.frame.contentWindow.postMessage(
176
+ { namespace, windowId, type, payload },
177
+ '*'
178
+ );
179
+ }
180
+
181
+ public muteWindow(windowId: string) {
182
+ const window = this.getWindow(windowId);
183
+ if (!window) {
184
+ throw new Error(`Failed to mute window, missing window id '${windowId}'`);
185
+ }
186
+
187
+ // There is no mute/unmute API for iframes so we rely on the window manager overriding any
188
+ // mute presentation properties.
189
+ }
190
+
191
+ public unMuteWindow(windowId: string) {
192
+ const window = this.getWindow(windowId);
193
+ if (!window) {
194
+ throw new Error(
195
+ `Failed to unmute window, missing window id '${windowId}'`
196
+ );
197
+ }
198
+
199
+ // There is no mute/unmute API for iframes so we rely on the window manager overriding any
200
+ // mute presentation properties.
201
+ }
202
+
203
+ public destroy() {
204
+ window.removeEventListener('message', this.globalMessageHandler, false);
205
+ }
206
+
207
+ private getWindow(windowId: string): IFrameRendererWindow | null {
208
+ return this.windows[windowId];
209
+ }
210
+
211
+ private globalMessageHandler = (event: MessageEvent) => {
212
+ // Ignore messages that don't start with the message prefix.
213
+ if (typeof event.data === 'string' && event.data.startsWith(namespace)) {
214
+ const { windowId, type, payload } = this.deserializeMessage(event.data);
215
+
216
+ if (!windowId || !type) {
217
+ console.warn(
218
+ `Invalid message received from child window '${event.data}'`
219
+ );
220
+ return;
221
+ }
222
+
223
+ const window = this.getWindow(windowId);
224
+ if (!window) {
225
+ throw new Error(
226
+ `Failed add receive message callback to window, window with window id: '${windowId}' was not found`
227
+ );
228
+ }
229
+
230
+ if (window.receiveMessageCallback) {
231
+ window.receiveMessageCallback(type, payload);
232
+ }
233
+ }
234
+ };
235
+
236
+ private deserializeMessage(message: string) {
237
+ let windowId = '';
238
+ let type = '';
239
+ let payload: unknown;
240
+
241
+ const json = message.replace('raydiant:', '');
242
+ try {
243
+ // Messages are sent as json in the format of { type: string, payload: unknown }.
244
+ ({ windowId, type, payload } = JSON.parse(json));
245
+ } catch (err) {
246
+ // Not a valid message, return empty type and payload.
247
+ }
248
+
249
+ return { windowId, type, payload };
250
+ }
251
+ }