@applicaster/zapp-react-native-utils 12.0.0-alpha.5895113034 → 12.0.0-alpha.6115034951

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/analyticsUtils/AnalyticsEvents/index.ts +7 -0
  2. package/analyticsUtils/AnalyticsEvents/sendShareEvent.ts +21 -0
  3. package/analyticsUtils/{playerAnalyticsTracker.ts → analyticsTracker.ts} +10 -19
  4. package/analyticsUtils/events.ts +6 -0
  5. package/analyticsUtils/index.tsx +6 -0
  6. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -3
  7. package/appUtils/focusManager/__tests__/focusManager.test.js +4 -24
  8. package/appUtils/focusManager/__tests__/index.test.ts +2 -3
  9. package/appUtils/focusManager/index.ts +76 -56
  10. package/appUtils/focusManager/treeDataStructure/Utils/index.js +75 -44
  11. package/appUtils/focusManager/utils/index.ts +100 -0
  12. package/appUtils/focusManagerAux/index.ts +76 -0
  13. package/appUtils/focusManagerAux/utils/index.ts +100 -0
  14. package/appUtils/keyCodes/keys.web.js +8 -0
  15. package/appUtils/keyCodes/keysByPlatform.js +11 -1
  16. package/appUtils/platform/const.ts +25 -0
  17. package/appUtils/platform/index.ts +3 -0
  18. package/appUtils/platform/platformUtils.ts +223 -0
  19. package/appUtils/playerManager/conts.ts +1 -0
  20. package/appUtils/playerManager/index.ts +38 -14
  21. package/appUtils/playerManager/nativePlayerManager.ts +12 -0
  22. package/appUtils/playerManager/player.ts +13 -16
  23. package/appUtils/playerManager/playerNative.ts +6 -16
  24. package/appUtils/playerManager/usePlayerControllerSetup.tsx +3 -17
  25. package/arrayUtils/__tests__/isFilledArray.test.ts +63 -0
  26. package/arrayUtils/index.ts +5 -0
  27. package/booleanUtils/index.ts +4 -4
  28. package/keyboardState/index.ts +0 -0
  29. package/navigationUtils/index.ts +48 -0
  30. package/numberUtils/__tests__/isLikeNumber.test.ts +18 -0
  31. package/numberUtils/index.ts +5 -18
  32. package/package.json +2 -2
  33. package/pluginUtils/index.ts +9 -0
  34. package/reactHooks/cell-click/__tests__/index.test.js +9 -5
  35. package/reactHooks/cell-click/index.ts +11 -11
  36. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +30 -70
  37. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +6 -2
  38. package/reactHooks/feed/useBuildPipesUrl.ts +7 -10
  39. package/reactHooks/feed/useFeedRefresh.tsx +9 -7
  40. package/reactHooks/navigation/index.ts +44 -7
  41. package/reactHooks/player/__tests__/useAutoSeek.test.tsx +1 -8
  42. package/reactHooks/player/useAutoSeek.tsx +8 -28
  43. package/reactHooks/player/useSeek.tsx +2 -4
  44. package/reactHooks/player/useTapSeek.tsx +7 -34
  45. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +21 -23
  46. package/reactHooks/screen/index.ts +2 -0
  47. package/reactHooks/screen/useScreenBackgroundColor.ts +23 -0
  48. package/reactHooks/screen/useScreenContext.ts +1 -2
  49. package/reactUtils/index.ts +102 -0
  50. package/analyticsUtils/AnalyticPlayerListener.ts +0 -155
  51. package/analyticsUtils/PlayerAnalyticsManager.ts +0 -90
  52. package/appUtils/focusManager/FocusableEvents.ts +0 -8
  53. package/appUtils/focusManager/treeDataStructure/Utils/__tests__/Utils_android.test.js +0 -1094
  54. package/appUtils/focusManager/treeDataStructure/Utils/index.android.js +0 -261
  55. package/appUtils/playerManager/DelayedSeekPlayerWrapper.tsx +0 -179
@@ -1,9 +1,13 @@
1
1
  import { renderHook } from "@testing-library/react-hooks";
2
2
 
3
- import { useFeedRefresh, feedRefreshLogger } from "../";
4
-
5
3
  jest.useFakeTimers();
6
4
 
