@volley/vwr-loader 1.4.0 → 1.6.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 (151) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +41 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/main.js +11 -11
  5. package/dist/main.js.map +1 -1
  6. package/dist/{datadog.d.ts → vwr/src/datadog.d.ts} +7 -3
  7. package/dist/vwr/src/datadog.d.ts.map +1 -0
  8. package/dist/{datadog.js → vwr/src/datadog.js} +6 -5
  9. package/dist/vwr/src/datadog.js.map +1 -0
  10. package/dist/{envDefaults.d.ts → vwr/src/envDefaults.d.ts} +1 -0
  11. package/dist/vwr/src/envDefaults.d.ts.map +1 -0
  12. package/dist/{envDefaults.js → vwr/src/envDefaults.js} +30 -3
  13. package/dist/vwr/src/envDefaults.js.map +1 -0
  14. package/dist/vwr/src/logger.d.ts +15 -0
  15. package/dist/vwr/src/logger.d.ts.map +1 -0
  16. package/dist/{logger.js → vwr/src/logger.js} +30 -46
  17. package/dist/vwr/src/logger.js.map +1 -0
  18. package/dist/vwr/src/native/device-info/getDeviceId.d.ts +13 -0
  19. package/dist/vwr/src/native/device-info/getDeviceId.d.ts.map +1 -0
  20. package/dist/vwr/src/native/device-info/getDeviceId.js +137 -0
  21. package/dist/vwr/src/native/device-info/getDeviceId.js.map +1 -0
  22. package/dist/{vwrConfig.d.ts → vwr/src/vwrConfig.d.ts} +5 -0
  23. package/dist/vwr/src/vwrConfig.d.ts.map +1 -0
  24. package/dist/{vwrConfig.js → vwr/src/vwrConfig.js} +13 -5
  25. package/dist/vwr/src/vwrConfig.js.map +1 -0
  26. package/dist/vwr-loader/src/__mocks__/@datadog/browser-logs.d.ts.map +1 -0
  27. package/dist/vwr-loader/src/__mocks__/@datadog/browser-logs.js.map +1 -0
  28. package/dist/vwr-loader/src/__mocks__/@datadog/browser-rum.d.ts.map +1 -0
  29. package/dist/vwr-loader/src/__mocks__/@datadog/browser-rum.js.map +1 -0
  30. package/dist/vwr-loader/src/__mocks__/vwrModule.d.ts +2 -0
  31. package/dist/vwr-loader/src/__mocks__/vwrModule.d.ts.map +1 -0
  32. package/dist/vwr-loader/src/__mocks__/vwrModule.js +3 -0
  33. package/dist/vwr-loader/src/__mocks__/vwrModule.js.map +1 -0
  34. package/dist/vwr-loader/src/amplitudeFlagFetcher.d.ts.map +1 -0
  35. package/dist/vwr-loader/src/amplitudeFlagFetcher.js.map +1 -0
  36. package/dist/vwr-loader/src/errors/InitializationError.d.ts.map +1 -0
  37. package/dist/vwr-loader/src/errors/InitializationError.js.map +1 -0
  38. package/dist/vwr-loader/src/errors/ensureError.d.ts.map +1 -0
  39. package/dist/vwr-loader/src/errors/ensureError.js.map +1 -0
  40. package/dist/vwr-loader/src/errors/index.d.ts.map +1 -0
  41. package/dist/vwr-loader/src/errors/index.js.map +1 -0
  42. package/dist/{exitHandler.d.ts → vwr-loader/src/exitHandler.d.ts} +14 -0
  43. package/dist/vwr-loader/src/exitHandler.d.ts.map +1 -0
  44. package/dist/{exitHandler.js → vwr-loader/src/exitHandler.js} +82 -1
  45. package/dist/vwr-loader/src/exitHandler.js.map +1 -0
  46. package/dist/vwr-loader/src/getEnvironment.d.ts.map +1 -0
  47. package/dist/vwr-loader/src/getEnvironment.js.map +1 -0
  48. package/dist/vwr-loader/src/getShellVersion.d.ts.map +1 -0
  49. package/dist/vwr-loader/src/getShellVersion.js.map +1 -0
  50. package/dist/vwr-loader/src/index.d.ts +8 -0
  51. package/dist/vwr-loader/src/index.d.ts.map +1 -0
  52. package/dist/vwr-loader/src/index.js +6 -0
  53. package/dist/vwr-loader/src/index.js.map +1 -0
  54. package/dist/vwr-loader/src/loadVwr.d.ts.map +1 -0
  55. package/dist/{loadVwr.js → vwr-loader/src/loadVwr.js} +29 -6
  56. package/dist/vwr-loader/src/loadVwr.js.map +1 -0
  57. package/dist/vwr-loader/src/logger.d.ts +4 -0
  58. package/dist/vwr-loader/src/logger.d.ts.map +1 -0
  59. package/dist/vwr-loader/src/logger.js +10 -0
  60. package/dist/vwr-loader/src/logger.js.map +1 -0
  61. package/dist/vwr-loader/src/main.d.ts +2 -0
  62. package/dist/vwr-loader/src/main.d.ts.map +1 -0
  63. package/dist/vwr-loader/src/main.js +96 -0
  64. package/dist/vwr-loader/src/main.js.map +1 -0
  65. package/dist/vwr-loader/src/polyfills.d.ts.map +1 -0
  66. package/dist/vwr-loader/src/polyfills.js.map +1 -0
  67. package/dist/{test-setup.d.ts.map → vwr-loader/src/test-setup.d.ts.map} +1 -1
  68. package/dist/vwr-loader/src/test-setup.js.map +1 -0
  69. package/package.json +10 -8
  70. package/src/__mocks__/vwrModule.ts +3 -0
  71. package/src/exitHandler.test.ts +241 -3
  72. package/src/exitHandler.ts +109 -1
  73. package/src/getShellVersion.ts +2 -1
  74. package/src/index.ts +3 -3
  75. package/src/loadVwr.test.ts +170 -10
  76. package/src/loadVwr.ts +39 -12
  77. package/src/logger.ts +5 -115
  78. package/src/main.ts +16 -8
  79. package/src/vite-env.d.ts +1 -0
  80. package/dist/__mocks__/@datadog/browser-logs.d.ts.map +0 -1
  81. package/dist/__mocks__/@datadog/browser-logs.js.map +0 -1
  82. package/dist/__mocks__/@datadog/browser-rum.d.ts.map +0 -1
  83. package/dist/__mocks__/@datadog/browser-rum.js.map +0 -1
  84. package/dist/amplitudeFlagFetcher.d.ts.map +0 -1
  85. package/dist/amplitudeFlagFetcher.js.map +0 -1
  86. package/dist/datadog.d.ts.map +0 -1
  87. package/dist/datadog.js.map +0 -1
  88. package/dist/envDefaults.d.ts.map +0 -1
  89. package/dist/envDefaults.js.map +0 -1
  90. package/dist/errors/InitializationError.d.ts.map +0 -1
  91. package/dist/errors/InitializationError.js.map +0 -1
  92. package/dist/errors/ensureError.d.ts.map +0 -1
  93. package/dist/errors/ensureError.js.map +0 -1
  94. package/dist/errors/index.d.ts.map +0 -1
  95. package/dist/errors/index.js.map +0 -1
  96. package/dist/exitHandler.d.ts.map +0 -1
  97. package/dist/exitHandler.js.map +0 -1
  98. package/dist/getDeviceId.d.ts +0 -26
  99. package/dist/getDeviceId.d.ts.map +0 -1
  100. package/dist/getDeviceId.js +0 -209
  101. package/dist/getDeviceId.js.map +0 -1
  102. package/dist/getEnvironment.d.ts.map +0 -1
  103. package/dist/getEnvironment.js.map +0 -1
  104. package/dist/getShellVersion.d.ts.map +0 -1
  105. package/dist/getShellVersion.js.map +0 -1
  106. package/dist/index.d.ts +0 -8
  107. package/dist/index.d.ts.map +0 -1
  108. package/dist/index.js +0 -6
  109. package/dist/index.js.map +0 -1
  110. package/dist/loadVwr.d.ts.map +0 -1
  111. package/dist/loadVwr.js.map +0 -1
  112. package/dist/logger.d.ts +0 -12
  113. package/dist/logger.d.ts.map +0 -1
  114. package/dist/logger.js.map +0 -1
  115. package/dist/polyfills.d.ts.map +0 -1
  116. package/dist/polyfills.js.map +0 -1
  117. package/dist/test-setup.js.map +0 -1
  118. package/dist/types.d.ts +0 -91
  119. package/dist/types.d.ts.map +0 -1
  120. package/dist/types.js +0 -5
  121. package/dist/types.js.map +0 -1
  122. package/dist/vwrConfig.d.ts.map +0 -1
  123. package/dist/vwrConfig.js.map +0 -1
  124. package/src/datadog.ts +0 -55
  125. package/src/envDefaults.ts +0 -93
  126. package/src/getDeviceId.test.ts +0 -298
  127. package/src/getDeviceId.ts +0 -224
  128. package/src/types.ts +0 -92
  129. package/src/vwrConfig.test.ts +0 -386
  130. package/src/vwrConfig.ts +0 -374
  131. /package/dist/{__mocks__ → vwr-loader/src/__mocks__}/@datadog/browser-logs.d.ts +0 -0
  132. /package/dist/{__mocks__ → vwr-loader/src/__mocks__}/@datadog/browser-logs.js +0 -0
  133. /package/dist/{__mocks__ → vwr-loader/src/__mocks__}/@datadog/browser-rum.d.ts +0 -0
  134. /package/dist/{__mocks__ → vwr-loader/src/__mocks__}/@datadog/browser-rum.js +0 -0
  135. /package/dist/{amplitudeFlagFetcher.d.ts → vwr-loader/src/amplitudeFlagFetcher.d.ts} +0 -0
  136. /package/dist/{amplitudeFlagFetcher.js → vwr-loader/src/amplitudeFlagFetcher.js} +0 -0
  137. /package/dist/{errors → vwr-loader/src/errors}/InitializationError.d.ts +0 -0
  138. /package/dist/{errors → vwr-loader/src/errors}/InitializationError.js +0 -0
  139. /package/dist/{errors → vwr-loader/src/errors}/ensureError.d.ts +0 -0
  140. /package/dist/{errors → vwr-loader/src/errors}/ensureError.js +0 -0
  141. /package/dist/{errors → vwr-loader/src/errors}/index.d.ts +0 -0
  142. /package/dist/{errors → vwr-loader/src/errors}/index.js +0 -0
  143. /package/dist/{getEnvironment.d.ts → vwr-loader/src/getEnvironment.d.ts} +0 -0
  144. /package/dist/{getEnvironment.js → vwr-loader/src/getEnvironment.js} +0 -0
  145. /package/dist/{getShellVersion.d.ts → vwr-loader/src/getShellVersion.d.ts} +0 -0
  146. /package/dist/{getShellVersion.js → vwr-loader/src/getShellVersion.js} +0 -0
  147. /package/dist/{loadVwr.d.ts → vwr-loader/src/loadVwr.d.ts} +0 -0
  148. /package/dist/{polyfills.d.ts → vwr-loader/src/polyfills.d.ts} +0 -0
  149. /package/dist/{polyfills.js → vwr-loader/src/polyfills.js} +0 -0
  150. /package/dist/{test-setup.d.ts → vwr-loader/src/test-setup.d.ts} +0 -0
  151. /package/dist/{test-setup.js → vwr-loader/src/test-setup.js} +0 -0
