@applicaster/zapp-react-dom-app 16.0.0-alpha.2295427024 → 16.0.0-alpha.2480030155

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.
@@ -65,6 +65,9 @@ export const PLATFORMS = {
65
65
  mobile: "Mobile",
66
66
  vizio: "VIZIO",
67
67
  vidaa: "VIDAA",
68
+ // VIDAA U3 and earlier (2019 and earlier) carry no "VIDAA" token at all —
69
+ // "Hisense" is the only signal on those models. See isVidaaPlatform().
70
+ hisense: "Hisense",
68
71
  smartcast: "SmartCast",
69
72
  conjure: "Conjure",
70
73
  };
@@ -75,14 +78,42 @@ export const PLATFORMS = {
75
78
 
76
79
  /**
77
80
  * Simple way of identifying if we have access to the tizen APIs
81
+ *
82
+ * NOTE: evaluated once, when this module is first imported. The native API
83
+ * scripts are injected asynchronously by loader.js, so this can be `false`
84
+ * simply because the bundle executed first. Prefer `hasTizenAPIs()` for
85
+ * runtime checks; this constant is retained for the DEFAULT values below,
86
+ * which are themselves import-time fallbacks.
78
87
  */
79
88
  export const hasTizen = typeof window?.tizen !== "undefined";
80
89
 
81
90
  /**
82
91
  * Simple way of identifying if we have access to the webOS
92
+ *
93
+ * NOTE: same import-time caveat as `hasTizen` — prefer `hasWebOSAPIs()`.
83
94
  */
84
95
  export const hasWebOS = typeof window?.webOS !== "undefined";
85
96
 
97
+ /**
98
+ * Checks, at call time, whether the Tizen APIs are available.
99
+ *
100
+ * loader.js injects `$WEBAPIS/webapis/webapis.js` via a dynamically-created
101
+ * <script>, which is async by default, so `window.tizen` may not exist yet when
102
+ * this module is imported. Checking on each call avoids latching a stale
103
+ * `false` for the lifetime of the app.
104
+ *
105
+ * @returns {boolean} True if the Tizen APIs are available.
106
+ */
107
+ export const hasTizenAPIs = () => typeof window?.tizen !== "undefined";
108
+
109
+ /**
110
+ * Checks, at call time, whether the webOS APIs are available.
111
+ * See `hasTizenAPIs()` for why this is a function rather than a constant.
112
+ *
113
+ * @returns {boolean} True if the webOS APIs are available.
114
+ */
115
+ export const hasWebOSAPIs = () => typeof window?.webOS !== "undefined";
116
+
86
117
  /**
87
118
  * Simple way of identifying if we are on a web based platform
88
119
  */
@@ -6,8 +6,8 @@ import { Platform } from "react-native";
6
6
  import {
7
7
  DEFAULT,
8
8
  desiredKeysMap,
9
- hasTizen,
10
- hasWebOS,
9
+ hasTizenAPIs,
10
+ hasWebOSAPIs,
11
11
  PLATFORMS,
12
12
  } from "./const";
13
13
 
@@ -27,7 +27,7 @@ export const getUserAgent = () =>
27
27
  * Checks if the device is HD (1280 width).
28
28
  * @returns {boolean} True if the device is HD, false otherwise.
29
29
  */
30
- export const isHD = () => window.innerWidth === 1280;
30
+ export const isHD = () => window?.innerWidth === 1280;
31
31
 
32
32
  /**
33
33
  * Checks if the platform is LG.
@@ -40,7 +40,7 @@ export const isLgPlatform = () => {
40
40
  return false;
41
41
  }
42
42
 
43
- return hasWebOS || userAgent.includes(PLATFORMS.webos);
43
+ return hasWebOSAPIs() || userAgent.includes(PLATFORMS.webos);
44
44
  };
45
45
 
46
46
  /**
@@ -55,7 +55,7 @@ export const isSamsungPlatform = () => {
55
55
  }
56
56
 
57
57
  return (
58
- hasTizen ||
58
+ hasTizenAPIs() ||
59
59
  userAgent.includes(PLATFORMS.samsung) ||
60
60
  userAgent.includes(PLATFORMS.tizen)
61
61
  );
@@ -68,6 +68,10 @@ export const isSamsungPlatform = () => {
68
68
  export const isVizioPlatform = () => {
69
69
  const userAgent = getUserAgent();
70
70
 
71
+ if (!userAgent) {
72
+ return false;
73
+ }
74
+
71
75
  return (
72
76
  userAgent.includes(PLATFORMS.vizio) ||
73
77
  userAgent.includes(PLATFORMS.smartcast) ||
@@ -76,13 +80,37 @@ export const isVizioPlatform = () => {
76
80
  };
77
81
 
78
82
  /**
79
- * Checks if the platform is Vida.
80
- * @returns {boolean} True if the platform is Vida, false otherwise.
83
+ * Checks if the platform is VIDAA.
84
+ *
85
+ * Two tokens are needed because the userAgent format changed with VIDAA U4:
86
+ * - 2020 and later (U4/U5/U6/U7): always carries a "VIDAA" token, e.g.
87
+ * `Model/Hisense-MT9602 VIDAA/5.0(Hisense;SmartTV;50A53FSV;...)`
88
+ * - 2019 and earlier (U3 and prior): carries NO "VIDAA" token at all — the only
89
+ * signal is "Hisense", e.g. `Model/Hisense-MSD6586 (Hisense;HE50N3050UWTS;...)`
90
+ *
91
+ * Matching "VIDAA" alone silently misses every U3-and-earlier device, which the
92
+ * VIDAA JS SDK still supports (getDeviceID/getBrand/config are all U3U4U5+).
93
+ *
94
+ * Caveat: "Hisense" also appears in the userAgent of Hisense-branded Android TV
95
+ * models, which are not VIDAA. That is tolerable here because this is only
96
+ * consulted on a VIDAA-targeted build (see getDeviceType), but it is why the
97
+ * vendor docs warn against using "Hisense" as a general-purpose signal. A
98
+ * stricter check would test for the native `Hisense_GetDeviceID` global, which
99
+ * VIDAA exposes from U3 onwards and Android TV does not.
100
+ *
101
+ * @see https://partner-doc.vidaa.com/vdocs/development/open-capacity.html
102
+ * @returns {boolean} True if the platform is VIDAA, false otherwise.
81
103
  */
82
104
  export const isVidaaPlatform = () => {
83
105
  const userAgent = getUserAgent();
84
106
 
85
- return userAgent.includes(PLATFORMS.vidaa);
107
+ if (!userAgent) {
108
+ return false;
109
+ }
110
+
111
+ return (
112
+ userAgent.includes(PLATFORMS.vidaa) || userAgent.includes(PLATFORMS.hisense)
113
+ );
86
114
  };
87
115
 
88
116
  /**
@@ -91,11 +119,194 @@ export const isVidaaPlatform = () => {
91
119
  */
92
120
  export const hasVizioAPIs = () => {
93
121
  return (
94
- typeof window.VIZIO !== "undefined" &&
122
+ typeof window?.VIZIO !== "undefined" &&
95
123
  window?.applicaster?.vizioLibraryDidLoad
96
124
  );
97
125
  };
98
126
 
127
+ /**
128
+ * Checks if the VIDAA JS SDK has finished loading.
129
+ * @returns {boolean} True if the `vidaatv` object is available.
130
+ */
131
+ export const hasVidaaAPIs = () => typeof window?.vidaatv !== "undefined";
132
+
133
+ /** How long to wait for a native device API callback before giving up, in ms. */
134
+ const NATIVE_API_TIMEOUT_MS = 3000;
135
+
136
+ /**
137
+ * Wraps a promise so that it *always* settles, and always resolves.
138
+ *
139
+ * The native device APIs are callback-based and wrapped in promises that can
140
+ * fail in two ways this guards against:
141
+ *
142
+ * 1. **Rejection.** Device info and connection info are requested together with
143
+ * `Promise.all`, which rejects as a whole on the first failure — so a routine
144
+ * "no network" error from the connection lookup would discard the model and
145
+ * OS version that were retrieved successfully.
146
+ * 2. **Never settling.** `webOS.deviceInfo(cb)`, `tizen.systeminfo(cb)` and
147
+ * `VIZIO.getFirmwareVersion(cb)` resolve only from their callbacks. If a
148
+ * callback never fires the promise hangs forever, and because `getDeviceData`
149
+ * is awaited during startup, app boot hangs with it.
150
+ *
151
+ * @param {Promise} promise The promise to guard.
152
+ * @param {object} options
153
+ * @param {string} options.label Name used in warnings.
154
+ * @param {number} [options.timeout=NATIVE_API_TIMEOUT_MS] Milliseconds to wait.
155
+ * @param {*} [options.fallback=null] Value to resolve with on failure/timeout.
156
+ * @returns {Promise<*>} Resolves with the value, or `fallback`.
157
+ */
158
+ const settleWithin = (
159
+ promise,
160
+ { label, timeout = NATIVE_API_TIMEOUT_MS, fallback = null } = {}
161
+ ) => {
162
+ return new Promise((resolve) => {
163
+ let settled = false;
164
+
165
+ const finish = (value) => {
166
+ if (settled) {
167
+ return;
168
+ }
169
+
170
+ settled = true;
171
+ clearTimeout(timeoutId);
172
+ resolve(value);
173
+ };
174
+
175
+ const timeoutId = setTimeout(() => {
176
+ console.warn(`${label}: timed out after ${timeout}ms`);
177
+ finish(fallback);
178
+ }, timeout);
179
+
180
+ Promise.resolve(promise)
181
+ .then(finish)
182
+ .catch((error) => {
183
+ console.warn(`${label}: failed`, error);
184
+ finish(fallback);
185
+ });
186
+ });
187
+ };
188
+
189
+ /** How long to wait for the VIDAA SDK before giving up, in ms. */
190
+ const VIDAA_SDK_TIMEOUT_MS = 3000;
191
+
192
+ /** How often to re-check for the VIDAA SDK while waiting, in ms. */
193
+ const VIDAA_SDK_POLL_MS = 50;
194
+
195
+ /**
196
+ * The value the VIDAA SDK returns for APIs the current platform version does
197
+ * not implement, per the vendor docs ("For features Not Supported by the
198
+ * platform, the interface returns `Not Supported`"). Treated as absent.
199
+ */
200
+ const VIDAA_NOT_SUPPORTED = "Not Supported";
201
+
202
+ /**
203
+ * Waits for the VIDAA JS SDK to become available on `window`.
204
+ *
205
+ * The SDK is injected by loader.js as a dynamically-created <script>, which is
206
+ * async by default and fetched from the VIDAA CDN. The app bundle is served
207
+ * locally and can therefore boot before that request completes, so reading
208
+ * `window.vidaatv` directly at startup is a race. This resolves as soon as the
209
+ * SDK appears, or with `null` once the timeout elapses.
210
+ *
211
+ * Resolves rather than rejects on timeout so a missing SDK degrades to partial
212
+ * device data instead of failing the whole `getDeviceData` call.
213
+ *
214
+ * @param {number} [timeout=VIDAA_SDK_TIMEOUT_MS] Milliseconds to wait.
215
+ * @returns {Promise<object|null>} The `vidaatv` object, or null if it never arrived.
216
+ */
217
+ export const waitForVidaaAPIs = (timeout = VIDAA_SDK_TIMEOUT_MS) => {
218
+ return new Promise((resolve) => {
219
+ if (hasVidaaAPIs()) {
220
+ resolve(window.vidaatv);
221
+
222
+ return;
223
+ }
224
+
225
+ const startedAt = Date.now();
226
+
227
+ const intervalId = setInterval(() => {
228
+ if (hasVidaaAPIs()) {
229
+ clearInterval(intervalId);
230
+ resolve(window.vidaatv);
231
+
232
+ return;
233
+ }
234
+
235
+ if (Date.now() - startedAt >= timeout) {
236
+ clearInterval(intervalId);
237
+ console.warn(
238
+ `waitForVidaaAPIs: VIDAA SDK did not load within ${timeout}ms`
239
+ );
240
+ resolve(null);
241
+ }
242
+ }, VIDAA_SDK_POLL_MS);
243
+ });
244
+ };
245
+
246
+ /**
247
+ * Reads a single value from the VIDAA SDK, tolerating APIs that the current
248
+ * platform version does not implement.
249
+ *
250
+ * Every accessor is optional: older platforms omit the method entirely, and
251
+ * supported-but-unavailable values come back as the string "Not Supported".
252
+ *
253
+ * @param {object} sdk The `vidaatv` object.
254
+ * @param {string} method The accessor name, e.g. "getModelName".
255
+ * @returns {string|null} The value, or null when unavailable.
256
+ */
257
+ const readVidaaValue = (sdk, method) => {
258
+ try {
259
+ if (typeof sdk?.[method] !== "function") {
260
+ return null;
261
+ }
262
+
263
+ const value = sdk[method]();
264
+
265
+ return value && value !== VIDAA_NOT_SUPPORTED ? value : null;
266
+ } catch (error) {
267
+ console.warn(`readVidaaValue: ${method} threw`, error);
268
+
269
+ return null;
270
+ }
271
+ };
272
+
273
+ /**
274
+ * Gets device info from the VIDAA JS SDK.
275
+ *
276
+ * Field availability varies by VIDAA version, so each value is read
277
+ * defensively and omitted when unavailable:
278
+ * - getBrand / getModelName / getFirmWareVersion / getCountryCode are U3+
279
+ * - getOSVersion / getNetType are U4+, hence the firmware fallback for
280
+ * osVersion so pre-2020 models still report something
281
+ *
282
+ * @returns {Promise<object|null>} Device info, or null if the SDK never loaded.
283
+ */
284
+ export const getVidaaInfo = async () => {
285
+ const sdk = await waitForVidaaAPIs();
286
+
287
+ if (!sdk) {
288
+ return null;
289
+ }
290
+
291
+ const modelName = readVidaaValue(sdk, "getModelName");
292
+ const brand = readVidaaValue(sdk, "getBrand");
293
+ const networkType = readVidaaValue(sdk, "getNetType");
294
+ const countryCode = readVidaaValue(sdk, "getCountryCode");
295
+
296
+ // getOSVersion is U4+; fall back to the firmware version, which is U3+.
297
+ const osVersion =
298
+ readVidaaValue(sdk, "getOSVersion") ||
299
+ readVidaaValue(sdk, "getFirmWareVersion");
300
+
301
+ return {
302
+ ...(modelName ? { modelName } : {}),
303
+ ...(brand ? { deviceMake: brand } : {}),
304
+ ...(osVersion ? { osVersion } : {}),
305
+ ...(networkType ? { networkType } : {}),
306
+ ...(countryCode ? { countryCode } : {}),
307
+ };
308
+ };
309
+
99
310
  /**
100
311
  * Determines the device type based on the platform.
101
312
  * @returns {DeviceType} The device type ("tv", "web", or "other").
@@ -165,7 +376,7 @@ export const getWebOSConnectionInfo = () => {
165
376
  */
166
377
  export const getTizenConnectionInfo = () => {
167
378
  return new Promise((resolve, reject) => {
168
- if (!isSamsungPlatform) {
379
+ if (!isSamsungPlatform()) {
169
380
  resolve(null);
170
381
 
171
382
  return;
@@ -189,6 +400,36 @@ export const getTizenConnectionInfo = () => {
189
400
  });
190
401
  };
191
402
 
403
+ /**
404
+ * Retrieves the firmware version from the Vizio companion library.
405
+ *
406
+ * `VIZIO.getFirmwareVersion` is callback-based. It was previously called
407
+ * without being awaited, assigning onto `deviceData` after `getDeviceData` had
408
+ * already returned — by which point the caller had spread the object into
409
+ * session storage, so the value was silently dropped whenever the callback did
410
+ * not fire synchronously. Wrapping it in a promise lets it be awaited like
411
+ * every other platform's device info.
412
+ *
413
+ * @returns {Promise<string|null>} The firmware version, or null if unavailable.
414
+ */
415
+ export const getVizioFirmwareVersion = () => {
416
+ return new Promise((resolve, reject) => {
417
+ if (!hasVizioAPIs()) {
418
+ resolve(null);
419
+
420
+ return;
421
+ }
422
+
423
+ try {
424
+ window.VIZIO.getFirmwareVersion((firmwareVersion) => {
425
+ resolve(firmwareVersion);
426
+ });
427
+ } catch (error) {
428
+ reject(error);
429
+ }
430
+ });
431
+ };
432
+
192
433
  /**
193
434
  * Gets all of the webOS device info available to us via webOS.deviceInfo
194
435
  */
@@ -279,9 +520,13 @@ export const getDeviceData = async () => {
279
520
  };
280
521
 
281
522
  if (isLgPlatform()) {
523
+ // Guarded individually so a failure in one lookup does not discard the
524
+ // other — see settleWithin.
282
525
  const [webOSInfo, webOSConnectionInfo] = await Promise.all([
283
- getWebOSInfo(),
284
- getWebOSConnectionInfo(),
526
+ settleWithin(getWebOSInfo(), { label: "getWebOSInfo" }),
527
+ settleWithin(getWebOSConnectionInfo(), {
528
+ label: "getWebOSConnectionInfo",
529
+ }),
285
530
  ]);
286
531
 
287
532
  deviceData = {
@@ -294,8 +539,10 @@ export const getDeviceData = async () => {
294
539
  };
295
540
  } else if (isSamsungPlatform()) {
296
541
  const [tizenInfo, tizenConnectionInfo] = await Promise.all([
297
- getTizenInfo(),
298
- getTizenConnectionInfo(),
542
+ settleWithin(getTizenInfo(), { label: "getTizenInfo" }),
543
+ settleWithin(getTizenConnectionInfo(), {
544
+ label: "getTizenConnectionInfo",
545
+ }),
299
546
  ]);
300
547
 
301
548
  deviceData = {
@@ -311,17 +558,35 @@ export const getDeviceData = async () => {
311
558
  ...deviceData,
312
559
  platform: "vizio",
313
560
  deviceMake: "Vizio",
314
- deviceType: "tv",
561
+ deviceType: getDeviceType(),
315
562
  ...deviceDimensions,
316
563
  };
317
564
 
318
565
  if (hasVizioAPIs()) {
319
- window.VIZIO.getFirmwareVersion(function (firmwareVersion) {
320
- deviceData.osVersion = firmwareVersion;
566
+ const firmwareVersion = await settleWithin(getVizioFirmwareVersion(), {
567
+ label: "getVizioFirmwareVersion",
321
568
  });
322
569
 
570
+ if (firmwareVersion) {
571
+ deviceData.osVersion = firmwareVersion;
572
+ }
573
+
323
574
  deviceData.deviceModel = window.VIZIO.deviceModel;
324
575
  }
576
+ } else if (isVidaaPlatform()) {
577
+ // Waits for the VIDAA SDK, which loader.js loads asynchronously from the
578
+ // VIDAA CDN. Resolves to null on timeout, leaving the static fields below.
579
+ const vidaaInfo = await getVidaaInfo();
580
+
581
+ deviceData = {
582
+ ...vidaaInfo,
583
+ platform: "vidaa",
584
+ // Brand is read from the SDK rather than hardcoded: VIDAA ships on
585
+ // OEM sets beyond Hisense, so getBrand() is the accurate source.
586
+ deviceMake: vidaaInfo?.deviceMake || "Hisense",
587
+ deviceType: getDeviceType(),
588
+ ...deviceDimensions,
589
+ };
325
590
  }
326
591
 
327
592
  return deviceData;
@@ -332,6 +597,10 @@ export const getDeviceData = async () => {
332
597
  error
333
598
  );
334
599
 
335
- return null;
600
+ // Returns an empty object rather than null: the caller
601
+ // (loadSessionStorageData) does `delete deviceData.sdkVersion` on the
602
+ // result, which throws a TypeError on null and turns a handled error into
603
+ // an unhandled one during startup.
604
+ return {};
336
605
  }
337
606
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-dom-app",
3
- "version": "16.0.0-alpha.2295427024",
3
+ "version": "16.0.0-alpha.2480030155",
4
4
  "description": "Zapp App Component for Applicaster's Quick Brick React Native App",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,11 +22,11 @@
22
22
  },
23
23
  "homepage": "https://github.com/applicaster/zapp-react-dom-app#readme",
24
24
  "dependencies": {
25
- "@applicaster/zapp-react-dom-ui-components": "16.0.0-alpha.2295427024",
26
- "@applicaster/zapp-react-native-bridge": "16.0.0-alpha.2295427024",
27
- "@applicaster/zapp-react-native-redux": "16.0.0-alpha.2295427024",
28
- "@applicaster/zapp-react-native-ui-components": "16.0.0-alpha.2295427024",
29
- "@applicaster/zapp-react-native-utils": "16.0.0-alpha.2295427024",
25
+ "@applicaster/zapp-react-dom-ui-components": "16.0.0-alpha.2480030155",
26
+ "@applicaster/zapp-react-native-bridge": "16.0.0-alpha.2480030155",
27
+ "@applicaster/zapp-react-native-redux": "16.0.0-alpha.2480030155",
28
+ "@applicaster/zapp-react-native-ui-components": "16.0.0-alpha.2480030155",
29
+ "@applicaster/zapp-react-native-utils": "16.0.0-alpha.2480030155",
30
30
  "abortcontroller-polyfill": "^1.7.5",
31
31
  "typeface-montserrat": "^0.0.54",
32
32
  "video.js": "7.14.3",