@volley/vwr-loader 1.1.0 → 1.3.0

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 (115) hide show
  1. package/README.md +7 -8
  2. package/dist/__mocks__/@datadog/browser-logs.d.ts +58 -0
  3. package/dist/__mocks__/@datadog/browser-logs.d.ts.map +1 -0
  4. package/dist/__mocks__/@datadog/browser-logs.js +34 -0
  5. package/dist/__mocks__/@datadog/browser-logs.js.map +1 -0
  6. package/dist/__mocks__/@datadog/browser-rum.d.ts +25 -0
  7. package/dist/__mocks__/@datadog/browser-rum.d.ts.map +1 -0
  8. package/dist/__mocks__/@datadog/browser-rum.js +25 -0
  9. package/dist/__mocks__/@datadog/browser-rum.js.map +1 -0
  10. package/dist/amplitudeFlagFetcher.d.ts +8 -3
  11. package/dist/amplitudeFlagFetcher.d.ts.map +1 -1
  12. package/dist/amplitudeFlagFetcher.js +14 -12
  13. package/dist/amplitudeFlagFetcher.js.map +1 -1
  14. package/dist/assets/intersection-observer-DAXvjMTZ.js +2 -0
  15. package/dist/assets/intersection-observer-DAXvjMTZ.js.map +1 -0
  16. package/dist/assets/profiler-BRmTNL9s.js +2 -0
  17. package/dist/assets/profiler-BRmTNL9s.js.map +1 -0
  18. package/dist/assets/resize-observer-DqyxKk7V.js +2 -0
  19. package/dist/assets/resize-observer-DqyxKk7V.js.map +1 -0
  20. package/dist/assets/startRecording-CuFdVhxx.js +3 -0
  21. package/dist/assets/startRecording-CuFdVhxx.js.map +1 -0
  22. package/dist/cli.js +42 -8
  23. package/dist/cli.js.map +1 -1
  24. package/dist/datadog.d.ts +19 -0
  25. package/dist/datadog.d.ts.map +1 -0
  26. package/dist/datadog.js +39 -0
  27. package/dist/datadog.js.map +1 -0
  28. package/dist/envDefaults.d.ts.map +1 -1
  29. package/dist/envDefaults.js +27 -2
  30. package/dist/envDefaults.js.map +1 -1
  31. package/dist/errors/InitializationError.d.ts +74 -0
  32. package/dist/errors/InitializationError.d.ts.map +1 -0
  33. package/dist/errors/InitializationError.js +77 -0
  34. package/dist/errors/InitializationError.js.map +1 -0
  35. package/dist/errors/ensureError.d.ts +2 -0
  36. package/dist/errors/ensureError.d.ts.map +1 -0
  37. package/dist/errors/ensureError.js +7 -0
  38. package/dist/errors/ensureError.js.map +1 -0
  39. package/dist/errors/index.d.ts +12 -0
  40. package/dist/errors/index.d.ts.map +1 -0
  41. package/dist/errors/index.js +10 -0
  42. package/dist/errors/index.js.map +1 -0
  43. package/dist/exitHandler.d.ts +32 -0
  44. package/dist/exitHandler.d.ts.map +1 -0
  45. package/dist/exitHandler.js +113 -0
  46. package/dist/exitHandler.js.map +1 -0
  47. package/dist/getDeviceId.d.ts +1 -2
  48. package/dist/getDeviceId.d.ts.map +1 -1
  49. package/dist/getDeviceId.js +17 -15
  50. package/dist/getDeviceId.js.map +1 -1
  51. package/dist/getEnvironment.d.ts +1 -2
  52. package/dist/getEnvironment.d.ts.map +1 -1
  53. package/dist/getEnvironment.js +4 -4
  54. package/dist/getEnvironment.js.map +1 -1
  55. package/dist/getShellVersion.d.ts +1 -2
  56. package/dist/getShellVersion.d.ts.map +1 -1
  57. package/dist/getShellVersion.js +4 -4
  58. package/dist/getShellVersion.js.map +1 -1
  59. package/dist/index.d.ts +0 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/loadVwr.d.ts +4 -4
  63. package/dist/loadVwr.d.ts.map +1 -1
  64. package/dist/loadVwr.js +151 -37
  65. package/dist/loadVwr.js.map +1 -1
  66. package/dist/logger.d.ts +11 -6
  67. package/dist/logger.d.ts.map +1 -1
  68. package/dist/logger.js +107 -4
  69. package/dist/logger.js.map +1 -1
  70. package/dist/main.js +11 -1
  71. package/dist/main.js.map +1 -1
  72. package/dist/polyfills.d.ts +29 -0
  73. package/dist/polyfills.d.ts.map +1 -0
  74. package/dist/polyfills.js +32 -0
  75. package/dist/polyfills.js.map +1 -0
  76. package/dist/test-setup.d.ts +2 -0
  77. package/dist/test-setup.d.ts.map +1 -0
  78. package/dist/test-setup.js +5 -0
  79. package/dist/test-setup.js.map +1 -0
  80. package/dist/types.d.ts +4 -0
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/vwrConfig.d.ts +14 -2
  83. package/dist/vwrConfig.d.ts.map +1 -1
  84. package/dist/vwrConfig.js +74 -29
  85. package/dist/vwrConfig.js.map +1 -1
  86. package/package.json +61 -53
  87. package/src/__mocks__/@datadog/browser-logs.ts +35 -0
  88. package/src/__mocks__/@datadog/browser-rum.ts +27 -0
  89. package/src/amplitudeFlagFetcher.test.ts +28 -34
  90. package/src/amplitudeFlagFetcher.ts +22 -14
  91. package/src/datadog.ts +55 -0
  92. package/src/envDefaults.ts +32 -3
  93. package/src/errors/InitializationError.ts +105 -0
  94. package/src/errors/ensureError.ts +6 -0
  95. package/src/errors/index.ts +16 -0
  96. package/src/exitHandler.test.ts +179 -0
  97. package/src/exitHandler.ts +125 -0
  98. package/src/getDeviceId.test.ts +4 -5
  99. package/src/getDeviceId.ts +19 -18
  100. package/src/getEnvironment.test.ts +44 -106
  101. package/src/getEnvironment.ts +4 -5
  102. package/src/getShellVersion.test.ts +12 -6
  103. package/src/getShellVersion.ts +4 -7
  104. package/src/index.ts +0 -1
  105. package/src/loadVwr.test.ts +394 -0
  106. package/src/loadVwr.ts +243 -58
  107. package/src/logger.ts +118 -11
  108. package/src/main.test.ts +157 -0
  109. package/src/main.ts +99 -11
  110. package/src/polyfills.ts +32 -0
  111. package/src/test-setup.ts +5 -0
  112. package/src/types.ts +4 -0
  113. package/src/vite-env.d.ts +15 -1
  114. package/src/vwrConfig.test.ts +104 -34
  115. package/src/vwrConfig.ts +115 -37
