@volley/vwr-loader 1.0.4 → 1.0.5
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.
- package/README.md +45 -21
- package/dist/envDefaults.d.ts +0 -3
- package/dist/envDefaults.d.ts.map +1 -1
- package/dist/envDefaults.js +4 -16
- package/dist/envDefaults.js.map +1 -1
- package/dist/getDeviceId.d.ts +10 -2
- package/dist/getDeviceId.d.ts.map +1 -1
- package/dist/getDeviceId.js +23 -39
- package/dist/getDeviceId.js.map +1 -1
- package/dist/getEnvironment.d.ts +25 -0
- package/dist/getEnvironment.d.ts.map +1 -0
- package/dist/getEnvironment.js +102 -0
- package/dist/getEnvironment.js.map +1 -0
- package/dist/getShellVersion.d.ts +2 -2
- package/dist/getShellVersion.d.ts.map +1 -1
- package/dist/getShellVersion.js +3 -5
- package/dist/getShellVersion.js.map +1 -1
- package/dist/index.html +1 -1
- package/dist/loadVwr.d.ts +6 -1
- package/dist/loadVwr.d.ts.map +1 -1
- package/dist/loadVwr.js +18 -10
- package/dist/loadVwr.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/vwrConfig.d.ts +0 -2
- package/dist/vwrConfig.d.ts.map +1 -1
- package/dist/vwrConfig.js +7 -25
- package/dist/vwrConfig.js.map +1 -1
- package/eslint.config.mjs +23 -0
- package/package.json +4 -15
- package/scripts/build.js +2 -0
- package/scripts/build.ts +207 -0
- package/src/amplitudeFlagFetcher.test.ts +0 -5
- package/src/envDefaults.ts +4 -23
- package/src/getDeviceId.test.ts +24 -79
- package/src/getDeviceId.ts +31 -56
- package/src/getEnvironment.ts +137 -0
- package/src/getShellVersion.test.ts +1 -1
- package/src/getShellVersion.ts +3 -7
- package/src/loadVwr.ts +25 -14
- package/src/main.ts +1 -3
- package/src/vite-env.d.ts +0 -3
- package/src/vwrConfig.ts +7 -28
- package/tsconfig.eslint.json +16 -0
- package/tsconfig.json +17 -0
- package/vite.config.ts +24 -0
- package/vitest.config.ts +8 -0
- package/dist/cli.js +0 -190
- package/dist/cli.js.map +0 -1
package/src/getDeviceId.test.ts
CHANGED
|
@@ -6,9 +6,8 @@ describe("getDeviceId", () => {
|
|
|
6
6
|
beforeEach(() => {
|
|
7
7
|
vi.clearAllMocks()
|
|
8
8
|
// Clean up window properties
|
|
9
|
-
delete (window as any).
|
|
10
|
-
delete (window as any).
|
|
11
|
-
delete (window as any).androidAppContext
|
|
9
|
+
delete (window as any).DeviceInfo
|
|
10
|
+
delete (window as any).NativeBridge
|
|
12
11
|
delete (window as any).webapis
|
|
13
12
|
delete (window as any).webOSDev
|
|
14
13
|
delete (window as any).webOS
|
|
@@ -16,26 +15,21 @@ describe("getDeviceId", () => {
|
|
|
16
15
|
|
|
17
16
|
afterEach(() => {
|
|
18
17
|
vi.restoreAllMocks()
|
|
19
|
-
delete (window as any).
|
|
20
|
-
delete (window as any).
|
|
21
|
-
delete (window as any).androidAppContext
|
|
18
|
+
delete (window as any).DeviceInfo
|
|
19
|
+
delete (window as any).NativeBridge
|
|
22
20
|
delete (window as any).webapis
|
|
23
21
|
delete (window as any).webOSDev
|
|
24
22
|
delete (window as any).webOS
|
|
25
23
|
})
|
|
26
24
|
|
|
27
25
|
describe("FIRE_TV platform", () => {
|
|
28
|
-
it("returns device ID from
|
|
26
|
+
it("returns device ID from DeviceInfo.getAndroidId when available", async () => {
|
|
29
27
|
const mockAndroidId = "firetv-android-id-123"
|
|
30
|
-
Object.defineProperty(window, "
|
|
28
|
+
Object.defineProperty(window, "DeviceInfo", {
|
|
31
29
|
value: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
androidId: mockAndroidId,
|
|
36
|
-
}),
|
|
37
|
-
},
|
|
38
|
-
},
|
|
30
|
+
getAndroidId: vi
|
|
31
|
+
.fn()
|
|
32
|
+
.mockResolvedValue({ androidId: mockAndroidId }),
|
|
39
33
|
},
|
|
40
34
|
writable: true,
|
|
41
35
|
configurable: true,
|
|
@@ -46,17 +40,11 @@ describe("getDeviceId", () => {
|
|
|
46
40
|
})
|
|
47
41
|
|
|
48
42
|
it("returns null when DeviceInfo throws error", async () => {
|
|
49
|
-
Object.defineProperty(window, "
|
|
43
|
+
Object.defineProperty(window, "DeviceInfo", {
|
|
50
44
|
value: {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.fn()
|
|
55
|
-
.mockRejectedValue(
|
|
56
|
-
new Error("DeviceInfo error")
|
|
57
|
-
),
|
|
58
|
-
},
|
|
59
|
-
},
|
|
45
|
+
getAndroidId: vi
|
|
46
|
+
.fn()
|
|
47
|
+
.mockRejectedValue(new Error("DeviceInfo error")),
|
|
60
48
|
},
|
|
61
49
|
writable: true,
|
|
62
50
|
configurable: true,
|
|
@@ -67,15 +55,9 @@ describe("getDeviceId", () => {
|
|
|
67
55
|
})
|
|
68
56
|
|
|
69
57
|
it("returns null when DeviceInfo returns empty", async () => {
|
|
70
|
-
Object.defineProperty(window, "
|
|
58
|
+
Object.defineProperty(window, "DeviceInfo", {
|
|
71
59
|
value: {
|
|
72
|
-
|
|
73
|
-
DeviceInfo: {
|
|
74
|
-
getAndroidId: vi
|
|
75
|
-
.fn()
|
|
76
|
-
.mockResolvedValue({ androidId: "" }),
|
|
77
|
-
},
|
|
78
|
-
},
|
|
60
|
+
getAndroidId: vi.fn().mockResolvedValue({ androidId: "" }),
|
|
79
61
|
},
|
|
80
62
|
writable: true,
|
|
81
63
|
configurable: true,
|
|
@@ -166,25 +148,11 @@ describe("getDeviceId", () => {
|
|
|
166
148
|
})
|
|
167
149
|
|
|
168
150
|
describe("MOBILE platform", () => {
|
|
169
|
-
it("returns device ID from
|
|
170
|
-
const mockDeviceId = "
|
|
171
|
-
Object.defineProperty(window, "
|
|
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", {
|
|
151
|
+
it("returns device ID from NativeBridge when available", async () => {
|
|
152
|
+
const mockDeviceId = "native-device-123"
|
|
153
|
+
Object.defineProperty(window, "NativeBridge", {
|
|
186
154
|
value: {
|
|
187
|
-
|
|
155
|
+
getDeviceId: vi.fn().mockResolvedValue(mockDeviceId),
|
|
188
156
|
},
|
|
189
157
|
writable: true,
|
|
190
158
|
configurable: true,
|
|
@@ -194,35 +162,12 @@ describe("getDeviceId", () => {
|
|
|
194
162
|
expect(result).toBe(mockDeviceId)
|
|
195
163
|
})
|
|
196
164
|
|
|
197
|
-
it("
|
|
198
|
-
Object.defineProperty(window, "
|
|
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", {
|
|
165
|
+
it("returns null when NativeBridge throws error", async () => {
|
|
166
|
+
Object.defineProperty(window, "NativeBridge", {
|
|
224
167
|
value: {
|
|
225
|
-
|
|
168
|
+
getDeviceId: vi
|
|
169
|
+
.fn()
|
|
170
|
+
.mockRejectedValue(new Error("Native error")),
|
|
226
171
|
},
|
|
227
172
|
writable: true,
|
|
228
173
|
configurable: true,
|
package/src/getDeviceId.ts
CHANGED
|
@@ -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:
|
|
10
|
+
* - Android/iOS: NativeBridge
|
|
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', '
|
|
15
|
+
* @param platform - Platform identifier ('FIRE_TV', 'SAMSUNG_TV', 'LG_TV', '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,9 +36,7 @@ export async function getDeviceId(
|
|
|
36
36
|
return await getSamsungDeviceId(logger)
|
|
37
37
|
case "LG_TV":
|
|
38
38
|
return await getLGDeviceId(logger)
|
|
39
|
-
case "
|
|
40
|
-
case "IOS_MOBILE":
|
|
41
|
-
case "MOBILE": // Legacy/Generic
|
|
39
|
+
case "MOBILE": // iOS and Android
|
|
42
40
|
return await getMobileDeviceId(logger)
|
|
43
41
|
case "WEB":
|
|
44
42
|
return getLocalStorageDeviceId()
|
|
@@ -66,62 +64,44 @@ function getLocalStorageDeviceId(): string {
|
|
|
66
64
|
*/
|
|
67
65
|
async function getFireTVDeviceId(logger: Logger): Promise<string | null> {
|
|
68
66
|
try {
|
|
69
|
-
|
|
70
|
-
const Capacitor = (window as any).Capacitor
|
|
71
|
-
const DeviceInfo = Capacitor?.Plugins?.DeviceInfo
|
|
72
|
-
|
|
73
|
-
if (!DeviceInfo?.getAndroidId) {
|
|
67
|
+
if (!window.Capacitor?.Plugins?.DeviceInfo?.getAndroidId) {
|
|
74
68
|
return null
|
|
75
69
|
}
|
|
76
70
|
|
|
77
|
-
const result = await DeviceInfo.getAndroidId()
|
|
71
|
+
const result = await window.Capacitor.Plugins.DeviceInfo.getAndroidId()
|
|
78
72
|
if (result?.androidId && result.androidId.trim()) {
|
|
79
73
|
return result.androidId
|
|
80
74
|
} else {
|
|
81
|
-
logger.error(
|
|
75
|
+
logger.error(
|
|
76
|
+
"Capacitor.Plugins.DeviceInfo.getAndroidId returned empty"
|
|
77
|
+
)
|
|
82
78
|
}
|
|
83
79
|
} catch (error) {
|
|
84
|
-
const message = "DeviceInfo.getAndroidId failed:"
|
|
80
|
+
const message = "Capacitor.Plugins.DeviceInfo.getAndroidId failed:"
|
|
85
81
|
logger.error(message, { error })
|
|
86
82
|
}
|
|
87
83
|
return null
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
/**
|
|
91
|
-
* Attempt to get device ID from
|
|
92
|
-
*
|
|
93
|
-
* iOS exposes phoneDeviceId via window.iosAppContext
|
|
94
|
-
* Android exposes phoneDeviceId via window.androidAppContext (also check window.top for iframes)
|
|
87
|
+
* Attempt to get device ID from Native Bridge (Android/iOS)
|
|
95
88
|
*/
|
|
96
|
-
function getMobileDeviceId(logger: Logger): string | null {
|
|
97
|
-
// Try iOS first
|
|
98
|
-
const iosId = (window as ExtendedWindow).iosAppContext?.phoneDeviceId
|
|
99
|
-
if (iosId && iosId.trim()) {
|
|
100
|
-
return iosId
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Try Android (check both window and window.top for iframe scenarios)
|
|
104
|
-
const androidContext = (window as ExtendedWindow).androidAppContext
|
|
89
|
+
async function getMobileDeviceId(logger: Logger): Promise<string | null> {
|
|
105
90
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
(window.top as ExtendedWindow | null)?.androidAppContext
|
|
109
|
-
?.phoneDeviceId
|
|
110
|
-
if (androidId && androidId.trim()) {
|
|
111
|
-
return androidId
|
|
91
|
+
if (!window.NativeBridge?.getDeviceId) {
|
|
92
|
+
return null
|
|
112
93
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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")
|
|
118
100
|
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const message = "NativeBridge.getDeviceId failed:"
|
|
103
|
+
logger.error(message, { error })
|
|
119
104
|
}
|
|
120
|
-
|
|
121
|
-
logger.error("[VWR:MobileDeviceId] phoneDeviceId not found", {
|
|
122
|
-
iosAppContext: !!(window as ExtendedWindow).iosAppContext,
|
|
123
|
-
androidAppContext: !!androidContext,
|
|
124
|
-
})
|
|
125
105
|
return null
|
|
126
106
|
}
|
|
127
107
|
|
|
@@ -229,23 +209,18 @@ interface LGDeviceIdResponse {
|
|
|
229
209
|
}>
|
|
230
210
|
}
|
|
231
211
|
|
|
232
|
-
/**
|
|
233
|
-
* Extended window interface for mobile app context
|
|
234
|
-
* Matches the types from @volley/sdk ExtendedWindow.ts
|
|
235
|
-
*/
|
|
236
|
-
interface ExtendedWindow extends Window {
|
|
237
|
-
iosAppContext?: {
|
|
238
|
-
phoneDeviceId: string
|
|
239
|
-
}
|
|
240
|
-
androidAppContext?: {
|
|
241
|
-
phoneDeviceId: string
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
212
|
// Type augmentation for platform-specific APIs
|
|
246
|
-
// Note: Capacitor types are declared in getShellVersion.ts with
|
|
213
|
+
// Note: Capacitor types are declared in getShellVersion.ts with index signatures
|
|
214
|
+
// to allow extensibility. Additional methods used here are accessed via
|
|
215
|
+
// the index signatures [key: string]: any
|
|
247
216
|
declare global {
|
|
248
217
|
interface Window {
|
|
218
|
+
DeviceInfo?: {
|
|
219
|
+
getAndroidId: () => Promise<{ androidId: string }>
|
|
220
|
+
}
|
|
221
|
+
NativeBridge?: {
|
|
222
|
+
getDeviceId: () => Promise<string>
|
|
223
|
+
}
|
|
249
224
|
webapis?: {
|
|
250
225
|
productinfo?: {
|
|
251
226
|
getDuid: () => string
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { defaultLogger, type Logger } from "./logger"
|
|
2
|
+
|
|
3
|
+
export type Environment = "local" | "dev" | "staging" | "prod"
|
|
4
|
+
export type EnvironmentSource = "native" | "build-time" | "default"
|
|
5
|
+
|
|
6
|
+
export interface EnvironmentResult {
|
|
7
|
+
environment: Environment
|
|
8
|
+
source: EnvironmentSource
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the environment for the current platform.
|
|
13
|
+
*
|
|
14
|
+
* Precedence:
|
|
15
|
+
* 1. Native shell (Fire TV only) - reads from BuildConfig.ENVIRONMENT
|
|
16
|
+
* 2. Build-time injection (VITE_ENVIRONMENT) - CLI override
|
|
17
|
+
* 3. Default fallback ("dev")
|
|
18
|
+
*
|
|
19
|
+
* This ensures production APKs cannot accidentally run dev VWR,
|
|
20
|
+
* since the native Android build variant determines the environment.
|
|
21
|
+
*
|
|
22
|
+
* @param platform - Platform identifier ('FIRE_TV', 'SAMSUNG_TV', 'LG_TV', 'MOBILE', 'WEB')
|
|
23
|
+
* @param buildTimeEnv - Optional build-time injected environment (from CLI)
|
|
24
|
+
* @param logger - Optional logger for reporting
|
|
25
|
+
* @returns Environment result with source information for logging
|
|
26
|
+
*/
|
|
27
|
+
export async function getEnvironment(
|
|
28
|
+
platform: string,
|
|
29
|
+
buildTimeEnv: string | undefined,
|
|
30
|
+
logger: Logger = defaultLogger
|
|
31
|
+
): Promise<EnvironmentResult> {
|
|
32
|
+
const normalizedPlatform = platform.toUpperCase()
|
|
33
|
+
|
|
34
|
+
// For Fire TV, try to read from native first (safest - prevents env mismatch)
|
|
35
|
+
if (normalizedPlatform === "FIRE_TV") {
|
|
36
|
+
const nativeEnv = await getFireTVEnvironment(logger)
|
|
37
|
+
if (nativeEnv) {
|
|
38
|
+
const mapped = mapNativeEnvironment(nativeEnv)
|
|
39
|
+
logger.info(
|
|
40
|
+
`[Shell] Environment: "${mapped}" (source: native BuildConfig.ENVIRONMENT="${nativeEnv}")`
|
|
41
|
+
)
|
|
42
|
+
return { environment: mapped, source: "native" }
|
|
43
|
+
}
|
|
44
|
+
// Fall through to build-time if native fails
|
|
45
|
+
logger.warn(
|
|
46
|
+
"[Shell] Failed to read environment from native, falling back to build-time"
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Build-time injection (CLI --env flag)
|
|
51
|
+
if (buildTimeEnv && isValidEnvironment(buildTimeEnv)) {
|
|
52
|
+
logger.info(
|
|
53
|
+
`[Shell] Environment: "${buildTimeEnv}" (source: build-time CLI injection)`
|
|
54
|
+
)
|
|
55
|
+
return {
|
|
56
|
+
environment: buildTimeEnv as Environment,
|
|
57
|
+
source: "build-time",
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Default fallback
|
|
62
|
+
const defaultEnv: Environment = "dev"
|
|
63
|
+
logger.warn(
|
|
64
|
+
`[Shell] Environment: "${defaultEnv}" (source: default fallback - no native or build-time env found)`
|
|
65
|
+
)
|
|
66
|
+
return { environment: defaultEnv, source: "default" }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Read environment from Fire TV native shell via Capacitor plugin.
|
|
71
|
+
*/
|
|
72
|
+
async function getFireTVEnvironment(logger: Logger): Promise<string | null> {
|
|
73
|
+
try {
|
|
74
|
+
if (
|
|
75
|
+
!window.Capacitor?.Plugins?.DeviceInfo?.getNativeShellAppEnvironment
|
|
76
|
+
) {
|
|
77
|
+
logger.warn(
|
|
78
|
+
"[Shell] DeviceInfo.getNativeShellAppEnvironment not available"
|
|
79
|
+
)
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result =
|
|
84
|
+
await window.Capacitor.Plugins.DeviceInfo.getNativeShellAppEnvironment()
|
|
85
|
+
if (result?.environment && result.environment.trim()) {
|
|
86
|
+
return result.environment
|
|
87
|
+
} else {
|
|
88
|
+
logger.error(
|
|
89
|
+
"[Shell] DeviceInfo.getNativeShellAppEnvironment returned empty"
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logger.error(
|
|
94
|
+
"[Shell] DeviceInfo.getNativeShellAppEnvironment failed:",
|
|
95
|
+
{ error }
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Map native Android BuildConfig.ENVIRONMENT values to vwr-loader environment names.
|
|
103
|
+
*
|
|
104
|
+
* Android BuildConfig values:
|
|
105
|
+
* - "development" (debug build)
|
|
106
|
+
* - "staging" (staging build)
|
|
107
|
+
* - "production" (release build)
|
|
108
|
+
*
|
|
109
|
+
* VWR environment names:
|
|
110
|
+
* - "dev"
|
|
111
|
+
* - "staging"
|
|
112
|
+
* - "prod"
|
|
113
|
+
*/
|
|
114
|
+
function mapNativeEnvironment(nativeEnv: string): Environment {
|
|
115
|
+
const normalized = nativeEnv.toLowerCase()
|
|
116
|
+
switch (normalized) {
|
|
117
|
+
case "development":
|
|
118
|
+
return "dev"
|
|
119
|
+
case "staging":
|
|
120
|
+
return "staging"
|
|
121
|
+
case "production":
|
|
122
|
+
return "prod"
|
|
123
|
+
default:
|
|
124
|
+
// If it's already in vwr format, use it
|
|
125
|
+
if (isValidEnvironment(normalized)) {
|
|
126
|
+
return normalized as Environment
|
|
127
|
+
}
|
|
128
|
+
return "dev"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isValidEnvironment(env: string): boolean {
|
|
133
|
+
return ["local", "dev", "staging", "prod"].includes(env)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Note: Window.Capacitor type is declared in getDeviceId.ts
|
|
137
|
+
// We just need to add the getNativeShellAppEnvironment method type there
|
|
@@ -268,7 +268,7 @@ describe("getShellVersion", () => {
|
|
|
268
268
|
expect(version).toBe("unknown")
|
|
269
269
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
270
270
|
"[Shell] Failed to get shell version:",
|
|
271
|
-
|
|
271
|
+
expect.any(Error)
|
|
272
272
|
)
|
|
273
273
|
|
|
274
274
|
consoleWarnSpy.mockRestore()
|
package/src/getShellVersion.ts
CHANGED
|
@@ -7,10 +7,10 @@ import { defaultLogger, type Logger } from "./logger"
|
|
|
7
7
|
* - SAMSUNG_TV: Tizen Application API `tizen.application.getAppInfo().version`
|
|
8
8
|
* - LG_TV: webOS Application API `webOS.fetchAppInfo()`
|
|
9
9
|
* - FIRE_TV: Native bridge via Capacitor DeviceInfo plugin
|
|
10
|
-
* -
|
|
10
|
+
* - MOBILE: Native bridge (future implementation)
|
|
11
11
|
* - WEB/Unknown: Returns 'unknown'
|
|
12
12
|
*
|
|
13
|
-
* @param platform - Platform identifier (SAMSUNG_TV, LG_TV, FIRE_TV,
|
|
13
|
+
* @param platform - Platform identifier (SAMSUNG_TV, LG_TV, FIRE_TV, MOBILE, WEB)
|
|
14
14
|
* @param logger - Optional logger for warning reporting. Defaults to defaultLogger.
|
|
15
15
|
* @returns Shell version string or 'unknown' if not available
|
|
16
16
|
*/
|
|
@@ -66,11 +66,7 @@ export async function getShellVersion(
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Mobile: Future implementation via native bridge
|
|
69
|
-
if (
|
|
70
|
-
normalizedPlatform === "ANDROID_MOBILE" ||
|
|
71
|
-
normalizedPlatform === "IOS_MOBILE" ||
|
|
72
|
-
normalizedPlatform === "MOBILE"
|
|
73
|
-
) {
|
|
69
|
+
if (normalizedPlatform === "MOBILE") {
|
|
74
70
|
// TODO: Implement native bridge version retrieval when available
|
|
75
71
|
return "unknown"
|
|
76
72
|
}
|
package/src/loadVwr.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import type * as VWR from "@volley/vwr"
|
|
2
|
-
|
|
3
1
|
import { fetchAmplitudeFlags } from "./amplitudeFlagFetcher"
|
|
4
2
|
import { ENV_DEFAULTS } from "./envDefaults"
|
|
5
3
|
import { getDeviceId } from "./getDeviceId"
|
|
4
|
+
import { getEnvironment } from "./getEnvironment"
|
|
6
5
|
import { getShellVersion } from "./getShellVersion"
|
|
7
6
|
import { defaultLogger, type Logger } from "./logger"
|
|
8
7
|
import type { VWRConfig, VWRConfigRequest } from "./vwrConfig"
|
|
9
8
|
import { getVWRConfig, validateConfig } from "./vwrConfig"
|
|
10
9
|
|
|
11
10
|
// Vite injects these at build time
|
|
12
|
-
|
|
11
|
+
// VITE_ENVIRONMENT is optional for platforms that support native env reading (Fire TV)
|
|
12
|
+
const BUILD_TIME_ENVIRONMENT = import.meta.env.VITE_ENVIRONMENT as
|
|
13
|
+
| string
|
|
14
|
+
| undefined
|
|
13
15
|
const PLATFORM = import.meta.env.VITE_PLATFORM
|
|
14
16
|
const CONFIG_URL = import.meta.env.VITE_CONFIG_URL
|
|
15
17
|
const CONFIG_FILE = import.meta.env.VITE_CONFIG_FILE
|
|
@@ -28,15 +30,20 @@ export type { Logger }
|
|
|
28
30
|
* this reduces startup latency from ~2-4s to ~500ms by skipping config fetches.
|
|
29
31
|
*
|
|
30
32
|
* Flow:
|
|
31
|
-
* 1. Get deviceId, shellVersion (fast, local)
|
|
33
|
+
* 1. Get deviceId, shellVersion, environment (fast, local/native)
|
|
32
34
|
* 2. Check vwr-enabled flag (single request, ~500ms)
|
|
33
35
|
* 3. If OFF → throw immediately (no config fetches)
|
|
34
36
|
* 4. If ON → fetch config, load VWR module, initialize
|
|
35
37
|
*
|
|
38
|
+
* Environment resolution:
|
|
39
|
+
* - Fire TV: reads from native BuildConfig.ENVIRONMENT (prevents env mismatch)
|
|
40
|
+
* - Other platforms: uses build-time injected VITE_ENVIRONMENT
|
|
41
|
+
* - Fallback: "dev"
|
|
42
|
+
*
|
|
36
43
|
* @param logger - Optional logger, defaults to console
|
|
37
44
|
*/
|
|
38
45
|
export const loadVwr = async (logger: Logger = defaultLogger) => {
|
|
39
|
-
if (!
|
|
46
|
+
if (!PLATFORM || !CONFIG_URL || !CONFIG_FILE) {
|
|
40
47
|
throw new Error("[Shell] Build config not injected properly")
|
|
41
48
|
}
|
|
42
49
|
|
|
@@ -48,6 +55,14 @@ export const loadVwr = async (logger: Logger = defaultLogger) => {
|
|
|
48
55
|
}
|
|
49
56
|
const shellVersion = await getShellVersion(PLATFORM, logger)
|
|
50
57
|
|
|
58
|
+
// Get environment from native (Fire TV) or build-time injection
|
|
59
|
+
// This ensures prod APKs cannot accidentally run dev VWR
|
|
60
|
+
const { environment: ENVIRONMENT } = await getEnvironment(
|
|
61
|
+
PLATFORM,
|
|
62
|
+
BUILD_TIME_ENVIRONMENT,
|
|
63
|
+
logger
|
|
64
|
+
)
|
|
65
|
+
|
|
51
66
|
// Get amplitude key: injected at build time OR from envDefaults
|
|
52
67
|
// Precedence: build-time injection > envDefaults
|
|
53
68
|
let amplitudeKey = AMPLITUDE_KEY
|
|
@@ -100,10 +115,7 @@ export const loadVwr = async (logger: Logger = defaultLogger) => {
|
|
|
100
115
|
vwrUrl: vwrConfig.vwrUrl,
|
|
101
116
|
})
|
|
102
117
|
try {
|
|
103
|
-
const vwr =
|
|
104
|
-
/* @vite-ignore */ vwrConfig.vwrUrl
|
|
105
|
-
)) as typeof VWR
|
|
106
|
-
|
|
118
|
+
const vwr = await import(/* @vite-ignore */ vwrConfig.vwrUrl)
|
|
107
119
|
logger.info("[Shell] VWR module loaded successfully")
|
|
108
120
|
|
|
109
121
|
if (typeof vwr.init !== "function") {
|
|
@@ -112,12 +124,11 @@ export const loadVwr = async (logger: Logger = defaultLogger) => {
|
|
|
112
124
|
|
|
113
125
|
await vwr.init({
|
|
114
126
|
hubUrl: vwrConfig.hubUrl,
|
|
127
|
+
launchUrl: vwrConfig.launchUrl,
|
|
115
128
|
platform: PLATFORM,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
platformAuthApiUrl: vwrConfig.platformAuthApiUrl,
|
|
120
|
-
trustedOrigins: new Set(vwrConfig.trustedDomains),
|
|
129
|
+
deviceId,
|
|
130
|
+
environment: ENVIRONMENT,
|
|
131
|
+
trustedDomains: vwrConfig.trustedDomains,
|
|
121
132
|
nativeShellVersion: shellVersion,
|
|
122
133
|
})
|
|
123
134
|
logger.info("[Shell] VWR initialized successfully")
|
package/src/main.ts
CHANGED
|
@@ -18,9 +18,7 @@ async function init() {
|
|
|
18
18
|
}
|
|
19
19
|
} finally {
|
|
20
20
|
//Fallback, can't trust config exists
|
|
21
|
-
|
|
22
|
-
fallbackUrl.searchParams.set("volley_platform", PLATFORM)
|
|
23
|
-
window.location.href = fallbackUrl.toString()
|
|
21
|
+
window.location.href = HUB_URL
|
|
24
22
|
}
|
|
25
23
|
}
|
|
26
24
|
}
|
package/src/vite-env.d.ts
CHANGED
|
@@ -8,9 +8,6 @@ interface ImportMetaEnv {
|
|
|
8
8
|
readonly VITE_SHELL_VERSION: string
|
|
9
9
|
readonly VITE_CONFIG_URL: string
|
|
10
10
|
readonly VITE_CONFIG_FILE: string
|
|
11
|
-
readonly VITE_PLATFORM_API_URL: string
|
|
12
|
-
readonly VITE_PLATFORM_AUTH_API_URL: string
|
|
13
|
-
readonly VITE_TRUSTED_ORIGINS: string
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
interface ImportMeta {
|
package/src/vwrConfig.ts
CHANGED
|
@@ -5,8 +5,6 @@ export type VWRConfig = {
|
|
|
5
5
|
hubUrl: string
|
|
6
6
|
vwrUrl: string
|
|
7
7
|
launchUrl: string | undefined
|
|
8
|
-
platformApiUrl: string
|
|
9
|
-
platformAuthApiUrl: string
|
|
10
8
|
trustedDomains: Array<string>
|
|
11
9
|
}
|
|
12
10
|
|
|
@@ -234,14 +232,6 @@ const tryGetVWRConfig = async (
|
|
|
234
232
|
const parseConfig = (config: VWRConfig): VWRConfig => {
|
|
235
233
|
const defaultConfig = getDefaultConfig()
|
|
236
234
|
|
|
237
|
-
if (!config.platformApiUrl) {
|
|
238
|
-
config.platformApiUrl = defaultConfig.platformApiUrl
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (!config.platformAuthApiUrl) {
|
|
242
|
-
config.platformAuthApiUrl = defaultConfig.platformAuthApiUrl
|
|
243
|
-
}
|
|
244
|
-
|
|
245
235
|
if (
|
|
246
236
|
!Array.isArray(config.trustedDomains) ||
|
|
247
237
|
config.trustedDomains.length === 0
|
|
@@ -251,26 +241,21 @@ const parseConfig = (config: VWRConfig): VWRConfig => {
|
|
|
251
241
|
|
|
252
242
|
if (!config.vwrUrl) {
|
|
253
243
|
config.vwrUrl = defaultConfig.vwrUrl
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
config.trustedDomains.push(vwrUrlOrigin)
|
|
244
|
+
if (!config.trustedDomains.includes(defaultConfig.vwrUrl)) {
|
|
245
|
+
config.trustedDomains.push(defaultConfig.vwrUrl)
|
|
257
246
|
}
|
|
258
247
|
}
|
|
259
248
|
|
|
260
249
|
if (!config.hubUrl) {
|
|
261
250
|
config.hubUrl = defaultConfig.hubUrl
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
config.trustedDomains.push(hubUrlOrigin)
|
|
251
|
+
if (!config.trustedDomains.includes(defaultConfig.hubUrl)) {
|
|
252
|
+
config.trustedDomains.push(defaultConfig.hubUrl)
|
|
265
253
|
}
|
|
266
254
|
}
|
|
267
255
|
|
|
268
256
|
// launchUrl is optional - only add to trustedDomains if explicitly set
|
|
269
|
-
if (config.launchUrl) {
|
|
270
|
-
|
|
271
|
-
if (!config.trustedDomains.includes(launchUrlOrigin)) {
|
|
272
|
-
config.trustedDomains.push(launchUrlOrigin)
|
|
273
|
-
}
|
|
257
|
+
if (config.launchUrl && !config.trustedDomains.includes(config.launchUrl)) {
|
|
258
|
+
config.trustedDomains.push(config.launchUrl)
|
|
274
259
|
}
|
|
275
260
|
|
|
276
261
|
return config
|
|
@@ -303,12 +288,6 @@ const getDefaultConfig = (): VWRConfig => {
|
|
|
303
288
|
hubUrl: defaults.hubUrl,
|
|
304
289
|
vwrUrl: defaults.vwrUrl,
|
|
305
290
|
launchUrl: undefined,
|
|
306
|
-
|
|
307
|
-
platformAuthApiUrl: defaults.platformAuthApiUrl,
|
|
308
|
-
trustedDomains: [
|
|
309
|
-
new URL(defaults.hubUrl).origin,
|
|
310
|
-
new URL(defaults.vwrUrl).origin,
|
|
311
|
-
...(defaults.trustedOrigins ?? []),
|
|
312
|
-
],
|
|
291
|
+
trustedDomains: [defaults.hubUrl, defaults.vwrUrl],
|
|
313
292
|
}
|
|
314
293
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"include": [
|
|
4
|
+
"src/**/*",
|
|
5
|
+
"scripts/**/*",
|
|
6
|
+
"vite.config.ts",
|
|
7
|
+
"vitest.config.ts",
|
|
8
|
+
"**/*.test.ts"
|
|
9
|
+
],
|
|
10
|
+
"exclude": ["node_modules", "dist"],
|
|
11
|
+
"compilerOptions": {
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"types": ["node", "vite/client", "vitest/globals"]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|