@volley/vwr-loader 1.0.0-alpha.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 (54) hide show
  1. package/README.md +205 -0
  2. package/dist/amplitudeFlagFetcher.d.ts +23 -0
  3. package/dist/amplitudeFlagFetcher.d.ts.map +1 -0
  4. package/dist/amplitudeFlagFetcher.js +60 -0
  5. package/dist/amplitudeFlagFetcher.js.map +1 -0
  6. package/dist/cli.js +177 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/envDefaults.d.ts +9 -0
  9. package/dist/envDefaults.d.ts.map +1 -0
  10. package/dist/envDefaults.js +36 -0
  11. package/dist/envDefaults.js.map +1 -0
  12. package/dist/getDeviceId.d.ts +65 -0
  13. package/dist/getDeviceId.d.ts.map +1 -0
  14. package/dist/getDeviceId.js +196 -0
  15. package/dist/getDeviceId.js.map +1 -0
  16. package/dist/getShellVersion.d.ts +34 -0
  17. package/dist/getShellVersion.d.ts.map +1 -0
  18. package/dist/getShellVersion.js +84 -0
  19. package/dist/getShellVersion.js.map +1 -0
  20. package/dist/index.d.ts +9 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.html +25 -0
  23. package/dist/index.js +6 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/loadVwr.d.ts +19 -0
  26. package/dist/loadVwr.d.ts.map +1 -0
  27. package/dist/loadVwr.js +104 -0
  28. package/dist/loadVwr.js.map +1 -0
  29. package/dist/logger.d.ts +7 -0
  30. package/dist/logger.d.ts.map +1 -0
  31. package/dist/logger.js +6 -0
  32. package/dist/logger.js.map +1 -0
  33. package/dist/main.js +2 -0
  34. package/dist/main.js.map +1 -0
  35. package/dist/vwrConfig.d.ts +19 -0
  36. package/dist/vwrConfig.d.ts.map +1 -0
  37. package/dist/vwrConfig.js +172 -0
  38. package/dist/vwrConfig.js.map +1 -0
  39. package/package.json +54 -0
  40. package/src/amplitudeFlagFetcher.test.ts +209 -0
  41. package/src/amplitudeFlagFetcher.ts +88 -0
  42. package/src/envDefaults.ts +45 -0
  43. package/src/getDeviceId.test.ts +237 -0
  44. package/src/getDeviceId.ts +243 -0
  45. package/src/getShellVersion.test.ts +278 -0
  46. package/src/getShellVersion.ts +114 -0
  47. package/src/index.html +25 -0
  48. package/src/index.ts +8 -0
  49. package/src/loadVwr.ts +126 -0
  50. package/src/logger.ts +14 -0
  51. package/src/main.ts +26 -0
  52. package/src/vite-env.d.ts +15 -0
  53. package/src/vwrConfig.test.ts +316 -0
  54. package/src/vwrConfig.ts +293 -0
