@mentra/engine 3.4.0-dev.1207 → 3.4.0-dev.1209
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/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +5 -3
- package/build/facades/glasses.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/AppRegistry.d.ts +2 -0
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +2 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +18 -1
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MentraJSCrashController.d.ts +2 -0
- package/build/services/MentraJSCrashController.d.ts.map +1 -1
- package/build/services/MentraJSCrashController.js +4 -0
- package/build/services/MentraJSCrashController.js.map +1 -1
- package/build/services/MentraJSRouter.d.ts.map +1 -1
- package/build/services/MentraJSRouter.js +14 -3
- package/build/services/MentraJSRouter.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts +6 -0
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +20 -3
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/fleet/FleetReporter.d.ts.map +1 -1
- package/build/services/fleet/FleetReporter.js +43 -0
- package/build/services/fleet/FleetReporter.js.map +1 -1
- package/build/services/fleet/fleetCollector.d.ts +1 -1
- package/build/services/fleet/fleetCollector.d.ts.map +1 -1
- package/build/services/fleet/fleetCollector.js +146 -5
- package/build/services/fleet/fleetCollector.js.map +1 -1
- package/build/services/fleet/fleetEvents.d.ts +39 -3
- package/build/services/fleet/fleetEvents.d.ts.map +1 -1
- package/build/services/fleet/fleetEvents.js +66 -22
- package/build/services/fleet/fleetEvents.js.map +1 -1
- package/build/services/fleet/fleetQueue.d.ts +26 -0
- package/build/services/fleet/fleetQueue.d.ts.map +1 -1
- package/build/services/fleet/fleetQueue.js +94 -8
- package/build/services/fleet/fleetQueue.js.map +1 -1
- package/build/services/fleet/telemetryLimits.d.ts +16 -0
- package/build/services/fleet/telemetryLimits.d.ts.map +1 -0
- package/build/services/fleet/telemetryLimits.js +69 -0
- package/build/services/fleet/telemetryLimits.js.map +1 -0
- package/build/services/installMiniapp.d.ts +13 -0
- package/build/services/installMiniapp.d.ts.map +1 -1
- package/build/services/installMiniapp.js +64 -1
- package/build/services/installMiniapp.js.map +1 -1
- package/build/services/miniappReleaseInstall.js +1 -1
- package/build/services/miniappReleaseInstall.js.map +1 -1
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +16 -1
- package/build/stores/apps.js.map +1 -1
- package/package.json +8 -8
- package/src/facades/glasses.ts +5 -3
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/services/AppRegistry.ts +2 -0
- package/src/services/LocalMiniappRuntime.ts +20 -1
- package/src/services/MentraJSCrashController.ts +5 -0
- package/src/services/MentraJSRouter.ts +15 -3
- package/src/services/MiniappLauncher.ts +18 -2
- package/src/services/fleet/FleetReporter.ts +40 -0
- package/src/services/fleet/fleetCollector.ts +160 -6
- package/src/services/fleet/fleetEvents.ts +104 -20
- package/src/services/fleet/fleetQueue.ts +100 -7
- package/src/services/fleet/telemetryLimits.ts +78 -0
- package/src/services/installMiniapp.ts +78 -2
- package/src/services/miniappReleaseInstall.ts +1 -1
- package/src/stores/apps.ts +19 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleetQueue.js","sourceRoot":"","sources":["../../../src/services/fleet/fleetQueue.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Fleet reporter's durable queue.\n *\n * The collectors record observations here and the uploader drains them to the phone's own Core. A partition is\n * one account on one Core: records queued as account A on Core X are only ever delivered as A to X, so a\n * partition's records are never returned for, or mixed with, another's. Every call takes the partition it works\n * on.\n *\n * Storage layout, in the `mentra-fleet` MMKV instance (which logout's `storage.clearAll()` does not touch):\n * - `q:<partition>:<seq>:<recordId>` holds one record, as `{enqueuedAt, record}` JSON. `<seq>` is the partition's\n * enqueue counter, zero-padded to 16 digits so that key order is queue order.\n * - `i:<partition>` is the partition's index: `{count, nextSeq, oldest, newest}`, the record count, the next\n * sequence and the oldest and newest record keys.\n * - `l:<partition>` holds the losses not yet reported: `{dropped, expired, from, to, updatedAt}`.\n * - `n:<partition>` holds the collector-session sequence counters, as `[collectorSessionId, next][]`.\n *\n * MMKV can list keys only by listing all of them, so the first call for a partition in a process scans the store\n * once, repairs the index if a crash left it behind, and keeps the partition's ordered record keys in memory.\n * After that every call costs in proportion to its own work: an enqueue is two writes (three at capacity), and\n * no call touches another partition's records. Because that list is the queue's view of the store, one queue\n * owns a store: `createFleetQueue()` returns the app's shared queue, and tests build theirs over a store of\n * their own.\n *\n * Writes are ordered so that a crash errs toward reporting a loss twice rather than never: a loss is recorded\n * before the record it describes is removed, and the index is written last and rebuilt from the records if it is\n * missing or stale.\n *\n * Logout spares this store, so an account that signs out and never returns leaves its partition behind.\n * `sweepExpired` is how the 7-day bound still holds for it: one scan finds every partition, expires its old\n * records and removes what an empty partition no longer needs.\n */\nimport {FLEET_LIMITS, type FleetRecord} from \"@mentra/cloud-protocol\"\nimport {normalizeCoreUrl} from \"./coreUrl\"\nimport {getFleetStore} from \"./fleetStorage\"\nimport {sha256HexOfText, utf8ByteLength} from \"./utf8\"\n\n/** The subset of an MMKV instance the queue uses; tests pass an in-memory Map-backed implementation. */\nexport interface FleetKeyValueStore {\n getString(key: string): string | undefined\n set(key: string, value: string): void\n remove(key: string): void\n getAllKeys(): string[]\n}\n\n/** What the queue lost since the last `takeLosses`, and the span of observation time it covers. */\nexport interface FleetLosses {\n /** Records discarded because the queue was full (or could not be read or written). */\n dropped: number\n /** Records discarded because they were older than the queue's maximum age. */\n expired: number\n /** The earliest `observedAt` among the lost records, or null if none had a readable one. */\n from: string | null\n /** The latest `observedAt` among the lost records, or null if none had a readable one. */\n to: string | null\n}\n\n/** A partition is one account on one Core. */\nexport interface FleetQueue {\n /** Adds a record. Drops the oldest beyond 10,000 and counts the drop. Never throws: a record that cannot be stored is counted as dropped. */\n enqueue(partition: string, record: FleetRecord): void\n /**\n * Up to `maxRecords` records, oldest first, whose own JSON (UTF-8 bytes, as serialized in the batch) adds up to\n * at most `maxBytes`. `maxBytes` is the caller's budget for the records themselves; the caller leaves headroom\n * for the envelope and the commas. The oldest record is always offered even if it alone exceeds `maxBytes`, so\n * one oversize record cannot block the queue; the server rejects it and the caller removes it. Records stay\n * queued until `remove`.\n */\n nextBatch(partition: string, maxRecords: number, maxBytes: number): FleetRecord[]\n /** Removes the records with these IDs. IDs that are not queued are ignored. */\n remove(partition: string, recordIds: string[]): void\n /**\n * Drops records older than 7 days, then returns the drops and expiries since the last call, with the span of\n * `observedAt` they cover, and resets the counters. The caller turns a non-zero result into a coverage gap and\n * should enqueue it straight away: the counters are cleared by this call.\n */\n takeLosses(partition: string, now: number): FleetLosses\n /**\n * Enforces the 7-day bound on every partition in the store, including those of accounts that signed out and\n * never came back (nothing else visits them, and logout spares this store). One scan finds the partitions; for\n * each, records older than 7 days are dropped and counted exactly as `takeLosses` counts expiries, so an\n * account that does return still gets its coverage gap. A partition left with no records loses its index and\n * sequence counters, and its loss counters too once they are themselves older than 7 days: a gap report that\n * old, for an account that never returned, can never be delivered. Never throws.\n *\n * The reporter calls this at start, and then at most hourly from its upload loop (only when an upload is due, so\n * an idle reporter does not wake to sweep). `now` defaults to the queue's clock.\n */\n sweepExpired(now?: number): void\n /** Forgets everything held for the partition: records, losses and sequence counters. */\n clearPartition(partition: string): void\n size(partition: string): number\n /** The next sequence number for a collector session, counting up from 0 and persisted. */\n nextSequence(partition: string, collectorSessionId: string): number\n}\n\nexport interface FleetQueueOptions {\n /** The clock that stamps `enqueuedAt`. Defaults to `Date.now`. */\n now?: () => number\n}\n\nconst SEQUENCE_WIDTH = 16\nconst MAX_TRACKED_COLLECTOR_SESSIONS = 16\n\n/** The losses as stored: the counters plus when they last changed, which tells a sweep when they are too old to matter. */\ninterface LossRecord extends FleetLosses {\n updatedAt: number\n}\nconst EMPTY_LOSSES: LossRecord = {dropped: 0, expired: 0, from: null, to: null, updatedAt: 0}\n\n/** The partition for an account on a Core: sha256 hex of `${normalizedCoreUrl}|${mentraUserId}`. */\nexport function partitionKey(coreUrl: string, mentraUserId: string): string {\n const core = normalizeCoreUrl(coreUrl)\n if (core === \"\" || mentraUserId.trim() === \"\") throw new Error(\"A Fleet partition needs a Core URL and an account\")\n return sha256HexOfText(`${core}|${mentraUserId}`)\n}\n\ninterface StoredEntry {\n enqueuedAt: number\n record: FleetRecord\n}\n\ninterface PartitionIndex {\n count: number\n nextSeq: number\n oldest: string | null\n newest: string | null\n}\n\ninterface PartitionState {\n /** The partition's record keys in queue order. */\n keys: string[]\n nextSeq: number\n}\n\ntype LossKind = \"dropped\" | \"expired\"\n\nconst recordPrefix = (partition: string) => `q:${partition}:`\nconst indexKey = (partition: string) => `i:${partition}`\nconst lossKey = (partition: string) => `l:${partition}`\nconst sequenceKey = (partition: string) => `n:${partition}`\n\nfunction recordKey(partition: string, sequence: number, recordId: string): string {\n return `${recordPrefix(partition)}${String(sequence).padStart(SEQUENCE_WIDTH, \"0\")}:${recordId}`\n}\n\nfunction isRecordKey(key: string, prefix: string): boolean {\n if (!key.startsWith(prefix)) return false\n for (let offset = 0; offset < SEQUENCE_WIDTH; offset += 1) {\n const code = key.charCodeAt(prefix.length + offset)\n if (!(code >= 48 && code <= 57)) return false\n }\n return key.charCodeAt(prefix.length + SEQUENCE_WIDTH) === 58\n}\n\nfunction isCount(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction warn(message: string, error: unknown): void {\n // Never log record content: only what failed.\n console.warn(`[FleetQueue] ${message}: ${describe(error)}`)\n}\n\nfunction parseTime(value: string | null): number {\n return value === null ? Number.NaN : Date.parse(value)\n}\n\nfunction earlier(a: string | null, b: string | null): string | null {\n if (Number.isNaN(parseTime(a))) return Number.isNaN(parseTime(b)) ? null : b\n if (Number.isNaN(parseTime(b))) return a\n return parseTime(b) < parseTime(a) ? b : a\n}\n\nfunction later(a: string | null, b: string | null): string | null {\n if (Number.isNaN(parseTime(a))) return Number.isNaN(parseTime(b)) ? null : b\n if (Number.isNaN(parseTime(b))) return a\n return parseTime(b) > parseTime(a) ? b : a\n}\n\nfunction mergeLosses(a: LossRecord, b: LossRecord): LossRecord {\n return {\n dropped: a.dropped + b.dropped,\n expired: a.expired + b.expired,\n from: earlier(a.from, b.from),\n to: later(a.to, b.to),\n updatedAt: Math.max(a.updatedAt, b.updatedAt),\n }\n}\n\nfunction lossesOf(kind: LossKind, observedAts: Array<string | undefined>, updatedAt: number): LossRecord {\n let from: string | null = null\n let to: string | null = null\n for (const observedAt of observedAts) {\n if (observedAt === undefined) continue\n from = earlier(from, observedAt)\n to = later(to, observedAt)\n }\n return {\n dropped: kind === \"dropped\" ? observedAts.length : 0,\n expired: kind === \"expired\" ? observedAts.length : 0,\n from,\n to,\n updatedAt,\n }\n}\n\n/** What a scan of the store found for one partition. */\ninterface FoundPartition {\n records: string[]\n hasIndex: boolean\n hasLosses: boolean\n hasSequences: boolean\n}\n\nconst RECORD_KEY_PARTITION = /^q:(.+?):\\d{16}:/\n\n/** Groups every Fleet queue key in the store by the partition it belongs to. */\nfunction groupByPartition(keys: string[]): Map<string, FoundPartition> {\n const found = new Map<string, FoundPartition>()\n const entry = (partition: string): FoundPartition => {\n let existing = found.get(partition)\n if (!existing) {\n existing = {records: [], hasIndex: false, hasLosses: false, hasSequences: false}\n found.set(partition, existing)\n }\n return existing\n }\n for (const key of keys) {\n if (key.startsWith(\"q:\")) {\n const partition = RECORD_KEY_PARTITION.exec(key)?.[1]\n if (partition !== undefined) entry(partition).records.push(key)\n } else if (key.startsWith(\"i:\")) entry(key.slice(2)).hasIndex = true\n else if (key.startsWith(\"l:\")) entry(key.slice(2)).hasLosses = true\n else if (key.startsWith(\"n:\")) entry(key.slice(2)).hasSequences = true\n }\n return found\n}\n\nfunction buildQueue(store: FleetKeyValueStore, options: FleetQueueOptions): FleetQueue {\n const now = options.now ?? Date.now\n const states = new Map<string, PartitionState>()\n /** Losses that could not be written to the store; reported by the next `takeLosses` in this process. */\n const unpersistedLosses = new Map<string, LossRecord>()\n const sequences = new Map<string, Array<[string, number]>>()\n\n function readJson(key: string): unknown {\n try {\n const raw = store.getString(key)\n return raw === undefined ? null : JSON.parse(raw)\n } catch {\n return null\n }\n }\n\n function readIndex(partition: string): PartitionIndex | null {\n const value = readJson(indexKey(partition)) as Partial<PartitionIndex> | null\n if (!value || !isCount(value.count) || !isCount(value.nextSeq)) return null\n const oldest = typeof value.oldest === \"string\" ? value.oldest : null\n const newest = typeof value.newest === \"string\" ? value.newest : null\n return {count: value.count, nextSeq: value.nextSeq, oldest, newest}\n }\n\n function writeIndex(partition: string, state: PartitionState): void {\n const index: PartitionIndex = {\n count: state.keys.length,\n nextSeq: state.nextSeq,\n oldest: state.keys[0] ?? null,\n newest: state.keys[state.keys.length - 1] ?? null,\n }\n store.set(indexKey(partition), JSON.stringify(index))\n }\n\n function readEntry(key: string): StoredEntry | null {\n const value = readJson(key) as Partial<StoredEntry> | null\n const record = value?.record as Partial<FleetRecord> | undefined\n if (!value || typeof value.enqueuedAt !== \"number\" || !record || typeof record.recordId !== \"string\") return null\n return value as StoredEntry\n }\n\n function readLosses(partition: string): LossRecord | null {\n const value = readJson(lossKey(partition)) as Partial<LossRecord> | null\n if (!value || !isCount(value.dropped) || !isCount(value.expired)) return null\n return {\n dropped: value.dropped,\n expired: value.expired,\n from: typeof value.from === \"string\" ? value.from : null,\n to: typeof value.to === \"string\" ? value.to : null,\n updatedAt: isCount(value.updatedAt) ? value.updatedAt : 0,\n }\n }\n\n /** Removes a key. A key that will not go is logged, not thrown: the queue has already moved on in memory. */\n function removeKey(key: string): void {\n try {\n store.remove(key)\n } catch (error) {\n warn(\"could not remove a key\", error)\n }\n }\n\n /** Writes the index. A failed write is logged, not thrown: the next load rebuilds the index from the records. */\n function persistIndex(partition: string, state: PartitionState): void {\n try {\n writeIndex(partition, state)\n } catch (error) {\n warn(\"could not update the index\", error)\n }\n }\n\n /** Counts losses, in the store if it will take them, otherwise in memory. */\n function noteLosses(\n partition: string,\n kind: LossKind,\n observedAts: Array<string | undefined>,\n at: number = now(),\n ): void {\n if (observedAts.length === 0) return\n const delta = lossesOf(kind, observedAts, at)\n try {\n store.set(lossKey(partition), JSON.stringify(mergeLosses(readLosses(partition) ?? EMPTY_LOSSES, delta)))\n } catch (error) {\n warn(\"could not persist a loss count\", error)\n unpersistedLosses.set(partition, mergeLosses(unpersistedLosses.get(partition) ?? EMPTY_LOSSES, delta))\n }\n }\n\n /** The partition's state from its record keys, repairing the index if a crash left it behind. */\n function stateFrom(partition: string, recordKeys: string[]): PartitionState {\n const prefix = recordPrefix(partition)\n const keys = [...recordKeys].sort()\n const index = readIndex(partition)\n const lastKey = keys[keys.length - 1]\n const lastSequence =\n lastKey === undefined ? -1 : Number(lastKey.slice(prefix.length, prefix.length + SEQUENCE_WIDTH))\n const state: PartitionState = {keys, nextSeq: Math.max(index?.nextSeq ?? 0, lastSequence + 1)}\n const inStep =\n index !== null &&\n index.count === keys.length &&\n index.nextSeq === state.nextSeq &&\n index.oldest === (keys[0] ?? null) &&\n index.newest === (lastKey ?? null)\n if (!inStep && (keys.length > 0 || index !== null)) persistIndex(partition, state)\n return state\n }\n\n /** The partition's records, read from the store the first time the process asks. */\n function load(partition: string): PartitionState {\n const loaded = states.get(partition)\n if (loaded) return loaded\n const prefix = recordPrefix(partition)\n const state = stateFrom(\n partition,\n store.getAllKeys().filter((key) => isRecordKey(key, prefix)),\n )\n states.set(partition, state)\n return state\n }\n\n /** Removes the first `count` records of the partition (they are already counted as lost). */\n function removeOldest(state: PartitionState, count: number): void {\n for (const key of state.keys.splice(0, count)) removeKey(key)\n }\n\n /** Discards records that cannot be read, counting them as dropped. */\n function discardUnreadable(partition: string, state: PartitionState, unreadable: string[]): void {\n noteLosses(\n partition,\n \"dropped\",\n unreadable.map(() => undefined),\n )\n const gone = new Set(unreadable)\n for (const key of unreadable) removeKey(key)\n state.keys = state.keys.filter((key) => !gone.has(key))\n persistIndex(partition, state)\n }\n\n function trimToCapacity(partition: string, state: PartitionState): void {\n const excess = state.keys.length - FLEET_LIMITS.phoneQueueMaxRecords\n if (excess <= 0) return\n const observedAts = state.keys.slice(0, excess).map((key) => readEntry(key)?.record.observedAt)\n noteLosses(partition, \"dropped\", observedAts)\n removeOldest(state, excess)\n }\n\n function expire(partition: string, state: PartitionState, at: number): void {\n const cutoff = at - FLEET_LIMITS.phoneQueueMaxAgeMs\n const expired: Array<string | undefined> = []\n const unreadable: Array<undefined> = []\n let head = 0\n // Records are in enqueue order, so the expired ones are a prefix of the queue. If the wall clock was stepped back,\n // a young record can sit ahead of older ones; those wait until it ages out, which only delays their expiry.\n for (; head < state.keys.length; head += 1) {\n const entry = readEntry(state.keys[head])\n if (entry === null) unreadable.push(undefined)\n else if (entry.enqueuedAt < cutoff) expired.push(entry.record.observedAt)\n else break\n }\n if (head === 0) return\n noteLosses(partition, \"expired\", expired, at)\n noteLosses(partition, \"dropped\", unreadable, at)\n removeOldest(state, head)\n persistIndex(partition, state)\n }\n\n /** Expires a partition's old records, then clears out what an empty partition no longer needs. */\n function sweepPartition(partition: string, found: FoundPartition, at: number): void {\n const state = states.get(partition) ?? stateFrom(partition, found.records)\n expire(partition, state, at)\n if (state.keys.length > 0) return\n if (found.hasIndex) removeKey(indexKey(partition))\n if (found.hasSequences) removeKey(sequenceKey(partition))\n if (found.hasLosses) {\n const losses = readLosses(partition)\n if (losses === null || at - losses.updatedAt > FLEET_LIMITS.phoneQueueMaxAgeMs) removeKey(lossKey(partition))\n }\n }\n\n function loadSequences(partition: string): Array<[string, number]> {\n const cached = sequences.get(partition)\n if (cached) return cached\n const stored = readJson(sequenceKey(partition))\n const counters = (Array.isArray(stored) ? stored : []).filter(\n (item): item is [string, number] => Array.isArray(item) && typeof item[0] === \"string\" && isCount(item[1]),\n )\n sequences.set(partition, counters)\n return counters\n }\n\n return {\n enqueue(partition, record) {\n let state: PartitionState\n try {\n state = load(partition)\n const key = recordKey(partition, state.nextSeq, record.recordId)\n store.set(key, JSON.stringify({enqueuedAt: now(), record}))\n state.keys.push(key)\n state.nextSeq += 1\n } catch (error) {\n warn(\"could not store a record\", error)\n noteLosses(partition, \"dropped\", [record?.observedAt])\n return\n }\n trimToCapacity(partition, state)\n persistIndex(partition, state)\n },\n\n nextBatch(partition, maxRecords, maxBytes) {\n if (!(maxRecords >= 1)) return []\n const state = load(partition)\n const batch: FleetRecord[] = []\n const unreadable: string[] = []\n let bytes = 0\n for (const key of state.keys) {\n if (batch.length >= maxRecords) break\n const entry = readEntry(key)\n if (entry === null) {\n unreadable.push(key)\n continue\n }\n const size = utf8ByteLength(JSON.stringify(entry.record))\n if (batch.length > 0 && !(bytes + size <= maxBytes)) break\n batch.push(entry.record)\n bytes += size\n }\n if (unreadable.length > 0) discardUnreadable(partition, state, unreadable)\n return batch\n },\n\n remove(partition, recordIds) {\n if (recordIds.length === 0) return\n const state = load(partition)\n const wanted = new Set(recordIds)\n const idOffset = recordPrefix(partition).length + SEQUENCE_WIDTH + 1\n const kept: string[] = []\n for (const key of state.keys) {\n if (wanted.has(key.slice(idOffset))) removeKey(key)\n else kept.push(key)\n }\n if (kept.length === state.keys.length) return\n state.keys = kept\n persistIndex(partition, state)\n },\n\n takeLosses(partition, at) {\n expire(partition, load(partition), at)\n const losses = mergeLosses(\n readLosses(partition) ?? EMPTY_LOSSES,\n unpersistedLosses.get(partition) ?? EMPTY_LOSSES,\n )\n if (losses.dropped === 0 && losses.expired === 0) return {dropped: 0, expired: 0, from: null, to: null}\n // If the counters will not go they are reported again next time, which is better than never.\n removeKey(lossKey(partition))\n unpersistedLosses.delete(partition)\n return {dropped: losses.dropped, expired: losses.expired, from: losses.from, to: losses.to}\n },\n\n sweepExpired(at = now()) {\n try {\n for (const [partition, found] of groupByPartition(store.getAllKeys())) {\n try {\n sweepPartition(partition, found, at)\n } catch (error) {\n warn(\"could not sweep a partition\", error)\n }\n }\n // Counts that never reached the store go with the process; forget the ones too old to report.\n for (const [partition, losses] of unpersistedLosses) {\n if (at - losses.updatedAt > FLEET_LIMITS.phoneQueueMaxAgeMs) unpersistedLosses.delete(partition)\n }\n } catch (error) {\n warn(\"could not sweep expired records\", error)\n }\n },\n\n clearPartition(partition) {\n const state = load(partition)\n for (const key of state.keys) removeKey(key)\n removeKey(indexKey(partition))\n removeKey(lossKey(partition))\n removeKey(sequenceKey(partition))\n state.keys = []\n state.nextSeq = 0\n unpersistedLosses.delete(partition)\n sequences.delete(partition)\n },\n\n size(partition) {\n return load(partition).keys.length\n },\n\n nextSequence(partition, collectorSessionId) {\n const counters = loadSequences(partition)\n const position = counters.findIndex(([id]) => id === collectorSessionId)\n const next = position >= 0 ? counters[position][1] : 0\n if (position >= 0) counters.splice(position, 1)\n counters.push([collectorSessionId, next + 1])\n while (counters.length > MAX_TRACKED_COLLECTOR_SESSIONS) counters.shift()\n try {\n store.set(sequenceKey(partition), JSON.stringify(counters))\n } catch (error) {\n // The counter still increases for the life of this process.\n warn(\"could not persist a sequence counter\", error)\n }\n return next\n },\n }\n}\n\nlet sharedQueue: FleetQueue | undefined\n\n/**\n * Without a store, the app's one shared queue over the `mentra-fleet` MMKV instance. A queue keeps its view of a\n * store in memory, so two queues over one store would not see each other's writes; share this one.\n *\n * With a store (tests), a new queue over it. `options` apply only then.\n */\nexport function createFleetQueue(store?: FleetKeyValueStore, options: FleetQueueOptions = {}): FleetQueue {\n if (store === undefined) return (sharedQueue ??= buildQueue(getFleetStore(), {}))\n return buildQueue(store, options)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fleetQueue.js","sourceRoot":"","sources":["../../../src/services/fleet/fleetQueue.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Fleet reporter's durable queue.\n *\n * The collectors record observations here and the uploader drains them to the phone's own Core. A partition is\n * one account on one Core: records queued as account A on Core X are only ever delivered as A to X, so a\n * partition's records are never returned for, or mixed with, another's. Every call takes the partition it works\n * on.\n *\n * Storage layout, in the `mentra-fleet` MMKV instance (which logout's `storage.clearAll()` does not touch):\n * - `q:<partition>:<seq>:<recordId>` holds one record, as `{enqueuedAt, record}` JSON. `<seq>` is the partition's\n * enqueue counter, zero-padded to 16 digits so that key order is queue order.\n * - `i:<partition>` is the partition's index: `{count, nextSeq, oldest, newest}`, the record count, the next\n * sequence and the oldest and newest record keys.\n * - `l:<partition>` holds the losses not yet reported: `{dropped, expired, from, to, updatedAt}`.\n * - `n:<partition>` holds the collector-session sequence counters, as `[collectorSessionId, next][]`.\n * - `t:<partition>` holds the miniapp telemetry lost and not yet reported, per package (`FleetTelemetryLosses`).\n *\n * Telemetry (Phase 12) has a share of the queue: a `telemetry_event`'s record ID starts with `tel.`, so its key says\n * what it is. Telemetry takes at most 2,000 of the 10,000 places. When that share or the queue is full, the oldest\n * telemetry record gives way, and a record of any other kind never does to telemetry: an arriving record that finds\n * the queue full evicts the oldest telemetry before anything else. Telemetry that is evicted, expires, or that MFM\n * rejects is counted per package under `t:`, never as a coverage gap, and the reporter sends it as `telemetry_loss`.\n *\n * MMKV can list keys only by listing all of them, so the first call for a partition in a process scans the store\n * once, repairs the index if a crash left it behind, and keeps the partition's ordered record keys in memory.\n * After that every call costs in proportion to its own work: an enqueue is two writes (three at capacity), and\n * no call touches another partition's records. Because that list is the queue's view of the store, one queue\n * owns a store: `createFleetQueue()` returns the app's shared queue, and tests build theirs over a store of\n * their own.\n *\n * Writes are ordered so that a crash errs toward reporting a loss twice rather than never: a loss is recorded\n * before the record it describes is removed, and the index is written last and rebuilt from the records if it is\n * missing or stale.\n *\n * Logout spares this store, so an account that signs out and never returns leaves its partition behind.\n * `sweepExpired` is how the 7-day bound still holds for it: one scan finds every partition, expires its old\n * records and removes what an empty partition no longer needs.\n */\nimport {FLEET_LIMITS, FLEET_TELEMETRY_LIMITS, type FleetRecord} from \"@mentra/cloud-protocol\"\nimport {normalizeCoreUrl} from \"./coreUrl\"\nimport {getFleetStore} from \"./fleetStorage\"\nimport {sha256HexOfText, utf8ByteLength} from \"./utf8\"\n\n/** The subset of an MMKV instance the queue uses; tests pass an in-memory Map-backed implementation. */\nexport interface FleetKeyValueStore {\n getString(key: string): string | undefined\n set(key: string, value: string): void\n remove(key: string): void\n getAllKeys(): string[]\n}\n\n/** What the queue lost since the last `takeLosses`, and the span of observation time it covers. */\nexport interface FleetLosses {\n /** Records discarded because the queue was full (or could not be read or written). */\n dropped: number\n /** Records discarded because they were older than the queue's maximum age. */\n expired: number\n /** The earliest `observedAt` among the lost records, or null if none had a readable one. */\n from: string | null\n /** The latest `observedAt` among the lost records, or null if none had a readable one. */\n to: string | null\n}\n\n/** The telemetry the queue (or the Mentra App's limits) lost, per package, since the last `takeTelemetryLosses`. */\nexport interface FleetTelemetryLosses {\n packages: Record<string, {overLimit: number; evicted: number; expired: number; rejected: number}>\n from: string | null\n to: string | null\n}\nexport type FleetTelemetryLossKind = \"overLimit\" | \"evicted\" | \"expired\" | \"rejected\"\n\n/** A telemetry record's ID starts with this, so the queue tells telemetry from the rest by its key. */\nexport const TELEMETRY_RECORD_PREFIX = \"tel.\"\nexport const isTelemetryRecordId = (recordId: string) => recordId.startsWith(TELEMETRY_RECORD_PREFIX)\n\n/** A partition is one account on one Core. */\nexport interface FleetQueue {\n /** Adds a record. Drops the oldest beyond 10,000 and counts the drop. Never throws: a record that cannot be stored is counted as dropped. */\n enqueue(partition: string, record: FleetRecord): void\n /**\n * Up to `maxRecords` records, oldest first, whose own JSON (UTF-8 bytes, as serialized in the batch) adds up to\n * at most `maxBytes`. `maxBytes` is the caller's budget for the records themselves; the caller leaves headroom\n * for the envelope and the commas. The oldest record is always offered even if it alone exceeds `maxBytes`, so\n * one oversize record cannot block the queue; the server rejects it and the caller removes it. Records stay\n * queued until `remove`.\n */\n nextBatch(partition: string, maxRecords: number, maxBytes: number): FleetRecord[]\n /** Removes the records with these IDs. IDs that are not queued are ignored. */\n remove(partition: string, recordIds: string[]): void\n /**\n * Drops records older than 7 days, then returns the drops and expiries since the last call, with the span of\n * `observedAt` they cover, and resets the counters. The caller turns a non-zero result into a coverage gap and\n * should enqueue it straight away: the counters are cleared by this call.\n */\n takeLosses(partition: string, now: number): FleetLosses\n /**\n * Enforces the 7-day bound on every partition in the store, including those of accounts that signed out and\n * never came back (nothing else visits them, and logout spares this store). One scan finds the partitions; for\n * each, records older than 7 days are dropped and counted exactly as `takeLosses` counts expiries, so an\n * account that does return still gets its coverage gap. A partition left with no records loses its index and\n * sequence counters, and its loss counters too once they are themselves older than 7 days: a gap report that\n * old, for an account that never returned, can never be delivered. Never throws.\n *\n * The reporter calls this at start, and then at most hourly from its upload loop (only when an upload is due, so\n * an idle reporter does not wake to sweep). `now` defaults to the queue's clock.\n */\n sweepExpired(now?: number): void\n /** Counts `count` of `packageName`'s telemetry as lost, as of `at` (epoch ms). Never throws. */\n noteTelemetryLoss(partition: string, packageName: string, kind: FleetTelemetryLossKind, count: number, at?: number): void\n /** The telemetry lost since the last call, per package, and resets the counters; `null` when nothing was lost. */\n takeTelemetryLosses(partition: string, now: number): FleetTelemetryLosses | null\n /** Forgets everything held for the partition: records, losses and sequence counters. */\n clearPartition(partition: string): void\n size(partition: string): number\n /** The next sequence number for a collector session, counting up from 0 and persisted. */\n nextSequence(partition: string, collectorSessionId: string): number\n}\n\nexport interface FleetQueueOptions {\n /** The clock that stamps `enqueuedAt`. Defaults to `Date.now`. */\n now?: () => number\n}\n\nconst SEQUENCE_WIDTH = 16\nconst MAX_TRACKED_COLLECTOR_SESSIONS = 16\n\n/** The losses as stored: the counters plus when they last changed, which tells a sweep when they are too old to matter. */\ninterface LossRecord extends FleetLosses {\n updatedAt: number\n}\nconst EMPTY_LOSSES: LossRecord = {dropped: 0, expired: 0, from: null, to: null, updatedAt: 0}\n\n/** The partition for an account on a Core: sha256 hex of `${normalizedCoreUrl}|${mentraUserId}`. */\nexport function partitionKey(coreUrl: string, mentraUserId: string): string {\n const core = normalizeCoreUrl(coreUrl)\n if (core === \"\" || mentraUserId.trim() === \"\") throw new Error(\"A Fleet partition needs a Core URL and an account\")\n return sha256HexOfText(`${core}|${mentraUserId}`)\n}\n\ninterface StoredEntry {\n enqueuedAt: number\n record: FleetRecord\n}\n\ninterface PartitionIndex {\n count: number\n nextSeq: number\n oldest: string | null\n newest: string | null\n}\n\ninterface PartitionState {\n /** The partition's record keys in queue order. */\n keys: string[]\n nextSeq: number\n}\n\ntype LossKind = \"dropped\" | \"expired\"\n\nconst recordPrefix = (partition: string) => `q:${partition}:`\nconst indexKey = (partition: string) => `i:${partition}`\nconst lossKey = (partition: string) => `l:${partition}`\nconst sequenceKey = (partition: string) => `n:${partition}`\nconst telemetryLossKey = (partition: string) => `t:${partition}`\n\nfunction recordKey(partition: string, sequence: number, recordId: string): string {\n return `${recordPrefix(partition)}${String(sequence).padStart(SEQUENCE_WIDTH, \"0\")}:${recordId}`\n}\n\nfunction isRecordKey(key: string, prefix: string): boolean {\n if (!key.startsWith(prefix)) return false\n for (let offset = 0; offset < SEQUENCE_WIDTH; offset += 1) {\n const code = key.charCodeAt(prefix.length + offset)\n if (!(code >= 48 && code <= 57)) return false\n }\n return key.charCodeAt(prefix.length + SEQUENCE_WIDTH) === 58\n}\n\nfunction isCount(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction warn(message: string, error: unknown): void {\n // Never log record content: only what failed.\n console.warn(`[FleetQueue] ${message}: ${describe(error)}`)\n}\n\nfunction parseTime(value: string | null): number {\n return value === null ? Number.NaN : Date.parse(value)\n}\n\nfunction earlier(a: string | null, b: string | null): string | null {\n if (Number.isNaN(parseTime(a))) return Number.isNaN(parseTime(b)) ? null : b\n if (Number.isNaN(parseTime(b))) return a\n return parseTime(b) < parseTime(a) ? b : a\n}\n\nfunction later(a: string | null, b: string | null): string | null {\n if (Number.isNaN(parseTime(a))) return Number.isNaN(parseTime(b)) ? null : b\n if (Number.isNaN(parseTime(b))) return a\n return parseTime(b) > parseTime(a) ? b : a\n}\n\nfunction mergeLosses(a: LossRecord, b: LossRecord): LossRecord {\n return {\n dropped: a.dropped + b.dropped,\n expired: a.expired + b.expired,\n from: earlier(a.from, b.from),\n to: later(a.to, b.to),\n updatedAt: Math.max(a.updatedAt, b.updatedAt),\n }\n}\n\nfunction lossesOf(kind: LossKind, observedAts: Array<string | undefined>, updatedAt: number): LossRecord {\n let from: string | null = null\n let to: string | null = null\n for (const observedAt of observedAts) {\n if (observedAt === undefined) continue\n from = earlier(from, observedAt)\n to = later(to, observedAt)\n }\n return {\n dropped: kind === \"dropped\" ? observedAts.length : 0,\n expired: kind === \"expired\" ? observedAts.length : 0,\n from,\n to,\n updatedAt,\n }\n}\n\n/** What a scan of the store found for one partition. */\ninterface FoundPartition {\n records: string[]\n hasIndex: boolean\n hasLosses: boolean\n hasTelemetryLosses: boolean\n hasSequences: boolean\n}\n\nconst RECORD_KEY_PARTITION = /^q:(.+?):\\d{16}:/\n\n/** Groups every Fleet queue key in the store by the partition it belongs to. */\nfunction groupByPartition(keys: string[]): Map<string, FoundPartition> {\n const found = new Map<string, FoundPartition>()\n const entry = (partition: string): FoundPartition => {\n let existing = found.get(partition)\n if (!existing) {\n existing = {records: [], hasIndex: false, hasLosses: false, hasTelemetryLosses: false, hasSequences: false}\n found.set(partition, existing)\n }\n return existing\n }\n for (const key of keys) {\n if (key.startsWith(\"q:\")) {\n const partition = RECORD_KEY_PARTITION.exec(key)?.[1]\n if (partition !== undefined) entry(partition).records.push(key)\n } else if (key.startsWith(\"i:\")) entry(key.slice(2)).hasIndex = true\n else if (key.startsWith(\"l:\")) entry(key.slice(2)).hasLosses = true\n else if (key.startsWith(\"n:\")) entry(key.slice(2)).hasSequences = true\n else if (key.startsWith(\"t:\")) entry(key.slice(2)).hasTelemetryLosses = true\n }\n return found\n}\n\nfunction buildQueue(store: FleetKeyValueStore, options: FleetQueueOptions): FleetQueue {\n const now = options.now ?? Date.now\n const states = new Map<string, PartitionState>()\n /** Losses that could not be written to the store; reported by the next `takeLosses` in this process. */\n const unpersistedLosses = new Map<string, LossRecord>()\n const sequences = new Map<string, Array<[string, number]>>()\n\n function readJson(key: string): unknown {\n try {\n const raw = store.getString(key)\n return raw === undefined ? null : JSON.parse(raw)\n } catch {\n return null\n }\n }\n\n function readIndex(partition: string): PartitionIndex | null {\n const value = readJson(indexKey(partition)) as Partial<PartitionIndex> | null\n if (!value || !isCount(value.count) || !isCount(value.nextSeq)) return null\n const oldest = typeof value.oldest === \"string\" ? value.oldest : null\n const newest = typeof value.newest === \"string\" ? value.newest : null\n return {count: value.count, nextSeq: value.nextSeq, oldest, newest}\n }\n\n function writeIndex(partition: string, state: PartitionState): void {\n const index: PartitionIndex = {\n count: state.keys.length,\n nextSeq: state.nextSeq,\n oldest: state.keys[0] ?? null,\n newest: state.keys[state.keys.length - 1] ?? null,\n }\n store.set(indexKey(partition), JSON.stringify(index))\n }\n\n function readEntry(key: string): StoredEntry | null {\n const value = readJson(key) as Partial<StoredEntry> | null\n const record = value?.record as Partial<FleetRecord> | undefined\n if (!value || typeof value.enqueuedAt !== \"number\" || !record || typeof record.recordId !== \"string\") return null\n return value as StoredEntry\n }\n\n function readLosses(partition: string): LossRecord | null {\n const value = readJson(lossKey(partition)) as Partial<LossRecord> | null\n if (!value || !isCount(value.dropped) || !isCount(value.expired)) return null\n return {\n dropped: value.dropped,\n expired: value.expired,\n from: typeof value.from === \"string\" ? value.from : null,\n to: typeof value.to === \"string\" ? value.to : null,\n updatedAt: isCount(value.updatedAt) ? value.updatedAt : 0,\n }\n }\n\n /** Removes a key. A key that will not go is logged, not thrown: the queue has already moved on in memory. */\n function removeKey(key: string): void {\n try {\n store.remove(key)\n } catch (error) {\n warn(\"could not remove a key\", error)\n }\n }\n\n /** Writes the index. A failed write is logged, not thrown: the next load rebuilds the index from the records. */\n function persistIndex(partition: string, state: PartitionState): void {\n try {\n writeIndex(partition, state)\n } catch (error) {\n warn(\"could not update the index\", error)\n }\n }\n\n /** Counts losses, in the store if it will take them, otherwise in memory. */\n function noteLosses(\n partition: string,\n kind: LossKind,\n observedAts: Array<string | undefined>,\n at: number = now(),\n ): void {\n if (observedAts.length === 0) return\n const delta = lossesOf(kind, observedAts, at)\n try {\n store.set(lossKey(partition), JSON.stringify(mergeLosses(readLosses(partition) ?? EMPTY_LOSSES, delta)))\n } catch (error) {\n warn(\"could not persist a loss count\", error)\n unpersistedLosses.set(partition, mergeLosses(unpersistedLosses.get(partition) ?? EMPTY_LOSSES, delta))\n }\n }\n\n /** The partition's state from its record keys, repairing the index if a crash left it behind. */\n function stateFrom(partition: string, recordKeys: string[]): PartitionState {\n const prefix = recordPrefix(partition)\n const keys = [...recordKeys].sort()\n const index = readIndex(partition)\n const lastKey = keys[keys.length - 1]\n const lastSequence =\n lastKey === undefined ? -1 : Number(lastKey.slice(prefix.length, prefix.length + SEQUENCE_WIDTH))\n const state: PartitionState = {keys, nextSeq: Math.max(index?.nextSeq ?? 0, lastSequence + 1)}\n const inStep =\n index !== null &&\n index.count === keys.length &&\n index.nextSeq === state.nextSeq &&\n index.oldest === (keys[0] ?? null) &&\n index.newest === (lastKey ?? null)\n if (!inStep && (keys.length > 0 || index !== null)) persistIndex(partition, state)\n return state\n }\n\n /** The partition's records, read from the store the first time the process asks. */\n function load(partition: string): PartitionState {\n const loaded = states.get(partition)\n if (loaded) return loaded\n const prefix = recordPrefix(partition)\n const state = stateFrom(\n partition,\n store.getAllKeys().filter((key) => isRecordKey(key, prefix)),\n )\n states.set(partition, state)\n return state\n }\n\n /** Removes the first `count` records of the partition (they are already counted as lost). */\n function removeOldest(state: PartitionState, count: number): void {\n for (const key of state.keys.splice(0, count)) removeKey(key)\n }\n\n /** Discards records that cannot be read, counting them as dropped. */\n function discardUnreadable(partition: string, state: PartitionState, unreadable: string[]): void {\n // An unreadable telemetry record names no miniapp to count it for, and is never a coverage gap.\n noteLosses(\n partition,\n \"dropped\",\n unreadable.filter((key) => !isTelemetryKey(partition, key)).map(() => undefined),\n )\n const gone = new Set(unreadable)\n for (const key of unreadable) removeKey(key)\n state.keys = state.keys.filter((key) => !gone.has(key))\n persistIndex(partition, state)\n }\n\n const idOffsetOf = (partition: string) => recordPrefix(partition).length + SEQUENCE_WIDTH + 1\n const isTelemetryKey = (partition: string, key: string) => isTelemetryRecordId(key.slice(idOffsetOf(partition)))\n\n /** Removes the telemetry record at `key`, counting it as `kind` for its package. */\n function evictTelemetry(partition: string, state: PartitionState, key: string, kind: \"evicted\" | \"expired\", at: number = now()): void {\n const record = readEntry(key)?.record\n const packageName = record?.kind === \"telemetry_event\" ? record.payload.packageName : null\n if (packageName !== null) noteTelemetryLoss(partition, packageName, kind, 1, at, record!.observedAt)\n state.keys.splice(state.keys.indexOf(key), 1)\n removeKey(key)\n }\n\n /**\n * Keeps the partition inside its places: telemetry inside its share, and the whole inside 10,000, the oldest\n * telemetry going first. Only a queue full of other records drops the oldest record of all, as a coverage gap.\n */\n function trimToCapacity(partition: string, state: PartitionState): void {\n const telemetry = state.keys.filter((key) => isTelemetryKey(partition, key))\n while (telemetry.length > FLEET_TELEMETRY_LIMITS.queueShare) evictTelemetry(partition, state, telemetry.shift()!, \"evicted\")\n while (state.keys.length > FLEET_LIMITS.phoneQueueMaxRecords && telemetry.length > 0) evictTelemetry(partition, state, telemetry.shift()!, \"evicted\")\n const excess = state.keys.length - FLEET_LIMITS.phoneQueueMaxRecords\n if (excess <= 0) return\n const observedAts = state.keys.slice(0, excess).map((key) => readEntry(key)?.record.observedAt)\n noteLosses(partition, \"dropped\", observedAts)\n removeOldest(state, excess)\n }\n\n function readTelemetryLosses(partition: string): (FleetTelemetryLosses & {updatedAt: number}) | null {\n const value = readJson(telemetryLossKey(partition)) as (FleetTelemetryLosses & {updatedAt: number}) | null\n return value && typeof value === \"object\" && value.packages && typeof value.packages === \"object\" ? value : null\n }\n\n function noteTelemetryLoss(partition: string, packageName: string, kind: FleetTelemetryLossKind, count: number, at: number = now(), observedAt?: string): void {\n if (!(count > 0)) return\n try {\n const stored = readTelemetryLosses(partition) ?? {packages: {}, from: null, to: null, updatedAt: at}\n const entry = stored.packages[packageName] ?? {overLimit: 0, evicted: 0, expired: 0, rejected: 0}\n entry[kind] += count\n stored.packages[packageName] = entry\n const when = observedAt ?? new Date(at).toISOString()\n stored.from = earlier(stored.from, when)\n stored.to = later(stored.to, when)\n stored.updatedAt = at\n store.set(telemetryLossKey(partition), JSON.stringify(stored))\n } catch (error) {\n // Telemetry losses are best effort: a count that cannot be stored is lost with the events it counted.\n warn(\"could not persist a telemetry loss count\", error)\n }\n }\n\n function expire(partition: string, state: PartitionState, at: number): void {\n const cutoff = at - FLEET_LIMITS.phoneQueueMaxAgeMs\n const expired: Array<string | undefined> = []\n const unreadable: Array<undefined> = []\n let head = 0\n // Records are in enqueue order, so the expired ones are a prefix of the queue. If the wall clock was stepped back,\n // a young record can sit ahead of older ones; those wait until it ages out, which only delays their expiry.\n for (; head < state.keys.length; head += 1) {\n const entry = readEntry(state.keys[head])\n if (entry === null) {\n if (!isTelemetryKey(partition, state.keys[head]!)) unreadable.push(undefined)\n } else if (entry.enqueuedAt < cutoff) {\n // Expired telemetry is counted per package, never as a coverage gap.\n if (entry.record.kind === \"telemetry_event\") {\n noteTelemetryLoss(partition, entry.record.payload.packageName, \"expired\", 1, at, entry.record.observedAt)\n } else expired.push(entry.record.observedAt)\n } else break\n }\n if (head === 0) return\n noteLosses(partition, \"expired\", expired, at)\n noteLosses(partition, \"dropped\", unreadable, at)\n removeOldest(state, head)\n persistIndex(partition, state)\n }\n\n /** Expires a partition's old records, then clears out what an empty partition no longer needs. */\n function sweepPartition(partition: string, found: FoundPartition, at: number): void {\n const state = states.get(partition) ?? stateFrom(partition, found.records)\n expire(partition, state, at)\n if (state.keys.length > 0) return\n if (found.hasIndex) removeKey(indexKey(partition))\n if (found.hasSequences) removeKey(sequenceKey(partition))\n if (found.hasLosses) {\n const losses = readLosses(partition)\n if (losses === null || at - losses.updatedAt > FLEET_LIMITS.phoneQueueMaxAgeMs) removeKey(lossKey(partition))\n }\n if (found.hasTelemetryLosses) {\n const losses = readTelemetryLosses(partition)\n if (losses === null || at - losses.updatedAt > FLEET_LIMITS.phoneQueueMaxAgeMs) removeKey(telemetryLossKey(partition))\n }\n }\n\n function loadSequences(partition: string): Array<[string, number]> {\n const cached = sequences.get(partition)\n if (cached) return cached\n const stored = readJson(sequenceKey(partition))\n const counters = (Array.isArray(stored) ? stored : []).filter(\n (item): item is [string, number] => Array.isArray(item) && typeof item[0] === \"string\" && isCount(item[1]),\n )\n sequences.set(partition, counters)\n return counters\n }\n\n return {\n enqueue(partition, record) {\n let state: PartitionState\n try {\n state = load(partition)\n const key = recordKey(partition, state.nextSeq, record.recordId)\n store.set(key, JSON.stringify({enqueuedAt: now(), record}))\n state.keys.push(key)\n state.nextSeq += 1\n } catch (error) {\n warn(\"could not store a record\", error)\n // Telemetry that cannot be stored is its miniapp's loss, never a coverage gap.\n if (record?.kind === \"telemetry_event\") noteTelemetryLoss(partition, record.payload.packageName, \"evicted\", 1, now(), record.observedAt)\n else noteLosses(partition, \"dropped\", [record?.observedAt])\n return\n }\n trimToCapacity(partition, state)\n persistIndex(partition, state)\n },\n\n nextBatch(partition, maxRecords, maxBytes) {\n if (!(maxRecords >= 1)) return []\n const state = load(partition)\n const batch: FleetRecord[] = []\n const unreadable: string[] = []\n let bytes = 0\n for (const key of state.keys) {\n if (batch.length >= maxRecords) break\n const entry = readEntry(key)\n if (entry === null) {\n unreadable.push(key)\n continue\n }\n const size = utf8ByteLength(JSON.stringify(entry.record))\n if (batch.length > 0 && !(bytes + size <= maxBytes)) break\n batch.push(entry.record)\n bytes += size\n }\n if (unreadable.length > 0) discardUnreadable(partition, state, unreadable)\n return batch\n },\n\n remove(partition, recordIds) {\n if (recordIds.length === 0) return\n const state = load(partition)\n const wanted = new Set(recordIds)\n const idOffset = recordPrefix(partition).length + SEQUENCE_WIDTH + 1\n const kept: string[] = []\n for (const key of state.keys) {\n if (wanted.has(key.slice(idOffset))) removeKey(key)\n else kept.push(key)\n }\n if (kept.length === state.keys.length) return\n state.keys = kept\n persistIndex(partition, state)\n },\n\n takeLosses(partition, at) {\n expire(partition, load(partition), at)\n const losses = mergeLosses(\n readLosses(partition) ?? EMPTY_LOSSES,\n unpersistedLosses.get(partition) ?? EMPTY_LOSSES,\n )\n if (losses.dropped === 0 && losses.expired === 0) return {dropped: 0, expired: 0, from: null, to: null}\n // If the counters will not go they are reported again next time, which is better than never.\n removeKey(lossKey(partition))\n unpersistedLosses.delete(partition)\n return {dropped: losses.dropped, expired: losses.expired, from: losses.from, to: losses.to}\n },\n\n sweepExpired(at = now()) {\n try {\n for (const [partition, found] of groupByPartition(store.getAllKeys())) {\n try {\n sweepPartition(partition, found, at)\n } catch (error) {\n warn(\"could not sweep a partition\", error)\n }\n }\n // Counts that never reached the store go with the process; forget the ones too old to report.\n for (const [partition, losses] of unpersistedLosses) {\n if (at - losses.updatedAt > FLEET_LIMITS.phoneQueueMaxAgeMs) unpersistedLosses.delete(partition)\n }\n } catch (error) {\n warn(\"could not sweep expired records\", error)\n }\n },\n\n noteTelemetryLoss(partition, packageName, kind, count, at) {\n noteTelemetryLoss(partition, packageName, kind, count, at)\n },\n\n takeTelemetryLosses(partition, at) {\n expire(partition, load(partition), at)\n const losses = readTelemetryLosses(partition)\n if (losses === null || Object.keys(losses.packages).length === 0) return null\n removeKey(telemetryLossKey(partition))\n return {packages: losses.packages, from: losses.from, to: losses.to}\n },\n\n clearPartition(partition) {\n const state = load(partition)\n for (const key of state.keys) removeKey(key)\n removeKey(indexKey(partition))\n removeKey(lossKey(partition))\n removeKey(telemetryLossKey(partition))\n removeKey(sequenceKey(partition))\n state.keys = []\n state.nextSeq = 0\n unpersistedLosses.delete(partition)\n sequences.delete(partition)\n },\n\n size(partition) {\n return load(partition).keys.length\n },\n\n nextSequence(partition, collectorSessionId) {\n const counters = loadSequences(partition)\n const position = counters.findIndex(([id]) => id === collectorSessionId)\n const next = position >= 0 ? counters[position][1] : 0\n if (position >= 0) counters.splice(position, 1)\n counters.push([collectorSessionId, next + 1])\n while (counters.length > MAX_TRACKED_COLLECTOR_SESSIONS) counters.shift()\n try {\n store.set(sequenceKey(partition), JSON.stringify(counters))\n } catch (error) {\n // The counter still increases for the life of this process.\n warn(\"could not persist a sequence counter\", error)\n }\n return next\n },\n }\n}\n\nlet sharedQueue: FleetQueue | undefined\n\n/**\n * Without a store, the app's one shared queue over the `mentra-fleet` MMKV instance. A queue keeps its view of a\n * store in memory, so two queues over one store would not see each other's writes; share this one.\n *\n * With a store (tests), a new queue over it. `options` apply only then.\n */\nexport function createFleetQueue(store?: FleetKeyValueStore, options: FleetQueueOptions = {}): FleetQueue {\n if (store === undefined) return (sharedQueue ??= buildQueue(getFleetStore(), {}))\n return buildQueue(store, options)\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface LimitedEvent {
|
|
2
|
+
event: string;
|
|
3
|
+
properties: Record<string, string | number | boolean>;
|
|
4
|
+
droppedProperties: number;
|
|
5
|
+
cutValues: number;
|
|
6
|
+
}
|
|
7
|
+
/** The event as it may be queued, or null when it must be dropped whole. Never throws. */
|
|
8
|
+
export declare function limitEvent(event: unknown, properties: unknown): LimitedEvent | null;
|
|
9
|
+
/** Each miniapp's rate on this phone: 60 events in any minute and 2,000 a UTC day. Kept in memory. */
|
|
10
|
+
export declare class TelemetryRate {
|
|
11
|
+
private readonly recent;
|
|
12
|
+
private readonly daily;
|
|
13
|
+
/** Whether `packageName` may send one more event at `at`; counts it when it may. */
|
|
14
|
+
admit(packageName: string, at: number): boolean;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=telemetryLimits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetryLimits.d.ts","sourceRoot":"","sources":["../../../src/services/fleet/telemetryLimits.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;IACrD,iBAAiB,EAAE,MAAM,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;CAClB;AAWD,0FAA0F;AAC1F,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAqBnF;AAKD,sGAAsG;AACtG,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkD;IAExE,oFAAoF;IACpF,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO;CAchD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Mentra App's limits on a miniapp's own events (Fleet spec, Miniapp telemetry), applied before an event is
|
|
3
|
+
* queued. MFM checks them again and rejects what still breaks one, so nothing here may let a bad record through.
|
|
4
|
+
*
|
|
5
|
+
* - An event whose name is not 1-64 lowercase letters, digits and underscores, starting with a letter, is dropped.
|
|
6
|
+
* - A property whose name breaks the same rule, or whose value is not a finite number, a boolean or a string, is
|
|
7
|
+
* dropped; a string longer than 64 characters is cut to 64. Only the first 25 properties, in the order given, are
|
|
8
|
+
* kept. What was dropped and cut is counted on the event.
|
|
9
|
+
* - A miniapp sends at most 60 events a minute and 2,000 a UTC day from this phone; the rest are dropped.
|
|
10
|
+
*
|
|
11
|
+
* Every drop of a whole event is counted by the caller as the miniapp's `overLimit` loss.
|
|
12
|
+
*/
|
|
13
|
+
import { FLEET_TELEMETRY_LIMITS, FLEET_TELEMETRY_NAME } from "@mentra/cloud-protocol";
|
|
14
|
+
/** Cuts a string to at most `max` UTF-16 code units without splitting a surrogate pair. */
|
|
15
|
+
function cut(value, max) {
|
|
16
|
+
if (value.length <= max)
|
|
17
|
+
return value;
|
|
18
|
+
const end = value.charCodeAt(max - 1);
|
|
19
|
+
return value.slice(0, end >= 0xd800 && end <= 0xdbff ? max - 1 : max);
|
|
20
|
+
}
|
|
21
|
+
const isName = (value) => typeof value === "string" && FLEET_TELEMETRY_NAME.test(value);
|
|
22
|
+
/** The event as it may be queued, or null when it must be dropped whole. Never throws. */
|
|
23
|
+
export function limitEvent(event, properties) {
|
|
24
|
+
if (!isName(event))
|
|
25
|
+
return null;
|
|
26
|
+
const kept = {};
|
|
27
|
+
let droppedProperties = 0;
|
|
28
|
+
let cutValues = 0;
|
|
29
|
+
let count = 0;
|
|
30
|
+
const entries = properties !== null && typeof properties === "object" && !Array.isArray(properties) ? Object.entries(properties) : [];
|
|
31
|
+
for (const [name, value] of entries) {
|
|
32
|
+
const valid = isName(name) && ((typeof value === "number" && Number.isFinite(value)) || typeof value === "boolean" || typeof value === "string");
|
|
33
|
+
if (!valid || count >= FLEET_TELEMETRY_LIMITS.maxProperties || name in kept) {
|
|
34
|
+
droppedProperties += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === "string" && value.length > FLEET_TELEMETRY_LIMITS.maxStringValueLength) {
|
|
38
|
+
kept[name] = cut(value, FLEET_TELEMETRY_LIMITS.maxStringValueLength);
|
|
39
|
+
cutValues += 1;
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
kept[name] = value;
|
|
43
|
+
count += 1;
|
|
44
|
+
}
|
|
45
|
+
return { event, properties: kept, droppedProperties, cutValues };
|
|
46
|
+
}
|
|
47
|
+
const MINUTE_MS = 60_000;
|
|
48
|
+
const utcDay = (at) => new Date(at).toISOString().slice(0, 10);
|
|
49
|
+
/** Each miniapp's rate on this phone: 60 events in any minute and 2,000 a UTC day. Kept in memory. */
|
|
50
|
+
export class TelemetryRate {
|
|
51
|
+
recent = new Map();
|
|
52
|
+
daily = new Map();
|
|
53
|
+
/** Whether `packageName` may send one more event at `at`; counts it when it may. */
|
|
54
|
+
admit(packageName, at) {
|
|
55
|
+
const times = (this.recent.get(packageName) ?? []).filter((time) => at - time < MINUTE_MS && time <= at);
|
|
56
|
+
const day = utcDay(at);
|
|
57
|
+
const today = this.daily.get(packageName);
|
|
58
|
+
const sent = today?.day === day ? today.count : 0;
|
|
59
|
+
if (times.length >= FLEET_TELEMETRY_LIMITS.eventsPerMinute || sent >= FLEET_TELEMETRY_LIMITS.eventsPerDay) {
|
|
60
|
+
this.recent.set(packageName, times);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
times.push(at);
|
|
64
|
+
this.recent.set(packageName, times);
|
|
65
|
+
this.daily.set(packageName, { day, count: sent + 1 });
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=telemetryLimits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetryLimits.js","sourceRoot":"","sources":["../../../src/services/fleet/telemetryLimits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAC,sBAAsB,EAAE,oBAAoB,EAAC,MAAM,wBAAwB,CAAA;AASnF,2FAA2F;AAC3F,SAAS,GAAG,CAAC,KAAa,EAAE,GAAW;IACrC,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,KAAK,CAAA;IACrC,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;IACrC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AACvE,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,KAAc,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAEjH,0FAA0F;AAC1F,MAAM,UAAU,UAAU,CAAC,KAAc,EAAE,UAAmB;IAC5D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/B,MAAM,IAAI,GAA8C,EAAE,CAAA;IAC1D,IAAI,iBAAiB,GAAG,CAAC,CAAA;IACzB,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,MAAM,OAAO,GACX,UAAU,KAAK,IAAI,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,UAAqC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAClJ,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,OAAO,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAA;QAChJ,IAAI,CAAC,KAAK,IAAI,KAAK,IAAI,sBAAsB,CAAC,aAAa,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YAC5E,iBAAiB,IAAI,CAAC,CAAA;YACtB,SAAQ;QACV,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,sBAAsB,CAAC,oBAAoB,EAAE,CAAC;YAC5F,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,EAAE,sBAAsB,CAAC,oBAAoB,CAAC,CAAA;YACpE,SAAS,IAAI,CAAC,CAAA;QAChB,CAAC;;YAAM,IAAI,CAAC,IAAI,CAAC,GAAG,KAAkC,CAAA;QACtD,KAAK,IAAI,CAAC,CAAA;IACZ,CAAC;IACD,OAAO,EAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAC,CAAA;AAChE,CAAC;AAED,MAAM,SAAS,GAAG,MAAM,CAAA;AACxB,MAAM,MAAM,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAEtE,sGAAsG;AACtG,MAAM,OAAO,aAAa;IACP,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAA;IACpC,KAAK,GAAG,IAAI,GAAG,EAAwC,CAAA;IAExE,oFAAoF;IACpF,KAAK,CAAC,WAAmB,EAAE,EAAU;QACnC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,CAAA;QACxG,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,CAAA;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACzC,MAAM,IAAI,GAAG,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QACjD,IAAI,KAAK,CAAC,MAAM,IAAI,sBAAsB,CAAC,eAAe,IAAI,IAAI,IAAI,sBAAsB,CAAC,YAAY,EAAE,CAAC;YAC1G,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;YACnC,OAAO,KAAK,CAAA;QACd,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QACnC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,EAAC,GAAG,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,EAAC,CAAC,CAAA;QACnD,OAAO,IAAI,CAAA;IACb,CAAC;CACF","sourcesContent":["/**\n * The Mentra App's limits on a miniapp's own events (Fleet spec, Miniapp telemetry), applied before an event is\n * queued. MFM checks them again and rejects what still breaks one, so nothing here may let a bad record through.\n *\n * - An event whose name is not 1-64 lowercase letters, digits and underscores, starting with a letter, is dropped.\n * - A property whose name breaks the same rule, or whose value is not a finite number, a boolean or a string, is\n * dropped; a string longer than 64 characters is cut to 64. Only the first 25 properties, in the order given, are\n * kept. What was dropped and cut is counted on the event.\n * - A miniapp sends at most 60 events a minute and 2,000 a UTC day from this phone; the rest are dropped.\n *\n * Every drop of a whole event is counted by the caller as the miniapp's `overLimit` loss.\n */\nimport {FLEET_TELEMETRY_LIMITS, FLEET_TELEMETRY_NAME} from \"@mentra/cloud-protocol\"\n\nexport interface LimitedEvent {\n event: string\n properties: Record<string, string | number | boolean>\n droppedProperties: number\n cutValues: number\n}\n\n/** Cuts a string to at most `max` UTF-16 code units without splitting a surrogate pair. */\nfunction cut(value: string, max: number): string {\n if (value.length <= max) return value\n const end = value.charCodeAt(max - 1)\n return value.slice(0, end >= 0xd800 && end <= 0xdbff ? max - 1 : max)\n}\n\nconst isName = (value: unknown): value is string => typeof value === \"string\" && FLEET_TELEMETRY_NAME.test(value)\n\n/** The event as it may be queued, or null when it must be dropped whole. Never throws. */\nexport function limitEvent(event: unknown, properties: unknown): LimitedEvent | null {\n if (!isName(event)) return null\n const kept: Record<string, string | number | boolean> = {}\n let droppedProperties = 0\n let cutValues = 0\n let count = 0\n const entries =\n properties !== null && typeof properties === \"object\" && !Array.isArray(properties) ? Object.entries(properties as Record<string, unknown>) : []\n for (const [name, value] of entries) {\n const valid = isName(name) && ((typeof value === \"number\" && Number.isFinite(value)) || typeof value === \"boolean\" || typeof value === \"string\")\n if (!valid || count >= FLEET_TELEMETRY_LIMITS.maxProperties || name in kept) {\n droppedProperties += 1\n continue\n }\n if (typeof value === \"string\" && value.length > FLEET_TELEMETRY_LIMITS.maxStringValueLength) {\n kept[name] = cut(value, FLEET_TELEMETRY_LIMITS.maxStringValueLength)\n cutValues += 1\n } else kept[name] = value as string | number | boolean\n count += 1\n }\n return {event, properties: kept, droppedProperties, cutValues}\n}\n\nconst MINUTE_MS = 60_000\nconst utcDay = (at: number) => new Date(at).toISOString().slice(0, 10)\n\n/** Each miniapp's rate on this phone: 60 events in any minute and 2,000 a UTC day. Kept in memory. */\nexport class TelemetryRate {\n private readonly recent = new Map<string, number[]>()\n private readonly daily = new Map<string, {day: string; count: number}>()\n\n /** Whether `packageName` may send one more event at `at`; counts it when it may. */\n admit(packageName: string, at: number): boolean {\n const times = (this.recent.get(packageName) ?? []).filter((time) => at - time < MINUTE_MS && time <= at)\n const day = utcDay(at)\n const today = this.daily.get(packageName)\n const sent = today?.day === day ? today.count : 0\n if (times.length >= FLEET_TELEMETRY_LIMITS.eventsPerMinute || sent >= FLEET_TELEMETRY_LIMITS.eventsPerDay) {\n this.recent.set(packageName, times)\n return false\n }\n times.push(at)\n this.recent.set(packageName, times)\n this.daily.set(packageName, {day, count: sent + 1})\n return true\n }\n}\n"]}
|
|
@@ -61,6 +61,12 @@ export interface InstallMiniappOptions {
|
|
|
61
61
|
expectedPackageName?: string;
|
|
62
62
|
/** True once the install should stop. Nothing commits after that. */
|
|
63
63
|
isCancelled?: () => boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Called once the installer knows the package, the release it would replace (null for none) and the version it would
|
|
66
|
+
* install, before anything is downloaded or asked: how Fleet reports the attempt whatever comes after
|
|
67
|
+
* (`installMiniapp`).
|
|
68
|
+
*/
|
|
69
|
+
onResolved?: (packageName: string, previousVersion: string | null, version: string) => void;
|
|
64
70
|
/**
|
|
65
71
|
* Headless only: called when the update of a running miniapp is staged and waits for the person's answer to its
|
|
66
72
|
* update prompt. The call resolves once they answer: `installed`, or `deferred` for Not now.
|
|
@@ -151,6 +157,13 @@ export declare function createMiniappInstaller(deps: InstallMiniappDependencies)
|
|
|
151
157
|
export declare function isLocalNetworkMiniappUrl(url: string): boolean;
|
|
152
158
|
/**
|
|
153
159
|
* Install the miniapp at a miniapp URL (see the module comment). Throws {@link InstallMiniappError}.
|
|
160
|
+
*
|
|
161
|
+
* Every attempt whose package the installer resolved is reported to Fleet (`miniapp_install`): an update when it
|
|
162
|
+
* replaces an installed release, an install otherwise; installed, deferred, or failed with its reason. One the person
|
|
163
|
+
* declined is deferred, not failed. An attempt that stopped before its package was known (an unreachable URL) cannot be
|
|
164
|
+
* attributed, and is not reported.
|
|
154
165
|
*/
|
|
155
166
|
export declare function installMiniapp(url: string, options: InstallMiniappOptions): Promise<InstallMiniappResult>;
|
|
167
|
+
/** `installer`, reporting each attempt to Fleet as `installMiniapp` describes. Exported for tests. */
|
|
168
|
+
export declare function withFleetReport(installer: (url: string, options: InstallMiniappOptions) => Promise<InstallMiniappResult>): (url: string, options: InstallMiniappOptions) => Promise<InstallMiniappResult>;
|
|
156
169
|
//# sourceMappingURL=installMiniapp.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installMiniapp.d.ts","sourceRoot":"","sources":["../../src/services/installMiniapp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"installMiniapp.d.ts","sourceRoot":"","sources":["../../src/services/installMiniapp.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { getConfigValues, isDevMiniappAllowed, isInstalledMiniappAllowed } from
|
|
|
3
3
|
import { MiniappRunningError } from "../utils/storeInstallRuntime";
|
|
4
4
|
import appRegistry from "./AppRegistry";
|
|
5
5
|
import { cloudClientService } from "./CloudClientService";
|
|
6
|
+
import { fleetEvents } from "./fleet/fleetEvents";
|
|
6
7
|
import localMiniappRuntime from "./LocalMiniappRuntime";
|
|
7
8
|
import { checkMiniappAge } from "./miniappAgeGate";
|
|
8
9
|
import { isPackageNameAllowedForUrl } from "./miniappDomainRule";
|
|
@@ -50,6 +51,7 @@ export function createMiniappInstaller(deps) {
|
|
|
50
51
|
// A miniapp installed from this URL opens, unless a development build of its name runs in its place: installing
|
|
51
52
|
// it again replaces that build. Fleet's reconciler decides for itself what an assignment installs.
|
|
52
53
|
const active = deps.activeRelease(packageName);
|
|
54
|
+
options.onResolved?.(packageName, active?.version ?? null, manifest.version);
|
|
53
55
|
if (options.source !== "assignment" && active && !deps.hasDevelopmentSelection(packageName)) {
|
|
54
56
|
const installedFrom = deps.updateUrl(packageName, active.identity);
|
|
55
57
|
if (installedFrom && normalizeMiniappUrl(installedFrom)?.url === target.url) {
|
|
@@ -176,6 +178,7 @@ async function installFromLocalNetwork(deps, target, options, cancelled, assertC
|
|
|
176
178
|
assertCurrent();
|
|
177
179
|
const { manifest } = served;
|
|
178
180
|
const packageName = manifest.packageName;
|
|
181
|
+
options.onResolved?.(packageName, deps.activeRelease(packageName)?.version ?? null, manifest.version);
|
|
179
182
|
if (!deps.developmentAllowed(packageName)) {
|
|
180
183
|
throw new InstallMiniappError("access_denied", `This deployment doesn't allow local-network builds of ${packageName}`);
|
|
181
184
|
}
|
|
@@ -488,11 +491,71 @@ const engineDependencies = {
|
|
|
488
491
|
warn: (message, error) => console.warn(`installMiniapp: ${message}`, error ?? ""),
|
|
489
492
|
};
|
|
490
493
|
let engineInstaller = null;
|
|
494
|
+
/** Fleet's install reason for an install that stopped (Phase 12): `assignment_outcome`'s reasons where they apply. */
|
|
495
|
+
const FLEET_INSTALL_REASON = {
|
|
496
|
+
not_a_miniapp: "not_a_miniapp",
|
|
497
|
+
domain_mismatch: "not_a_miniapp",
|
|
498
|
+
archived: "archived",
|
|
499
|
+
needs_newer_app: "incompatible_host",
|
|
500
|
+
access_denied: "access_denied",
|
|
501
|
+
age_restricted: "access_denied",
|
|
502
|
+
network: "network",
|
|
503
|
+
cancelled: "user_cancelled",
|
|
504
|
+
integrity: "download_failed",
|
|
505
|
+
signature_invalid: "signature_invalid",
|
|
506
|
+
};
|
|
491
507
|
/**
|
|
492
508
|
* Install the miniapp at a miniapp URL (see the module comment). Throws {@link InstallMiniappError}.
|
|
509
|
+
*
|
|
510
|
+
* Every attempt whose package the installer resolved is reported to Fleet (`miniapp_install`): an update when it
|
|
511
|
+
* replaces an installed release, an install otherwise; installed, deferred, or failed with its reason. One the person
|
|
512
|
+
* declined is deferred, not failed. An attempt that stopped before its package was known (an unreachable URL) cannot be
|
|
513
|
+
* attributed, and is not reported.
|
|
493
514
|
*/
|
|
494
515
|
export function installMiniapp(url, options) {
|
|
495
|
-
engineInstaller ??= createMiniappInstaller(engineDependencies);
|
|
516
|
+
engineInstaller ??= withFleetReport(createMiniappInstaller(engineDependencies));
|
|
496
517
|
return engineInstaller(url, options);
|
|
497
518
|
}
|
|
519
|
+
/** `installer`, reporting each attempt to Fleet as `installMiniapp` describes. Exported for tests. */
|
|
520
|
+
export function withFleetReport(installer) {
|
|
521
|
+
return (url, options) => reportAttempt(installer, url, options);
|
|
522
|
+
}
|
|
523
|
+
async function reportAttempt(installer, url, options) {
|
|
524
|
+
const source = options.source === "assignment" ? "assignment" : options.source === "local" ? "local" : "url";
|
|
525
|
+
let attempt = null;
|
|
526
|
+
const resolved = {
|
|
527
|
+
...options,
|
|
528
|
+
onResolved: (packageName, previousVersion, version) => {
|
|
529
|
+
attempt = { packageName, previousVersion, version };
|
|
530
|
+
options.onResolved?.(packageName, previousVersion, version);
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
// Every report names the version the attempt was installing, whatever is installed when it is made.
|
|
534
|
+
const report = (fields) => {
|
|
535
|
+
const known = attempt;
|
|
536
|
+
if (known === null)
|
|
537
|
+
return;
|
|
538
|
+
fleetEvents.install({
|
|
539
|
+
packageName: known.packageName,
|
|
540
|
+
operation: known.previousVersion ? "update" : "install",
|
|
541
|
+
source,
|
|
542
|
+
...(known.previousVersion ? { fromVersion: known.previousVersion } : {}),
|
|
543
|
+
toVersion: known.version,
|
|
544
|
+
...fields,
|
|
545
|
+
});
|
|
546
|
+
};
|
|
547
|
+
try {
|
|
548
|
+
const result = await installer(url, resolved);
|
|
549
|
+
if (result.status === "installed")
|
|
550
|
+
report({ outcome: "succeeded" });
|
|
551
|
+
else if (result.status === "deferred")
|
|
552
|
+
report({ outcome: "deferred", reason: "unknown" });
|
|
553
|
+
return result;
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
const reason = error instanceof InstallMiniappError ? FLEET_INSTALL_REASON[error.code] : "unknown";
|
|
557
|
+
report({ outcome: reason === "user_cancelled" ? "deferred" : "failed", reason });
|
|
558
|
+
throw error;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
498
561
|
//# sourceMappingURL=installMiniapp.js.map
|