@volley/vwr-loader 1.0.5 → 1.0.6

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/README.md +21 -0
  2. package/dist/amplitudeFlagFetcher.d.ts +4 -2
  3. package/dist/amplitudeFlagFetcher.d.ts.map +1 -1
  4. package/dist/amplitudeFlagFetcher.js +26 -19
  5. package/dist/amplitudeFlagFetcher.js.map +1 -1
  6. package/dist/cli.js +234 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/envDefaults.d.ts +3 -0
  9. package/dist/envDefaults.d.ts.map +1 -1
  10. package/dist/envDefaults.js +26 -4
  11. package/dist/envDefaults.js.map +1 -1
  12. package/dist/getDeviceId.d.ts +2 -10
  13. package/dist/getDeviceId.d.ts.map +1 -1
  14. package/dist/getDeviceId.js +31 -18
  15. package/dist/getDeviceId.js.map +1 -1
  16. package/dist/getEnvironment.d.ts +1 -1
  17. package/dist/getEnvironment.d.ts.map +1 -1
  18. package/dist/getEnvironment.js +10 -7
  19. package/dist/getEnvironment.js.map +1 -1
  20. package/dist/getShellVersion.d.ts +2 -2
  21. package/dist/getShellVersion.d.ts.map +1 -1
  22. package/dist/getShellVersion.js +5 -3
  23. package/dist/getShellVersion.js.map +1 -1
  24. package/dist/index.html +1 -1
  25. package/dist/loadVwr.d.ts.map +1 -1
  26. package/dist/loadVwr.js +8 -6
  27. package/dist/loadVwr.js.map +1 -1
  28. package/dist/main.js +1 -1
  29. package/dist/main.js.map +1 -1
  30. package/dist/vwrConfig.d.ts +2 -0
  31. package/dist/vwrConfig.d.ts.map +1 -1
  32. package/dist/vwrConfig.js +148 -119
  33. package/dist/vwrConfig.js.map +1 -1
  34. package/package.json +16 -5
  35. package/src/amplitudeFlagFetcher.test.ts +36 -59
  36. package/src/amplitudeFlagFetcher.ts +31 -22
  37. package/src/envDefaults.ts +33 -4
  38. package/src/getDeviceId.test.ts +79 -24
  39. package/src/getDeviceId.ts +47 -22
  40. package/src/getEnvironment.test.ts +457 -0
  41. package/src/getEnvironment.ts +12 -9
  42. package/src/getShellVersion.test.ts +1 -1
  43. package/src/getShellVersion.ts +7 -3
  44. package/src/loadVwr.ts +13 -6
  45. package/src/main.ts +3 -1
  46. package/src/vite-env.d.ts +3 -0
  47. package/src/vwrConfig.test.ts +2 -2
  48. package/src/vwrConfig.ts +202 -199
  49. package/eslint.config.mjs +0 -23
  50. package/scripts/build.js +0 -2
  51. package/scripts/build.ts +0 -207
  52. package/tsconfig.eslint.json +0 -16
  53. package/tsconfig.json +0 -17
  54. package/vite.config.ts +0 -24
  55. package/vitest.config.ts +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volley/vwr-loader",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "Vite-based VWR loader for all Volley platforms",
6
6
  "main": "dist/index.js",
@@ -13,31 +13,42 @@
13
13
  "./envDefaults": "./dist/envDefaults.js"
14
14
  },
15
15
  "bin": {
16
- "build-loader": "./scripts/build.js"
16
+ "build-loader": "./dist/cli.js"
17
17
  },
18
+ "files": [
19
+ "dist",
20
+ "src"
21
+ ],
18
22
  "scripts": {
19
- "build": "tsc && vite build",
23
+ "build": "tsc && vite build && vite build -c vite.cli.config.ts && chmod +x dist/cli.js",
24
+ "build:cli": "vite build -c vite.cli.config.ts",
20
25
  "build:loader": "vite build",
21
26
  "typecheck": "tsc --noEmit",
22
27
  "test": "vitest run",
23
28
  "test:watch": "vitest",
24
29
  "format": "eslint . --fix",
25
- "lint": "eslint ."
30
+ "lint": "eslint .",
31
+ "semantic-release": "semantic-release"
26
32
  },