@@ -1,6 +1,11 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
2
2
 
3
- import { exitIfBackNavigation, setupLGBackButtonHandler } from "./exitHandler"
3
+ import {
4
+ exitIfBackNavigation,
5
+ setupFireTVBackButtonHandler,
6
+ setupLGBackButtonHandler,
7
+ } from "./exitHandler"
8
+ import { logger } from "./logger"
4
9
 
5
10
  describe("exitHandler", () => {
6
11
  beforeEach(() => {
@@ -81,14 +86,247 @@ describe("exitHandler", () => {
81
86
  expect(() => exitIfBackNavigation("SAMSUNG_TV")).not.toThrow()
82
87
  })
83
88
 
84
- it("should handle platforms without specific exit behavior", () => {
89
+ it("should exit Fire TV app when back_forward detected", () => {
85
90
  vi.spyOn(performance, "getEntriesByType").mockReturnValue([
86
91
  { type: "back_forward" } as PerformanceNavigationTiming,
87
92
  ])
88
93
 
89
- // Fire TV doesn't have platform-specific exit - just logs
94
+ const mockExitApp = vi.fn().mockResolvedValue(undefined)
95
+ window.Capacitor = {
96
+ Plugins: {
97
+ AppLifecycle: { exitApp: mockExitApp },
98
+ },
99
+ }
100
+
101
+ exitIfBackNavigation("FIRE_TV")
102
+
103
+ expect(mockExitApp).toHaveBeenCalled()
104
+ })
105
+
106
+ it("should handle Fire TV without Capacitor gracefully", () => {
107
+ vi.spyOn(performance, "getEntriesByType").mockReturnValue([
108
+ { type: "back_forward" } as PerformanceNavigationTiming,
109
+ ])
110
+
111
+ window.Capacitor = undefined
112
+
90
113
  expect(() => exitIfBackNavigation("FIRE_TV")).not.toThrow()
91
114
  })
115
+
116
+ it("should log when Fire TV exit promise rejects", async () => {
117
+ vi.spyOn(performance, "getEntriesByType").mockReturnValue([
118
+ { type: "back_forward" } as PerformanceNavigationTiming,
119
+ ])
120
+
121
+ const mockExitApp = vi
122
+ .fn()
123
+ .mockRejectedValue(new Error("exit failed"))
124
+ const loggerErrorSpy = vi.spyOn(logger, "error")
125
+
126
+ window.Capacitor = {
127
+ Plugins: {
128
+ AppLifecycle: { exitApp: mockExitApp },
129
+ },
130
+ }
131
+
132
+ exitIfBackNavigation("FIRE_TV")
133
+
134
+ await vi.waitFor(() => {
135
+ expect(loggerErrorSpy).toHaveBeenCalledWith(
136
+ { error: expect.any(Error) },
137
+ "[Shell] Failed to exit Fire TV app"
138
+ )
139
+ })
140
+ })
141
+ })
142
+
143
+ describe("setupFireTVBackButtonHandler", () => {
144
+ const ANDROID_BACK_KEYCODE = 4
145
+ type KeyDownListener = (event: { keyCode: number }) => void
146
+
147
+ it("should call Capacitor AppLifecycle.exitApp when InputHandler emits back keyDown", async () => {
148
+ const mockExitApp = vi.fn().mockResolvedValue(undefined)
149
+ const mockRemoveListener = vi.fn().mockResolvedValue(undefined)
150
+ let capturedListener: KeyDownListener | null = null
151
+ const mockAddListener = vi
152
+ .fn()
153
+ .mockImplementation(
154
+ async (eventName: string, listener: KeyDownListener) => {
155
+ if (eventName === "keyDown") {
156
+ capturedListener = listener
157
+ }
158
+
159
+ return {
160
+ remove: mockRemoveListener,
161
+ }
162
+ }
163
+ )
164
+
165
+ window.Capacitor = {
166
+ Plugins: {
167
+ AppLifecycle: { exitApp: mockExitApp },
168
+ InputHandler: {
169
+ addListener: mockAddListener,
170
+ },
171
+ },
172
+ }
173
+
174
+ const cleanup = setupFireTVBackButtonHandler()
175
+
176
+ await vi.waitFor(() => {
177
+ expect(mockAddListener).toHaveBeenCalledWith(
178
+ "keyDown",
179
+ expect.any(Function)
180
+ )
181
+ })
182
+
183
+ capturedListener?.({ keyCode: ANDROID_BACK_KEYCODE })
184
+
185
+ expect(mockExitApp).toHaveBeenCalled()
186
+
187
+ cleanup()
188
+ })
189
+
190
+ it("should handle missing Capacitor gracefully", () => {
191
+ window.Capacitor = undefined
192
+
193
+ const cleanup = setupFireTVBackButtonHandler()
194
+
195
+ expect(() => cleanup()).not.toThrow()
196
+ cleanup()
197
+ })
198
+
199
+ it("should not trigger when InputHandler emits non-back keyDown", async () => {
200
+ const mockExitApp = vi.fn().mockResolvedValue(undefined)
201
+ let capturedListener: KeyDownListener | null = null
202
+ const mockAddListener = vi
203
+ .fn()
204
+ .mockImplementation(
205
+ async (eventName: string, listener: KeyDownListener) => {
206
+ if (eventName === "keyDown") {
207
+ capturedListener = listener
208
+ }
209
+
210
+ return {
211
+ remove: vi.fn().mockResolvedValue(undefined),
212
+ }
213
+ }
214
+ )
215
+
216
+ window.Capacitor = {
217
+ Plugins: {
218
+ AppLifecycle: { exitApp: mockExitApp },
219
+ InputHandler: {
220
+ addListener: mockAddListener,
221
+ },
222
+ },
223
+ }
224
+
225
+ const cleanup = setupFireTVBackButtonHandler()
226
+
227
+ await vi.waitFor(() => {
228
+ expect(mockAddListener).toHaveBeenCalledWith(
229
+ "keyDown",
230
+ expect.any(Function)
231
+ )
232
+ })
233
+
234
+ capturedListener?.({ keyCode: 13 })
235
+
236
+ expect(mockExitApp).not.toHaveBeenCalled()
237
+
238
+ cleanup()
239
+ })
240
+
241
+ it("should remove InputHandler listener when cleanup is called", async () => {
242
+ const mockExitApp = vi.fn().mockResolvedValue(undefined)
243
+ const mockRemoveListener = vi.fn().mockResolvedValue(undefined)
244
+ const mockAddListener = vi.fn().mockResolvedValue({
245
+ remove: mockRemoveListener,
246
+ })
247
+
248
+ window.Capacitor = {
249
+ Plugins: {
250
+ AppLifecycle: { exitApp: mockExitApp },
251
+ InputHandler: {
252
+ addListener: mockAddListener,
253
+ },
254
+ },
255
+ }
256
+
257
+ const cleanup = setupFireTVBackButtonHandler()
258
+
259
+ await vi.waitFor(() => {
260
+ expect(mockAddListener).toHaveBeenCalledWith(
261
+ "keyDown",
262
+ expect.any(Function)
263
+ )
264
+ })
265
+
266
+ cleanup()
267
+
268
+ await vi.waitFor(() => {
269
+ expect(mockRemoveListener).toHaveBeenCalled()
270
+ })
271
+ })
272
+
273
+ it("should support InputHandler addListener returning a sync handle", async () => {
274
+ const mockExitApp = vi.fn().mockResolvedValue(undefined)
275
+ const mockRemoveListener = vi.fn().mockResolvedValue(undefined)
276
+ let capturedListener: KeyDownListener | null = null
277
+ const mockAddListener = vi
278
+ .fn()
279
+ .mockImplementation(
280
+ (eventName: string, listener: KeyDownListener) => {
281
+ if (eventName === "keyDown") {
282
+ capturedListener = listener
283
+ }
284
+
285
+ return {
286
+ remove: mockRemoveListener,
287
+ }
288
+ }
289
+ )
290
+
291
+ window.Capacitor = {
292
+ Plugins: {
293
+ AppLifecycle: { exitApp: mockExitApp },
294
+ InputHandler: {
295
+ addListener: mockAddListener,
296
+ },
297
+ },
298
+ }
299
+
300
+ const cleanup = setupFireTVBackButtonHandler()
301
+
302
+ expect(mockAddListener).toHaveBeenCalledWith(
303
+ "keyDown",
304
+ expect.any(Function)
305
+ )
306
+
307
+ capturedListener?.({ keyCode: ANDROID_BACK_KEYCODE })
308
+ expect(mockExitApp).toHaveBeenCalled()
309
+
310
+ cleanup()
311
+
312
+ await vi.waitFor(() => {
313
+ expect(mockRemoveListener).toHaveBeenCalled()
314
+ })
315
+ })
316
+
317
+ it("should handle missing InputHandler plugin gracefully", () => {
318
+ const mockExitApp = vi.fn().mockResolvedValue(undefined)
319
+ window.Capacitor = {
320
+ Plugins: {
321
+ AppLifecycle: { exitApp: mockExitApp },
322
+ },
323
+ }
324
+
325
+ const cleanup = setupFireTVBackButtonHandler()
326
+
327
+ expect(mockExitApp).not.toHaveBeenCalled()
328
+ expect(() => cleanup()).not.toThrow()
329
+ })
92
330
  })
93
331
 
94
332
  describe("setupLGBackButtonHandler", () => {
@@ -10,6 +10,9 @@ import { logger } from "./logger"
10
10
  /** LG webOS back button keycode */
11
11
  const LG_BACK_KEYCODE = 461
12
12
 
13
+ /** Android back button keycode (used by Fire TV InputHandler plugin) */
14
+ const ANDROID_BACK_KEYCODE = 4
15
+
13
16
  /**
14
17
  * Exit the app if the page was loaded via back/forward navigation.
15
18
  *
@@ -80,6 +83,86 @@ export function setupLGBackButtonHandler(): () => void {
80
83
  }
81
84
  }
82
85
 
86
+ /**
87
+ * Set up Fire TV back button (keycode 4) handler.
88
+ *
89
+ * On Fire TV, the native shell intercepts the back button at the Activity
90
+ * level and forwards it to JavaScript via the InputHandler Capacitor plugin.
91
+ * During the vwr-loader loading phase, we subscribe directly to InputHandler
92
+ * keyDown events and call Capacitor AppLifecycle.exitApp().
93
+ *
94
+ * Once VWR loads successfully, call the returned cleanup function - VWR
95
+ * will take over back button handling.
96
+ *
97
+ * @returns Cleanup function to remove the listener
98
+ */
99
+ export function setupFireTVBackButtonHandler(): () => void {
100
+ const inputHandler = window.Capacitor?.Plugins?.InputHandler
101
+
102
+ if (!inputHandler?.addListener) {
103
+ logger.warn("[Shell] Capacitor InputHandler plugin not available")
104
+ return () => {
105
+ logger.info("[Shell] Fire TV back button handler removed")
106
+ }
107
+ }
108
+
109
+ let listenerHandle: { remove: () => Promise<void> | void } | null = null
110
+ let isCleanedUp = false
111
+
112
+ const listenerRegistration = Promise.resolve(
113
+ inputHandler.addListener("keyDown", (event: { keyCode: number }) => {
114
+ if (event.keyCode === ANDROID_BACK_KEYCODE) {
115
+ logger.info("[Shell] Fire TV back button pressed, exiting app")
116
+ exitFireTVApp()
117
+ }
118
+ })
119
+ )
120
+ .then((handle: { remove: () => Promise<void> | void }) => {
121
+ listenerHandle = handle
122
+
123
+ if (isCleanedUp) {
124
+ return Promise.resolve(handle.remove()).catch(
125
+ (error: unknown) => {
126
+ logger.error(
127
+ { error },
128
+ "[Shell] Failed to remove Fire TV back button handler"
129
+ )
130
+ }
131
+ )
132
+ }
133
+
134
+ return undefined
135
+ })
136
+ .catch((error: unknown) => {
137
+ logger.error(
138
+ { error },
139
+ "[Shell] Failed to register Fire TV back button handler"
140
+ )
141
+ })
142
+
143
+ logger.info("[Shell] Fire TV back button handler registered")
144
+
145
+ return () => {
146
+ isCleanedUp = true
147
+
148
+ if (listenerHandle) {
149
+ void Promise.resolve(listenerHandle.remove()).catch(
150
+ (error: unknown) => {
151
+ logger.error(
152
+ { error },
153
+ "[Shell] Failed to remove Fire TV back button handler"
154
+ )
155
+ }
156
+ )
157
+ listenerHandle = null
158
+ } else {
159
+ void listenerRegistration
160
+ }
161
+
162
+ logger.info("[Shell] Fire TV back button handler removed")
163
+ }
164
+ }
165
+
83
166
  /**
84
167
  * Exit the app using platform-specific methods.
85
168
  *
@@ -93,7 +176,10 @@ function exitApp(platform: Platform): void {
93
176
  case "LG_TV":
94
177
  exitLGAppViaBackNavigation()
95
178
  break
96
- // Fire TV and mobile apps handle exit at the native layer
179
+ case "FIRE_TV":
180
+ exitFireTVApp()
181
+ break
182
+ // Mobile apps handle exit at the native layer
97
183
  // Web doesn't have a meaningful "exit" concept
98
184
  default:
99
185
  logger.info({ platform }, "[Shell] No platform-specific exit")
@@ -123,3 +209,25 @@ function exitLGAppViaBackNavigation(): void {
123
209
  logger.error({ error }, "[Shell] Failed to exit LG app")
124
210
  }
125
211
  }
212
+
213
+ /**
214
+ * Exit Fire TV app via Capacitor AppLifecycle plugin.
215
+ */
216
+ function exitFireTVApp(): void {
217
+ try {
218
+ if (window.Capacitor?.Plugins?.AppLifecycle?.exitApp) {
219
+ void window.Capacitor.Plugins.AppLifecycle.exitApp().catch(
220
+ (error: unknown) => {
221
+ logger.error(
222
+ { error },
223
+ "[Shell] Failed to exit Fire TV app"
224
+ )
225
+ }
226
+ )
227
+ } else {
228
+ logger.warn("[Shell] Capacitor AppLifecycle plugin not available")
229
+ }
230
+ } catch (error) {
231
+ logger.error({ error }, "[Shell] Failed to exit Fire TV app")
232
+ }
233
+ }
@@ -1,5 +1,6 @@
1
+ import type { MobileExtendedWindow } from "@volley/vwr/src/types"
2
+
1
3
  import { logger } from "./logger"
2
- import type { MobileExtendedWindow } from "./types"
3
4
 
4
5
  /**
5
6
  * Retrieve shell app version for different platforms.
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
+ export { getDeviceId } from "../../vwr/src/native/device-info/getDeviceId"
2
+ export type { VWRConfig, VWRConfigRequest } from "../../vwr/src/vwrConfig"
3
+ export { getVWRConfig } from "../../vwr/src/vwrConfig"
1
4
  export type { AmplitudeConfig, FlagResult } from "./amplitudeFlagFetcher"
2
5
  export { fetchAmplitudeFlags } from "./amplitudeFlagFetcher"
3
- export { getDeviceId } from "./getDeviceId"
4
6
  export { getShellVersion } from "./getShellVersion"
5
7
  export { loadVwr } from "./loadVwr"
6
- export type { VWRConfig, VWRConfigRequest } from "./vwrConfig"
7
- export { getVWRConfig } from "./vwrConfig"
@@ -1,7 +1,7 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
2
2
 
3
3
  // Mock all dependencies before importing loadVwr
4
- vi.mock("./getDeviceId", () => ({
4
+ vi.mock("../../vwr/src/native/device-info/getDeviceId", () => ({
5
5
  getDeviceId: vi.fn(),
6
6
  }))
7
7
 
@@ -19,7 +19,7 @@ vi.mock("./amplitudeFlagFetcher", () => ({
19
19
  },
20
20
  }))
21
21
 
22
- vi.mock("./vwrConfig", () => ({
22
+ vi.mock("../../vwr/src/vwrConfig", () => ({
23
23
  getVWRConfig: vi.fn(),
24
24
  validateConfig: vi.fn(),
25
25
  }))
@@ -29,11 +29,14 @@ vi.mock("./getEnvironment", () => ({
29
29
  }))
30
30
 
31
31
  // Import mocked modules
32
+
33
+ import type { DatadogRum } from "@datadog/browser-rum"
34
+
35
+ import { getDeviceId } from "../../vwr/src/native/device-info/getDeviceId"
36
+ import { getVWRConfig, validateConfig } from "../../vwr/src/vwrConfig"
32
37
  import { fetchAmplitudeFlags } from "./amplitudeFlagFetcher"
33
- import { getDeviceId } from "./getDeviceId"
34
38
  import { getEnvironment } from "./getEnvironment"
35
39
  import { getShellVersion } from "./getShellVersion"
36
- import { getVWRConfig, validateConfig } from "./vwrConfig"
37
40
 
38
41
  const mockGetDeviceId = vi.mocked(getDeviceId)
39
42
  const mockGetShellVersion = vi.mocked(getShellVersion)
@@ -43,12 +46,18 @@ const mockGetVWRConfig = vi.mocked(getVWRConfig)
43
46
  const mockValidateConfig = vi.mocked(validateConfig)
44
47
 
45
48
  // Mock RUM object for tests
46
- const createMockRum = () => ({
47
- addAction: vi.fn(),
48
- addError: vi.fn(),
49
- addTiming: vi.fn(),
50
- setGlobalContextProperty: vi.fn(),
51
- })
49
+ const createMockRum = () =>
50
+ ({
51
+ addAction: vi.fn(),
52
+ addError: vi.fn(),
53
+ addTiming: vi.fn(),
54
+ setGlobalContextProperty: vi.fn(),
55
+ }) as unknown as DatadogRum & {
56
+ addAction: ReturnType<typeof vi.fn>
57
+ addError: ReturnType<typeof vi.fn>
58
+ addTiming: ReturnType<typeof vi.fn>
59
+ setGlobalContextProperty: ReturnType<typeof vi.fn>
60
+ }
52
61
 
53
62
  describe("loadVwr", () => {
54
63
  beforeEach(() => {
@@ -71,6 +80,7 @@ describe("loadVwr", () => {
71
80
  platformApiUrl: "https://platform.example.com",
72
81
  platformAuthApiUrl: "https://auth.example.com",
73
82
  trustedDomains: ["https://hub.example.com"],
83
+ trustedShellOrigins: [],
74
84
  },
75
85
  source: "environment",
76
86
  attempts: [],
@@ -321,6 +331,7 @@ describe("loadVwr", () => {
321
331
  platformApiUrl: "https://platform.example.com",
322
332
  platformAuthApiUrl: "https://auth.example.com",
323
333
  trustedDomains: ["https://hub.example.com"],
334
+ trustedShellOrigins: [],
324
335
  },
325
336
  source: "device",
326
337
  attempts: [
@@ -391,4 +402,153 @@ describe("loadVwr", () => {
391
402
  )
392
403
  })
393
404
  })
405
+
406
+ describe("configSource display string", () => {
407
+ const mockVwrModulePath = "./__mocks__/vwrModule"
408
+
409
+ const setupWithConfigSource = (
410
+ source:
411
+ | "device"
412
+ | "shellVersion"
413
+ | "environment"
414
+ | "defaults"
415
+ | "local"
416
+ ) => {
417
+ mockGetVWRConfig.mockResolvedValue({
418
+ config: {
419
+ vwrUrl: mockVwrModulePath,
420
+ hubUrl: "https://hub.example.com",
421
+ launchUrl: undefined,
422
+ platformApiUrl: "https://platform.example.com",
423
+ platformAuthApiUrl: "https://auth.example.com",
424
+ trustedDomains: ["https://hub.example.com"],
425
+ trustedShellOrigins: [],
426
+ },
427
+ source,
428
+ attempts: [],
429
+ total_duration_ms: 100,
430
+ })
431
+ }
432
+
433
+ beforeEach(async () => {
434
+ vi.stubEnv("VITE_ENVIRONMENT", "dev")
435
+ vi.stubEnv("VITE_PLATFORM", "FIRE_TV")
436
+ vi.stubEnv("VITE_CONFIG_URL", "https://config.example.com")
437
+ vi.stubEnv("VITE_CONFIG_FILE", "config.json")
438
+
439
+ // Reset the mock init function between tests
440
+ const vwrModule = await import("./__mocks__/vwrModule")
441
+ vwrModule.init.mockReset()
442
+ })
443
+
444
+ it('passes "device (<deviceId>)" for device config source', async () => {
445
+ setupWithConfigSource("device")
446
+
447
+ const { loadVwr } = await import("./loadVwr")
448
+ const mockRum = createMockRum()
449
+
450
+ try {
451
+ await loadVwr(mockRum)
452
+ } catch {
453
+ // May fail after init
454
+ }
455
+
456
+ const vwrModule = await import("./__mocks__/vwrModule")
457
+ expect(vwrModule.init).toHaveBeenCalledWith(
458
+ expect.objectContaining({
459
+ configSource: "device (test-device-id)",
460
+ }),
461
+ expect.anything(),
462
+ expect.anything()
463
+ )
464
+ })
465
+
466
+ it('passes "environment (<env>)" for environment config source', async () => {
467
+ setupWithConfigSource("environment")
468
+
469
+ const { loadVwr } = await import("./loadVwr")
470
+ const mockRum = createMockRum()
471
+
472
+ try {
473
+ await loadVwr(mockRum)
474
+ } catch {
475
+ // May fail after init
476
+ }
477
+
478
+ const vwrModule = await import("./__mocks__/vwrModule")
479
+ expect(vwrModule.init).toHaveBeenCalledWith(
480
+ expect.objectContaining({
481
+ configSource: "environment (dev)",
482
+ }),
483
+ expect.anything(),
484
+ expect.anything()
485
+ )
486
+ })
487
+
488
+ it('passes "shell-version (<env>/<platform>/<version>)" for shellVersion config source', async () => {
489
+ setupWithConfigSource("shellVersion")
490
+
491
+ const { loadVwr } = await import("./loadVwr")
492
+ const mockRum = createMockRum()
493
+
494
+ try {
495
+ await loadVwr(mockRum)
496
+ } catch {
497
+ // May fail after init
498
+ }
499
+
500
+ const vwrModule = await import("./__mocks__/vwrModule")
501
+ expect(vwrModule.init).toHaveBeenCalledWith(
502
+ expect.objectContaining({
503
+ configSource: "shell-version (dev/FIRE_TV/1.0.0)",
504
+ }),
505
+ expect.anything(),
506
+ expect.anything()
507
+ )
508
+ })
509
+
510
+ it('passes "local" for local config source', async () => {
511
+ setupWithConfigSource("local")
512
+
513
+ const { loadVwr } = await import("./loadVwr")
514
+ const mockRum = createMockRum()
515
+
516
+ try {
517
+ await loadVwr(mockRum)
518
+ } catch {
519
+ // May fail after init
520
+ }
521
+
522
+ const vwrModule = await import("./__mocks__/vwrModule")
523
+ expect(vwrModule.init).toHaveBeenCalledWith(
524
+ expect.objectContaining({
525
+ configSource: "local",
526
+ }),
527
+ expect.anything(),
528
+ expect.anything()
529
+ )
530
+ })
531
+
532
+ it('passes "defaults" for defaults config source', async () => {
533
+ setupWithConfigSource("defaults")
534
+
535
+ const { loadVwr } = await import("./loadVwr")
536
+ const mockRum = createMockRum()
537
+
538
+ try {
539
+ await loadVwr(mockRum)
540
+ } catch {
541
+ // May fail after init
542
+ }
543
+
544
+ const vwrModule = await import("./__mocks__/vwrModule")
545
+ expect(vwrModule.init).toHaveBeenCalledWith(
546
+ expect.objectContaining({
547
+ configSource: "defaults",
548
+ }),
549
+ expect.anything(),
550
+ expect.anything()
551
+ )
552
+ })
553
+ })
394
554
  })