@raydiant/playlist-window-manager 0.27.1 → 0.27.3-rc.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.
@@ -31,6 +31,7 @@ export default class BrowserWindowRenderer implements PresentationWindowRenderer
31
31
  removeWindow(windowId: string): Promise<void>;
32
32
  sendMessageString(windowId: string, message: string): void;
33
33
  sendMessageObject(windowId: string, type: string, payload?: any): void;
34
+ sendOnlineStatus(windowId: string, isOnline: boolean): void;
34
35
  muteWindow(windowId: string): void;
35
36
  unMuteWindow(windowId: string): void;
36
37
  private getWindow;
@@ -90,7 +90,7 @@ var BrowserWindowRenderer = /** @class */ (function () {
90
90
  * This option fixes window not being able to take fullscreen leaving
91
91
  * 1px gap at the right and bottom of the screen.
92
92
  */
93
- resizable: false,
93
+ // resizable: false,
94
94
  frame: this.opts.frame,
95
95
  hasShadow: this.opts.hasShadow,
96
96
  webPreferences: {
@@ -133,7 +133,7 @@ var BrowserWindowRenderer = /** @class */ (function () {
133
133
  * This option fixes window not being able to take fullscreen leaving
134
134
  * 1px gap at the right and bottom of the screen.
135
135
  */
136
- resizable: false,
136
+ // resizable: false,
137
137
  frame: this.opts.frame,
138
138
  // NOTE: useWindowPool: true does not work with renderChildWindowsAsBrowserViews: false
139
139
  // because the parent can't be changed after creating the BrowserWindow.
@@ -320,6 +320,13 @@ var BrowserWindowRenderer = /** @class */ (function () {
320
320
  }
321
321
  window.view.webContents.send('electron-main', { type: type, payload: payload });
322
322
  };
323
+ BrowserWindowRenderer.prototype.sendOnlineStatus = function (windowId, isOnline) {
324
+ var window = this.getWindow(windowId);
325
+ if (!window) {
326
+ throw new Error("Failed to send isOnline status to window, missing window id '".concat(windowId, "'"));
327
+ }
328
+ window.view.webContents.send('online-status', isOnline);
329
+ };
323
330
  BrowserWindowRenderer.prototype.muteWindow = function (windowId) {
324
331
  var window = this.getWindow(windowId);
325
332
  if (!window) {
@@ -20,6 +20,7 @@ export interface PlaylistWindowManagerOptions {
20
20
  onEnableInterruptLock?: () => void;
21
21
  onDisableInterruptLock?: () => void;
22
22
  getTerminal?: () => Terminal | null;
23
+ getOnlineStatus?: () => boolean;
23
24
  }
24
25
  export default class PlaylistWindowManager extends EventEmitter {
25
26
  private options;
@@ -80,6 +81,7 @@ export default class PlaylistWindowManager extends EventEmitter {
80
81
  sendChargeResponse(response: ChargeResponse): void;
81
82
  sendCharge(charge: Charge): void;
82
83
  sendTerminal(terminal: Terminal | null): void;
84
+ sendOnlineStatus(isOnline: boolean): void;
83
85
  private createWindow;
84
86
  private loadCurrentWindow;
85
87
  private shouldPreload;
@@ -676,6 +676,14 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
676
676
  this.nextWindow.sendTerminal(terminal);
677
677
  }
678
678
  };
679
+ PlaylistWindowManager.prototype.sendOnlineStatus = function (isOnline) {
680
+ if (this.currentWindow) {
681
+ this.currentWindow.sendOnlineStatus(isOnline);
682
+ }
683
+ if (this.nextWindow) {
684
+ this.nextWindow.sendOnlineStatus(isOnline);
685
+ }
686
+ };
679
687
  PlaylistWindowManager.prototype.createWindow = function (pr) {
680
688
  var _a = this.options, readyTimeoutDurationSeconds = _a.readyTimeoutDurationSeconds, appRendererUrl = _a.appRendererUrl, appRendererOfflineSupport = _a.appRendererOfflineSupport, backgroundColor = _a.backgroundColor, device = _a.device, logger = _a.logger, windowName = _a.windowName, parentWindowId = _a.parentWindowId, isPreview = _a.isPreview;
681
689
  if (!this.ruleEngine) {
@@ -721,6 +729,7 @@ var PlaylistWindowManager = /** @class */ (function (_super) {
721
729
  isPreview: isPreview,
722
730
  workerState: this.workerState,
723
731
  getTerminal: this.options.getTerminal,
732
+ getOnlineStatus: this.options.getOnlineStatus,
724
733
  });
725
734
  };
726
735
  PlaylistWindowManager.prototype.loadCurrentWindow = function () {
@@ -33,6 +33,7 @@ export interface PresentationWindowOptions {
33
33
  [key: string]: string;
34
34
  }) => void;
35
35
  getTerminal?: () => Terminal | null;
36
+ getOnlineStatus?: () => boolean;
36
37
  }
37
38
  export default class PresentationWindow {
38
39
  private options;
@@ -102,6 +103,7 @@ export default class PresentationWindow {
102
103
  sendChargeResponse(response: ChargeResponse): void;
103
104
  sendCharge(charge: Charge): void;
104
105
  sendTerminal(terminal: Terminal | null): void;
106
+ sendOnlineStatus(isOnline: boolean): void;
105
107
  private isEmbeddedApp;
106
108
  private allChildWindowManagersReady;
107
109
  private checkIsReadyCallback;
@@ -755,6 +755,17 @@ var PresentationWindow = /** @class */ (function () {
755
755
  return wm.sendTerminal(terminal);
756
756
  });
757
757
  };
758
+ PresentationWindow.prototype.sendOnlineStatus = function (isOnline) {
759
+ var _a = this.options, windowId = _a.windowId, renderer = _a.renderer;
760
+ // Send online-status event. This Separate from sendMessageObject since we
761
+ // are bypassing Raydiant SDK message receivers.
762
+ if (renderer.sendOnlineStatus) {
763
+ renderer.sendOnlineStatus(windowId, isOnline);
764
+ }
765
+ Object.values(this.windowManagers).forEach(function (wm) {
766
+ wm.sendOnlineStatus(isOnline);
767
+ });
768
+ };
758
769
  PresentationWindow.prototype.isEmbeddedApp = function () {
759
770
  var _a = this.options, applicationVersion = _a.applicationVersion, isPreview = _a.isPreview;
760
771
  // If the application has a source url, always use it for rendering the preview.
@@ -1068,6 +1079,7 @@ var PresentationWindow = /** @class */ (function () {
1068
1079
  }
1069
1080
  },
1070
1081
  getTerminal: this.options.getTerminal,
1082
+ getOnlineStatus: this.options.getOnlineStatus,
1071
1083
  });
1072
1084
  // Hook up child window manager events to bubble up to the parent.
1073
1085
  if (parentWindowManager) {
@@ -56,6 +56,8 @@ function createEmbeddedApp(presentation, applicationVersion, metadata) {
56
56
  value = presentation.applicationVariables[queryParamName];
57
57
  if (value === undefined)
58
58
  value = '';
59
+ if (value === null)
60
+ value = '';
59
61
  if (Array.isArray(value)) {
60
62
  value = JSON.stringify(value);
61
63
  }
@@ -149,3 +149,11 @@ test('Replaces $deviceId with empty string when in the query string and metadata
149
149
  });
150
150
  expect((0, embeddedApps_1.default)(presentation, applicationVersion, createMetadata())).toBe("https://example.com/content?device=");
151
151
  });
152
+ test('Replaces null with empty string', function () {
153
+ expect((0, embeddedApps_1.default)(createPresentation({
154
+ applicationVariables: { file: null },
155
+ }), createApplicationVersion({
156
+ embeddedUrlFormat: "https://example.come?image=:file",
157
+ presentationProperties: [{ type: 'file', name: 'file' }],
158
+ }), createMetadata())).toBe("https://example.come?image=");
159
+ });
package/build/preload.js CHANGED
@@ -4,7 +4,7 @@ var electron_1 = require("electron");
4
4
  var internals = {
5
5
  sendMessage: function (windowId, type, payload) {
6
6
  electron_1.ipcRenderer.send(windowId, type, payload);
7
- }
7
+ },
8
8
  };
9
9
  // Set __RAYDIANT_INTERNALS on window so it is accessible in to the running application.
10
10
  window.__RAYDIANT_INTERNALS = internals;
@@ -18,3 +18,104 @@ electron_1.ipcRenderer.on('electron-main', function (_, message) {
18
18
  console.error('Failed to handle message from main process, receiveMessage is not defined');
19
19
  }
20
20
  });
21
+ // Create a custom event that mimics the behavior of the native online/offline events.
22
+ function makeWindowEvent(type) {
23
+ var event = new Event(type);
24
+ Object.defineProperty(event, 'srcElement', {
25
+ get: function () { return window; },
26
+ });
27
+ Object.defineProperty(event, 'target', {
28
+ get: function () { return window; },
29
+ });
30
+ return event;
31
+ }
32
+ // Synchronously get the initial online status. This is handled in photon by the renderer
33
+ // service. Calling sendSync avoids a flash of incorrect status when loading or refreshing
34
+ // the page.
35
+ var isOnline = electron_1.ipcRenderer.sendSync('get-online-status');
36
+ // Listen for online status changes from the main process.
37
+ electron_1.ipcRenderer.on('online-status', function (_, nextIsOnline) {
38
+ console.log("Online status: ".concat(nextIsOnline));
39
+ if (nextIsOnline !== isOnline) {
40
+ console.log('Online status changed, updating navigator.onLine');
41
+ isOnline = nextIsOnline;
42
+ var events_1 = makeWindowEvent(isOnline ? 'online' : 'offline');
43
+ var listeners = isOnline ? onlineListeners : offlineListeners;
44
+ console.log("Running ".concat(isOnline ? 'online' : 'offline', " listeners, count: ").concat(listeners.size));
45
+ listeners.forEach(function (listener) {
46
+ if (typeof listener === 'function') {
47
+ listener(events_1);
48
+ }
49
+ else if (listener.handleEvent) {
50
+ listener.handleEvent(events_1);
51
+ }
52
+ });
53
+ }
54
+ });
55
+ // Replace navigator.onLine with our implementation that uses a the device’s local connectivity
56
+ // service. This will allow apps to receive an accurate and reliable value for navigator.onLine
57
+ // when running on the device.
58
+ Object.defineProperty(navigator, 'onLine', {
59
+ get: function () {
60
+ console.log("navigator.onLine called, returning ".concat(isOnline));
61
+ return isOnline;
62
+ },
63
+ });
64
+ var offlineListeners = new Set();
65
+ var onlineListeners = new Set();
66
+ var originalAddEventListener = window.addEventListener.bind(window);
67
+ Object.defineProperty(window, 'addEventListener', {
68
+ value: function (type, listener) {
69
+ if (type === 'online') {
70
+ console.log("Intercepted online addEventListener");
71
+ onlineListeners.add(listener);
72
+ }
73
+ else if (type === 'offline') {
74
+ console.log("Intercepted offline addEventListener");
75
+ offlineListeners.add(listener);
76
+ }
77
+ else {
78
+ return originalAddEventListener(type, listener);
79
+ }
80
+ },
81
+ });
82
+ var originalRemoveEventListener = window.removeEventListener.bind(window);
83
+ Object.defineProperty(window, 'removeEventListener', {
84
+ value: function (type, listener) {
85
+ if (type === 'online') {
86
+ console.log("Intercepted online removeEventListener");
87
+ onlineListeners.delete(listener);
88
+ }
89
+ else if (type === 'offline') {
90
+ console.log("Intercepted offline removeEventListener");
91
+ offlineListeners.delete(listener);
92
+ }
93
+ else {
94
+ return originalRemoveEventListener(type, listener);
95
+ }
96
+ },
97
+ });
98
+ // // TODO: Remove this (for local dev only).
99
+ // (window as any).simulateOnline = () => {
100
+ // isOnline = true;
101
+ // console.log(`Running online listeners, count: ${onlineListeners.size}`);
102
+ // onlineListeners.forEach((listener) => {
103
+ // if (typeof listener === 'function') {
104
+ // listener(makeWindowEvent('online'));
105
+ // } else if (listener.handleEvent) {
106
+ // listener.handleEvent(makeWindowEvent('online'));
107
+ // }
108
+ // });
109
+ // };
110
+ // // TODO: Remove this (for local dev only).
111
+ // (window as any).simulateOffline = () => {
112
+ // isOnline = false;
113
+ // console.log(`Running offline listeners, count: ${offlineListeners.size}`);
114
+ // offlineListeners.forEach((listener) => {
115
+ // if (typeof listener === 'function') {
116
+ // listener(makeWindowEvent('offline'));
117
+ // } else if (listener.handleEvent) {
118
+ // listener.handleEvent(makeWindowEvent('offline'));
119
+ // }
120
+ // });
121
+ // };
package/build/types.d.ts CHANGED
@@ -112,6 +112,7 @@ export interface PresentationWindowRenderer {
112
112
  sendMessageObject(windowId: string, type: string, payload?: any): void;
113
113
  muteWindow(windowId: string): void;
114
114
  unMuteWindow(windowId: string): void;
115
+ sendOnlineStatus?(windowId: string, isOnline: boolean): void;
115
116
  }
116
117
  export interface OpenChildWindowOptions {
117
118
  windowId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raydiant/playlist-window-manager",
3
- "version": "0.27.1",
3
+ "version": "0.27.3-rc.1",
4
4
  "description": "Window manager for playlist playback.",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
@@ -67,7 +67,7 @@ export default class BrowserWindowRenderer
67
67
  * This option fixes window not being able to take fullscreen leaving
68
68
  * 1px gap at the right and bottom of the screen.
69
69
  */
70
- resizable: false,
70
+ // resizable: false,
71
71
  frame: this.opts.frame,
72
72
  hasShadow: this.opts.hasShadow,
73
73
  webPreferences: {
@@ -119,7 +119,7 @@ export default class BrowserWindowRenderer
119
119
  * This option fixes window not being able to take fullscreen leaving
120
120
  * 1px gap at the right and bottom of the screen.
121
121
  */
122
- resizable: false,
122
+ // resizable: false,
123
123
  frame: this.opts.frame,
124
124
  // NOTE: useWindowPool: true does not work with renderChildWindowsAsBrowserViews: false
125
125
  // because the parent can't be changed after creating the BrowserWindow.
@@ -348,6 +348,17 @@ export default class BrowserWindowRenderer
348
348
  window.view.webContents.send('electron-main', { type, payload });
349
349
  }
350
350
 
351
+ public sendOnlineStatus(windowId: string, isOnline: boolean) {
352
+ const window = this.getWindow(windowId);
353
+ if (!window) {
354
+ throw new Error(
355
+ `Failed to send isOnline status to window, missing window id '${windowId}'`
356
+ );
357
+ }
358
+
359
+ window.view.webContents.send('online-status', isOnline);
360
+ }
361
+
351
362
  public muteWindow(windowId: string) {
352
363
  const window = this.getWindow(windowId);
353
364
  if (!window) {
@@ -78,6 +78,9 @@ export interface PlaylistWindowManagerOptions {
78
78
 
79
79
  // Called when a window needs the current terminal object.
80
80
  getTerminal?: () => Terminal | null;
81
+
82
+ // Called when a window needs to check if the device is online.
83
+ getOnlineStatus?: () => boolean;
81
84
  }
82
85
 
83
86
  // Wait at least 3 seconds before restarting or moving onto the next window
@@ -488,6 +491,16 @@ export default class PlaylistWindowManager extends EventEmitter {
488
491
  }
489
492
  }
490
493
 
494
+ public sendOnlineStatus(isOnline: boolean) {
495
+ if (this.currentWindow) {
496
+ this.currentWindow.sendOnlineStatus(isOnline);
497
+ }
498
+
499
+ if (this.nextWindow) {
500
+ this.nextWindow.sendOnlineStatus(isOnline);
501
+ }
502
+ }
503
+
491
504
  private createWindow(pr: RE.NextPresentationID): PresentationWindow {
492
505
  const {
493
506
  readyTimeoutDurationSeconds,
@@ -555,6 +568,7 @@ export default class PlaylistWindowManager extends EventEmitter {
555
568
  isPreview,
556
569
  workerState: this.workerState,
557
570
  getTerminal: this.options.getTerminal,
571
+ getOnlineStatus: this.options.getOnlineStatus,
558
572
  });
559
573
  }
560
574
 
@@ -59,6 +59,7 @@ export interface PresentationWindowOptions {
59
59
  string: { [key: string]: string }
60
60
  ) => void;
61
61
  getTerminal?: () => Terminal | null;
62
+ getOnlineStatus?: () => boolean;
62
63
  }
63
64
 
64
65
  // TODO: Automatically inject latest version when building in CI.
@@ -525,6 +526,20 @@ export default class PresentationWindow {
525
526
  );
526
527
  }
527
528
 
529
+ public sendOnlineStatus(isOnline: boolean) {
530
+ const { windowId, renderer } = this.options;
531
+ // Send online-status event. This Separate from sendMessageObject since we
532
+ // are bypassing Raydiant SDK message receivers.
533
+
534
+ if (renderer.sendOnlineStatus) {
535
+ renderer.sendOnlineStatus(windowId, isOnline);
536
+ }
537
+
538
+ Object.values(this.windowManagers).forEach((wm) => {
539
+ wm.sendOnlineStatus(isOnline);
540
+ });
541
+ }
542
+
528
543
  private isEmbeddedApp() {
529
544
  const { applicationVersion, isPreview } = this.options;
530
545
 
@@ -876,6 +891,7 @@ export default class PresentationWindow {
876
891
  }
877
892
  },
878
893
  getTerminal: this.options.getTerminal,
894
+ getOnlineStatus: this.options.getOnlineStatus,
879
895
  });
880
896
 
881
897
  // Hook up child window manager events to bubble up to the parent.
@@ -277,3 +277,18 @@ test('Replaces $deviceId with empty string when in the query string and metadata
277
277
  createEmbeddedApp(presentation, applicationVersion, createMetadata())
278
278
  ).toBe(`https://example.com/content?device=`);
279
279
  });
