@assetlib/sdk-core 0.7.4-preview.1 → 0.7.6-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +36 -6
- package/dist/telemetry.js +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -224,18 +224,35 @@ async function fetchBounded(fetcher, url, maxBytes, timeoutMs, accept = 'image/w
|
|
|
224
224
|
controller.abort();
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
|
+
/** Iterative, so any depth JSON.parse accepted freezes without exhausting the stack. */
|
|
228
|
+
function deepFreeze(value) {
|
|
229
|
+
const pending = [value];
|
|
230
|
+
while (pending.length) {
|
|
231
|
+
const item = pending.pop();
|
|
232
|
+
if (item === null || typeof item !== 'object')
|
|
233
|
+
continue;
|
|
234
|
+
Object.freeze(item);
|
|
235
|
+
for (const child of Object.values(item))
|
|
236
|
+
pending.push(child);
|
|
237
|
+
}
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
227
240
|
/** Durable replay identity survives authorized route and signing-key changes. */
|
|
228
241
|
export function storageNamespace(config) {
|
|
229
242
|
return hashBytes(utf8ToBytes(JSON.stringify([new URL(config.manifestUrl).origin, config.orgId, config.appId, config.environment]))).slice(0, 32);
|
|
230
243
|
}
|
|
231
244
|
/** Use the same verification for normal state loads and legacy namespace migration. */
|
|
232
245
|
export function verifyStoredState(serialized, config) {
|
|
246
|
+
return verifyStoredHistory(serialized, config).state;
|
|
247
|
+
}
|
|
248
|
+
function verifyStoredHistory(serialized, config) {
|
|
233
249
|
if (utf8ToBytes(serialized).length > SDK_LIMITS.stateBytes)
|
|
234
250
|
throw new Error('Stored SDK state exceeds its bound.');
|
|
235
251
|
const state = JSON.parse(serialized);
|
|
236
252
|
if (!record(state) || state.version !== 1 || !integer(state.highestSequence, 1, 2_147_483_647) || !Array.isArray(state.history) || state.history.length < 1 || state.history.length > SDK_LIMITS.retainedReleases)
|
|
237
253
|
throw new Error('Stored SDK state is invalid.');
|
|
238
254
|
let previous = state.highestSequence + 1;
|
|
255
|
+
const payloads = [];
|
|
239
256
|
for (const [index, entry] of state.history.entries()) {
|
|
240
257
|
const { payload } = verifySignedManifest(entry, config);
|
|
241
258
|
if (payload.sequence >= previous)
|
|
@@ -243,13 +260,16 @@ export function verifyStoredState(serialized, config) {
|
|
|
243
260
|
if (index === 0 && payload.sequence !== state.highestSequence)
|
|
244
261
|
throw new Error('Stored sequence does not match its signed manifest.');
|
|
245
262
|
previous = payload.sequence;
|
|
263
|
+
payloads.push(payload);
|
|
246
264
|
}
|
|
247
|
-
return state;
|
|
265
|
+
return { state: state, payloads };
|
|
248
266
|
}
|
|
249
267
|
export class AssetClient {
|
|
250
268
|
options;
|
|
251
269
|
config;
|
|
252
270
|
state = { version: 1, highestSequence: 0, history: [] };
|
|
271
|
+
adoptedRaw = null;
|
|
272
|
+
payloads = [];
|
|
253
273
|
initialized = false;
|
|
254
274
|
storageFailure = null;
|
|
255
275
|
lastError = null;
|
|
@@ -322,16 +342,22 @@ export class AssetClient {
|
|
|
322
342
|
throw new Error('Stored release state disappeared.');
|
|
323
343
|
return;
|
|
324
344
|
}
|
|
325
|
-
|
|
345
|
+
// These exact bytes were already verified with this client's fixed config.
|
|
346
|
+
if (raw === this.adoptedRaw)
|
|
347
|
+
return;
|
|
348
|
+
const { state: next, payloads } = verifyStoredHistory(raw, this.config);
|
|
326
349
|
if (next.highestSequence < this.state.highestSequence)
|
|
327
350
|
throw new Error('Stored release state moved backwards.');
|
|
328
|
-
const accepted = new Map(this.state.history.map(entry => [
|
|
329
|
-
for (const entry of next.history) {
|
|
330
|
-
const previous = accepted.get(
|
|
351
|
+
const accepted = new Map(this.state.history.map((entry, index) => [this.payloads[index].sequence, entry.payload]));
|
|
352
|
+
for (const [index, entry] of next.history.entries()) {
|
|
353
|
+
const previous = accepted.get(payloads[index].sequence);
|
|
331
354
|
if (previous !== undefined && previous !== entry.payload)
|
|
332
355
|
throw new Error('Stored release state conflicts with an accepted release.');
|
|
333
356
|
}
|
|
357
|
+
const frozen = payloads.map(deepFreeze);
|
|
334
358
|
this.state = next;
|
|
359
|
+
this.payloads = frozen;
|
|
360
|
+
this.adoptedRaw = raw;
|
|
335
361
|
}
|
|
336
362
|
catch {
|
|
337
363
|
this.storageFailure = 'Stored release state could not be verified. Using bundled assets; repair or explicitly reset app data before reconnecting.';
|
|
@@ -367,8 +393,12 @@ export class AssetClient {
|
|
|
367
393
|
const serialized = JSON.stringify(next);
|
|
368
394
|
if (utf8ToBytes(serialized).length > SDK_LIMITS.stateBytes)
|
|
369
395
|
throw new Error('Release history exceeds the storage bound.');
|
|
396
|
+
const payloads = [deepFreeze(payload), ...this.payloads].slice(0, SDK_LIMITS.retainedReleases);
|
|
370
397
|
await this.options.storage.saveState(serialized);
|
|
398
|
+
// Nothing may throw between these: state, payloads and adoptedRaw describe one history.
|
|
371
399
|
this.state = next;
|
|
400
|
+
this.payloads = payloads;
|
|
401
|
+
this.adoptedRaw = serialized;
|
|
372
402
|
this.lastError = null;
|
|
373
403
|
return { updated: true, sequence: payload.sequence };
|
|
374
404
|
}
|
|
@@ -382,7 +412,7 @@ export class AssetClient {
|
|
|
382
412
|
}
|
|
383
413
|
/** Only metadata transitions use the serial queue. Image transfers are bounded and concurrent. */
|
|
384
414
|
snapshot() {
|
|
385
|
-
return this.serial(async () => { await this.load(); return this.storageFailure ? [] : this.
|
|
415
|
+
return this.serial(async () => { await this.load(); return this.storageFailure ? [] : this.payloads.map(payload => ({ payload })); });
|
|
386
416
|
}
|
|
387
417
|
withTransfer(work, signal) {
|
|
388
418
|
throwIfAborted(signal);
|
package/dist/telemetry.js
CHANGED
|
@@ -28,7 +28,7 @@ export class ObservationReporter {
|
|
|
28
28
|
this.fetcher = fetcher;
|
|
29
29
|
this.interval = boundedInteger(options.flushIntervalMs, 60_000, 2_147_483_647);
|
|
30
30
|
this.maxBatch = boundedInteger(options.maxBatch, 200, 200);
|
|
31
|
-
const sdk = options.sdk ?? { name: 'sdk-core', version: '0.7.
|
|
31
|
+
const sdk = options.sdk ?? { name: 'sdk-core', version: '0.7.6-preview.1' };
|
|
32
32
|
const build = options.build ?? { platform: 'web', appVersion: 'unknown', buildNumber: 'unknown' };
|
|
33
33
|
if (!version(sdk.name) || !version(sdk.version) || !['ios', 'android', 'web', 'expo'].includes(build.platform) || !version(build.appVersion) || !version(build.buildNumber) || (options.installId !== undefined && !validId(options.installId)))
|
|
34
34
|
throw new Error('Invalid observation configuration.');
|