27
33
  "dependencies": {
28
34
  "commander": "^12.0.0",
29
35
  "dotenv": "^16.0.0",
30
36
  "fs-extra": "^11.0.0",
31
- "tsx": "^4.7.0",
32
37
  "vite": "^5.0.0"
33
38
  },
34
39
  "devDependencies": {
40
+ "tsx": "^4.7.0",
35
41
  "@platform/eslint-config": "workspace:*",
42
+ "@semantic-release/git": "^10.0.1",
43
+ "@semantic-release/npm": "^12.0.1",
36
44
  "@types/fs-extra": "^11.0.0",
37
45
  "@types/node": "^20.0.0",
46
+ "@volley/vwr": "workspace:*",
38
47
  "eslint": "^9.25.1",
39
48
  "jsdom": "^24.1.3",
40
49
  "prettier": "~3.0.3",
50
+ "semantic-release": "^24.2.3",
51
+ "semantic-release-monorepo": "^8.0.2",
41
52
  "typescript": "^5.0.0",
42
53
  "vitest": "^1.0.0"
43
54
  }
@@ -1,26 +1,16 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
2
2
 
3
3
  import { fetchAmplitudeFlags } from "./amplitudeFlagFetcher"
4
- import * as getShellVersionModule from "./getShellVersion"
5
4
 
6
5
  describe("fetchAmplitudeFlags", () => {
7
6
  const mockFetch = vi.fn()
8
- const originalLocation = window.location
9
7
 
10
8
  beforeEach(() => {
11
9
  global.fetch = mockFetch
12
10
  vi.clearAllMocks()
13
-
14
- // Mock window.location for getShellVersion
15
- delete (window as any).location
16
- window.location = {
17
- ...originalLocation,
18
- href: "http://localhost:3000/?version=1.0.0",
19
- } as Location
20
11
  })
21
12
 
22
13
  afterEach(() => {
23
- window.location = originalLocation
24
14
  vi.restoreAllMocks()
25
15
  })
26
16
 
@@ -36,9 +26,12 @@ describe("fetchAmplitudeFlags", () => {
36
26
  }),
37
27
  })
38
28
 
39
- const result = await fetchAmplitudeFlags("device-123", "SAMSUNG_TV", {
40
- apiKey: "test-key",
41
- })
29
+ const result = await fetchAmplitudeFlags(
30
+ "device-123",
31
+ "SAMSUNG_TV",
32
+ { apiKey: "test-key" },
33
+ "1.0.0"
34
+ )
42
35
 
43
36
  expect(result).toEqual({
44
37
  "vwr-enabled": true,
@@ -98,45 +91,17 @@ describe("fetchAmplitudeFlags", () => {
98
91
  })
99
92
  })
100
93
 