280
+
281
+ test('Replaces null with empty string', () => {
282
+ expect(
283
+ createEmbeddedApp(
284
+ createPresentation({
285
+ applicationVariables: { file: null },
286
+ }),
287
+ createApplicationVersion({
288
+ embeddedUrlFormat: `https://example.come?image=:file`,
289
+ presentationProperties: [{ type: 'file', name: 'file' }],
290
+ }),
291
+ createMetadata()
292
+ )
293
+ ).toBe(`https://example.come?image=`);
294
+ });
@@ -66,6 +66,7 @@ export default function createEmbeddedApp(
66
66
 
67
67
  value = presentation.applicationVariables[queryParamName];
68
68
  if (value === undefined) value = '';
69
+ if (value === null) value = '';
69
70
  if (Array.isArray(value)) {
70
71
  value = JSON.stringify(value);
71
72
  }
package/src/preload.ts CHANGED
@@ -8,7 +8,7 @@ interface RaydiantInternals {
8
8
  const internals: RaydiantInternals = {
9
9
  sendMessage: (windowId: string, type: string, payload?: any) => {
10
10
  ipcRenderer.send(windowId, type, payload);
11
- }
11
+ },
12
12
  };
13
13
 
14
14
  // Set __RAYDIANT_INTERNALS on window so it is accessible in to the running application.
@@ -25,3 +25,124 @@ ipcRenderer.on('electron-main', (_: IpcRendererEvent, message: string) => {
25
25
  );
26
26
  }
27
27
  });
