@mentra/engine 3.2.0-dev.137 → 3.2.0-dev.154
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/build/engine.d.ts.map +1 -1
- package/build/engine.js +15 -9
- package/build/engine.js.map +1 -1
- package/build/facades/ota.d.ts +1 -0
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +1 -0
- package/build/facades/ota.js.map +1 -1
- package/build/facades/reports.d.ts.map +1 -1
- package/build/facades/reports.js +3 -0
- package/build/facades/reports.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
- package/build/react/MentraLiveOtaFlow.js +12 -0
- package/build/react/MentraLiveOtaFlow.js.map +1 -1
- package/build/react/useMentraLiveOta.d.ts +3 -1
- package/build/react/useMentraLiveOta.d.ts.map +1 -1
- package/build/react/useMentraLiveOta.js +19 -1
- package/build/react/useMentraLiveOta.js.map +1 -1
- package/build/runtime/bootstrap.d.ts +46 -3
- package/build/runtime/bootstrap.d.ts.map +1 -1
- package/build/runtime/bootstrap.js +60 -1
- package/build/runtime/bootstrap.js.map +1 -1
- package/build/runtime/config.d.ts +4 -0
- package/build/runtime/config.d.ts.map +1 -1
- package/build/runtime/config.js.map +1 -1
- package/build/runtime/streamConfig.d.ts.map +1 -1
- package/build/runtime/streamConfig.js +6 -0
- package/build/runtime/streamConfig.js.map +1 -1
- package/build/services/AppRegistry.d.ts +15 -1
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +48 -12
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/CloudClientService.d.ts +3 -1
- package/build/services/CloudClientService.d.ts.map +1 -1
- package/build/services/CloudClientService.js +75 -18
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +54 -12
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +9 -1
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/NavigationHandlers.d.ts.map +1 -1
- package/build/services/NavigationHandlers.js +8 -0
- package/build/services/NavigationHandlers.js.map +1 -1
- package/build/services/OtaUpdateCheckService.d.ts +10 -1
- package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
- package/build/services/OtaUpdateCheckService.js +24 -5
- package/build/services/OtaUpdateCheckService.js.map +1 -1
- package/build/services/miniappInstallIdentity.d.ts +10 -0
- package/build/services/miniappInstallIdentity.d.ts.map +1 -0
- package/build/services/miniappInstallIdentity.js +10 -0
- package/build/services/miniappInstallIdentity.js.map +1 -0
- package/build/services/otaManifestPolicy.d.ts +3 -0
- package/build/services/otaManifestPolicy.d.ts.map +1 -1
- package/build/services/otaManifestPolicy.js +8 -0
- package/build/services/otaManifestPolicy.js.map +1 -1
- package/build/services/otaManifestUrl.d.ts.map +1 -1
- package/build/services/otaManifestUrl.js +10 -2
- package/build/services/otaManifestUrl.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +6 -0
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +20 -3
- package/build/stores/settings.js.map +1 -1
- package/package.json +7 -7
- package/src/engine.ts +22 -12
- package/src/facades/ota.ts +1 -0
- package/src/facades/reports.ts +4 -6
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +2 -0
- package/src/react/MentraLiveOtaFlow.tsx +21 -0
- package/src/react/useMentraLiveOta.ts +23 -2
- package/src/runtime/bootstrap.ts +117 -4
- package/src/runtime/config.ts +4 -0
- package/src/runtime/streamConfig.ts +4 -0
- package/src/services/AppRegistry.ts +94 -25
- package/src/services/CloudClientService.ts +70 -24
- package/src/services/LocalMiniappRuntime.ts +85 -26
- package/src/services/MiniappLauncher.ts +15 -1
- package/src/services/NavigationHandlers.ts +8 -0
- package/src/services/OtaUpdateCheckService.ts +32 -6
- package/src/services/miniappInstallIdentity.ts +18 -0
- package/src/services/otaManifestPolicy.ts +11 -0
- package/src/services/otaManifestUrl.ts +10 -2
- package/src/stores/glasses.ts +6 -0
- package/src/stores/settings.ts +20 -3
|
@@ -34,6 +34,7 @@ export type MentraLiveOtaScreen =
|
|
|
34
34
|
| "wifi_required"
|
|
35
35
|
| "up_to_date"
|
|
36
36
|
| "dev_build"
|
|
37
|
+
| "unofficial_client"
|
|
37
38
|
| "check_failed"
|
|
38
39
|
| "update_info_unavailable"
|
|
39
40
|
| "starting"
|
|
@@ -103,6 +104,8 @@ export type MentraLiveOtaState = {
|
|
|
103
104
|
releaseTransition: MentraLiveOtaReleaseTransition | null
|
|
104
105
|
/** Release notes crossed by this update, newest first. Populated on completion. */
|
|
105
106
|
changelogs: ReleaseChangelog[]
|
|
107
|
+
/** Sideloaded glasses client package, set only on the `unofficial_client` screen. */
|
|
108
|
+
glassesPackageName: string | null
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
export type UseMentraLiveOtaOptions = {
|
|
@@ -129,7 +132,7 @@ export type MentraLiveOtaController = {
|
|
|
129
132
|
openWifiSetup: () => void
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
type CheckState = "checking" | "update_available" | "no_update" | "dev_build" | "error"
|
|
135
|
+
type CheckState = "checking" | "update_available" | "no_update" | "dev_build" | "unofficial_client" | "error"
|
|
133
136
|
|
|
134
137
|
const AUTO_CHAIN_NETWORK_RETRY_DELAY_MS = 5000
|
|
135
138
|
const AUTO_CHAIN_COMPLETE_DELAY_MS = 750
|
|
@@ -216,6 +219,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
216
219
|
const [isUpdateRequired, setIsUpdateRequired] = useState(true)
|
|
217
220
|
const [isVersionChange, setIsVersionChange] = useState(false)
|
|
218
221
|
const [errorKind, setErrorKind] = useState<"network" | "pin_unavailable">("network")
|
|
222
|
+
const [unofficialClientPackage, setUnofficialClientPackage] = useState<string | null>(null)
|
|
219
223
|
const [updateFingerprint, setUpdateFingerprint] = useState<string | null>(null)
|
|
220
224
|
const [offeredReleaseTransition, setOfferedReleaseTransition] = useState<MentraLiveOtaReleaseTransition | null>(null)
|
|
221
225
|
const [completedReleaseTransition, setCompletedReleaseTransition] = useState<MentraLiveOtaReleaseTransition | null>(
|
|
@@ -379,6 +383,14 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
379
383
|
setCheckState("dev_build")
|
|
380
384
|
return
|
|
381
385
|
}
|
|
386
|
+
if (result.skippedReason === "unofficial_client") {
|
|
387
|
+
stopOtaAutoChain()
|
|
388
|
+
checkCompletedRef.current = true
|
|
389
|
+
ota.clearUpdateAvailable()
|
|
390
|
+
setUnofficialClientPackage(result.packageName ?? null)
|
|
391
|
+
setCheckState("unofficial_client")
|
|
392
|
+
return
|
|
393
|
+
}
|
|
382
394
|
if (!result.hasCheckCompleted) {
|
|
383
395
|
stopOtaAutoChain()
|
|
384
396
|
checkCompletedRef.current = true
|
|
@@ -642,6 +654,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
642
654
|
completedUpdate: false,
|
|
643
655
|
releaseTransition: null,
|
|
644
656
|
changelogs: [],
|
|
657
|
+
glassesPackageName: null,
|
|
645
658
|
}
|
|
646
659
|
}
|
|
647
660
|
|
|
@@ -653,6 +666,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
653
666
|
screen = wifiRequired ? "wifi_required" : batteryBlocked ? "battery_required" : "update_available"
|
|
654
667
|
} else if (checkState === "no_update") screen = "up_to_date"
|
|
655
668
|
else if (checkState === "dev_build") screen = "dev_build"
|
|
669
|
+
else if (checkState === "unofficial_client") screen = "unofficial_client"
|
|
656
670
|
else screen = errorKind === "pin_unavailable" ? "update_info_unavailable" : "check_failed"
|
|
657
671
|
|
|
658
672
|
let error: MentraLiveOtaError | null = null
|
|
@@ -688,7 +702,11 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
688
702
|
error,
|
|
689
703
|
canInstall: screen === "update_available" && canInstall,
|
|
690
704
|
canRetry: screen === "check_failed",
|
|
691
|
-
canFinish:
|
|
705
|
+
canFinish:
|
|
706
|
+
screen === "up_to_date" ||
|
|
707
|
+
screen === "dev_build" ||
|
|
708
|
+
screen === "unofficial_client" ||
|
|
709
|
+
screen === "update_info_unavailable",
|
|
692
710
|
canDismiss:
|
|
693
711
|
(screen === "update_available" || screen === "wifi_required" || screen === "battery_required") &&
|
|
694
712
|
!isUpdateRequired &&
|
|
@@ -706,6 +724,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
706
724
|
? completedReleaseTransition
|
|
707
725
|
: null,
|
|
708
726
|
changelogs: screen === "up_to_date" ? completedChangelogs : [],
|
|
727
|
+
glassesPackageName: screen === "unofficial_client" ? unofficialClientPackage : null,
|
|
709
728
|
}
|
|
710
729
|
}
|
|
711
730
|
|
|
@@ -768,8 +787,10 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
|
|
|
768
787
|
completedUpdate: false,
|
|
769
788
|
releaseTransition: releaseTransitionFromRange(otaAutoChainReleaseRange()),
|
|
770
789
|
changelogs,
|
|
790
|
+
glassesPackageName: null,
|
|
771
791
|
}
|
|
772
792
|
}, [
|
|
793
|
+
unofficialClientPackage,
|
|
773
794
|
checkState,
|
|
774
795
|
batteryBlocked,
|
|
775
796
|
completedChangelogs,
|
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -10,7 +10,11 @@ export type SubjectTokenType = "supabase" | "authing" | (string & {})
|
|
|
10
10
|
|
|
11
11
|
export interface IslandAuth {
|
|
12
12
|
/** Returns the host's current (auto-refreshed) subject token for the backend. */
|
|
13
|
-
getSubjectToken
|
|
13
|
+
getSubjectToken?: () => Promise<{token: string; type: SubjectTokenType}>
|
|
14
|
+
/** Supplies a token issued directly for Runtime in a Core-free deployment. */
|
|
15
|
+
getRuntimeToken?: (opts?: {forceRefresh?: boolean}) => Promise<string>
|
|
16
|
+
/** Stable, deployment-scoped local identity for bundled miniapp storage. */
|
|
17
|
+
getUserId?: () => Promise<string> | string
|
|
14
18
|
/**
|
|
15
19
|
* Optional auth-session listener. Hosts that can emit auth changes should
|
|
16
20
|
* wire this so engine can reconnect backend sessions after restored login
|
|
@@ -21,9 +25,23 @@ export interface IslandAuth {
|
|
|
21
25
|
|
|
22
26
|
export interface IslandConfigValues {
|
|
23
27
|
/** cloud-v2 core service base URL (defaults resolved by the cloud client). */
|
|
24
|
-
coreUrl?: string
|
|
28
|
+
coreUrl?: string | null
|
|
25
29
|
/** cloud-v2 runtime service base URL. */
|
|
26
|
-
runtimeUrl?: string
|
|
30
|
+
runtimeUrl?: string | null
|
|
31
|
+
/** Open Runtime's live WebSocket/audio session. Defaults to true. */
|
|
32
|
+
runtimeRealtimeSession?: boolean
|
|
33
|
+
/** Complete allowlist for bundled/local miniapps; null or omitted allows all. */
|
|
34
|
+
localMiniappAllowlist?: readonly string[] | null
|
|
35
|
+
/** Provenance-aware policy for workspace SYSTEM and managed miniapps. */
|
|
36
|
+
localMiniappPolicy?: LocalMiniappPolicy
|
|
37
|
+
/** Optional per-package configuration supplied by the host deployment. */
|
|
38
|
+
miniappConfiguration?: Readonly<Record<string, Readonly<Record<string, string>>>>
|
|
39
|
+
/** Deployment-scoped key for the persisted Core refresh token. */
|
|
40
|
+
cloudAuthStorageKey?: string
|
|
41
|
+
/** Deployment-pinned Mentra Live OTA manifest; explicit null disables remote OTA. */
|
|
42
|
+
otaManifestUrl?: string | null
|
|
43
|
+
/** Deployment capability policy. Omitted entries preserve consumer behavior. */
|
|
44
|
+
features?: Partial<Record<IslandFeatureName, boolean>>
|
|
27
45
|
/** OEM identifier (Mentra is OEM #0); reserved for OEM auth/telemetry. */
|
|
28
46
|
oemId?: string
|
|
29
47
|
/**
|
|
@@ -38,6 +56,21 @@ export interface IslandConfigValues {
|
|
|
38
56
|
devServerHost?: () => string | undefined
|
|
39
57
|
}
|
|
40
58
|
|
|
59
|
+
export interface ManagedMiniappPolicyEntry {
|
|
60
|
+
packageName: string
|
|
61
|
+
version: string
|
|
62
|
+
sha256: string
|
|
63
|
+
deploymentId: string
|
|
64
|
+
deploymentOrigin: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface LocalMiniappPolicy {
|
|
68
|
+
systemPackageNames: readonly string[] | null
|
|
69
|
+
managed: readonly ManagedMiniappPolicyEntry[]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type IslandFeatureName = "managedStreams" | "nativeMeetings" | "cloudSpeech" | "onDeviceSpeech" | "navigation"
|
|
73
|
+
|
|
41
74
|
export type IslandAnalytics = (event: string, props?: Record<string, unknown>) => void
|
|
42
75
|
|
|
43
76
|
/**
|
|
@@ -74,6 +107,62 @@ export interface IslandConfigureOptions {
|
|
|
74
107
|
ui?: IslandUiSeams
|
|
75
108
|
}
|
|
76
109
|
|
|
110
|
+
export function isLocalMiniappPackageAllowed(packageName: string): boolean {
|
|
111
|
+
const policy = options?.config?.localMiniappPolicy
|
|
112
|
+
if (policy) {
|
|
113
|
+
return (
|
|
114
|
+
policy.systemPackageNames === null ||
|
|
115
|
+
policy.systemPackageNames.includes(packageName) ||
|
|
116
|
+
policy.managed.some((entry) => entry.packageName === packageName)
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
const allowlist = options?.config?.localMiniappAllowlist
|
|
120
|
+
return allowlist == null || allowlist.includes(packageName)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function isOfflineSystemMiniappAllowed(packageName: string): boolean {
|
|
124
|
+
const policy = options?.config?.localMiniappPolicy
|
|
125
|
+
if (!policy) return isLocalMiniappPackageAllowed(packageName)
|
|
126
|
+
return policy.systemPackageNames === null || policy.systemPackageNames.includes(packageName)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isInstalledMiniappAllowed(
|
|
130
|
+
packageName: string,
|
|
131
|
+
version: string | undefined,
|
|
132
|
+
releaseIdentity: {
|
|
133
|
+
source: string
|
|
134
|
+
bundleSha256?: string
|
|
135
|
+
deploymentId?: string
|
|
136
|
+
deploymentOrigin?: string
|
|
137
|
+
} | null,
|
|
138
|
+
): boolean {
|
|
139
|
+
const policy = options?.config?.localMiniappPolicy
|
|
140
|
+
if (!policy) return isLocalMiniappPackageAllowed(packageName)
|
|
141
|
+
|
|
142
|
+
const systemApproved = policy.systemPackageNames === null || policy.systemPackageNames.includes(packageName)
|
|
143
|
+
if (systemApproved && releaseIdentity?.source === "bundled_asset") return true
|
|
144
|
+
if (!version || releaseIdentity?.source !== "deployment_manifest") return false
|
|
145
|
+
|
|
146
|
+
return policy.managed.some(
|
|
147
|
+
(entry) =>
|
|
148
|
+
entry.packageName === packageName &&
|
|
149
|
+
entry.version === version &&
|
|
150
|
+
entry.sha256 === releaseIdentity.bundleSha256?.toLowerCase() &&
|
|
151
|
+
entry.deploymentId === releaseIdentity.deploymentId &&
|
|
152
|
+
entry.deploymentOrigin === releaseIdentity.deploymentOrigin,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Read a defensive package-scoped configuration snapshot. */
|
|
157
|
+
export function getMiniappConfiguration(packageName: string): Record<string, string> {
|
|
158
|
+
const configuration = options?.config?.miniappConfiguration?.[packageName]
|
|
159
|
+
return configuration ? {...configuration} : {}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function isFeatureEnabled(feature: IslandFeatureName): boolean {
|
|
163
|
+
return options?.config?.features?.[feature] !== false
|
|
164
|
+
}
|
|
165
|
+
|
|
77
166
|
let options: IslandConfigureOptions | null = null
|
|
78
167
|
let started = false
|
|
79
168
|
|
|
@@ -91,7 +180,31 @@ export function configure(opts: IslandConfigureOptions): void {
|
|
|
91
180
|
console.warn("engine.configure() called after engine.start(); ignored — stop() the runtime before reconfiguring")
|
|
92
181
|
return
|
|
93
182
|
}
|
|
94
|
-
|
|
183
|
+
const localMiniappAllowlist = opts.config?.localMiniappAllowlist
|
|
184
|
+
const localMiniappPolicy = opts.config?.localMiniappPolicy
|
|
185
|
+
options = {
|
|
186
|
+
...opts,
|
|
187
|
+
config: opts.config
|
|
188
|
+
? {
|
|
189
|
+
...opts.config,
|
|
190
|
+
localMiniappAllowlist: Array.isArray(localMiniappAllowlist)
|
|
191
|
+
? Object.freeze([...localMiniappAllowlist])
|
|
192
|
+
: localMiniappAllowlist,
|
|
193
|
+
localMiniappPolicy: localMiniappPolicy
|
|
194
|
+
? Object.freeze({
|
|
195
|
+
systemPackageNames: Array.isArray(localMiniappPolicy.systemPackageNames)
|
|
196
|
+
? Object.freeze([...localMiniappPolicy.systemPackageNames])
|
|
197
|
+
: null,
|
|
198
|
+
managed: Object.freeze(
|
|
199
|
+
localMiniappPolicy.managed.map((entry) =>
|
|
200
|
+
Object.freeze({...entry, sha256: entry.sha256.toLowerCase()}),
|
|
201
|
+
),
|
|
202
|
+
),
|
|
203
|
+
})
|
|
204
|
+
: undefined,
|
|
205
|
+
}
|
|
206
|
+
: undefined,
|
|
207
|
+
}
|
|
95
208
|
}
|
|
96
209
|
|
|
97
210
|
/**
|
package/src/runtime/config.ts
CHANGED
|
@@ -101,6 +101,10 @@ export interface StreamVideoConfig {
|
|
|
101
101
|
width?: number
|
|
102
102
|
height?: number
|
|
103
103
|
bitrate?: number
|
|
104
|
+
/** WHIP minimum target in bps; omitted leaves it unset. Clamped to the maximum. */
|
|
105
|
+
minBitrateBps?: number
|
|
106
|
+
/** WHIP startup bitrate in bps, clamped to the requested bounds. */
|
|
107
|
+
initialBitrateBps?: number
|
|
104
108
|
fps?: number
|
|
105
109
|
}
|
|
106
110
|
|
|
@@ -12,11 +12,15 @@ export const normalizeStreamVideoConfig = (value: unknown): StreamVideoConfig |
|
|
|
12
12
|
const width = finiteNumber(value.width)
|
|
13
13
|
const height = finiteNumber(value.height)
|
|
14
14
|
const bitrate = finiteNumber(value.bitrate)
|
|
15
|
+
const minBitrateBps = finiteNumber(value.minBitrateBps)
|
|
16
|
+
const initialBitrateBps = finiteNumber(value.initialBitrateBps)
|
|
15
17
|
const frameRate = finiteNumber(value.frameRate)
|
|
16
18
|
const fps = frameRate ?? finiteNumber(value.fps)
|
|
17
19
|
if (width !== undefined) config.width = width
|
|
18
20
|
if (height !== undefined) config.height = height
|
|
19
21
|
if (bitrate !== undefined) config.bitrate = bitrate
|
|
22
|
+
if (minBitrateBps !== undefined) config.minBitrateBps = Math.max(0, Math.trunc(minBitrateBps))
|
|
23
|
+
if (initialBitrateBps !== undefined) config.initialBitrateBps = Math.max(0, Math.trunc(initialBitrateBps))
|
|
20
24
|
if (fps !== undefined) config.fps = fps
|
|
21
25
|
return Object.keys(config).length > 0 ? config : undefined
|
|
22
26
|
}
|
|
@@ -28,12 +28,25 @@ import {HardwareRequirement, HardwareRequirementLevel, HardwareType} from "../ty
|
|
|
28
28
|
import {configuredDevHost} from "../utils/configuredDevHost"
|
|
29
29
|
import {storage} from "../utils/storage/storage"
|
|
30
30
|
import {printDirectory} from "../utils/storage/zip"
|
|
31
|
+
import {isInstalledMiniappAllowed, isOfflineSystemMiniappAllowed} from "../runtime/bootstrap"
|
|
31
32
|
import {checkManifestVersions} from "./manifestVersionGate"
|
|
32
33
|
import {normalizeManifestActions} from "./manifestActions"
|
|
34
|
+
import {miniappInstallIdentityError, type MiniappInstallExpectations} from "./miniappInstallIdentity"
|
|
33
35
|
import {miniappRunningRegistry} from "./MiniappRunningRegistry"
|
|
34
36
|
|
|
35
37
|
export {normalizeManifestActions} from "./manifestActions"
|
|
36
38
|
|
|
39
|
+
let installQueue: Promise<void> = Promise.resolve()
|
|
40
|
+
|
|
41
|
+
function serializeInstall<T>(operation: () => Promise<T>): Promise<T> {
|
|
42
|
+
const result = installQueue.then(operation, operation)
|
|
43
|
+
installQueue = result.then(
|
|
44
|
+
() => undefined,
|
|
45
|
+
() => undefined,
|
|
46
|
+
)
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
const ALLOWED_PERMISSION_TYPES: ReadonlySet<AppPermissionType> = new Set<AppPermissionType>([
|
|
38
51
|
"MICROPHONE",
|
|
39
52
|
"CAMERA",
|
|
@@ -141,10 +154,12 @@ interface InstalledLma {
|
|
|
141
154
|
}
|
|
142
155
|
|
|
143
156
|
export interface MiniappReleaseIdentity {
|
|
144
|
-
source: "direct_download" | "bundled_asset" | "preinstalled_registry" | "dev_snapshot"
|
|
157
|
+
source: "direct_download" | "bundled_asset" | "preinstalled_registry" | "deployment_manifest" | "dev_snapshot"
|
|
145
158
|
releaseId?: string
|
|
146
159
|
bundleSha256?: string
|
|
147
160
|
channel?: string
|
|
161
|
+
deploymentId?: string
|
|
162
|
+
deploymentOrigin?: string
|
|
148
163
|
}
|
|
149
164
|
|
|
150
165
|
function releaseIdentityKey(packageName: string, version: string): string {
|
|
@@ -209,6 +224,7 @@ async function downloadMiniAppZip(url: string): Promise<string> {
|
|
|
209
224
|
async function unpackMiniApp(
|
|
210
225
|
zipPath: string,
|
|
211
226
|
versionOverride?: string,
|
|
227
|
+
expected?: MiniappInstallExpectations,
|
|
212
228
|
): Promise<{packageName: string; version: string}> {
|
|
213
229
|
const unzipDir = new Directory(Paths.cache, "lma_unzip")
|
|
214
230
|
try {
|
|
@@ -246,6 +262,8 @@ async function unpackMiniApp(
|
|
|
246
262
|
throw "READ_MANIFEST_FAILED"
|
|
247
263
|
}
|
|
248
264
|
const version = versionOverride ?? manifestVersion
|
|
265
|
+
const identityError = miniappInstallIdentityError({packageName, version: manifestVersion}, expected)
|
|
266
|
+
if (identityError) throw new Error(identityError)
|
|
249
267
|
console.log(`ZIP: installing ${packageName} as version ${version}`)
|
|
250
268
|
|
|
251
269
|
const basePackageDir = new Directory(Paths.document, "lmas", packageName)
|
|
@@ -260,6 +278,9 @@ async function unpackMiniApp(
|
|
|
260
278
|
|
|
261
279
|
const versionDir = new Directory(basePackageDir, version)
|
|
262
280
|
try {
|
|
281
|
+
if (expected?.rejectExistingVersion && versionDir.exists) {
|
|
282
|
+
throw new Error(`Miniapp ${packageName}@${version} is already installed`)
|
|
283
|
+
}
|
|
263
284
|
if (!versionDir.exists) {
|
|
264
285
|
versionDir.create()
|
|
265
286
|
} else {
|
|
@@ -299,10 +320,11 @@ async function unpackMiniApp(
|
|
|
299
320
|
async function downloadAndInstallMiniApp(
|
|
300
321
|
url: string,
|
|
301
322
|
versionOverride?: string,
|
|
323
|
+
expected?: MiniappInstallExpectations,
|
|
302
324
|
): Promise<{packageName: string; version: string}> {
|
|
303
325
|
const downloadedZipPath = await downloadMiniAppZip(url)
|
|
304
326
|
console.log("ZIP: done downloading, starting unzip")
|
|
305
|
-
return unpackMiniApp(downloadedZipPath, versionOverride)
|
|
327
|
+
return unpackMiniApp(downloadedZipPath, versionOverride, expected)
|
|
306
328
|
}
|
|
307
329
|
|
|
308
330
|
type Listener = () => void
|
|
@@ -445,17 +467,29 @@ class AppRegistry {
|
|
|
445
467
|
*/
|
|
446
468
|
public installFromUrl(
|
|
447
469
|
url: string,
|
|
448
|
-
opts?: {
|
|
470
|
+
opts?: {
|
|
471
|
+
versionOverride?: string
|
|
472
|
+
releaseIdentity?: MiniappReleaseIdentity
|
|
473
|
+
expectedPackageName?: string
|
|
474
|
+
expectedVersion?: string
|
|
475
|
+
rejectExistingVersion?: boolean
|
|
476
|
+
},
|
|
449
477
|
): AsyncResult<void, Error> {
|
|
450
|
-
return Res.try_async(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
478
|
+
return Res.try_async(() =>
|
|
479
|
+
serializeInstall(async () => {
|
|
480
|
+
const {packageName, version} = await downloadAndInstallMiniApp(url, opts?.versionOverride, {
|
|
481
|
+
packageName: opts?.expectedPackageName,
|
|
482
|
+
version: opts?.expectedVersion,
|
|
483
|
+
rejectExistingVersion: opts?.rejectExistingVersion,
|
|
484
|
+
})
|
|
485
|
+
console.log("APP_REGISTRY: Downloaded and installed mini app")
|
|
486
|
+
this.finalizeInstall(
|
|
487
|
+
packageName,
|
|
488
|
+
version,
|
|
489
|
+
opts?.releaseIdentity ?? {source: version.startsWith("dev-") ? "dev_snapshot" : "direct_download"},
|
|
490
|
+
)
|
|
491
|
+
}),
|
|
492
|
+
)
|
|
459
493
|
}
|
|
460
494
|
|
|
461
495
|
/**
|
|
@@ -467,14 +501,26 @@ class AppRegistry {
|
|
|
467
501
|
*/
|
|
468
502
|
public installFromLocalZip(
|
|
469
503
|
zipPath: string,
|
|
470
|
-
opts?: {
|
|
504
|
+
opts?: {
|
|
505
|
+
versionOverride?: string
|
|
506
|
+
releaseIdentity?: MiniappReleaseIdentity
|
|
507
|
+
expectedPackageName?: string
|
|
508
|
+
expectedVersion?: string
|
|
509
|
+
rejectExistingVersion?: boolean
|
|
510
|
+
},
|
|
471
511
|
): AsyncResult<{packageName: string; version: string}, Error> {
|
|
472
|
-
return Res.try_async(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
512
|
+
return Res.try_async(() =>
|
|
513
|
+
serializeInstall(async () => {
|
|
514
|
+
const {packageName, version} = await unpackMiniApp(zipPath, opts?.versionOverride, {
|
|
515
|
+
packageName: opts?.expectedPackageName,
|
|
516
|
+
version: opts?.expectedVersion,
|
|
517
|
+
rejectExistingVersion: opts?.rejectExistingVersion,
|
|
518
|
+
})
|
|
519
|
+
console.log("APP_REGISTRY: Installed mini app from local zip")
|
|
520
|
+
this.finalizeInstall(packageName, version, opts?.releaseIdentity ?? {source: "bundled_asset"})
|
|
521
|
+
return {packageName, version}
|
|
522
|
+
}),
|
|
523
|
+
)
|
|
478
524
|
}
|
|
479
525
|
|
|
480
526
|
/**
|
|
@@ -504,6 +550,22 @@ class AppRegistry {
|
|
|
504
550
|
return result.is_ok() ? result.value : null
|
|
505
551
|
}
|
|
506
552
|
|
|
553
|
+
/** Enumerate installed releases carrying deployment ownership metadata. */
|
|
554
|
+
public getDeploymentOwnedReleases(): Array<{
|
|
555
|
+
packageName: string
|
|
556
|
+
version: string
|
|
557
|
+
identity: MiniappReleaseIdentity
|
|
558
|
+
}> {
|
|
559
|
+
const releases: Array<{packageName: string; version: string; identity: MiniappReleaseIdentity}> = []
|
|
560
|
+
for (const packageName of this.getPackageNames()) {
|
|
561
|
+
for (const version of this.getInstalledVersions(packageName)) {
|
|
562
|
+
const identity = this.getReleaseIdentity(packageName, version)
|
|
563
|
+
if (identity?.source === "deployment_manifest") releases.push({packageName, version, identity})
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return releases
|
|
567
|
+
}
|
|
568
|
+
|
|
507
569
|
public installFromJsonUrl(baseUrl: string): AsyncResult<{packageName: string; version: string; name: string}, Error> {
|
|
508
570
|
return Res.try_async(async () => {
|
|
509
571
|
const trimmed = baseUrl.replace(/\/$/, "")
|
|
@@ -732,12 +794,21 @@ class AppRegistry {
|
|
|
732
794
|
* registration in finalizeInstall. Native offline apps keep top priority.
|
|
733
795
|
*/
|
|
734
796
|
private mergeProjectedApps(diskApps: ClientApp[]): ClientApp[] {
|
|
735
|
-
const offline = this.projectOfflineApps()
|
|
797
|
+
const offline = this.projectOfflineApps().filter((app) => isOfflineSystemMiniappAllowed(app.packageName))
|
|
736
798
|
const offlinePackages = new Set(offline.map((app) => app.packageName))
|
|
737
|
-
const dev = this.projectDevApps().filter(
|
|
799
|
+
const dev = this.projectDevApps().filter(
|
|
800
|
+
(app) => isInstalledMiniappAllowed(app.packageName, undefined, null) && !offlinePackages.has(app.packageName),
|
|
801
|
+
)
|
|
738
802
|
const devPackages = new Set(dev.map((app) => app.packageName))
|
|
739
803
|
const installed = diskApps.filter(
|
|
740
|
-
(app) =>
|
|
804
|
+
(app) =>
|
|
805
|
+
isInstalledMiniappAllowed(
|
|
806
|
+
app.packageName,
|
|
807
|
+
app.version,
|
|
808
|
+
app.version ? this.getReleaseIdentity(app.packageName, app.version) : null,
|
|
809
|
+
) &&
|
|
810
|
+
!offlinePackages.has(app.packageName) &&
|
|
811
|
+
!devPackages.has(app.packageName),
|
|
741
812
|
)
|
|
742
813
|
return [...installed, ...dev, ...offline]
|
|
743
814
|
}
|
|
@@ -1003,7 +1074,6 @@ export interface DevAppRecord {
|
|
|
1003
1074
|
const DEV_APPS_INDEX_KEY = "dev_apps_index"
|
|
1004
1075
|
const DEV_APP_ICONS_DIR = "dev-miniapp-icons"
|
|
1005
1076
|
|
|
1006
|
-
|
|
1007
1077
|
function isPrivateLanHost(hostname: string): boolean {
|
|
1008
1078
|
return (
|
|
1009
1079
|
hostname === "localhost" ||
|
|
@@ -1167,8 +1237,7 @@ export async function registerDevApp(record: DevAppRecord): Promise<void> {
|
|
|
1167
1237
|
// Relaunch paths (developer-URL screen, loadDevMiniapp) omit mdnsHost, so an
|
|
1168
1238
|
// omitted field keeps whatever the QR scan stored instead of wiping the
|
|
1169
1239
|
// `.local` failover host.
|
|
1170
|
-
const mdnsHost =
|
|
1171
|
-
record.mdnsHost !== undefined ? record.mdnsHost.trim() || undefined : readStoredMdnsHost(packageName)
|
|
1240
|
+
const mdnsHost = record.mdnsHost !== undefined ? record.mdnsHost.trim() || undefined : readStoredMdnsHost(packageName)
|
|
1172
1241
|
const devRecord: DevAppRecord = {
|
|
1173
1242
|
...record,
|
|
1174
1243
|
packageName,
|