package/src/main.ts CHANGED
@@ -1,25 +1,113 @@
1
+ // Load polyfills FIRST before any other code
2
+ import "./polyfills"
3
+
4
+ import { initRumGlobal } from "./datadog"
5
+ import { InitializationError, isVWRInitializationError } from "./errors"
6
+ import { exitIfBackNavigation, setupLGBackButtonHandler } from "./exitHandler"
1
7
  import { loadVwr } from "./loadVwr"
8
+ import { logger } from "./logger"
2
9
 
3
10
  const PLATFORM = import.meta.env.VITE_PLATFORM
4
11
  const HUB_URL = import.meta.env.VITE_HUB_URL
12
+ const ENVIRONMENT = import.meta.env.VITE_ENVIRONMENT as string | undefined
13
+ const SHELL_VERSION = import.meta.env.VITE_SHELL_VERSION as string | undefined
14
+
15
+ /**
16
+ * Handle initialization errors with structured logging and RUM tracking.
17
+ * Logger automatically sends errors to both Datadog Logs and RUM Error Tracking.
18
+ */
19
+ function handleInitializationError(error: unknown) {
20
+ if (
21
+ error instanceof InitializationError ||
22
+ isVWRInitializationError(error)
23
+ ) {
24
+ logger.error(
25
+ {
26
+ context: error.getContext(),
27
+ error_type: error.name,
28
+ error: error,
29
+ },
30
+ `[Shell] FALLBACK TO HUB - ${error.message}`
31
+ )
32
+ } else {
33
+ // Unknown error - normalize to Error instance
34
+ const normalizedError =
35
+ error instanceof Error ? error : new Error(String(error))
36
+
37
+ logger.error(
38
+ {
39
+ reason: normalizedError.message,
40
+ error_type: normalizedError.name,
41
+ error: normalizedError,
42
+ },
43
+ "[Shell] FALLBACK TO HUB - Init failed (unknown error)"
44
+ )
45
+ }
46
+ }
5
47
 
