@oddington/sdk 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -312,12 +312,13 @@ both shipped SDK inputs and every emitted helper, rejects unexpected inputs and
312
312
  imports, and retains the full credential/server/customer leak scan. Node retains
313
313
  the complete package-root exports; provider classes are never public.
314
314
 
315
- Current auth measurement is **149,045 minified bytes**, including **5,177 fixture
316
- bytes** and **143,868 SDK bytes**, with **38,870 total gzip bytes**. Current
317
- unchanged Data/Files fixture measures **46,164 total / 5,893 fixture / 40,271 SDK
318
- bytes**, with **14,316 total gzip bytes**.
319
- Both fit their explicitly reviewed ceilings: **41,000 SDK bytes** for the pinned
320
- Data/Files fixture and **150,000 SDK bytes** for the separately pinned auth fixture.
315
+ Current auth measurement is **171,346 minified bytes**, including **6,322 fixture
316
+ bytes** and **165,024 SDK bytes**, with **45,466 total gzip bytes**. Current
317
+ Data/Files fixture measures **47,254 total / 5,893 fixture / 41,361 SDK
318
+ bytes**, with **14,906 total gzip bytes**. The shipped runtime now also carries
319
+ the local Questions transport and transparent Local Host renewal.
320
+ Both fit their explicitly reviewed ceilings: **42,000 SDK bytes** for the pinned
321
+ Data/Files fixture and **166,000 SDK bytes** for the separately pinned auth fixture.
321
322
  Those ceilings are fixed review decisions, not values regenerated from later
322
323
  measurements. The installed auth realm/host-handoff proof passes alongside the
323
324
  Node exports, declarations, tarball leak scan and browser compatibility audits.
@@ -331,17 +332,17 @@ bytes; generated helpers count toward the SDK. Gzip is diagnostic only.
331
332
 
332
333
  | Same named-import fixture | Legacy diagnostic | Current strict proof |
333
334
  | --- | ---: | ---: |
334
- | Total minified JavaScript bytes | 488,539 | 46,164 |
335
+ | Total minified JavaScript bytes | 488,539 | 47,254 |
335
336
  | Fixture-attributed bytes | 5,954 | 5,893 |
336
- | SDK bytes | 482,585 | 40,271 |
337
- | Total gzip bytes | 198,612 | 14,316 |
337
+ | SDK bytes | 482,585 | 41,361 |
338
+ | Total gzip bytes | 198,612 | 14,906 |
338
339
 
339
340
  The legacy diagnostic externalized three unresolved Node built-ins and failed
340
341
  browser compatibility. The current strict proof has no imports, shims or warnings
341
- and reduces SDK bytes by 91.7%. Its literal ceiling is **41,000 SDK bytes**:
342
- 729 bytes of deliberately limited headroom. The reviewed measurement includes
343
- the Local Host bootstrap and opaque browser-auth handoff that `os.init` must
344
- select at runtime. The verifier pins the fixture
342
+ and reduces SDK bytes by 91.4%. Its literal ceiling is **42,000 SDK bytes**:
343
+ 639 bytes of deliberately limited headroom. The reviewed measurement includes
344
+ the Local Host bootstrap, local Questions transport, transparent renewal and
345
+ opaque browser-auth handoff that `os.init` must select at runtime. The verifier pins the fixture
345
346
  hash and bundler version; changing either requires a reviewed measurement
346
347
  comparison. Future ceiling increases require explicit review of added browser
347
348
  capability and must never be automatically regenerated from an oversized output.
package/dist/browser.js CHANGED
@@ -2149,6 +2149,19 @@ var sessionKey = "_os.app.session", responseChecks = /* @__PURE__ */ new WeakMap
2149
2149
  }, AppContextDenied = class extends Error {
2150
2150
  }, AppRenewalUnavailable = class extends Error {
2151
2151
  };