@@ -0,0 +1,237 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
2
+
3
+ import { getDeviceId } from "./getDeviceId"
4
+
5
+ describe("getDeviceId", () => {
6
+ beforeEach(() => {
7
+ vi.clearAllMocks()
8
+ // Clean up window properties
9
+ delete (window as any).DeviceInfo
10
+ delete (window as any).NativeBridge
11
+ delete (window as any).webapis
12
+ delete (window as any).webOSDev
13
+ delete (window as any).webOS
14
+ })
15
+
16
+ afterEach(() => {
17
+ vi.restoreAllMocks()
18
+ delete (window as any).DeviceInfo
19
+ delete (window as any).NativeBridge
20
+ delete (window as any).webapis
21
+ delete (window as any).webOSDev
22
+ delete (window as any).webOS
23
+ })
24
+
25
+ describe("FIRE_TV platform", () => {
26
+ it("returns device ID from DeviceInfo.getAndroidId when available", async () => {
27
+ const mockAndroidId = "firetv-android-id-123"
28
+ Object.defineProperty(window, "DeviceInfo", {
29
+ value: {
30
+ getAndroidId: vi
31
+ .fn()
32
+ .mockResolvedValue({ androidId: mockAndroidId }),
33
+ },
34
+ writable: true,
35
+ configurable: true,
36
+ })
37
+
38
+ const result = await getDeviceId("FIRE_TV")
39
+ expect(result).toBe(mockAndroidId)
40
+ })
41
+
42
+ it("returns null when DeviceInfo throws error", async () => {
43
+ Object.defineProperty(window, "DeviceInfo", {
44
+ value: {
45
+ getAndroidId: vi
46
+ .fn()
47
+ .mockRejectedValue(new Error("DeviceInfo error")),
48
+ },
49
+ writable: true,
50
+ configurable: true,
51
+ })
52
+
53
+ const result = await getDeviceId("FIRE_TV")
54
+ expect(result).toBeNull()
55
+ })
56
+
57
+ it("returns null when DeviceInfo returns empty", async () => {
58
+ Object.defineProperty(window, "DeviceInfo", {
59
+ value: {
60
+ getAndroidId: vi.fn().mockResolvedValue({ androidId: "" }),
61
+ },
62
+ writable: true,
63
+ configurable: true,
64
+ })
65
+ const result = await getDeviceId("FIRE_TV")
66
+ expect(result).toBeNull()
67
+ })
68
+ })
69
+
70
+ describe("SAMSUNG_TV platform", () => {
71
+ it("returns device ID from Samsung webapis when available", async () => {
72
+ const mockDeviceId = "samsung-duid-123"
73
+ Object.defineProperty(window, "webapis", {
74
+ value: {
75
+ productinfo: {
76
+ getDuid: vi.fn().mockReturnValue(mockDeviceId),
77
+ },
78
+ },
79
+ writable: true,
80
+ configurable: true,
81
+ })
82
+
83
+ const result = await getDeviceId("SAMSUNG_TV")
84
+ expect(result).toBe(mockDeviceId)
85
+ })
86
+
87
+ it("returns null when webapis throws error", async () => {
88
+ Object.defineProperty(window, "webapis", {
89
+ value: {
90
+ productinfo: {
91
+ getDuid: vi.fn().mockImplementation(() => {
92
+ throw new Error("Samsung API error")
93
+ }),
94
+ },
95
+ },
96
+ writable: true,
97
+ configurable: true,
98
+ })
99
+
100
+ const result = await getDeviceId("SAMSUNG_TV")
101
+ expect(result).toBeNull()
102
+ })
103
+ })
104
+
105
+ describe("LG_TV platform", () => {
106
+ it("returns device ID from LG webOS.service.request when available", async () => {
107
+ const mockDeviceId = "lg-udid-123"
108
+ const mockRequest = vi.fn().mockImplementation((_uri, options) => {
109
+ options.onSuccess?.({
110
+ idList: [{ idValue: mockDeviceId }],
111
+ })
112
+ return { cancel: vi.fn() }
113
+ })
114
+
115
+ Object.defineProperty(window, "webOS", {
116
+ value: {
117
+ service: {
118
+ request: mockRequest,
119
+ },
120
+ },
121
+ writable: true,
122
+ configurable: true,
123
+ })
124
+
125
+ const result = await getDeviceId("LG_TV")
126
+ expect(result).toBe(mockDeviceId)
127
+ })
128
+
129
+ it("returns null when webOS.service.request fails", async () => {
130
+ const mockRequest = vi.fn().mockImplementation((_uri, options) => {
131
+ options.onFailure?.({ errorText: "LG API error" })
132
+ return { cancel: vi.fn() }
133
+ })
134
+
135
+ Object.defineProperty(window, "webOS", {
136
+ value: {
137
+ service: {
138
+ request: mockRequest,
139
+ },
140
+ },
141
+ writable: true,
142
+ configurable: true,
143
+ })
144
+
145
+ const result = await getDeviceId("LG_TV")
146
+ expect(result).toBeNull()
147
+ })
148
+ })
149
+
150
+ describe("MOBILE platform", () => {
151
+ it("returns device ID from NativeBridge when available", async () => {
152
+ const mockDeviceId = "native-device-123"
153
+ Object.defineProperty(window, "NativeBridge", {
154
+ value: {
155
+ getDeviceId: vi.fn().mockResolvedValue(mockDeviceId),
156
+ },
157
+ writable: true,
158
+ configurable: true,
159
+ })
160
+
161
+ const result = await getDeviceId("MOBILE")
162
+ expect(result).toBe(mockDeviceId)
163
+ })
164
+
165
+ it("returns null when NativeBridge throws error", async () => {
166
+ Object.defineProperty(window, "NativeBridge", {
167
+ value: {
168
+ getDeviceId: vi
169
+ .fn()
170
+ .mockRejectedValue(new Error("Native error")),
171
+ },
172
+ writable: true,
173
+ configurable: true,
174
+ })
175
+
176
+ const result = await getDeviceId("MOBILE")
177
+ expect(result).toBeNull()
178
+ })
179
+ })
180
+
181
+ describe("WEB platform", () => {
182
+ beforeEach(() => {
183
+ localStorage.clear()
184
+ })
185
+
186
+ it("generates and stores new device ID when none exists", async () => {
187
+ const mockUUID = "web-uuid-123"
188
+ Object.defineProperty(window.crypto, "randomUUID", {
189
+ value: vi.fn().mockReturnValue(mockUUID),
190
+ writable: true,
191
+ configurable: true,
192
+ })
193
+
194
+ const result = await getDeviceId("WEB")
195
+
196
+ expect(result).toBe(mockUUID)
197
+ expect(localStorage.getItem("volley_device_id")).toBe(mockUUID)
198
+ })
199
+
200
+ it("retrieves existing device ID from localStorage", async () => {
201
+ const existingId = "existing-web-id"
202
+ localStorage.setItem("volley_device_id", existingId)
203
+
204
+ const result = await getDeviceId("WEB")
205
+
206
+ expect(result).toBe(existingId)
207
+ })
208
+ })
209
+
210
+ describe("Unknown platform", () => {
211
+ it("returns null for unknown platform", async () => {
212
+ const result = await getDeviceId("UNKNOWN_PLATFORM")
213
+ expect(result).toBeNull()
214
+ })
215
+ })
216
+
217
+ describe("Case insensitivity", () => {
218
+ it("is case-insensitive for platform parameter", async () => {
219
+ const mockSamsungId = "samsung-case-test"
220
+ Object.defineProperty(window, "webapis", {
221
+ value: {
222
+ productinfo: {
223
+ getDuid: vi.fn().mockReturnValue(mockSamsungId),
224
+ },
225
+ },
226
+ writable: true,
227
+ configurable: true,
228
+ })
229
+
230
+ const result1 = await getDeviceId("samsung_tv")
231
+ const result2 = await getDeviceId("SAMSUNG_TV")
232
+
233
+ expect(result1).toBe(mockSamsungId)
234
+ expect(result2).toBe(mockSamsungId)
235
+ })
236
+ })
237
+ })
@@ -0,0 +1,243 @@
1
+ import { defaultLogger, type Logger } from "./logger"
2
+
3
+ /**
4
+ * Get a unique device identifier for the current platform.
5
+ *
6
+ * This function attempts to retrieve a device ID from platform-specific APIs.
7
+ *
8
+ * Platform handling:
9
+ * - FireTV: DeviceInfo (Capacitor plugin)
10
+ * - Android/iOS: NativeBridge
11
+ * - Samsung TV: webapis
12
+ * - LG TV: webOS Luna service
13
+ * - Web: localStorage (with generated UUID fallback)
14
+ *
15
+ * @param platform - Platform identifier ('FIRE_TV', 'SAMSUNG_TV', 'LG_TV', 'ANDROID_MOBILE', 'IOS_MOBILE', 'WEB')
16
+ * @param logger - Optional logger for error reporting. Defaults to defaultLogger.
17
+ * @returns A promise that resolves to a unique device identifier string, or null if retrieval fails
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const deviceId = await getDeviceId('SAMSUNG_TV', logger)
22
+ * if (!deviceId) {
23
+ * // Handle error
24
+ * }
25
+ * ```
26
+ */
27
+ export async function getDeviceId(
28
+ platform: string,
29
+ logger: Logger = defaultLogger
30
+ ): Promise<string | null> {
31
+ const normalizedPlatform = platform.toUpperCase()
32
+ switch (normalizedPlatform) {
33
+ case "FIRE_TV":
34
+ return await getFireTVDeviceId(logger)
35
+ case "SAMSUNG_TV":
36
+ return await getSamsungDeviceId(logger)
37
+ case "LG_TV":
38
+ return await getLGDeviceId(logger)
39
+ case "ANDROID_MOBILE":
40
+ case "IOS_MOBILE":
41
+ case "MOBILE": // Legacy/Generic
42
+ return await getMobileDeviceId(logger)
43
+ case "WEB":
44
+ return getLocalStorageDeviceId()
45
+ default:
46
+ const message = `[getDeviceId] Unknown platform: ${platform}`
47
+ logger.error(message)
48
+ return null
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Get or create device ID from localStorage (for Web platform)
54
+ */
55
+ function getLocalStorageDeviceId(): string {
56
+ let deviceId = localStorage.getItem("volley_device_id")
57
+ if (!deviceId) {
58
+ deviceId = crypto.randomUUID()
59
+ localStorage.setItem("volley_device_id", deviceId)
60
+ }
61
+ return deviceId
62
+ }
63
+
64
+ /**
65
+ * Attempt to get device ID from Capacitor DeviceInfo plugin (FireTV)
66
+ */
67
+ async function getFireTVDeviceId(logger: Logger): Promise<string | null> {
68
+ try {
69
+ if (!window.DeviceInfo?.getAndroidId) {
70
+ return null
71
+ }
72
+
73
+ const result = await window.DeviceInfo.getAndroidId()
74
+ if (result?.androidId && result.androidId.trim()) {
75
+ return result.androidId
76
+ } else {
77
+ logger.error("DeviceInfo.getAndroidId returned empty")
78
+ }
79
+ } catch (error) {
80
+ const message = "DeviceInfo.getAndroidId failed:"
81
+ logger.error(message, { error })
82
+ }
83
+ return null
84
+ }
85
+
86
+ /**
87
+ * Attempt to get device ID from Native Bridge (Android/iOS)
88
+ */
89
+ async function getMobileDeviceId(logger: Logger): Promise<string | null> {
90
+ try {
91
+ if (!window.NativeBridge?.getDeviceId) {
92
+ return null
93
+ }
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")
100
+ }
101
+ } catch (error) {
102
+ const message = "NativeBridge.getDeviceId failed:"
103
+ logger.error(message, { error })
104
+ }
105
+ return null
106
+ }
107
+
108
+ /**
109
+ * Attempt to get device ID from Samsung TV webapis
110
+ */
111
+ async function getSamsungDeviceId(logger: Logger): Promise<string | null> {
112
+ try {
113
+ if (!window.webapis?.productinfo?.getDuid) {
114
+ return null
115
+ }
116
+
117
+ const id = window.webapis.productinfo.getDuid()
118
+ if (id && id.trim()) {
119
+ return id
120
+ } else {
121
+ logger.error("Samsung webapis.getDuid returned empty")
122
+ }
123
+ } catch (error) {
124
+ const message = "Samsung webapis.getDuid failed:"
125
+ logger.error(message, { error })
126
+ }
127
+ return null
128
+ }
129
+
130
+ /**
131
+ * Attempt to get device ID from LG TV using Luna Service.
132
+ *
133
+ * This implementation is copied from PSDK's LGTVDeviceInfo to ensure
134
+ * the shell and SDK return the exact same device ID.
135
+ *
136
+ * TODO: In the future, PSDK should import this function from vwr-loader
137
+ * to maintain a single source of truth for LG device ID retrieval.
138
+ */
139
+ const LG_LUNA_SERVICE_TIMEOUT = 5000
140
+
141
+ async function getLGDeviceId(logger: Logger): Promise<string | null> {
142
+ if (!window.webOS?.service?.request) {
143
+ const message = "LG webOS service request not available"
144
+ logger.error(message)
145
+ return null
146
+ }
147
+
148
+ return new Promise((resolve) => {
149
+ let timeoutId: ReturnType<typeof setTimeout> | undefined
150
+ let request: { cancel: () => void } | undefined
151
+ let resolved = false
152
+
153
+ const cleanup = () => {
154
+ if (timeoutId) {
155
+ clearTimeout(timeoutId)
156
+ timeoutId = undefined
157
+ }
158
+ request?.cancel()
159
+ }
160
+
161
+ const finish = (value: string | null) => {
162
+ if (resolved) return
163
+ resolved = true
164
+ cleanup()
165
+ resolve(value)
166
+ }
167
+
168
+ timeoutId = setTimeout(() => {
169
+ const message = "LG Luna service timed out"
170
+ logger.error(message)
171
+ finish(null)
172
+ }, LG_LUNA_SERVICE_TIMEOUT)
173
+
174
+ try {
175
+ request = window.webOS?.service?.request(
176
+ "luna://com.webos.service.sm",
177
+ {
178
+ method: "deviceid/getIDs",
179
+ parameters: { idType: ["LGUDID"] },
180
+ onSuccess: (response: LGDeviceIdResponse) => {
181
+ const deviceId = response.idList?.[0]?.idValue
182
+ if (!deviceId) {
183
+ const message =
184
+ "LG Luna service returned no device ID"
185
+ logger.error(message)
186
+ }
187
+ finish(deviceId || null)
188
+ },
189
+ onFailure: (error) => {
190
+ const message = "LG Luna service failed:"
191
+ logger.error(message, { error })
192
+ finish(null)
193
+ },
194
+ subscribe: false,
195
+ }
196
+ )
197
+ } catch (error) {
198
+ const message = "LG Luna service request threw error:"
199
+ logger.error(message, { error })
200
+ finish(null)
201
+ }
202
+ })
203
+ }
204
+
205
+ // Type definitions for LG Luna service response
206
+ interface LGDeviceIdResponse {
207
+ idList?: Array<{
208
+ idValue?: string
209
+ }>
210
+ }
211
+
212
+ // Type augmentation for platform-specific APIs
213
+ declare global {
214
+ interface Window {
215
+ DeviceInfo?: {
216
+ getAndroidId: () => Promise<{ androidId: string }>
217
+ }
218
+ NativeBridge?: {
219
+ getDeviceId: () => Promise<string>
220
+ }
221
+ webapis?: {
222
+ productinfo?: {
223
+ getDuid: () => string
224
+ }
225
+ }
226
+ webOS?: {
227
+ service?: {
228
+ request: (
229
+ uri: string,
230
+ options: {
231
+ method?: string
232
+ parameters?: Record<string, unknown>
233
+ onSuccess?: (response: LGDeviceIdResponse) => void
234
+ onFailure?: (error: { errorText?: string }) => void
235
+ subscribe?: boolean
236
+ }
237
+ ) => {
238
+ cancel: () => void
239
+ }
240
+ }
241
+ }
242
+ }
243
+ }