101
- it("auto-fetches shell version when not provided", async () => {
102
- const getShellVersionSpy = vi.spyOn(
103
- getShellVersionModule,
104
- "getShellVersion"
105
- )
106
-
107
- mockFetch.mockResolvedValueOnce({
108
- ok: true,
109
- json: async () => ({
110
- "vwr-enabled": {
111
- payload: {
112
- "vwr-enabled": false,
113
- },
114
- },
115
- }),
116
- })
117
-
118
- await fetchAmplitudeFlags("device-456", "LG_TV", {
119
- apiKey: "test-key",
120
- })
121
-
122
- expect(getShellVersionSpy).toHaveBeenCalledWith("LG_TV")
123
-
124
- const callArgs = mockFetch.mock.calls[0]
125
- const url = new URL(callArgs[0])
126
- const context = JSON.parse(url.searchParams.get("context") || "{}")
127
-
128
- expect(context.user_properties).toHaveProperty("nativeShellAppVersion")
129
- })
130
-
131
94
  it("returns safe defaults on timeout", async () => {
132
95
  mockFetch.mockImplementationOnce(
133
96
  () => new Promise((resolve) => setTimeout(resolve, 3000))
134
97
  )
135
98
 
136
- const result = await fetchAmplitudeFlags("device-123", "FIRE_TV", {
137
- apiKey: "test-key",
138
- timeout: 100,
139
- })
99
+ const result = await fetchAmplitudeFlags(
100
+ "device-123",
101
+ "FIRE_TV",
102
+ { apiKey: "test-key", timeout: 100 },
103
+ "1.0.0"
104
+ )
140
105
 
141
106
  expect(result).toEqual({
142
107
  "vwr-enabled": false,
@@ -149,9 +114,12 @@ describe("fetchAmplitudeFlags", () => {
149
114
  status: 500,
150
115
  })
151
116
 
152
- const result = await fetchAmplitudeFlags("device-123", "MOBILE", {
153
- apiKey: "test-key",
154
- })
117
+ const result = await fetchAmplitudeFlags(
118
+ "device-123",
119
+ "MOBILE",
120
+ { apiKey: "test-key" },
121
+ "1.0.0"
122
+ )
155
123
 
156
124
  expect(result).toEqual({
157
125
  "vwr-enabled": false,
@@ -161,9 +129,12 @@ describe("fetchAmplitudeFlags", () => {
161
129
  it("returns safe defaults on network error", async () => {
162
130
  mockFetch.mockRejectedValueOnce(new Error("Network error"))
163
131
 
164
- const result = await fetchAmplitudeFlags("device-123", "MOBILE", {
165
- apiKey: "test-key",
166
- })
132
+ const result = await fetchAmplitudeFlags(
133
+ "device-123",
134
+ "MOBILE",
135
+ { apiKey: "test-key" },
136
+ "1.0.0"
137
+ )
167
138
 
168
139
  expect(result).toEqual({
169
140
  "vwr-enabled": false,
@@ -176,9 +147,12 @@ describe("fetchAmplitudeFlags", () => {
176
147
  json: async () => ({}),
177
148
  })
178
149
 
179
- const result = await fetchAmplitudeFlags("device-123", "LG_TV", {
180
- apiKey: "test-key",
181
- })
150
+ const result = await fetchAmplitudeFlags(
151
+ "device-123",
152
+ "LG_TV",
153
+ { apiKey: "test-key" },
154
+ "1.0.0"
155
+ )
182
156
 
183
157
  expect(result).toEqual({
184
158
  "vwr-enabled": false,
@@ -193,9 +167,12 @@ describe("fetchAmplitudeFlags", () => {
193
167
  }),
194
168
  })
195
169
 
196
- const result = await fetchAmplitudeFlags("device-123", "LG_TV", {
197
- apiKey: "test-key",
198
- })
170
+ const result = await fetchAmplitudeFlags(
171
+ "device-123",
172
+ "LG_TV",
173
+ { apiKey: "test-key" },
174
+ "1.0.0"
175
+ )
199
176
 
200
177
  expect(result).toEqual({
201
178
  "vwr-enabled": false,
@@ -1,4 +1,4 @@
1
- import { getShellVersion } from "./getShellVersion"
1
+ import { defaultLogger, type Logger } from "./logger"
2
2
 
3
3
  export interface AmplitudeConfig {
4
4
  apiKey: string
@@ -20,14 +20,16 @@ export interface FlagResult {
20
20
  * @param deviceId - Unique device identifier
21
21
  * @param platform - Platform name (samsung, lg, firetv, ios, android)
22
22
  * @param config - Amplitude configuration
23
- * @param shellVersion - Optional shell version (if not provided, will be fetched automatically)
23
+ * @param shellVersion - Shell version for flag targeting
24
+ * @param logger - Optional logger, defaults to console
24
25
  * @returns Flag values with safe defaults on error
25
26
  */
26
27
  export async function fetchAmplitudeFlags(
27
28
  deviceId: string,
28
29
  platform: string,
29
30
  config: AmplitudeConfig,
30
- shellVersion?: string
31
+ shellVersion: string,
32
+ logger: Logger = defaultLogger
31
33
  ): Promise<FlagResult> {
32
34
  const {
33
35
  apiKey,
@@ -35,28 +37,26 @@ export async function fetchAmplitudeFlags(
35
37
  timeout = 2000,
36
38
  } = config
37
39
 
40
+ // Build context with user_properties
41
+ const context = {
42
+ user_properties: {
43
+ platformEnum: platform,
44
+ nativeShellAppVersion: shellVersion,
45
+ },
46
+ }
47
+
48
+ // Build query parameters
49
+ const params = new URLSearchParams({
50
+ user_id: deviceId,
51
+ flag_keys: "vwr-enabled",
52
+ context: JSON.stringify(context),
53
+ })
54
+
55
+ // Start timeout immediately before the fetch call for accurate timing
38
56
  const controller = new AbortController()
39
57
  const timeoutId = setTimeout(() => controller.abort(), timeout)
40
58
 
41
59
  try {
42
- // Get shell version if not provided
43
- const version = shellVersion ?? (await getShellVersion(platform))
44
-
45
- // Build context with user_properties
46
- const context = {
47
- user_properties: {
48
- platformEnum: platform,
49
- nativeShellAppVersion: version,
50
- },
51
- }
52
-
53
- // Build query parameters
54
- const params = new URLSearchParams({
55
- user_id: deviceId,
56
- flag_keys: "vwr-enabled",
57
- context: JSON.stringify(context),
58
- })
59
-
60
60
  const response = await fetch(`${apiUrl}?${params}`, {
61
61
  method: "GET",
62
62
  headers: {
@@ -78,7 +78,16 @@ export async function fetchAmplitudeFlags(
78
78
  data["vwr-enabled"]?.payload?.["vwr-enabled"] ?? false,
79
79
  }
80
80
  } catch (error) {
81
- console.error("[Shell] Amplitude flag fetch failed:", error)
81
+ clearTimeout(timeoutId)
82
+
83
+ // Provide clear warning message for abort vs other errors
84
+ if (error instanceof Error && error.name === "AbortError") {
85
+ logger.warn(
86
+ `[Shell] Amplitude flag fetch aborted because it took longer than ${timeout}ms`
87
+ )
88
+ } else {
89
+ logger.warn("[Shell] Amplitude flag fetch failed:", { error })
90
+ }
82
91
 
83
92
  // Return safe defaults - runs legacy path
84
93
  return {
@@ -3,7 +3,10 @@ export interface EnvConfig {
3
3
  vwrUrl: string
4
4
  configUrl: string
5
5
  configFile: string
6
+ platformApiUrl: string
7
+ platformAuthApiUrl: string
6
8
  amplitudeKey: string // Used for amplitude flag fetch, not part of VWRConfig
9
+ trustedOrigins?: string[]
7
10
  }
8
11
 
9
12
  const CONFIG_URL_DEFAULT = "https://vwr.volley.tv/config/"
@@ -15,22 +18,36 @@ const prodConfig: EnvConfig = {
15
18
  vwrUrl: `https://vwr.volley.tv/${VWR_URL_PATH_DEFAULT}`,
16
19
  configUrl: CONFIG_URL_DEFAULT,
17
20
  configFile: CONFIG_FILE_DEFAULT,
21
+ platformApiUrl: "https://platform.volley-services.net",
22
+ platformAuthApiUrl: "https://auth.volley.tv",
18
23
  amplitudeKey: "",
19
24
  }
20
25
 
21
26
  export const ENV_DEFAULTS: Record<string, EnvConfig> = {
22
27
  local: {
23
- hubUrl: "http://localhost:5173",
24
- vwrUrl: "http://localhost:5174/vwr.js",
25
- configUrl: "http://localhost:5174/config/",
26
- configFile: CONFIG_FILE_DEFAULT,
28
+ hubUrl: import.meta.env.VITE_HUB_URL || "http://localhost:5173",
29
+ vwrUrl: import.meta.env.VITE_VWR_URL || "http://localhost:5174/vwr.js",
30
+ configUrl:
31
+ import.meta.env.VITE_CONFIG_URL || "http://localhost:5174/config/",
32
+ configFile: import.meta.env.VITE_CONFIG_FILE || CONFIG_FILE_DEFAULT,
33
+ platformApiUrl:
34
+ import.meta.env.VITE_PLATFORM_API_URL ||
35
+ "https://platform-dev.volley-services.net",
36
+ platformAuthApiUrl:
37
+ import.meta.env.VITE_PLATFORM_AUTH_API_URL ||
38
+ "https://auth-dev.volley.tv",
27
39
  amplitudeKey: "client-uJJVW3zKPC1G9kqPhUumLnZN6eaY42iQ",
40
+ trustedOrigins: import.meta.env.VITE_TRUSTED_ORIGINS?.split(",").filter(
41
+ Boolean
42
+ ),
28
43
  },
29
44
  dev: {
30
45
  hubUrl: "https://game-clients-dev.volley.tv/hub",
31
46
  vwrUrl: `https://vwr.volley.tv/dev/${VWR_URL_PATH_DEFAULT}`,
32
47
  configUrl: CONFIG_URL_DEFAULT,
33
48
  configFile: CONFIG_FILE_DEFAULT,
49
+ platformApiUrl: "https://platform-dev.volley-services.net",
50
+ platformAuthApiUrl: "https://auth-dev.volley.tv",
34
51
  amplitudeKey: "client-uJJVW3zKPC1G9kqPhUumLnZN6eaY42iQ",
35
52
  },
36
53
  staging: {
@@ -38,8 +55,20 @@ export const ENV_DEFAULTS: Record<string, EnvConfig> = {
38
55
  vwrUrl: `https://vwr.volley.tv/staging/${VWR_URL_PATH_DEFAULT}`,
39
56
  configUrl: CONFIG_URL_DEFAULT,
40
57
  configFile: CONFIG_FILE_DEFAULT,
58
+ platformApiUrl: "https://platform-staging.volley-services.net",
59
+ platformAuthApiUrl: "https://auth-staging.volley.tv",
41
60
  amplitudeKey: "",
42
61
  },
43
62
  prod: prodConfig,
44
63
  production: prodConfig, // Alias for prod
64
+ qa: {
65
+ // QA uses staging infrastructure
66
+ hubUrl: "https://game-clients-staging.volley.tv/hub",
67
+ vwrUrl: `https://vwr.volley.tv/staging/${VWR_URL_PATH_DEFAULT}`,
68
+ configUrl: CONFIG_URL_DEFAULT,
69
+ configFile: CONFIG_FILE_DEFAULT,
70
+ platformApiUrl: "https://platform-staging.volley-services.net",
71
+ platformAuthApiUrl: "https://auth-staging.volley.tv",
72
+ amplitudeKey: "",
73
+ },
45
74
  }
@@ -6,8 +6,9 @@ describe("getDeviceId", () => {
6
6
  beforeEach(() => {
7
7
  vi.clearAllMocks()
8
8
  // Clean up window properties
9
- delete (window as any).DeviceInfo
10
- delete (window as any).NativeBridge
9
+ delete (window as any).Capacitor
10
+ delete (window as any).iosAppContext
11
+ delete (window as any).androidAppContext
11
12
  delete (window as any).webapis
12
13
  delete (window as any).webOSDev
13
14
  delete (window as any).webOS
@@ -15,21 +16,26 @@ describe("getDeviceId", () => {
15
16
 
16
17
  afterEach(() => {
17
18
  vi.restoreAllMocks()
18
- delete (window as any).DeviceInfo
19
- delete (window as any).NativeBridge
19
+ delete (window as any).Capacitor
20
+ delete (window as any).iosAppContext
21
+ delete (window as any).androidAppContext
20
22
  delete (window as any).webapis
21
23
  delete (window as any).webOSDev
22
24
  delete (window as any).webOS
23
25
  })
24
26
 
25
27
  describe("FIRE_TV platform", () => {
26
- it("returns device ID from DeviceInfo.getAndroidId when available", async () => {
28
+ it("returns device ID from Capacitor DeviceInfo.getAndroidId when available", async () => {
27
29
  const mockAndroidId = "firetv-android-id-123"
28
- Object.defineProperty(window, "DeviceInfo", {
30
+ Object.defineProperty(window, "Capacitor", {
29
31
  value: {
30
- getAndroidId: vi
31
- .fn()
32
- .mockResolvedValue({ androidId: mockAndroidId }),
32
+ Plugins: {
33
+ DeviceInfo: {
34
+ getAndroidId: vi.fn().mockResolvedValue({
35
+ androidId: mockAndroidId,
36
+ }),
37
+ },
38
+ },
33
39
  },
34
40
  writable: true,
35
41
  configurable: true,
@@ -40,11 +46,17 @@ describe("getDeviceId", () => {
40
46
  })
41
47
 
42
48
  it("returns null when DeviceInfo throws error", async () => {
43
- Object.defineProperty(window, "DeviceInfo", {
49
+ Object.defineProperty(window, "Capacitor", {
44
50
  value: {
45
- getAndroidId: vi
46
- .fn()
47
- .mockRejectedValue(new Error("DeviceInfo error")),
51
+ Plugins: {
52
+ DeviceInfo: {
53
+ getAndroidId: vi
54
+ .fn()
55
+ .mockRejectedValue(
56
+ new Error("DeviceInfo error")
57
+ ),
58
+ },
59
+ },
48
60
  },
49
61
  writable: true,
50
62
  configurable: true,
@@ -55,9 +67,15 @@ describe("getDeviceId", () => {
55
67
  })
56
68
 
57
69
  it("returns null when DeviceInfo returns empty", async () => {
58
- Object.defineProperty(window, "DeviceInfo", {
70
+ Object.defineProperty(window, "Capacitor", {
59
71
  value: {
60
- getAndroidId: vi.fn().mockResolvedValue({ androidId: "" }),
72
+ Plugins: {
73
+ DeviceInfo: {
74
+ getAndroidId: vi
75
+ .fn()
76
+ .mockResolvedValue({ androidId: "" }),
77
+ },
78
+ },
61
79
  },
62
80
  writable: true,
63
81
  configurable: true,
@@ -148,11 +166,25 @@ describe("getDeviceId", () => {
148
166
  })
149
167
 
150
168
  describe("MOBILE platform", () => {
151
- it("returns device ID from NativeBridge when available", async () => {
152
- const mockDeviceId = "native-device-123"
153
- Object.defineProperty(window, "NativeBridge", {
169
+ it("returns device ID from iosAppContext when available", async () => {
170
+ const mockDeviceId = "ios-device-123"
171
+ Object.defineProperty(window, "iosAppContext", {
172
+ value: {
173
+ phoneDeviceId: mockDeviceId,
174
+ },
175
+ writable: true,
176
+ configurable: true,
177
+ })
178
+
179
+ const result = await getDeviceId("MOBILE")
180
+ expect(result).toBe(mockDeviceId)
181
+ })
182
+
183
+ it("returns device ID from androidAppContext when available", async () => {
184
+ const mockDeviceId = "android-device-123"
185
+ Object.defineProperty(window, "androidAppContext", {
154
186
  value: {
155
- getDeviceId: vi.fn().mockResolvedValue(mockDeviceId),
187
+ phoneDeviceId: mockDeviceId,
156
188
  },
157
189
  writable: true,
158
190
  configurable: true,
@@ -162,12 +194,35 @@ describe("getDeviceId", () => {
162
194
  expect(result).toBe(mockDeviceId)
163
195
  })
164
196
 
165
- it("returns null when NativeBridge throws error", async () => {
166
- Object.defineProperty(window, "NativeBridge", {
197
+ it("prefers iosAppContext over androidAppContext", async () => {
198
+ Object.defineProperty(window, "iosAppContext", {
199
+ value: {
200
+ phoneDeviceId: "ios-id",
201
+ },
202
+ writable: true,
203
+ configurable: true,
204
+ })
205
+ Object.defineProperty(window, "androidAppContext", {
206
+ value: {
207
+ phoneDeviceId: "android-id",
208
+ },
209
+ writable: true,
210
+ configurable: true,
211
+ })
212
+
213
+ const result = await getDeviceId("MOBILE")
214
+ expect(result).toBe("ios-id")
215
+ })
216
+
217
+ it("returns null when no app context is available", async () => {
218
+ const result = await getDeviceId("MOBILE")
219
+ expect(result).toBeNull()
220
+ })
221
+
222
+ it("returns null when phoneDeviceId is empty", async () => {
223
+ Object.defineProperty(window, "iosAppContext", {
167
224
  value: {
168
- getDeviceId: vi
169
- .fn()
170
- .mockRejectedValue(new Error("Native error")),
225
+ phoneDeviceId: "",
171
226
  },
172
227
  writable: true,
173
228
  configurable: true,
@@ -7,12 +7,12 @@ import { defaultLogger, type Logger } from "./logger"
7
7
  *
8
8
  * Platform handling:
9
9
  * - FireTV: DeviceInfo (Capacitor plugin)
10
- * - Android/iOS: NativeBridge
10
+ * - Android/iOS: window.iosAppContext / window.androidAppContext (phoneDeviceId)
11
11
  * - Samsung TV: webapis
12
12
  * - LG TV: webOS Luna service
13
13
  * - Web: localStorage (with generated UUID fallback)
14
14
  *
15
- * @param platform - Platform identifier ('FIRE_TV', 'SAMSUNG_TV', 'LG_TV', 'MOBILE', 'WEB')
15
+ * @param platform - Platform identifier ('FIRE_TV', 'SAMSUNG_TV', 'LG_TV', 'ANDROID_MOBILE', 'IOS_MOBILE', 'WEB')
16
16
  * @param logger - Optional logger for error reporting. Defaults to defaultLogger.
17
17
  * @returns A promise that resolves to a unique device identifier string, or null if retrieval fails
18
18
  *
@@ -36,7 +36,9 @@ export async function getDeviceId(
36
36
  return await getSamsungDeviceId(logger)
37
37
  case "LG_TV":
38
38
  return await getLGDeviceId(logger)
39
- case "MOBILE": // iOS and Android
39
+ case "ANDROID_MOBILE":
40
+ case "IOS_MOBILE":
41
+ case "MOBILE": // Legacy/Generic
40
42
  return await getMobileDeviceId(logger)
41
43
  case "WEB":
42
44
  return getLocalStorageDeviceId()
@@ -84,24 +86,40 @@ async function getFireTVDeviceId(logger: Logger): Promise<string | null> {
84
86
  }
85
87
 
86
88
  /**
87
- * Attempt to get device ID from Native Bridge (Android/iOS)
89
+ * Attempt to get device ID from mobile app context (Android/iOS)
90
+ *
91
+ * iOS exposes phoneDeviceId via window.iosAppContext
92
+ * Android exposes phoneDeviceId via window.androidAppContext (also check window.top for iframes)
88
93
  */
89
- async function getMobileDeviceId(logger: Logger): Promise<string | null> {
94
+ function getMobileDeviceId(logger: Logger): string | null {
95
+ // Try iOS first
96
+ const iosId = (window as ExtendedWindow).iosAppContext?.phoneDeviceId
97
+ if (iosId && iosId.trim()) {
98
+ return iosId
99
+ }
100
+
101
+ // Try Android (check both window and window.top for iframe scenarios)
102
+ const androidContext = (window as ExtendedWindow).androidAppContext
90
103
  try {
91
- if (!window.NativeBridge?.getDeviceId) {
92
- return null
104
+ const androidId =
105
+ androidContext?.phoneDeviceId ??
106
+ (window.top as ExtendedWindow | null)?.androidAppContext
107
+ ?.phoneDeviceId
108
+ if (androidId && androidId.trim()) {
109
+ return androidId
93
110
  }
94
-
95
- const id = await window.NativeBridge.getDeviceId()
96
- if (id && id.trim()) {
97
- return id
98
- } else {
99
- logger.error("NativeBridge.getDeviceId returned empty")
111
+ } catch {
112
+ // window.top access can throw if cross-origin
113
+ const androidId = androidContext?.phoneDeviceId
114
+ if (androidId && androidId.trim()) {
115
+ return androidId
100
116
  }
101
- } catch (error) {
102
- const message = "NativeBridge.getDeviceId failed:"
103
- logger.error(message, { error })
104
117
  }
118
+
119
+ logger.error("[VWR:MobileDeviceId] phoneDeviceId not found", {
120
+ iosAppContext: !!(window as ExtendedWindow).iosAppContext,
121
+ androidAppContext: !!androidContext,
122
+ })
105
123
  return null
106
124
  }
107
125
 
@@ -209,18 +227,25 @@ interface LGDeviceIdResponse {
209
227
  }>
210
228
  }
211
229
 
230
+ /**
231
+ * Extended window interface for mobile app context
232
+ * Matches the types from @volley/sdk ExtendedWindow.ts
233
+ */
234
+ interface ExtendedWindow extends Window {
235
+ iosAppContext?: {
236
+ phoneDeviceId: string
237
+ }
238
+ androidAppContext?: {
239
+ phoneDeviceId: string
240
+ }
241
+ }
242
+
212
243
  // Type augmentation for platform-specific APIs
213
244
  // Note: Capacitor types are declared in getShellVersion.ts with index signatures
214
245
  // to allow extensibility. Additional methods used here are accessed via
215
246
  // the index signatures [key: string]: any
216
247
  declare global {
217
248
  interface Window {
218
- DeviceInfo?: {
219
- getAndroidId: () => Promise<{ androidId: string }>
220
- }
221
- NativeBridge?: {
222
- getDeviceId: () => Promise<string>
223
- }
224
249
  webapis?: {
225
250
  productinfo?: {
226
251
  getDuid: () => string