28
+
29
+ // Create a custom event that mimics the behavior of the native online/offline events.
30
+ function makeWindowEvent(type: string) {
31
+ const event = new Event(type);
32
+
33
+ Object.defineProperty(event, 'srcElement', {
34
+ get: () => window,
35
+ });
36
+
37
+ Object.defineProperty(event, 'target', {
38
+ get: () => window,
39
+ });
40
+
41
+ return event;
42
+ }
43
+
44
+ // Synchronously get the initial online status. This is handled in photon by the renderer
45
+ // service. Calling sendSync avoids a flash of incorrect status when loading or refreshing
46
+ // the page.
47
+ let isOnline = ipcRenderer.sendSync('get-online-status');
48
+
49
+ // Listen for online status changes from the main process.
50
+ ipcRenderer.on(
51
+ 'online-status',
52
+ (_: IpcRendererEvent, nextIsOnline: boolean) => {
53
+ console.log(`Online status: ${nextIsOnline}`);
54
+
55
+ if (nextIsOnline !== isOnline) {
56
+ console.log('Online status changed, updating navigator.onLine');
57
+ isOnline = nextIsOnline;
58
+
59
+ const events = makeWindowEvent(isOnline ? 'online' : 'offline');
60
+ const listeners = isOnline ? onlineListeners : offlineListeners;
61
+
62
+ console.log(
63
+ `Running ${isOnline ? 'online' : 'offline'} listeners, count: ${
64
+ listeners.size
65
+ }`
66
+ );
67
+
68
+ listeners.forEach((listener) => {
69
+ if (typeof listener === 'function') {
70
+ listener(events);
71
+ } else if (listener.handleEvent) {
72
+ listener.handleEvent(events);
73
+ }
74
+ });
75
+ }
76
+ }
77
+ );
78
+
79
+ // Replace navigator.onLine with our implementation that uses a the device’s local connectivity
80
+ // service. This will allow apps to receive an accurate and reliable value for navigator.onLine
81
+ // when running on the device.
82
+ Object.defineProperty(navigator, 'onLine', {
83
+ get: () => {
84
+ console.log(`navigator.onLine called, returning ${isOnline}`);
85
+ return isOnline;
86
+ },
87
+ });
88
+
89
+ const offlineListeners = new Set<EventListenerOrEventListenerObject>();
90
+ const onlineListeners = new Set<EventListenerOrEventListenerObject>();
91
+
92
+ const originalAddEventListener = window.addEventListener.bind(window);
93
+ Object.defineProperty(window, 'addEventListener', {
94
+ value: (type: string, listener: EventListenerOrEventListenerObject) => {
95
+ if (type === 'online') {
96
+ console.log(`Intercepted online addEventListener`);
97
+ onlineListeners.add(listener);
98
+ } else if (type === 'offline') {
99
+ console.log(`Intercepted offline addEventListener`);
100
+ offlineListeners.add(listener);
101
+ } else {
102
+ return originalAddEventListener(type, listener);
103
+ }
104
+ },
105
+ });
106
+
107
+ const originalRemoveEventListener = window.removeEventListener.bind(window);
108
+ Object.defineProperty(window, 'removeEventListener', {
109
+ value: (type: string, listener: EventListenerOrEventListenerObject) => {
110
+ if (type === 'online') {
111
+ console.log(`Intercepted online removeEventListener`);
112
+ onlineListeners.delete(listener);
113
+ } else if (type === 'offline') {
114
+ console.log(`Intercepted offline removeEventListener`);
115
+ offlineListeners.delete(listener);
116
+ } else {
117
+ return originalRemoveEventListener(type, listener);
118
+ }
119
+ },
120
+ });
121
+
122
+ // // TODO: Remove this (for local dev only).
123
+ // (window as any).simulateOnline = () => {
124
+ // isOnline = true;
125
+
126
+ // console.log(`Running online listeners, count: ${onlineListeners.size}`);
127
+ // onlineListeners.forEach((listener) => {
128
+ // if (typeof listener === 'function') {
129
+ // listener(makeWindowEvent('online'));
130
+ // } else if (listener.handleEvent) {
131
+ // listener.handleEvent(makeWindowEvent('online'));
132
+ // }
133
+ // });
134
+ // };
135
+
136
+ // // TODO: Remove this (for local dev only).
137
+ // (window as any).simulateOffline = () => {
138
+ // isOnline = false;
139
+
140
+ // console.log(`Running offline listeners, count: ${offlineListeners.size}`);
141
+ // offlineListeners.forEach((listener) => {
142
+ // if (typeof listener === 'function') {
143
+ // listener(makeWindowEvent('offline'));
144
+ // } else if (listener.handleEvent) {
145
+ // listener.handleEvent(makeWindowEvent('offline'));
146
+ // }
147
+ // });
148
+ // };
package/src/types.ts CHANGED
@@ -139,6 +139,11 @@ export interface PresentationWindowRenderer {
139
139
 
140
140
  // Called when unmuting a window.
141
141
  unMuteWindow(windowId: string): void;
142
+
143
+ // Called when sending isOnline status to the inner window. This is used to override
144
+ // the navigator.isOnline property in the inner window. This is only defined for Electron
145
+ // renderers. Browser renderers do not need to override navigator.isOnline.
146
+ sendOnlineStatus?(windowId: string, isOnline: boolean): void;
142
147
  }
143
148
 
144
149
  export interface OpenChildWindowOptions {