5
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
6
+ useIsScreenActive: () => true,
7
+ }));
8
+
9
+ const { useFeedRefresh, feedRefreshLogger } = require("..");
10
+
7
11
  describe("useFeedRefresh", () => {
8
12
  const reloadData = jest.fn();
9
13
 
@@ -1,7 +1,6 @@
1
- // @ts-ignore
2
1
  import { RequestBuilder } from "@applicaster/zapp-pipes-v2-client";
3
- import { useEffect, useState } from "react";
4
- import { useInflatedUrl } from "./useInflatedUrl";
2
+ import * as React from "react";
3
+ import { useInflatedUrl } from "@applicaster/zapp-react-native-utils/reactHooks/feed/useInflatedUrl";
5
4
  import { reactHooksLogger } from "../logger";
6
5
 
7
6
  export const logger = reactHooksLogger.addSubsystem("build-pipes-url");
@@ -15,18 +14,16 @@ type PipesBuildRequestObject = {
15
14
  export function useBuildPipesUrl({
16
15
  url,
17
16
  mapping = {},
17
+ requestBuilder = new RequestBuilder(),
18
18
  }): PipesBuildRequestObject {
19
- const [requestParams, setRequestParams] = useState<object | null>(null);
20
- const [error, setError] = useState(null);
19
+ const [requestParams, setRequestParams] = React.useState<object | null>(null);
21
20
 
21
+ const [error, setError] = React.useState(null);
22
22
  const inflatedUrl = useInflatedUrl({ feedUrl: url, mapping });
23
23
 
24
- const requestBuilder = new RequestBuilder();
25
-
26
24
  const buildRequest = async (_url) => {
27
25
  try {
28
26
  const result = await requestBuilder.build(_url);
29
-
30
27
  setRequestParams(result);
31
28
  } catch (e) {
32
29
  setError(e);
@@ -34,7 +31,7 @@ export function useBuildPipesUrl({
34
31
  }
35
32
  };
36
33
 
37
- useEffect(() => {
34
+ React.useEffect(() => {
38
35
  if (inflatedUrl && !requestParams) {
39
36
  buildRequest(inflatedUrl);
40
37
  }
@@ -42,7 +39,7 @@ export function useBuildPipesUrl({
42
39
 
43
40
  return {
44
41
  url: inflatedUrl || "",
45
- requestParams: requestParams, // TODO should it be: || {} ?
42
+ requestParams, // TODO should it be: || {} ?
46
43
  error: error || undefined,
47
44
  };
48
45
  }
@@ -1,7 +1,8 @@
1
1
  import * as React from "react";
2
- import * as R from "ramda";
2
+ import { path, max } from "ramda";
3
3
  import { isTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
4
4
  import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
5
+ import { useIsScreenActive } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
6
 
6
7
  import { reactHooksLogger } from "../logger";
7
8
  import { ReloadDataFunction } from "./useFeedLoader";
@@ -18,9 +19,8 @@ const minimumRefreshingInterval = 5;
18
19
  const defaultRefreshInterval = 60;
19
20
 
20
21
  export const useFeedRefresh = ({ reloadData, component }: Props): void => {
21
- const enable_data_refreshing = R.path(
22
- ["rules", "enable_data_refreshing"],
23
- component
22
+ const isDataRefreshingEnabled = isTrue(
23
+ path(["rules", "enable_data_refreshing"], component)
24
24
  );
25
25
 
26
26
  const refreshing_interval = toNumberWithDefault(
@@ -29,10 +29,12 @@ export const useFeedRefresh = ({ reloadData, component }: Props): void => {
29
29
  );
30
30
 
31
31
  const refreshingIntervalInMilliseconds =
32
- R.max(refreshing_interval, minimumRefreshingInterval) * 1000;
32
+ max(refreshing_interval, minimumRefreshingInterval) * 1000;
33
+
34
+ const isScreenActive = useIsScreenActive();
33
35
 
34
36
  React.useEffect(() => {
35
- if (isTrue(enable_data_refreshing) && refreshing_interval) {
37
+ if (isDataRefreshingEnabled && refreshing_interval && isScreenActive) {
36
38
  if (refreshing_interval < minimumRefreshingInterval) {
37
39
  feedRefreshLogger.warning({
38
40
  message: `You set your feed refresh interval to ${refreshing_interval} when minimum value is ${minimumRefreshingInterval}seconds.Your feed will refresh after ${minimumRefreshingInterval}seconds.`,
@@ -59,5 +61,5 @@ export const useFeedRefresh = ({ reloadData, component }: Props): void => {
59
61
  clearInterval(refreshInterval);
60
62
  };
61
63
  }
62
- }, [reloadData]);
64
+ }, [reloadData, isScreenActive]);
63
65
  };
@@ -4,7 +4,6 @@
4
4
  import React, { useContext, useEffect, useMemo } from "react";
5
5
  import { BackHandler, InteractionManager, NativeModules } from "react-native";
6
6
  import { useSafeAreaInsets } from "react-native-safe-area-context";
7
- import { isEmpty } from "ramda";
8
7
 
9
8
  import {
10
9
  useContentTypes,
@@ -13,7 +12,11 @@ import {
13
12
  import { HooksManager } from "@applicaster/zapp-react-native-utils/appUtils/HooksManager";
14
13
 
15
14
  import { legacyScreenData } from "@applicaster/quick-brick-core/App/NavigationProvider/utils";
16
- import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
15
+ import {
16
+ LONG_KEY_PRESS_TIMEOUT,
17
+ QUICK_BRICK_NAVBAR,
18
+ QUICK_BRICK_CONTENT,
19
+ } from "@applicaster/quick-brick-core/const";
17
20
  import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/ZappHooksModal";
18
21
  import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
19
22
  import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
@@ -28,7 +31,7 @@ import { platformSelect } from "../../reactUtils";
28
31
  import { useConnectionInfo } from "../connection";
29
32
  import { getTargetScreenData } from "../screen/useTargetScreenData";
30
33
 
31
- import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
34
+ import { isTV, isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
32
35
  import { useNavbarState } from "../screen";
33
36
  import { useNavigation } from "./useNavigation";
34
37
  import { useModalStore } from "../../modalState";
@@ -75,6 +78,10 @@ export function isNavBarVisible(
75
78
  return false;
76
79
  }
77
80
 
81
+ if (screenData?.plugin_type === "login" && isTV()) {
82
+ return false;
83
+ }
84
+
78
85
  /* Match if screen is Hook */
79
86
  if (route.startsWith("/hooks/")) {
80
87
  const module = screenData?.module;
@@ -207,10 +214,7 @@ export const useRoute = (): {
207
214
  return { screenData: screenDataContext, pathname };
208
215
  }
209
216
 
210
- // Cover cases where screenContext is used before navigation is initialized
211
- const screenData = isEmpty(screenDataContext)
212
- ? navigator.screenData
213
- : screenDataContext;
217
+ const screenData = screenDataContext;
214
218
 
215
219
  return { screenData, pathname };
216
220
  };
@@ -452,3 +456,36 @@ export const useIsScreenActive = () => {
452
456
 
453
457
  return pathname === currentRoute;
454
458
  };
459
+
460
+ type Return = (prefix: string) => string;
461
+
462
+ export const useUniqueRouteSuffix = (): Return => {
463
+ const pathname = usePathname();
464
+ const { currentRoute } = useNavigation();
465
+
466
+ const suffix = `currentRoute:${currentRoute}_pathname:${pathname}`;
467
+
468
+ const withUniqueRouteSuffix = React.useCallback((prefix: string) => {
469
+ if (isWeb()) {
470
+ // we should use extended route prefix only on Web, where we have stack-navigator
471
+ return `${prefix}___${suffix}`;
472
+ }
473
+
474
+ // for other platforms use previous version
475
+ return prefix;
476
+ }, []);
477
+
478
+ return withUniqueRouteSuffix;
479
+ };
480
+
481
+ export const useNavbarId = () => {
482
+ const withUniqueRouteSuffix = useUniqueRouteSuffix();
483
+
484
+ return withUniqueRouteSuffix(QUICK_BRICK_NAVBAR);
485
+ };
486
+
487
+ export const useContentId = () => {
488
+ const withUniqueRouteSuffix = useUniqueRouteSuffix();
489
+
490
+ return withUniqueRouteSuffix(QUICK_BRICK_CONTENT);
491
+ };
@@ -9,18 +9,11 @@ jest.useFakeTimers();
9
9
 
10
10
  const rewind = jest.fn();
11
11
  const forward = jest.fn();
12
- const exitDelayedSeek = jest.fn();
13
- const enterDelayedSeek = jest.fn();
14
12
 
15
13
  jest
16
14
  .spyOn(playerManager, "getInstanceController")
17
15
  // @ts-ignore
18
- .mockImplementation(() => ({
19
- rewind,
20
- forward,
21
- exitDelayedSeek,
22
- enterDelayedSeek,
23
- }));
16
+ .mockImplementation(() => ({ rewind, forward }));
24
17
 
25
18
  describe("useAutoSeek", () => {
26
19
  beforeEach(() => {
@@ -14,24 +14,19 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
14
14
  const [isRunning, setIsRunning] = useState<boolean>(false);
15
15
  const [direction, setDirection] = useState<Nullable<SEEK_TYPE>>(null);
16
16
  const [multiplier, setMultiplier] = useState(1);
17
-
18
17
  const intervalTimer = React.useRef(null);
19
18
  const prevRunning = usePrevious(isRunning);
20
19
 
21
20
  const setIntervalTimer = (cb) => {
22
- if (intervalTimer.current) {
23
- return;
24
- }
25
-
26
- intervalTimer.current = setInterval(() => {
27
- intervalTimer.current = null;
28
- cb();
29
- }, ON_HOLD_INTERVAL);
21
+ intervalTimer.current = setInterval(cb, ON_HOLD_INTERVAL);
30
22
  };
31
23
 
32
24
  const clearIntervalTimer = () => {
33
- clearInterval(intervalTimer.current);
34
- intervalTimer.current = null;
25
+ try {
26
+ clearInterval(intervalTimer.current);
27
+ } catch (err) {
28
+ intervalTimer.current = null;
29
+ }
35
30
  };
36
31
 
37
32
  const resetState = () => {
@@ -42,21 +37,6 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
42
37
  };
43
38
 
44
39
  useEffect(() => {
45
- const player = playerManager.getInstanceController();
46
- if (!player) return;
47
-
48
- if (isRunning) {
49
- player?.enterDelayedSeek();
50
- } else if (!isRunning) {
51
- player.exitDelayedSeek();
52
- }
53
- }, [isRunning]);
54
-
55
- useEffect(() => {
56
- if (!isRunning) {
57
- return;
58
- }
59
-
60
40
  const player = playerManager.getInstanceController();
61
41
 
62
42
  if (!player) return;
@@ -65,7 +45,7 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
65
45
  player[direction](R.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier));
66
46
  resetHUDTimer();
67
47
  }
68
- }, [multiplier, direction, isRunning]);
48
+ }, [multiplier, direction]);
69
49
 
70
50
  useEffect(() => {
71
51
  if (isRunning && isRunning !== prevRunning) {
@@ -95,7 +75,7 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
95
75
  setDirection(eventType);
96
76
  setIsRunning(true);
97
77
  },
98
- [isRunning, autoSeekStop]
78
+ [isRunning]
99
79
  );
100
80
 
101
81
  return React.useMemo(
@@ -9,11 +9,11 @@ export const useSeek = (resetHUDTimer = noop) => {
9
9
  const { autoSeekStart, autoSeekStop } = useAutoSeek(resetHUDTimer);
10
10
 
11
11
  const onForwardPress = () => {
12
- // handlePressEvent(SEEK_TYPE.FORWARD);
12
+ handlePressEvent(SEEK_TYPE.FORWARD);
13
13
  };
14
14
 
15
15
  const onRewindPress = () => {
16
- // handlePressEvent(SEEK_TYPE.REWIND);
16
+ handlePressEvent(SEEK_TYPE.REWIND);
17
17
  };
18
18
 
19
19
  const onForwardPressIn = () => {
@@ -21,7 +21,6 @@ export const useSeek = (resetHUDTimer = noop) => {
21
21
  };
22
22
 
23
23
  const onForwardPressOut = () => {
24
- handlePressEvent(SEEK_TYPE.FORWARD);
25
24
  autoSeekStop();
26
25
  };
27
26
 
@@ -38,7 +37,6 @@ export const useSeek = (resetHUDTimer = noop) => {
38
37
  };
39
38
 
40
39
  const onRewindPressOut = () => {
41
- handlePressEvent(SEEK_TYPE.REWIND);
42
40
  autoSeekStop();
43
41
  };
44
42
 
@@ -1,4 +1,4 @@
1
- import { useCallback, useRef, useState } from "react";
1
+ import { useCallback, useRef } from "react";
2
2
  import * as R from "ramda";
3
3
  import {
4
4
  MAX_SEEK_TIME,
@@ -10,11 +10,13 @@ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
10
10
 
11
11
  type SeekType = SEEK_TYPE | null;
12
12
 
13
- const getSkipTime = (multiplier) =>
14
- R.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier);
13
+ const getSkipTime = (multiplier) => {
14
+ const skipTime = R.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier);
15
+
16
+ return skipTime;
17
+ };
15
18
 
16
19
  export const useTapSeek = () => {
17
- const [resumeTimer, setResumeTimer] = useState<NodeJS.Timeout>(null);
18
20
  const onPressMultiplier = useRef(1);
19
21
  const lastPressTime = useRef(0);
20
22
  const currentOnPressEventType = useRef<SeekType>(null);
@@ -44,41 +46,12 @@ export const useTapSeek = () => {
44
46
  }
45
47
 
46
48
  const seekTime = getSkipTime(onPressMultiplier.current);
47
-
48
49
  player[eventType](seekTime);
49
50
 
50
- if (resumeTimer) {
51
- clearTimeout(resumeTimer);
52
-
53
- const timer = setTimeout(() => {
54
- player.exitDelayedSeek();
55
- setResumeTimer(null);
56
- }, 2000);
57
-
58
- setResumeTimer(timer);
59
- }
60
-
61
- const clicksBeforePause = 3;
62
-
63
- // Only pause player if user starts to seek on large increments
64
- if (onPressMultiplier.current > clicksBeforePause) {
65
- if (!player.isPaused()) {
66
- player.enterDelayedSeek();
67
-
68
- const timer = setTimeout(() => {
69
- player.exitDelayedSeek();
70
- setResumeTimer(null);
71
- }, 2000);
72
-
73
- clearTimeout(resumeTimer);
74
- setResumeTimer(timer);
75
- }
76
- }
77
-
78
51
  lastPressTime.current = now;
79
52
  currentOnPressEventType.current = eventType;
80
53
  },
81
- [resetMultiplier, resumeTimer]
54
+ [resetMultiplier]
82
55
  );
83
56
 
84
57
  return handlePressEvent;
@@ -3,23 +3,28 @@ import { Provider } from "react-redux";
3
3
  import { renderHook } from "@testing-library/react-hooks";
4
4
  import configureStore from "redux-mock-store";
5
5
  import thunk from "redux-thunk";
6
- import { NavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NavigationContext";
7
6
 
8
7
  const mockStore = configureStore([thunk]);
9
8
 
10
- import { useCurrentScreenData } from "../useCurrentScreenData";
11
-
12
- describe("useScreenData", function () {
13
- const river_id_2: ZappRiver = {
14
- name: "River 2",
15
- id: "A1234",
16
- home: false,
17
- home_offline: false,
18
- supports_offline: false,
19
- navigations: [],
20
- hooks: { preload_plugins: [] },
21
- };
22
-
9
+ const river_id_2: ZappRiver = {
10
+ name: "River 2",
11
+ id: "A1234",
12
+ home: false,
13
+ home_offline: false,
14
+ supports_offline: false,
15
+ navigations: [],
16
+ hooks: { preload_plugins: [] },
17
+ };
18
+
19
+ jest.mock("../useScreenContext", () => ({
20
+ useScreenContext: () => ({
21
+ screen: river_id_2,
22
+ }),
23
+ }));
24
+
25
+ const { useCurrentScreenData } = require("../useCurrentScreenData");
26
+
27
+ describe("useScreenData", () => {
23
28
  const river_id_1 = { id: "B5678" };
24
29
 
25
30
  const store = mockStore({
@@ -27,17 +32,10 @@ describe("useScreenData", function () {
27
32
  });
28
33
 
29
34
  const wrapper: React.FC<any> = ({ children }) => (
30
- <NavigationContext.Provider
31
- value={{
32
- currentRoute: "/river/river_id_1/river/river_id_2",
33
- data: { screen: river_id_2 },
34
- }}
35
- >
36
- <Provider store={store}>{children}</Provider>
37
- </NavigationContext.Provider>
35
+ <Provider store={store}>{children}</Provider>
38
36
  );
39
37
 
40
- it("should return river data for the current screen/route", function () {
38
+ it("should return river data for the current screen/route", () => {
41
39
  const { result } = renderHook(() => useCurrentScreenData(), { wrapper });
42
40
 
43
41
  expect(result.current).toBe(river_id_2);
@@ -10,3 +10,5 @@ export {
10
10
  useNavbarState,
11
11
  useSetNavbarState,
12
12
  } from "./useScreenContext";
13
+
14
+ export { useScreenBackgroundColor } from "./useScreenBackgroundColor";
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
3
+ import { useScreenConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration";
4
+ import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
5
+
6
+ export const useScreenBackgroundColor = (screenId: string): string => {
7
+ const { backgroundColor: screenBackgroundColor } =
8
+ useScreenConfiguration(screenId);
9
+
10
+ const theme = useTheme();
11
+
12
+ const themeBackgroundColor = React.useMemo(
13
+ () => theme.app_background_color,
14
+ [theme.app_background_color]
15
+ );
16
+
17
+ const backgroundColor = ifEmptyUseFallback(
18
+ screenBackgroundColor,
19
+ themeBackgroundColor
20
+ );
21
+
22
+ return backgroundColor;
23
+ };
@@ -45,8 +45,7 @@ export function useSetNavbarState() {
45
45
 
46
46
  export function useScreenContext(): NavigationScreenData {
47
47
  const navigator = useNavigation();
48
- // Cover cases where screenContext is used before navigation is initialized
49
- const screenData = useContext(ScreenDataContext) || navigator.data;
48
+ const screenData = useContext(ScreenDataContext);
50
49
 
51
50
  const modalScreenData = navigator?.modalData;
52
51
 
@@ -108,3 +108,105 @@ export const isAndroidPlatform = () => Platform.OS === "android";
108
108
  export const isTvOSPlatform = () => getPlatform() === "tvos";
109
109
 
110
110
  export const isAndroidTVPlatform = () => getPlatform() === "android_tv";
111
+
112
+ declare global {
113
+ interface Window {
114
+ VIZIO?: {
115
+ exitApplication: () => void;
116
+ setClosedCaptionHandler: (
117
+ callback: (isCCEnabled: boolean) => void
118
+ ) => void;
119
+ };
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Platforms found in userAgent string, please note that simulators
125
+ * might not have the same agents that the devices have
126
+ */
127
+ export const PLATFORMS = {
128
+ webos: "Web0S",
129
+ tizen: "Tizen",
130
+ samsung: "SMART-TV",
131
+ mac: "Mac",
132
+ win: "Win",
133
+ linux: "Linux",
134
+ iphone: "iPhone",
135
+ android: "Android",
136
+ mobile: "Mobile",
137
+ vizio: "VIZIO",
138
+ smartcast: "SmartCast",
139
+ conjure: "Conjure",
140
+ };
141
+
142
+ /**
143
+ * Simple way of identifying if we have access to the tizen APIs
144
+ */
145
+ export const hasTizen = typeof window?.tizen !== "undefined";
146
+
147
+ /**
148
+ * Simple way of identifying if we have access to the webOS APIs
149
+ */
150
+ export const hasWebOS = typeof window?.webOS !== "undefined";
151
+
152
+ export const getUserAgent = () =>
153
+ window?.navigator?.userAgent || global?.navigator?.userAgent;
154
+
155
+ export const isLgPlatform = () => {
156
+ const userAgent = getUserAgent();
157
+
158
+ if (!userAgent) {
159
+ return false;
160
+ }
161
+
162
+ // Note: LG uses the number 0 in their userAgent, possibly because of Palm WebOS
163
+ return hasWebOS || userAgent.includes(PLATFORMS.webos);
164
+ };
165
+
166
+ export const isSamsungPlatform = () => {
167
+ const userAgent = getUserAgent();
168
+
169
+ if (!userAgent) {
170
+ return false;
171
+ }
172
+
173
+ return (
174
+ hasTizen ||
175
+ userAgent.includes(PLATFORMS.samsung) ||
176
+ userAgent.includes(PLATFORMS.tizen)
177
+ );
178
+ };
179
+
180
+ /**
181
+ * Checks if we are on the Vizio platform by using the userAgent string
182
+ *
183
+ * Use this method when you need to identify the platform on runtime
184
+ * before the VIZIO Companion Library has loaded
185
+ */
186
+ export const isVizioPlatform = () => {
187
+ const userAgent = getUserAgent();
188
+
189
+ if (!userAgent) {
190
+ return false;
191
+ }
192
+
193
+ const isVizioAgent =
194
+ userAgent.includes(PLATFORMS.vizio) ||
195
+ userAgent.includes(PLATFORMS.smartcast) ||
196
+ userAgent.includes(PLATFORMS.conjure);
197
+
198
+ return isVizioAgent;
199
+ };
200
+
201
+ /**
202
+ * Checks if we are on the Vizio platform by checking for the global VIZIO object
203
+ * and the VIZIO Companion Library. This method does not use the userAgent string,
204
+ * because it mainly checks whether we have access to apis, which may not be available yet
205
+ */
206
+ export const hasVizioAPIs = () => {
207
+ const hasAPIs =
208
+ typeof window.VIZIO !== "undefined" &&
209
+ window?.applicaster?.vizioLibraryDidLoad;
210
+
211
+ return hasAPIs;
212
+ };