2152
+ async function untilAborted(work, signal) {
2153
+ if (signal.aborted)
2154
+ throw work.catch(() => {
2155
+ }), new AppRenewalUnavailable();
2156
+ let abort, cancelled = new Promise((_resolve, reject) => {
2157
+ abort = () => reject(new AppRenewalUnavailable()), signal.addEventListener("abort", abort, { once: !0 });
2158
+ });
2159
+ try {
2160
+ return await Promise.race([work, cancelled]);
2161
+ } finally {
2162
+ signal.removeEventListener("abort", abort);
2163
+ }
2164
+ }
2152
2165
  async function appResponse(response2, maximum, signal) {
2153
2166
  if (signal.aborted || !response2.body || response2.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json") throw new Error();
2154
2167
  let reader = response2.body.getReader(), abort = () => {
@@ -2188,159 +2201,164 @@ function isAppOrigin(value) {
2188
2201
  }
2189
2202
  }
2190
2203
  function appContext(origin, active2) {
2191
- let binding, credential, denied = !1, controller = new AbortController(), expiry, startup, renewalTimer, renewal, observation, resolution2, disposed = !1, stored = () => globalThis.localStorage.getItem(sessionKey), alive = () => {
2204
+ let binding, credential, pair, serialized, denied = !1, controller = new AbortController(), access = new AbortController(), expiry, startup, renewalTimer, renewal, observation, resolution2, disposed = !1, stored = () => globalThis.localStorage.getItem(sessionKey), alive = () => {
2192
2205
  try {
2193
- return !denied && !disposed && active2() && globalThis.location.origin === origin && (!binding || binding.expiresAt > Date.now());
2206
+ return !denied && !disposed && active2() && globalThis.location.origin === origin;
2194
2207
  } catch {
2195
2208
  return !1;
2196
2209
  }
2197
2210
  }, cancel = () => {
2198
- controller.abort(), clearTimeout(expiry), clearTimeout(startup), clearTimeout(renewalTimer), clearInterval(monitor), globalThis.removeEventListener?.("storage", storageChanged);
2211
+ controller.abort(), access.abort(), clearTimeout(expiry), clearTimeout(startup), clearTimeout(renewalTimer), clearInterval(monitor), globalThis.removeEventListener?.("storage", storageChanged);
2212
+ for (let event2 of ["pageshow", "focus", "visibilitychange"]) globalThis.removeEventListener?.(event2, wake);
2199
2213
  }, invalidate = () => {
2200
2214
  denied = !0, cancel();
2201
2215
  try {
2202
- !disposed && active2() && globalThis.location.origin === origin && (!credential || globalThis.localStorage.getItem(sessionKey) === credential) && globalThis.localStorage.removeItem(sessionKey);
2216
+ !disposed && active2() && globalThis.location.origin === origin && (!serialized || globalThis.localStorage.getItem(sessionKey) === serialized) && globalThis.localStorage.removeItem(sessionKey);
2203
2217
  } catch {
2204
2218
  }
2205
2219
  }, current2 = () => {
2206
2220
  try {
2207
- return alive() && stored() === credential;
2221
+ return alive() && !!binding && binding.expiresAt > Date.now() && stored() === serialized;
2208
2222
  } catch {
2209
2223
  return !1;
2210
2224
  }
2211
- }, sameSession = (value) => !binding || value.appId === binding.appId && value.memberId === binding.memberId && value.workspace === binding.workspace && value.sessionId === binding.sessionId, bounded = (value) => validBinding(value) && value.expiresAt > Date.now() && value.expiresAt <= Date.now() + 6e5 && sameSession(value);
2212
- async function proof(candidate, signal = controller.signal) {
2213
- if (!alive() || !/^[A-Za-z0-9_-]{43}$/.test(candidate)) throw new Error();
2214
- let url = `${origin}/_os/app/context`, response2 = await fetch(url, {
2215
- credentials: "omit",
2216
- redirect: "error",
2217
- cache: "no-store",
2218
- referrerPolicy: "no-referrer",
2219
- headers: { authorization: `Bearer ${candidate}` },
2220
- signal
2221
- });
2225
+ }, sameSession = (value) => !binding || ["appId", "memberId", "workspace", "sessionId"].every((key) => value[key] === binding[key]), bounded = (value) => validBinding(value) && value.expiresAt > Date.now() && value.expiresAt <= Date.now() + 6e5 && sameSession(value), readPair = (candidate) => {
2226
+ let value = JSON.parse(candidate ?? "null");
2227
+ if (!validPair(value) || value.context.expiresAt > Date.now() + 6e5 || !sameSession(value.context)) throw new Error();
2228
+ return value;
2229
+ }, expireAccess = () => {
2230
+ binding && binding.expiresAt <= Date.now() && access.abort();
2231
+ };
2232
+ async function send3(path, candidate, signal, init2 = {}) {
2233
+ let url = `${origin}/_os/app/${path}`, response2;
2234
+ try {
2235
+ response2 = await fetch(url, {
2236
+ ...init2,
2237
+ credentials: "omit",
2238
+ redirect: "error",
2239
+ cache: "no-store",
2240
+ referrerPolicy: "no-referrer",
2241
+ signal,
2242
+ headers: {
2243
+ ...init2.body === void 0 ? {} : { "content-type": "application/json" },
2244
+ authorization: `Bearer ${candidate}`
2245
+ }
2246
+ });
2247
+ } catch {
2248
+ throw new AppRenewalUnavailable();
2249
+ }
2222
2250
  if (response2.redirected || response2.url && response2.url !== url) throw new Error();
2251
+ return response2;
2252
+ }
2253
+ async function proof(candidate, signal = controller.signal) {
2254
+ if (!alive() || !validProof(candidate)) throw new Error();
2255
+ let response2 = await send3("context", candidate, signal);
2223
2256
  if (response2.status === 401 || response2.status === 403) throw new AppContextDenied();
2257
+ if (response2.status >= 500) throw new AppRenewalUnavailable();
2224
2258
  if (!response2.ok) throw new Error();
2225
2259
  let value = await appResponse(response2, 4096, signal);
2226
2260
  if (!alive() || !bounded(value)) throw new Error();
2227
2261
  return value;
2228
2262
  }
2229
- function install(value, candidate) {
2230
- binding = { ...value }, credential = candidate, clearTimeout(expiry), clearTimeout(renewalTimer);
2263
+ function install(value, candidate, raw) {
2264
+ expireAccess(), access.signal.aborted && (access = new AbortController()), binding = { ...value }, pair = candidate, credential = candidate.credential, serialized = raw, clearTimeout(expiry), clearTimeout(renewalTimer);
2231
2265
  let remaining = binding.expiresAt - Date.now();
2232
- expiry = setTimeout(invalidate, remaining), renewalTimer = setTimeout(() => {
2233
- renew();
2266
+ expiry = setTimeout(expireAccess, remaining), renewalTimer = setTimeout(() => {
2267
+ renew().catch(() => {
2268
+ });
2234
2269
  }, remaining - Math.min(6e4, remaining / 5));
2235
2270
  }
2236
- async function synchronize() {
2271
+ async function synchronize(signal = controller.signal) {
2237
2272
  if (!alive()) throw new Error();
2238
- if (credential && stored() !== credential) {
2239
- if (!binding) throw new Error();
2240
- if (observation) return observation;
2241
- observation = (async () => {
2242
- for (; alive(); ) {
2243
- let candidate = stored();
2244
- if (!candidate) throw new Error();
2245
- let value;
2246
- try {
2247
- value = await proof(candidate);
2248
- } catch {
2249
- if (stored() !== candidate) continue;
2250
- throw new Error();
2251
- }
2252
- if (stored() === candidate) {
2253
- install(value, candidate);
2254
- return;
2255
- }
2273
+ if (signal.aborted) throw new AppRenewalUnavailable();
2274
+ if (!credential || stored() === serialized) return;
2275
+ if (!binding) throw new Error();
2276
+ if (observation) return untilAborted(observation, signal);
2277
+ observation = (async () => {
2278
+ for (; alive(); ) {
2279
+ let candidate = stored();
2280
+ if (!candidate) throw new Error();
2281
+ let value, replacement;
2282
+ try {
2283
+ replacement = readPair(candidate), value = await proof(replacement.credential, signal);
2284
+ } catch (error) {
2285
+ if (stored() !== candidate) continue;
2286
+ throw error;
2287
+ }
2288
+ if (stored() === candidate) {
2289
+ if (value.expiresAt !== replacement.context.expiresAt) throw new Error();
2290
+ install(value, replacement, candidate);
2291
+ return;
2256
2292
  }
2257
- throw new Error();
2258
- })();
2259
- try {
2260
- await observation;
2261
- } catch {
2262
- throw invalidate(), new Error("Local app context unavailable.");
2263
- } finally {
2264
- observation = void 0;
2265
2293
  }
2294
+ throw new Error();
2295
+ })();
2296
+ let observed = observation;
2297
+ try {
2298
+ await untilAborted(observed, signal);
2299
+ } catch (error) {
2300
+ throw error instanceof AppRenewalUnavailable || error instanceof AppResponseReadError || invalidate(), error;
2301
+ } finally {
2302
+ observation === observed && (observation = void 0);
2266
2303
  }
2267
2304
  }
2268
2305
  async function renew() {
2269
- if (!(renewal || !binding || !alive())) {
2270
- renewal = (async () => {
2271
- let deadline = binding.expiresAt, requestController = new AbortController(), abort = () => requestController.abort();
2272
- controller.signal.addEventListener("abort", abort, { once: !0 });
2273
- let timeout = setTimeout(abort, Math.min(2e4, deadline - Date.now())), previous = credential;
2274
- try {
2275
- if (await synchronize(), credential !== previous) return;
2276
- let url = `${origin}/_os/app/renew`, response2;
2277
- try {
2278
- response2 = await fetch(url, {
2279
- method: "POST",
2280
- credentials: "omit",
2281
- redirect: "error",
2282
- cache: "no-store",
2283
- referrerPolicy: "no-referrer",
2284
- signal: requestController.signal,
2285
- headers: { authorization: `Bearer ${previous}` }
2306
+ if (renewal) return renewal;
2307
+ if (!binding || !pair || !alive()) throw new Error();
2308
+ renewal = (async () => {
2309
+ let requestController = new AbortController(), abort = () => requestController.abort();
2310
+ controller.signal.addEventListener("abort", abort, { once: !0 });
2311
+ let timeout = setTimeout(abort, 2e4), previous = serialized;
2312
+ try {
2313
+ if (await synchronize(requestController.signal), serialized !== previous) return;
2314
+ let response2 = await send3("renew", pair.renewalCredential, requestController.signal, { method: "POST" }), value = await appResponse(response2, 4096, requestController.signal);
2315
+ if (response2.status === 503 && value && typeof value == "object" && !Array.isArray(value) && Object.keys(value).join(",") === "error" && "error" in value && value.error === "renewal_unavailable")
2316
+ throw new AppRenewalUnavailable();
2317
+ if (response2.status === 403 && value && typeof value == "object" && !Array.isArray(value) && Object.keys(value).join(",") === "error" && "error" in value && value.error === "renewal_conflict") {
2318
+ for (; alive() && stored() === previous && !requestController.signal.aborted; )
2319
+ await new Promise((resolve2, reject) => {
2320
+ let abortWait = () => {
2321
+ clearTimeout(timer), reject(new Error());
2322
+ }, timer = setTimeout(() => {
2323
+ requestController.signal.removeEventListener("abort", abortWait), resolve2();
2324
+ }, 100);
2325
+ requestController.signal.addEventListener("abort", abortWait, { once: !0 });
2286
2326
  });
2287
- } catch {
2288
- throw new AppRenewalUnavailable();
2289
- }
2290
- if (response2.redirected || response2.url && response2.url !== url) throw new Error();
2291
- let value = await appResponse(response2, 4096, requestController.signal);
2292
- if (response2.status === 503 && value && typeof value == "object" && !Array.isArray(value) && Object.keys(value).join(",") === "error" && "error" in value && value.error === "renewal_unavailable")
2293
- throw new AppRenewalUnavailable();
2294
- if (response2.status === 403 && value && typeof value == "object" && !Array.isArray(value) && Object.keys(value).join(",") === "error" && "error" in value && value.error === "renewal_conflict") {
2295
- for (; alive() && stored() === previous && !requestController.signal.aborted; )
2296
- await new Promise((resolve2, reject) => {
2297
- let abortWait = () => {
2298
- clearTimeout(timer), reject(new Error());
2299
- }, timer = setTimeout(() => {
2300
- requestController.signal.removeEventListener("abort", abortWait), resolve2();
2301
- }, 100);
2302
- requestController.signal.addEventListener("abort", abortWait, { once: !0 });
2303
- });
2304
- if (requestController.signal.aborted) throw new Error();
2305
- await synchronize();
2306
- return;
2307
- }
2308
- if (!response2.ok) throw new Error();
2309
- if (!alive() || Date.now() >= deadline || !value || typeof value != "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "context,credential") throw new Error();
2310
- let result2 = value;
2311
- if (!bounded(result2.context) || typeof result2.credential != "string" || !/^[A-Za-z0-9_-]{43}$/.test(result2.credential) || result2.credential === previous) throw new Error();
2312
- if (stored() !== previous) {
2313
- await synchronize();
2327
+ if (requestController.signal.aborted) throw new Error();
2328
+ await synchronize(requestController.signal);
2329
+ return;
2330
+ }
2331
+ if (!response2.ok) throw new Error();
2332
+ if (!alive() || !validPair(value) || !bounded(value.context) || (/* @__PURE__ */ new Set([value.credential, value.renewalCredential, pair.credential, pair.renewalCredential])).size !== 4) throw new Error();
2333
+ if (stored() !== previous) {
2334
+ await synchronize(requestController.signal);
2335
+ return;
2336
+ }
2337
+ let raw = JSON.stringify(value);
2338
+ globalThis.localStorage.setItem(sessionKey, raw), install(value.context, value, raw);
2339
+ } catch (error) {
2340
+ try {
2341
+ if (alive() && stored() && stored() !== previous) {
2342
+ await synchronize(requestController.signal);
2314
2343
  return;
2315
2344
  }
2316
- globalThis.localStorage.setItem(sessionKey, result2.credential), install(result2.context, result2.credential);
2317
- } catch (error) {
2318
- try {
2319
- if (alive() && stored() && stored() !== previous) {
2320
- await synchronize();
2321
- return;
2322
- }
2323
- } catch {
2324
- }
2325
- try {
2326
- if (alive() && stored() && stored() !== previous) {
2327
- await synchronize();
2328
- return;
2329
- }
2330
- } catch {
2331
- }
2332
- (error instanceof AppRenewalUnavailable || error instanceof AppResponseReadError || requestController.signal.aborted) && current2() && Date.now() < deadline ? renewalTimer = setTimeout(() => {
2333
- renew();
2334
- }, Math.min(5e3, (deadline - Date.now()) / 5)) : invalidate();
2335
- } finally {
2336
- clearTimeout(timeout), controller.signal.removeEventListener("abort", abort);
2345
+ } catch {
2337
2346
  }
2338
- })();
2339
- try {
2340
- await renewal;
2347
+ throw (error instanceof AppRenewalUnavailable || error instanceof AppResponseReadError || requestController.signal.aborted) && alive() ? current2() && (renewalTimer = setTimeout(
2348
+ () => {
2349
+ renew().catch(() => {
2350
+ });
2351
+ },
2352
+ Math.min(5e3, (binding.expiresAt - Date.now()) / 5)
2353
+ )) : invalidate(), new AppRenewalUnavailable();
2341
2354
  } finally {
2342
- renewal = void 0;
2355
+ clearTimeout(timeout), controller.signal.removeEventListener("abort", abort);
2343
2356
  }
2357
+ })();
2358
+ try {
2359
+ await renewal;
2360
+ } finally {
2361
+ renewal = void 0;
2344
2362
  }
2345
2363
  }
2346
2364
  let storageChanged = () => {
@@ -2349,36 +2367,33 @@ function appContext(origin, active2) {
2349
2367
  invalidate();
2350
2368
  return;
2351
2369
  }
2352
- synchronize().catch(invalidate);
2370
+ expireAccess(), synchronize().catch(() => {
2371
+ });
2353
2372
  }
2354
2373
  }, monitor = setInterval(() => {
2355
2374
  storageChanged();
2356
2375
  }, 100);
2357
2376
  globalThis.addEventListener?.("storage", storageChanged);
2377
+ let wake = () => {
2378
+ alive() && resolve().catch(() => {
2379
+ });
2380
+ };
2381
+ for (let event2 of ["pageshow", "focus", "visibilitychange"]) globalThis.addEventListener?.(event2, wake);
2358
2382
  let dispose = () => {
2359
2383
  disposed = !0, cancel();
2360
2384
  };
2385
+ async function settledAccess(signal) {
2386
+ for (await synchronize(); alive() && stored() !== serialized; ) await synchronize();
2387
+ if (signal.aborted || !current2()) throw new Error();
2388
+ }
2361
2389
  async function requestData(operation2, input) {
2362
2390
  try {
2363
2391
  if (operation2 !== "data/query" || !current2()) throw new Error();
2364
- let url = `${origin}/_os/app/data`, admitted = credential, response2 = await fetch(url, {
2365
- method: "POST",
2366
- credentials: "omit",
2367
- redirect: "error",
2368
- cache: "no-store",
2369
- referrerPolicy: "no-referrer",
2370
- signal: controller.signal,
2371
- headers: { "content-type": "application/json", authorization: `Bearer ${credential}` },
2372
- body: JSON.stringify(input)
2373
- });
2374
- for (await synchronize(); alive() && stored() !== credential; ) await synchronize();
2375
- if (!current2() || response2.redirected || response2.url && response2.url !== url) throw new Error();
2376
- if (response2.status === 401 || response2.status === 403)
2377
- throw admitted === credential && invalidate(), new DataOperationError("access_denied");
2378
- let result2 = await appResponse(response2, Number.MAX_SAFE_INTEGER, controller.signal);
2379
- for (await synchronize(); alive() && stored() !== credential; ) await synchronize();
2380
- if (!current2()) throw new Error();
2381
- if (!response2.ok) throw dataTransportError(response2.status, result2);
2392
+ let admitted = credential, signal = access.signal, response2 = await send3("data", credential, signal, { method: "POST", body: JSON.stringify(input) });
2393
+ if (await settledAccess(signal), response2.status === 401 || response2.status === 403)
2394
+ throw admitted === credential && current2() && invalidate(), new DataOperationError("access_denied");
2395
+ let result2 = await appResponse(response2, Number.MAX_SAFE_INTEGER, signal);
2396
+ if (await settledAccess(signal), !response2.ok) throw dataTransportError(response2.status, result2);
2382
2397
  return result2;
2383
2398
  } catch (error) {
2384
2399
  throw error instanceof DataOperationError ? error : !current2() || controller.signal.aborted ? new DataOperationError("context_unavailable") : new DataOperationError("unavailable", !0);
@@ -2386,43 +2401,30 @@ function appContext(origin, active2) {
2386
2401
  }
2387
2402
  async function requestInbox(operation2, input) {
2388
2403
  if (!["questions.list", "questions.answer", "questions.searchRecords"].includes(operation2)) throw new Error("Inbox operation unavailable");
2389
- return requestChecked(`${origin}/_os/app/inbox`, { method: "POST", body: JSON.stringify({ operation: operation2, input }) }, !0);
2404
+ return requestChecked("inbox", { method: "POST", body: JSON.stringify({ operation: operation2, input }) }, !0);
2390
2405
  }
2391
2406
  async function requestRoutine(key, suffix, init2 = {}) {
2392
- return requestChecked(`${origin}/_os/app/routines/${encodeURIComponent(key)}/${suffix}`, init2);
2407
+ return requestChecked(`routines/${encodeURIComponent(key)}/${suffix}`, init2);
2393
2408
  }
2394
- async function requestChecked(url, init2, inbox2 = !1) {
2395
- if (!current2())
2396
- throw invalidate(), new Error("Local app context unavailable.");
2397
- let admitted = credential, response2 = await fetch(url, {
2398
- ...init2,
2399
- credentials: "omit",
2400
- redirect: "error",
2401
- cache: "no-store",
2402
- referrerPolicy: "no-referrer",
2403
- signal: controller.signal,
2404
- headers: { ...init2.body === void 0 ? {} : { "content-type": "application/json" }, authorization: `Bearer ${credential}` }
2405
- });
2406
- for (await synchronize(); alive() && stored() !== credential; ) await synchronize();
2407
- if (!current2() || response2.redirected || response2.url && response2.url !== url)
2408
- throw invalidate(), new Error("Local app context unavailable.");
2409
- return (response2.status === 401 || !inbox2 && response2.status === 403) && admitted === credential && invalidate(), responseChecks.set(response2, async (value) => {
2410
- for (await synchronize(); alive() && stored() !== credential; ) await synchronize();
2411
- admitted === credential && value && typeof value == "object" && "error" in value && value.error === "authority_unavailable" && invalidate();
2409
+ async function requestChecked(path, init2, inbox2 = !1) {
2410
+ if (!current2()) throw new Error("Local app context unavailable.");
2411
+ let admitted = credential, signal = access.signal, response2 = await send3(path, credential, signal, init2);
2412
+ return await settledAccess(signal), (response2.status === 401 || !inbox2 && response2.status === 403) && admitted === credential && current2() && invalidate(), responseChecks.set(response2, async (value) => {
2413
+ await settledAccess(signal), admitted === credential && current2() && value && typeof value == "object" && "error" in value && value.error === "authority_unavailable" && invalidate();
2412
2414
  }), response2;
2413
2415
  }
2414
2416
  function context2() {
2415
- let issued = credential;
2417
+ let issued = credential, signal = access.signal;
2416
2418
  return {
2417
2419
  kind: "local",
2418
2420
  ...binding,
2419
2421
  get expiresAt() {
2420
2422
  return binding.expiresAt;
2421
2423
  },
2422
- isCurrent: current2,
2423
- signal: controller.signal,
2424
+ isCurrent: () => !signal.aborted && current2(),
2425
+ signal,
2424
2426
  invalidate: () => {
2425
- issued === credential && invalidate();
2427
+ issued === credential && current2() && invalidate();
2426
2428
  },
2427
2429
  requestRoutine,
2428
2430
  requestData,
@@ -2433,25 +2435,34 @@ function appContext(origin, active2) {
2433
2435
  let resolveOnce = async () => {
2434
2436
  let attempted;
2435
2437
  try {
2436
- if (denied || !active2() || globalThis.location.origin !== origin) throw new Error();
2437
- credential && await synchronize();
2438
- let candidate = stored();
2439
- if (!candidate || !/^[A-Za-z0-9_-]{43}$/.test(candidate)) throw new Error();
2440
- if (credential = candidate, attempted = candidate, !current2()) throw new Error();
2441
- let value = await proof(candidate);
2442
- if (stored() !== candidate || credential !== candidate)
2443
- return binding || (binding = { ...value }), await synchronize(), context2();
2438
+ if (!alive()) throw new Error();
2439
+ if (!pair) {
2440
+ let candidate2 = stored(), retained2 = readPair(candidate2);
2441
+ pair = retained2, binding = { ...retained2.context }, credential = retained2.credential, serialized = candidate2;
2442
+ }
2443
+ if (expireAccess(), binding.expiresAt <= Date.now()) {
2444
+ if (await renew(), !current2()) throw new AppRenewalUnavailable();
2445
+ return context2();
2446
+ }
2447
+ await synchronize();
2448
+ let candidate = stored(), retained = readPair(candidate);
2449
+ if (attempted = candidate ?? void 0, !current2()) throw new AppRenewalUnavailable();
2450
+ let value = await proof(retained.credential);
2451
+ if (stored() !== candidate || serialized !== candidate)
2452
+ return await synchronize(), context2();
2444
2453
  if (!current2()) throw new Error();
2445
- if (binding && value.expiresAt > binding.expiresAt) throw new Error();
2446
- return (!binding || value.expiresAt !== binding.expiresAt) && install(value, candidate), context2();
2454
+ if (expiry && binding && value.expiresAt > binding.expiresAt) throw new Error();
2455
+ return (!expiry || value.expiresAt !== binding.expiresAt) && install(value, retained, candidate), context2();
2447
2456
  } catch (error) {
2457
+ if (alive() && error instanceof AppRenewalUnavailable)
2458
+ throw new Error();
2448
2459
  try {
2449
- if (binding && current2() && attempted && attempted !== credential) return context2();
2450
- if (binding && alive() && stored() && stored() !== credential)
2460
+ if (binding && current2() && attempted && attempted !== serialized) return context2();
2461
+ if (binding && alive() && stored() && stored() !== serialized)
2451
2462
  return await synchronize(), context2();
2452
2463
  } catch {
2453
2464
  }
2454
- throw error instanceof AppContextDenied && binding && alive() && renewal ? new Error("Local app context unavailable.") : (invalidate(), new Error("Local app context unavailable."));
2465
+ throw error instanceof AppContextDenied && binding && alive() && renewal ? new Error() : alive() && (error instanceof AppResponseReadError || pair && stored() === serialized && !current2()) ? new Error() : (invalidate(), new Error());
2455
2466
  }
2456
2467
  }, resolve = () => resolution2 || (resolution2 = resolveOnce().finally(() => {
2457
2468
  resolution2 = void 0;
@@ -2463,10 +2474,18 @@ function appContext(origin, active2) {
2463
2474
  }, 0);
2464
2475
  }, invalidate, dispose };
2465
2476
  }
2477
+ function validProof(value) {
2478
+ return typeof value == "string" && /^[A-Za-z0-9_-]{43}$/.test(value);
2479
+ }
2466
2480
  function validBinding(value) {
2467
2481
  if (!value || typeof value != "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "appId,expiresAt,memberId,sessionId,workspace") return !1;
2468
2482
  let binding = value;
2469
- return typeof binding.appId == "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(binding.appId) && typeof binding.workspace == "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(binding.workspace) && typeof binding.memberId == "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(binding.memberId) && typeof binding.sessionId == "string" && /^[A-Za-z0-9_-]{43}$/.test(binding.sessionId) && typeof binding.expiresAt == "number" && Number.isFinite(binding.expiresAt);
2483
+ return typeof binding.appId == "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(binding.appId) && typeof binding.workspace == "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(binding.workspace) && typeof binding.memberId == "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(binding.memberId) && validProof(binding.sessionId) && typeof binding.expiresAt == "number" && Number.isFinite(binding.expiresAt);
2484
+ }
2485
+ function validPair(value) {
2486
+ if (!value || typeof value != "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "context,credential,renewalCredential") return !1;
2487
+ let pair = value;
2488
+ return validProof(pair.credential) && validProof(pair.renewalCredential) && pair.credential !== pair.renewalCredential && validBinding(pair.context);
2470
2489
  }
2471
2490
 
2472
2491
  var unavailable2 = () => new InboxOperationError(503, "unavailable"), stale = () => new InboxOperationError(401, "authority_unavailable");