6
48
  async function init() {
49
+ // Exit app if user pressed back - safety net for Samsung/LG TVs
50
+ // when VWR fails to load and user navigates back to loader
51
+ exitIfBackNavigation(PLATFORM)
52
+
53
+ // Initialize Datadog RUM early (if credentials provided)
54
+ // For mobile webviews with WebViewTracking.enable(), events are
55
+ // automatically forwarded to the mobile app's RUM session
56
+ logger.info("VWR loader starting init flow")
57
+
58
+ const rum = initRumGlobal({
59
+ env: ENVIRONMENT ?? "unknown",
60
+ version: SHELL_VERSION,
61
+ service: "vwr-loader",
62
+ })
63
+
64
+ // Set up LG back button handler while loader is active.
65
+ // On LG with disableBackHistoryAPI: true, the platform doesn't handle back automatically.
66
+ // Once VWR loads, it will take over with PSDK's custom exit modal.
67
+ let cleanupLGBackHandler: (() => void) | undefined
68
+ if (PLATFORM === "LG_TV") {
69
+ cleanupLGBackHandler = setupLGBackButtonHandler()
70
+ }
71
+
7
72
  try {
8
- await loadVwr()
73
+ await loadVwr(rum)
74
+ // VWR loaded successfully - remove our back handler, PSDK takes over
75
+ cleanupLGBackHandler?.()
9
76
  } catch (error) {
77
+ // Keep LG back handler active during fallback - it will handle exit
78
+ // if user presses back on the Hub page and navigates back here
10
79
  try {
11
- console.error("[Shell] ⚠️ FALLBACK TO HUB - Init failed:", error)
80
+ handleInitializationError(error)
81
+ } finally {
82
+ // Fallback to gameControllerUrl if it exists
83
+ // Otherwise fallback to HUB_URL with volley_platform param
84
+ let fallbackUrl: URL
85
+ const gameControllerUrl =
86
+ new URLSearchParams(window.location.search)
87
+ .get("gameControllerUrl")
88
+ ?.trim() || null
89
+
90
+ if (gameControllerUrl) {
91
+ fallbackUrl = new URL(gameControllerUrl)
12
92
 
13
- if ((window as any).DD_LOGS) {
14
- ;(window as any).DD_LOGS.logger.error("shell_init_failed", {
15
- error: (error as Error).message,
16
- platform: PLATFORM,
17
- })
93
+ logger.info(
94
+ {
95
+ gameControllerUrl,
96
+ },
97
+ "[Shell] Using gameControllerUrl as fallback"
98
+ )
99
+ } else {
100
+ fallbackUrl = new URL(HUB_URL)
101
+ fallbackUrl.searchParams.set("volley_platform", PLATFORM)
102
+
103
+ logger.info(
104
+ {
105
+ PLATFORM,
106
+ },
107
+ "[Shell] No gameControllerUrl, redirecting to Hub"
108
+ )
18
109
  }
19
- } finally {
20
- //Fallback, can't trust config exists
21
- const fallbackUrl = new URL(HUB_URL)
22
- fallbackUrl.searchParams.set("volley_platform", PLATFORM)
110
+
23
111
  window.location.href = fallbackUrl.toString()
24
112
  }
25
113
  }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Polyfills for Chrome 68+ compatibility (Samsung/LG TVs)
3
+ *
4
+ * Import this at the top of main.ts BEFORE any other code runs.
5
+ * Conservative approach: polyfill all features not available in Chrome 68.
6
+ *
7
+ * ECMAScript polyfills (via core-js):
8
+ * - globalThis (Chrome 71)
9
+ * - Array.prototype.flat/flatMap (Chrome 69)
10
+ * - Object.fromEntries (Chrome 73)
11
+ * - String.prototype.matchAll (Chrome 73)
12
+ * - Promise.allSettled (Chrome 76)
13
+ * - String.prototype.replaceAll (Chrome 85)
14
+ * - Array.prototype.at / String.prototype.at (Chrome 92)
15
+ *
16
+ * Web API polyfills:
17
+ * - focus-visible (Chrome 86) - critical for TV remote navigation
18
+ */
19
+
20
+ // ECMAScript polyfills via core-js
21
+ import "core-js/modules/es.global-this.js"
22
+ import "core-js/modules/es.array.flat.js"
23
+ import "core-js/modules/es.array.flat-map.js"
24
+ import "core-js/modules/es.object.from-entries.js"
25
+ import "core-js/modules/es.string.match-all.js"
26
+ import "core-js/modules/es.promise.all-settled.js"
27
+ import "core-js/modules/es.string.replace-all.js"
28
+ import "core-js/modules/es.array.at.js"
29
+ import "core-js/modules/es.string.at-alternative.js"
30
+ // Web API polyfills
31
+ // focus-visible - Critical for TV remote focus rings
32
+ import "focus-visible"
@@ -0,0 +1,5 @@
1
+ import { vi } from "vitest"
2
+
3
+ // Global mocks for Datadog browser SDKs
4
+ vi.mock("@datadog/browser-rum")
5
+ vi.mock("@datadog/browser-logs")
package/src/types.ts CHANGED
@@ -67,10 +67,14 @@ declare global {
67
67
  fetchAppInfo?: (
68
68
  callback: (appInfo?: { version?: string }) => void
69
69
  ) => void
70
+ platformBack?: () => void
70
71
  }
71
72
  tizen?: {
72
73
  application?: {
73
74
  getAppInfo: () => { version?: string }
75
+ getCurrentApplication: () => {
76
+ exit: () => void
77
+ }
74
78
  }
75
79
  }
76
80
  }
package/src/vite-env.d.ts CHANGED
@@ -1,16 +1,30 @@
1
+ /**
2
+ * Supported platform identifiers.
3
+ * These must be uppercase - invalid values will cause build-time TypeScript errors.
4
+ */
5
+ type Platform =
6
+ | "FIRE_TV"
7
+ | "SAMSUNG_TV"
8
+ | "LG_TV"
9
+ | "ANDROID_MOBILE"
10
+ | "IOS_MOBILE"
11
+ | "WEB"
12
+
1
13
  interface ImportMetaEnv {
2
14
  readonly VITE_HUB_URL: string
3
15
  readonly VITE_VWR_URL: string
4
16
  readonly VITE_LAUNCH_URL: string
5
17
  readonly VITE_AMPLITUDE_DEPLOYMENT_KEY: string
6
18
  readonly VITE_ENVIRONMENT: string
7
- readonly VITE_PLATFORM: string
19
+ readonly VITE_PLATFORM: Platform
8
20
  readonly VITE_SHELL_VERSION: string
9
21
  readonly VITE_CONFIG_URL: string
10
22
  readonly VITE_CONFIG_FILE: string
11
23
  readonly VITE_PLATFORM_API_URL: string
12
24
  readonly VITE_PLATFORM_AUTH_API_URL: string
13
25
  readonly VITE_TRUSTED_ORIGINS: string
26
+ readonly VITE_DATADOG_APPLICATION_ID: string
27
+ readonly VITE_DATADOG_CLIENT_TOKEN: string
14
28
  }
15
29
 
16
30
  interface ImportMeta {
@@ -1,3 +1,4 @@
1
+ import type { ILogger } from "@volley/logger"
1
2
  import { describe, expect, it, vi } from "vitest"
2
3
 
3
4
  // Stub environment to use "production" which maps to ENV_DEFAULTS["production"]
@@ -5,6 +6,14 @@ vi.stubEnv("VITE_ENVIRONMENT", "production")
5
6
 
6
7
  import type { VWRConfig, VWRConfigRequest } from "./vwrConfig"
7
8
 
9
+ const mockLogger = {
10
+ info: vi.fn(),
11
+ warn: vi.fn(),
12
+ error: vi.fn(),
13
+ debug: vi.fn(),
14
+ child: vi.fn(() => mockLogger),
15
+ } as unknown as ILogger
16
+
8
17
  describe("vwrConfig", async () => {
9
18
  const { getVWRConfig, validateConfig } = await import("./vwrConfig")
10
19
  describe("validateConfig", () => {
@@ -13,6 +22,8 @@ describe("vwrConfig", async () => {
13
22
  hubUrl: "https://hub.example.com",
14
23
  launchUrl: "https://launch.example.com",
15
24
  trustedDomains: ["example.com"],
25
+ platformApiUrl: "https://api.example.com",
26
+ platformAuthApiUrl: "https://auth.example.com",
16
27
  }
17
28
 
18
29
  it("returns false for empty vwrUrl", () => {
@@ -83,16 +94,31 @@ describe("vwrConfig", async () => {
83
94
 
84
95
  const fetchSpy = vi.spyOn(global, "fetch")
85
96
 
97
+ vi.spyOn(global, "Request").mockImplementation(
98
+ (_input: URL | RequestInfo, _init?: RequestInit) => {
99
+ return {} as Request
100
+ }
101
+ )
102
+
86
103
  it("returns default config when no configs are found", async () => {
87
- const config = await getVWRConfig(configRequest)
104
+ const result = await getVWRConfig(configRequest, mockLogger)
88
105
  // Defaults come from ENV_DEFAULTS["production"]
89
- expect(config.vwrUrl).toBe("https://vwr.volley.tv/v1/latest/vwr.js")
90
- expect(config.hubUrl).toBe("https://game-clients.volley.tv/hub")
91
- expect(config.launchUrl).toBeUndefined() // Optional, not set by default
92
- expect(config.trustedDomains).toEqual([
106
+ expect(result.config.vwrUrl).toBe(
107
+ "https://vwr.volley.tv/v1/latest/vwr.js"
108
+ )
109
+ expect(result.config.hubUrl).toBe(
110
+ "https://game-clients.volley.tv/hub"
111
+ )
112
+ expect(result.config.launchUrl).toBeUndefined() // Optional, not set by default
113
+ // trustedDomains includes: hubUrl origin, vwrUrl origin, plus trustedOrigins from env defaults
114
+ expect(result.config.trustedDomains).toEqual([
93
115
  "https://game-clients.volley.tv",
94
116
  "https://vwr.volley.tv",
117
+ "https://volley.tv",
118
+ "https://vl.ly",
119
+ "https://vly.gg",
95
120
  ])
121
+ expect(result.source).toBe("defaults")
96
122
  })
97
123
 
98
124
  it("returns local config when available", async () => {
@@ -152,19 +178,25 @@ describe("vwrConfig", async () => {
152
178
  })
153
179
  )
154
180
  )
155
- const config = await getVWRConfig(configRequest)
156
- expect(validateConfig(config)).toBe(true)
157
- expect(config.vwrUrl).toBe("https://local-vwr.example.com")
158
- expect(config.hubUrl).toBe("https://local-hub.example.com")
159
- expect(config.launchUrl).toBe("https://local-launch.example.com")
160
- expect(config.trustedDomains).toEqual([
181
+ const result = await getVWRConfig(configRequest, mockLogger)
182
+ expect(validateConfig(result.config)).toBe(true)
183
+ expect(result.config.vwrUrl).toBe("https://local-vwr.example.com")
184
+ expect(result.config.hubUrl).toBe("https://local-hub.example.com")
185
+ expect(result.config.launchUrl).toBe(
186
+ "https://local-launch.example.com"
187
+ )
188
+ expect(result.config.trustedDomains).toEqual([
161
189
  "https://local-hub.example.com",
162
190
  "https://local-vwr.example.com",
163
191
  "https://local-launch.example.com",
164
192
  ])
193
+ expect(result.source).toBe("local")
165
194
  })
166
195
 
167
196
  it("returns device config when available", async () => {
197
+ // local - 404 (not found)
198
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 404 }))
199
+ // device - success
168
200
  fetchSpy.mockResolvedValueOnce(
169
201
  new Response(
170
202
  JSON.stringify({
@@ -179,6 +211,7 @@ describe("vwrConfig", async () => {
179
211
  })
180
212
  )
181
213
  )
214
+ // shellVersion - success (but lower priority)
182
215
  fetchSpy.mockResolvedValueOnce(
183
216
  new Response(
184
217
  JSON.stringify({
@@ -193,6 +226,7 @@ describe("vwrConfig", async () => {
193
226
  })
194
227
  )
195
228
  )
229
+ // environment - success (but lower priority)
196
230
  fetchSpy.mockResolvedValueOnce(
197
231
  new Response(
198
232
  JSON.stringify({
@@ -207,19 +241,27 @@ describe("vwrConfig", async () => {
207
241
  })
208
242
  )
209
243
  )
210
- const config = await getVWRConfig(configRequest)
211
- expect(validateConfig(config)).toBe(true)
212
- expect(config.vwrUrl).toBe("https://device-vwr.example.com")
213
- expect(config.hubUrl).toBe("https://device-hub.example.com")
214
- expect(config.launchUrl).toBe("https://device-launch.example.com")
215
- expect(config.trustedDomains).toEqual([
244
+ const result = await getVWRConfig(configRequest, mockLogger)
245
+ expect(validateConfig(result.config)).toBe(true)
246
+ expect(result.config.vwrUrl).toBe("https://device-vwr.example.com")
247
+ expect(result.config.hubUrl).toBe("https://device-hub.example.com")
248
+ expect(result.config.launchUrl).toBe(
249
+ "https://device-launch.example.com"
250
+ )
251
+ expect(result.config.trustedDomains).toEqual([
216
252
  "https://device-hub.example.com",
217
253
  "https://device-vwr.example.com",
218
254
  "https://device-launch.example.com",
219
255
  ])
256
+ expect(result.source).toBe("device")
220
257
  })
221
258
 
222
259
  it("returns shell config when available", async () => {
260
+ // local - 404 (not found)
261
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 404 }))
262
+ // device - 404 (not found)
263
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 404 }))
264
+ // shellVersion - success
223
265
  fetchSpy.mockResolvedValueOnce(
224
266
  new Response(
225
267
  JSON.stringify({
@@ -234,6 +276,7 @@ describe("vwrConfig", async () => {
234
276
  })
235
277
  )
236
278
  )
279
+ // environment - success (but lower priority)
237
280
  fetchSpy.mockResolvedValueOnce(
238
281
  new Response(
239
282
  JSON.stringify({
@@ -248,19 +291,29 @@ describe("vwrConfig", async () => {
248
291
  })
249
292
  )
250
293
  )
251
- const config = await getVWRConfig(configRequest)
252
- expect(validateConfig(config)).toBe(true)
253
- expect(config.vwrUrl).toBe("https://shell-vwr.example.com")
254
- expect(config.hubUrl).toBe("https://shell-hub.example.com")
255
- expect(config.launchUrl).toBe("https://shell-launch.example.com")
256
- expect(config.trustedDomains).toEqual([
294
+ const result = await getVWRConfig(configRequest, mockLogger)
295
+ expect(validateConfig(result.config)).toBe(true)
296
+ expect(result.config.vwrUrl).toBe("https://shell-vwr.example.com")
297
+ expect(result.config.hubUrl).toBe("https://shell-hub.example.com")
298
+ expect(result.config.launchUrl).toBe(
299
+ "https://shell-launch.example.com"
300
+ )
301
+ expect(result.config.trustedDomains).toEqual([
257
302
  "https://shell-hub.example.com",
258
303
  "https://shell-vwr.example.com",
259
304
  "https://shell-launch.example.com",
260
305
  ])
306
+ expect(result.source).toBe("shellVersion")
261
307
  })
262
308
 
263
309
  it("returns environment config when available", async () => {
310
+ // local - 404 (not found)
311
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 404 }))
312
+ // device - 404 (not found)
313
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 404 }))
314
+ // shellVersion - 404 (not found)
315
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 404 }))
316
+ // environment - success
264
317
  fetchSpy.mockResolvedValueOnce(
265
318
  new Response(
266
319
  JSON.stringify({
@@ -275,21 +328,33 @@ describe("vwrConfig", async () => {
275
328
  })
276
329
  )
277
330
  )
278
- const config = await getVWRConfig(configRequest)
279
- expect(validateConfig(config)).toBe(true)
280
- expect(config.vwrUrl).toBe("https://environment-vwr.example.com")
281
- expect(config.hubUrl).toBe("https://environment-hub.example.com")
282
- expect(config.launchUrl).toBe(
331
+ const result = await getVWRConfig(configRequest, mockLogger)
332
+ expect(validateConfig(result.config)).toBe(true)
333
+ expect(result.config.vwrUrl).toBe(
334
+ "https://environment-vwr.example.com"
335
+ )
336
+ expect(result.config.hubUrl).toBe(
337
+ "https://environment-hub.example.com"
338
+ )
339
+ expect(result.config.launchUrl).toBe(
283
340
  "https://environment-launch.example.com"
284
341
  )
285
- expect(config.trustedDomains).toEqual([
342
+ expect(result.config.trustedDomains).toEqual([
286
343
  "https://environment-hub.example.com",
287
344
  "https://environment-vwr.example.com",
288
345
  "https://environment-launch.example.com",
289
346
  ])
347
+ expect(result.source).toBe("environment")
290
348
  })
291
349
 
292
350
  it("returns default config for missing values", async () => {
351
+ // local - 404 (not found)
352
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 404 }))
353
+ // device - 404 (not found)
354
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 404 }))
355
+ // shellVersion - 404 (not found)
356
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 404 }))
357
+ // environment - success (partial config, missing launchUrl)
293
358
  fetchSpy.mockResolvedValueOnce(
294
359
  new Response(
295
360
  JSON.stringify({
@@ -302,15 +367,20 @@ describe("vwrConfig", async () => {
302
367
  })
303
368
  )
304
369
  )
305
- const config = await getVWRConfig(configRequest)
306
- expect(config.vwrUrl).toBe("https://environment-vwr.example.com")
307
- expect(config.hubUrl).toBe("https://environment-hub.example.com")
370
+ const result = await getVWRConfig(configRequest, mockLogger)
371
+ expect(result.config.vwrUrl).toBe(
372
+ "https://environment-vwr.example.com"
373
+ )
374
+ expect(result.config.hubUrl).toBe(
375
+ "https://environment-hub.example.com"
376
+ )
308
377
  // launchUrl is optional, not filled when missing
309
- expect(config.launchUrl).toBeUndefined()
310
- expect(config.trustedDomains).toEqual([
378
+ expect(result.config.launchUrl).toBeUndefined()
379
+ expect(result.config.trustedDomains).toEqual([
311
380
  "https://environment-hub.example.com",
312
381
  "https://environment-vwr.example.com",
313
382
  ])
383
+ expect(result.source).toBe("environment")
314
384
  })
315
385
  })
316
386
  })