@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.
- package/README.md +205 -0
- package/dist/amplitudeFlagFetcher.d.ts +23 -0
- package/dist/amplitudeFlagFetcher.d.ts.map +1 -0
- package/dist/amplitudeFlagFetcher.js +60 -0
- package/dist/amplitudeFlagFetcher.js.map +1 -0
- package/dist/cli.js +177 -0
- package/dist/cli.js.map +1 -0
- package/dist/envDefaults.d.ts +9 -0
- package/dist/envDefaults.d.ts.map +1 -0
- package/dist/envDefaults.js +36 -0
- package/dist/envDefaults.js.map +1 -0
- package/dist/getDeviceId.d.ts +65 -0
- package/dist/getDeviceId.d.ts.map +1 -0
- package/dist/getDeviceId.js +196 -0
- package/dist/getDeviceId.js.map +1 -0
- package/dist/getShellVersion.d.ts +34 -0
- package/dist/getShellVersion.d.ts.map +1 -0
- package/dist/getShellVersion.js +84 -0
- package/dist/getShellVersion.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.html +25 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/loadVwr.d.ts +19 -0
- package/dist/loadVwr.d.ts.map +1 -0
- package/dist/loadVwr.js +104 -0
- package/dist/loadVwr.js.map +1 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +6 -0
- package/dist/logger.js.map +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -0
- package/dist/vwrConfig.d.ts +19 -0
- package/dist/vwrConfig.d.ts.map +1 -0
- package/dist/vwrConfig.js +172 -0
- package/dist/vwrConfig.js.map +1 -0
- package/package.json +54 -0
- package/src/amplitudeFlagFetcher.test.ts +209 -0
- package/src/amplitudeFlagFetcher.ts +88 -0
- package/src/envDefaults.ts +45 -0
- package/src/getDeviceId.test.ts +237 -0
- package/src/getDeviceId.ts +243 -0
- package/src/getShellVersion.test.ts +278 -0
- package/src/getShellVersion.ts +114 -0
- package/src/index.html +25 -0
- package/src/index.ts +8 -0
- package/src/loadVwr.ts +126 -0
- package/src/logger.ts +14 -0
- package/src/main.ts +26 -0
- package/src/vite-env.d.ts +15 -0
- package/src/vwrConfig.test.ts +316 -0
- package/src/vwrConfig.ts +293 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { getShellVersion } from "./getShellVersion"
|
|
4
|
+
|
|
5
|
+
describe("getShellVersion", () => {
|
|
6
|
+
const originalLocation = window.location
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Mock window.location
|
|
10
|
+
delete (window as any).location
|
|
11
|
+
window.location = {
|
|
12
|
+
...originalLocation,
|
|
13
|
+
href: "http://localhost:3000/",
|
|
14
|
+
} as Location
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
window.location = originalLocation
|
|
19
|
+
vi.restoreAllMocks()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe("Samsung TV", () => {
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
delete (window as any).tizen
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("returns version from Tizen Application API", async () => {
|
|
28
|
+
;(window as any).tizen = {
|
|
29
|
+
application: {
|
|
30
|
+
getAppInfo: vi.fn().mockReturnValue({ version: "1.2.3" }),
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const version = await getShellVersion("SAMSUNG_TV")
|
|
35
|
+
|
|
36
|
+
expect(version).toBe("1.2.3")
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("returns unknown when tizen is not available", async () => {
|
|
40
|
+
const version = await getShellVersion("SAMSUNG_TV")
|
|
41
|
+
|
|
42
|
+
expect(version).toBe("unknown")
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("returns unknown when version is missing from appInfo", async () => {
|
|
46
|
+
;(window as any).tizen = {
|
|
47
|
+
application: {
|
|
48
|
+
getAppInfo: vi.fn().mockReturnValue({}),
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const version = await getShellVersion("SAMSUNG_TV")
|
|
53
|
+
|
|
54
|
+
expect(version).toBe("unknown")
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("is case insensitive", async () => {
|
|
58
|
+
;(window as any).tizen = {
|
|
59
|
+
application: {
|
|
60
|
+
getAppInfo: vi.fn().mockReturnValue({ version: "2.0.0" }),
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const version = await getShellVersion("samsung_tv")
|
|
65
|
+
|
|
66
|
+
expect(version).toBe("2.0.0")
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe("LG TV", () => {
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
delete (window as any).webOS
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("returns version from webOS.fetchAppInfo", async () => {
|
|
76
|
+
;(window as any).webOS = {
|
|
77
|
+
fetchAppInfo: vi.fn((callback) => {
|
|
78
|
+
callback({ version: "3.4.5" })
|
|
79
|
+
}),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const version = await getShellVersion("LG_TV")
|
|
83
|
+
|
|
84
|
+
expect(version).toBe("3.4.5")
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("returns unknown when webOS is not available", async () => {
|
|
88
|
+
const version = await getShellVersion("LG_TV")
|
|
89
|
+
|
|
90
|
+
expect(version).toBe("unknown")
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("returns unknown when fetchAppInfo returns no version", async () => {
|
|
94
|
+
;(window as any).webOS = {
|
|
95
|
+
fetchAppInfo: vi.fn((callback) => {
|
|
96
|
+
callback({})
|
|
97
|
+
}),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const version = await getShellVersion("LG_TV")
|
|
101
|
+
|
|
102
|
+
expect(version).toBe("unknown")
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it("returns unknown when fetchAppInfo callback receives undefined", async () => {
|
|
106
|
+
;(window as any).webOS = {
|
|
107
|
+
fetchAppInfo: vi.fn((callback) => {
|
|
108
|
+
callback(undefined)
|
|
109
|
+
}),
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const version = await getShellVersion("LG_TV")
|
|
113
|
+
|
|
114
|
+
expect(version).toBe("unknown")
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it("is case insensitive", async () => {
|
|
118
|
+
;(window as any).webOS = {
|
|
119
|
+
fetchAppInfo: vi.fn((callback) => {
|
|
120
|
+
callback({ version: "4.0.0" })
|
|
121
|
+
}),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const version = await getShellVersion("lg_tv")
|
|
125
|
+
|
|
126
|
+
expect(version).toBe("4.0.0")
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
describe("FireTV", () => {
|
|
131
|
+
it("returns version from Capacitor DeviceInfo plugin", async () => {
|
|
132
|
+
const mockGetDeviceInfo = vi.fn().mockResolvedValue({
|
|
133
|
+
versionName: "5.6.7",
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
;(window as any).Capacitor = {
|
|
137
|
+
Plugins: {
|
|
138
|
+
DeviceInfo: {
|
|
139
|
+
getDeviceInfo: mockGetDeviceInfo,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const version = await getShellVersion("FIRE_TV")
|
|
145
|
+
|
|
146
|
+
expect(version).toBe("5.6.7")
|
|
147
|
+
expect(mockGetDeviceInfo).toHaveBeenCalled()
|
|
148
|
+
|
|
149
|
+
delete (window as any).Capacitor
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it("returns unknown when Capacitor is not available", async () => {
|
|
153
|
+
const version = await getShellVersion("FIRE_TV")
|
|
154
|
+
|
|
155
|
+
expect(version).toBe("unknown")
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it("returns unknown when DeviceInfo plugin is not available", async () => {
|
|
159
|
+
;(window as any).Capacitor = {
|
|
160
|
+
Plugins: {},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const version = await getShellVersion("FIRE_TV")
|
|
164
|
+
|
|
165
|
+
expect(version).toBe("unknown")
|
|
166
|
+
|
|
167
|
+
delete (window as any).Capacitor
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it("returns unknown when getDeviceInfo is not available", async () => {
|
|
171
|
+
;(window as any).Capacitor = {
|
|
172
|
+
Plugins: {
|
|
173
|
+
DeviceInfo: {},
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const version = await getShellVersion("FIRE_TV")
|
|
178
|
+
|
|
179
|
+
expect(version).toBe("unknown")
|
|
180
|
+
|
|
181
|
+
delete (window as any).Capacitor
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it("returns unknown when versionName is missing", async () => {
|
|
185
|
+
const mockGetDeviceInfo = vi.fn().mockResolvedValue({})
|
|
186
|
+
|
|
187
|
+
;(window as any).Capacitor = {
|
|
188
|
+
Plugins: {
|
|
189
|
+
DeviceInfo: {
|
|
190
|
+
getDeviceInfo: mockGetDeviceInfo,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const version = await getShellVersion("FIRE_TV")
|
|
196
|
+
|
|
197
|
+
expect(version).toBe("unknown")
|
|
198
|
+
|
|
199
|
+
delete (window as any).Capacitor
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it("is case insensitive", async () => {
|
|
203
|
+
const mockGetDeviceInfo = vi.fn().mockResolvedValue({
|
|
204
|
+
versionName: "6.0.0",
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
;(window as any).Capacitor = {
|
|
208
|
+
Plugins: {
|
|
209
|
+
DeviceInfo: {
|
|
210
|
+
getDeviceInfo: mockGetDeviceInfo,
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const version = await getShellVersion("fire_tv")
|
|
216
|
+
|
|
217
|
+
expect(version).toBe("6.0.0")
|
|
218
|
+
|
|
219
|
+
delete (window as any).Capacitor
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe("Mobile", () => {
|
|
224
|
+
it("returns unknown (not yet implemented)", async () => {
|
|
225
|
+
const version = await getShellVersion("MOBILE")
|
|
226
|
+
|
|
227
|
+
expect(version).toBe("unknown")
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
describe("Web and Unknown Platforms", () => {
|
|
232
|
+
it("returns unknown for web platform", async () => {
|
|
233
|
+
const version = await getShellVersion("WEB")
|
|
234
|
+
|
|
235
|
+
expect(version).toBe("unknown")
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it("returns unknown for unknown platform", async () => {
|
|
239
|
+
const version = await getShellVersion("unknown-platform")
|
|
240
|
+
|
|
241
|
+
expect(version).toBe("unknown")
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it("returns unknown for empty string", async () => {
|
|
245
|
+
const version = await getShellVersion("")
|
|
246
|
+
|
|
247
|
+
expect(version).toBe("unknown")
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
describe("Error Handling", () => {
|
|
252
|
+
it("returns unknown and logs warning on error", async () => {
|
|
253
|
+
const consoleWarnSpy = vi
|
|
254
|
+
.spyOn(console, "warn")
|
|
255
|
+
.mockImplementation(() => {})
|
|
256
|
+
|
|
257
|
+
// Force an error by making tizen.application.getAppInfo throw
|
|
258
|
+
;(window as any).tizen = {
|
|
259
|
+
application: {
|
|
260
|
+
getAppInfo: vi.fn(() => {
|
|
261
|
+
throw new Error("Tizen API error")
|
|
262
|
+
}),
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const version = await getShellVersion("SAMSUNG_TV")
|
|
267
|
+
|
|
268
|
+
expect(version).toBe("unknown")
|
|
269
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
270
|
+
"[Shell] Failed to get shell version:",
|
|
271
|
+
{ error: expect.any(Error) }
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
consoleWarnSpy.mockRestore()
|
|
275
|
+
delete (window as any).tizen
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { defaultLogger, type Logger } from "./logger"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Retrieve shell app version for different platforms.
|
|
5
|
+
*
|
|
6
|
+
* Platform-specific methods:
|
|
7
|
+
* - SAMSUNG_TV: Tizen Application API `tizen.application.getAppInfo().version`
|
|
8
|
+
* - LG_TV: webOS Application API `webOS.fetchAppInfo()`
|
|
9
|
+
* - FIRE_TV: Native bridge via Capacitor DeviceInfo plugin
|
|
10
|
+
* - ANDROID_MOBILE/IOS_MOBILE: Native bridge (future implementation)
|
|
11
|
+
* - WEB/Unknown: Returns 'unknown'
|
|
12
|
+
*
|
|
13
|
+
* @param platform - Platform identifier (SAMSUNG_TV, LG_TV, FIRE_TV, ANDROID_MOBILE, IOS_MOBILE, WEB)
|
|
14
|
+
* @param logger - Optional logger for warning reporting. Defaults to defaultLogger.
|
|
15
|
+
* @returns Shell version string or 'unknown' if not available
|
|
16
|
+
*/
|
|
17
|
+
export async function getShellVersion(
|
|
18
|
+
platform: string,
|
|
19
|
+
logger: Logger = defaultLogger
|
|
20
|
+
): Promise<string> {
|
|
21
|
+
try {
|
|
22
|
+
const normalizedPlatform = platform.toUpperCase()
|
|
23
|
+
|
|
24
|
+
// Samsung TV: Get version from Tizen Application API
|
|
25
|
+
if (normalizedPlatform === "SAMSUNG_TV") {
|
|
26
|
+
if (typeof window !== "undefined" && (window as any).tizen) {
|
|
27
|
+
const tizen = (window as any).tizen
|
|
28
|
+
if (tizen.application?.getAppInfo) {
|
|
29
|
+
const appInfo = tizen.application.getAppInfo()
|
|
30
|
+
return appInfo?.version ?? "unknown"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return "unknown"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// LG TV: Get version from webOS API
|
|
37
|
+
// Note: webOS.fetchAppInfo(callback, path) takes a single callback and optional path.
|
|
38
|
+
// The callback receives the parsed appinfo.json or undefined on failure.
|
|
39
|
+
if (normalizedPlatform === "LG_TV") {
|
|
40
|
+
if (typeof window !== "undefined" && (window as any).webOS) {
|
|
41
|
+
const webOS = (window as any).webOS
|
|
42
|
+
if (webOS.fetchAppInfo) {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
webOS.fetchAppInfo((appInfo?: { version?: string }) => {
|
|
45
|
+
resolve(appInfo?.version ?? "unknown")
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return "unknown"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// FireTV: Get version from Capacitor DeviceInfo plugin
|
|
54
|
+
if (normalizedPlatform === "FIRE_TV") {
|
|
55
|
+
// Check if Capacitor is available
|
|
56
|
+
if (typeof window !== "undefined" && (window as any).Capacitor) {
|
|
57
|
+
const Capacitor = (window as any).Capacitor
|
|
58
|
+
const DeviceInfo = Capacitor.Plugins?.DeviceInfo
|
|
59
|
+
|
|
60
|
+
if (DeviceInfo?.getDeviceInfo) {
|
|
61
|
+
const deviceInfo = await DeviceInfo.getDeviceInfo()
|
|
62
|
+
return deviceInfo?.versionName ?? "unknown"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return "unknown"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Mobile: Future implementation via native bridge
|
|
69
|
+
if (
|
|
70
|
+
normalizedPlatform === "ANDROID_MOBILE" ||
|
|
71
|
+
normalizedPlatform === "IOS_MOBILE" ||
|
|
72
|
+
normalizedPlatform === "MOBILE"
|
|
73
|
+
) {
|
|
74
|
+
// TODO: Implement native bridge version retrieval when available
|
|
75
|
+
return "unknown"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Web platform: Check for injected version at build time
|
|
79
|
+
if (normalizedPlatform === "WEB") {
|
|
80
|
+
if (
|
|
81
|
+
typeof import.meta.env !== "undefined" &&
|
|
82
|
+
import.meta.env.VITE_SHELL_VERSION
|
|
83
|
+
) {
|
|
84
|
+
return import.meta.env.VITE_SHELL_VERSION
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Other unknown platforms
|
|
89
|
+
return "unknown"
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const message = "[Shell] Failed to get shell version:"
|
|
92
|
+
logger.warn(message, { error })
|
|
93
|
+
return "unknown"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Global type declarations for platform APIs
|
|
98
|
+
declare global {
|
|
99
|
+
interface Window {
|
|
100
|
+
Capacitor?: {
|
|
101
|
+
Plugins?: {
|
|
102
|
+
DeviceInfo?: {
|
|
103
|
+
getDeviceInfo(): Promise<{
|
|
104
|
+
versionName?: string
|
|
105
|
+
[key: string]: any
|
|
106
|
+
}>
|
|
107
|
+
[key: string]: any
|
|
108
|
+
}
|
|
109
|
+
[key: string]: any
|
|
110
|
+
}
|
|
111
|
+
[key: string]: any
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/index.html
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Volley</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
background: #010020;
|
|
12
|
+
}
|
|
13
|
+
#loading {
|
|
14
|
+
color: #ffec37;
|
|
15
|
+
text-align: center;
|
|
16
|
+
padding-top: 40vh;
|
|
17
|
+
font-family: sans-serif;
|
|
18
|
+
}
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<div id="loading">Loading...</div>
|
|
23
|
+
<script type="module" src="./main.ts"></script>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { AmplitudeConfig, FlagResult } from "./amplitudeFlagFetcher"
|
|
2
|
+
export { fetchAmplitudeFlags } from "./amplitudeFlagFetcher"
|
|
3
|
+
export { getDeviceId } from "./getDeviceId"
|
|
4
|
+
export { getShellVersion } from "./getShellVersion"
|
|
5
|
+
export type { Logger } from "./loadVwr"
|
|
6
|
+
export { loadVwr } from "./loadVwr"
|
|
7
|
+
export type { VWRConfig, VWRConfigRequest } from "./vwrConfig"
|
|
8
|
+
export { getVWRConfig } from "./vwrConfig"
|
package/src/loadVwr.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { fetchAmplitudeFlags } from "./amplitudeFlagFetcher"
|
|
2
|
+
import { ENV_DEFAULTS } from "./envDefaults"
|
|
3
|
+
import { getDeviceId } from "./getDeviceId"
|
|
4
|
+
import { getShellVersion } from "./getShellVersion"
|
|
5
|
+
import { defaultLogger, type Logger } from "./logger"
|
|
6
|
+
import type { VWRConfig, VWRConfigRequest } from "./vwrConfig"
|
|
7
|
+
import { getVWRConfig, validateConfig } from "./vwrConfig"
|
|
8
|
+
|
|
9
|
+
// Vite injects these at build time
|
|
10
|
+
const ENVIRONMENT = import.meta.env.VITE_ENVIRONMENT
|
|
11
|
+
const PLATFORM = import.meta.env.VITE_PLATFORM
|
|
12
|
+
const CONFIG_URL = import.meta.env.VITE_CONFIG_URL
|
|
13
|
+
const CONFIG_FILE = import.meta.env.VITE_CONFIG_FILE
|
|
14
|
+
|
|
15
|
+
// AMPLITUDE_KEY can be optionally injected at build time (via build.ts script)
|
|
16
|
+
// Falls back to envDefaults if not injected (e.g., vite.config.dev.ts)
|
|
17
|
+
const AMPLITUDE_KEY = import.meta.env.VITE_AMPLITUDE_DEPLOYMENT_KEY
|
|
18
|
+
|
|
19
|
+
export type { Logger }
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load VWR runtime.
|
|
23
|
+
*
|
|
24
|
+
* Optimized flow: checks vwr-enabled flag FIRST using baked-in Amplitude key,
|
|
25
|
+
* before doing any config fetches. For flag-OFF users (100% at initial launch),
|
|
26
|
+
* this reduces startup latency from ~2-4s to ~500ms by skipping config fetches.
|
|
27
|
+
*
|
|
28
|
+
* Flow:
|
|
29
|
+
* 1. Get deviceId, shellVersion (fast, local)
|
|
30
|
+
* 2. Check vwr-enabled flag (single request, ~500ms)
|
|
31
|
+
* 3. If OFF → throw immediately (no config fetches)
|
|
32
|
+
* 4. If ON → fetch config, load VWR module, initialize
|
|
33
|
+
*
|
|
34
|
+
* @param logger - Optional logger, defaults to console
|
|
35
|
+
*/
|
|
36
|
+
export const loadVwr = async (logger: Logger = defaultLogger) => {
|
|
37
|
+
if (!ENVIRONMENT || !PLATFORM || !CONFIG_URL || !CONFIG_FILE) {
|
|
38
|
+
throw new Error("[Shell] Build config not injected properly")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const deviceId = await getDeviceId(PLATFORM, logger)
|
|
42
|
+
if (!deviceId) {
|
|
43
|
+
const message = `[Shell] Failed to retrieve device ID for platform: ${PLATFORM}`
|
|
44
|
+
logger.error(message)
|
|
45
|
+
throw new Error(message)
|
|
46
|
+
}
|
|
47
|
+
const shellVersion = await getShellVersion(PLATFORM, logger)
|
|
48
|
+
|
|
49
|
+
// Get amplitude key: injected at build time OR from envDefaults
|
|
50
|
+
// Precedence: build-time injection > envDefaults
|
|
51
|
+
let amplitudeKey = AMPLITUDE_KEY
|
|
52
|
+
if (!amplitudeKey) {
|
|
53
|
+
const envDefaults = ENV_DEFAULTS[ENVIRONMENT]
|
|
54
|
+
if (!envDefaults) {
|
|
55
|
+
throw new Error(`[Shell] Unknown environment: ${ENVIRONMENT}`)
|
|
56
|
+
}
|
|
57
|
+
amplitudeKey = envDefaults.amplitudeKey
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// FAST PATH: Check flag first using amplitude key
|
|
61
|
+
// This avoids config fetches for flag-OFF users (majority at launch)
|
|
62
|
+
const flags = await fetchAmplitudeFlags(
|
|
63
|
+
deviceId,
|
|
64
|
+
PLATFORM,
|
|
65
|
+
{
|
|
66
|
+
apiKey: amplitudeKey,
|
|
67
|
+
timeout: 2000,
|
|
68
|
+
},
|
|
69
|
+
shellVersion
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
logger.info("[Shell] Flags fetched", { flags })
|
|
73
|
+
|
|
74
|
+
if (!flags["vwr-enabled"]) {
|
|
75
|
+
// Flag OFF: skip all config fetches, fail fast
|
|
76
|
+
throw new Error("[Shell] VWR not enabled via Amplitude flags")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Flag ON: proceed with full config loading
|
|
80
|
+
logger.info("[Shell] VWR flag enabled, fetching config...")
|
|
81
|
+
|
|
82
|
+
const vwrConfigRequest: VWRConfigRequest = {
|
|
83
|
+
configUrl: CONFIG_URL,
|
|
84
|
+
configFile: CONFIG_FILE,
|
|
85
|
+
platform: PLATFORM,
|
|
86
|
+
deviceId: deviceId,
|
|
87
|
+
environment: ENVIRONMENT,
|
|
88
|
+
shellVersion: shellVersion,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const vwrConfig: VWRConfig = await getVWRConfig(vwrConfigRequest, logger)
|
|
92
|
+
|
|
93
|
+
if (!validateConfig(vwrConfig)) {
|
|
94
|
+
throw new Error("Invalid config, falling back to default hub")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
logger.info("[Shell] VWR enabled, loading module", {
|
|
98
|
+
vwrUrl: vwrConfig.vwrUrl,
|
|
99
|
+
})
|
|
100
|
+
try {
|
|
101
|
+
const vwr = await import(/* @vite-ignore */ vwrConfig.vwrUrl)
|
|
102
|
+
logger.info("[Shell] VWR module loaded successfully")
|
|
103
|
+
|
|
104
|
+
if (typeof vwr.init !== "function") {
|
|
105
|
+
throw new Error("[Shell] VWR module missing init() function")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await vwr.init({
|
|
109
|
+
hubUrl: vwrConfig.hubUrl,
|
|
110
|
+
launchUrl: vwrConfig.launchUrl,
|
|
111
|
+
platform: PLATFORM,
|
|
112
|
+
deviceId,
|
|
113
|
+
environment: ENVIRONMENT,
|
|
114
|
+
trustedDomains: vwrConfig.trustedDomains,
|
|
115
|
+
nativeShellVersion: shellVersion,
|
|
116
|
+
})
|
|
117
|
+
logger.info("[Shell] VWR initialized successfully")
|
|
118
|
+
} catch (vwrError) {
|
|
119
|
+
logger.error("[Shell] VWR load failed", {
|
|
120
|
+
error:
|
|
121
|
+
vwrError instanceof Error ? vwrError.message : String(vwrError),
|
|
122
|
+
vwrUrl: vwrConfig.vwrUrl,
|
|
123
|
+
})
|
|
124
|
+
throw vwrError // Re-throw to outer catch for fallback
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Logger {
|
|
2
|
+
info: (message: string, context?: Record<string, unknown>) => void
|
|
3
|
+
warn: (message: string, context?: Record<string, unknown>) => void
|
|
4
|
+
error: (message: string, context?: Record<string, unknown>) => void
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const defaultLogger: Logger = {
|
|
8
|
+
info: (message, context) =>
|
|
9
|
+
context ? console.log(message, context) : console.log(message),
|
|
10
|
+
warn: (message, context) =>
|
|
11
|
+
context ? console.warn(message, context) : console.warn(message),
|
|
12
|
+
error: (message, context) =>
|
|
13
|
+
context ? console.error(message, context) : console.error(message),
|
|
14
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { loadVwr } from "./loadVwr"
|
|
2
|
+
|
|
3
|
+
const PLATFORM = import.meta.env.VITE_PLATFORM
|
|
4
|
+
const HUB_URL = import.meta.env.VITE_HUB_URL
|
|
5
|
+
|
|
6
|
+
async function init() {
|
|
7
|
+
try {
|
|
8
|
+
await loadVwr()
|
|
9
|
+
} catch (error) {
|
|
10
|
+
try {
|
|
11
|
+
console.error("[Shell] ⚠️ FALLBACK TO HUB - Init failed:", error)
|
|
12
|
+
|
|
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
|
+
})
|
|
18
|
+
}
|
|
19
|
+
} finally {
|
|
20
|
+
//Fallback, can't trust config exists
|
|
21
|
+
window.location.href = HUB_URL
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
init()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface ImportMetaEnv {
|
|
2
|
+
readonly VITE_HUB_URL: string
|
|
3
|
+
readonly VITE_VWR_URL: string
|
|
4
|
+
readonly VITE_LAUNCH_URL: string
|
|
5
|
+
readonly VITE_AMPLITUDE_DEPLOYMENT_KEY: string
|
|
6
|
+
readonly VITE_ENVIRONMENT: string
|
|
7
|
+
readonly VITE_PLATFORM: string
|
|
8
|
+
readonly VITE_SHELL_VERSION: string
|
|
9
|
+
readonly VITE_CONFIG_URL: string
|
|
10
|
+
readonly VITE_CONFIG_FILE: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ImportMeta {
|
|
14
|
+
readonly env: ImportMetaEnv
|
|
15
|
+
}
|