@tinycloud/node-sdk 3.2.6-beta.1 → 3.2.6-beta.2

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.js CHANGED
@@ -18476,6 +18476,13 @@ function cloneAbilitiesMap(actions) {
18476
18476
  var NodeUserAuthorization = class {
18477
18477
  constructor(config) {
18478
18478
  this.activeSessionKeyId = "default";
18479
+ /** Incremented by resetSession; a sign-in started earlier publishes nothing. */
18480
+ this.sessionGeneration = 0;
18481
+ /**
18482
+ * Sign-in saves run one at a time, in order, each together with its own
18483
+ * cancellation cleanup, so no later write can land between the two.
18484
+ */
18485
+ this.persistenceQueue = Promise.resolve();
18479
18486
  this.extensions = [];
18480
18487
  this._nodeFeatures = [];
18481
18488
  this._nodeFeaturesKnown = false;
@@ -18606,6 +18613,27 @@ var NodeUserAuthorization = class {
18606
18613
  this.tinycloudHosts = [...hosts];
18607
18614
  }
18608
18615
  }
18616
+ /**
18617
+ * @internal End the session: adopt a fresh session manager whose active key
18618
+ * is `sessionKeyId`, and return to the constructed state.
18619
+ */
18620
+ resetSession(sessionManager, sessionKeyId, hosts) {
18621
+ this.sessionGeneration++;
18622
+ this.installRestoredSession(sessionManager, void 0, hosts);
18623
+ this.activeSessionKeyId = sessionKeyId;
18624
+ }
18625
+ /** Throws when the session was reset (signed out) after `generation` was read. */
18626
+ assertSignInCurrent(generation) {
18627
+ if (generation !== this.sessionGeneration) {
18628
+ throw new Error("Sign-in was cancelled by sign-out.");
18629
+ }
18630
+ }
18631
+ /** Runs `task` after every earlier queued persistence task has settled. */
18632
+ enqueuePersistence(task) {
18633
+ const run = this.persistenceQueue.then(task);
18634
+ this.persistenceQueue = run.catch(() => void 0);
18635
+ return run;
18636
+ }
18609
18637
  /** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
18610
18638
  installRestoredSession(sessionManager, session, hosts, nodeFeatures) {
18611
18639
  this.sessionManager = sessionManager;
@@ -19294,11 +19322,14 @@ var NodeUserAuthorization = class {
19294
19322
  * @param options - Optional per-call SIWE overrides for this sign-in only
19295
19323
  */
19296
19324
  async signIn(options) {
19297
- this._address = canonicalizeAddress(await this.signer.getAddress());
19298
- this._chainId = await this.signer.getChainId();
19299
- const address = this._address;
19300
- const chainId = this._chainId;
19325
+ const generation = this.sessionGeneration;
19326
+ const address = canonicalizeAddress(await this.signer.getAddress());
19327
+ const chainId = await this.signer.getChainId();
19328
+ this.assertSignInCurrent(generation);
19329
+ this._address = address;
19330
+ this._chainId = chainId;
19301
19331
  await this.resolveTinyCloudHostsForSignIn(address, chainId);
19332
+ this.assertSignInCurrent(generation);
19302
19333
  const keyId = `session-${Date.now()}`;
19303
19334
  this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
19304
19335
  this.activeSessionKeyId = keyId;
@@ -19331,6 +19362,7 @@ var NodeUserAuthorization = class {
19331
19362
  type: "siwe",
19332
19363
  purpose: "sign-in"
19333
19364
  });
19365
+ this.assertSignInCurrent(generation);
19334
19366
  const session = this.wasm.completeSessionSetup({
19335
19367
  ...prepared,
19336
19368
  signature: signature2
@@ -19375,18 +19407,27 @@ var NodeUserAuthorization = class {
19375
19407
  version: "1.0",
19376
19408
  tinycloudHosts: this.tinycloudHosts
19377
19409
  };
19378
- await this.sessionStorage.save(address, persistedData);
19410
+ await this.enqueuePersistence(async () => {
19411
+ await this.sessionStorage.save(address, persistedData);
19412
+ if (generation !== this.sessionGeneration) {
19413
+ await this.sessionStorage.clear(address);
19414
+ }
19415
+ });
19416
+ this.assertSignInCurrent(generation);
19379
19417
  this._session = clientSession;
19380
19418
  this._tinyCloudSession = tinyCloudSession;
19381
19419
  this._address = address;
19382
19420
  this._chainId = chainId;
19383
19421
  await this.refreshNodeInfo();
19422
+ this.assertSignInCurrent(generation);
19384
19423
  for (const ext of this.extensions) {
19385
19424
  if (ext.afterSignIn) {
19386
19425
  await ext.afterSignIn(clientSession);
19426
+ this.assertSignInCurrent(generation);
19387
19427
  }
19388
19428
  }
19389
19429
  await this.ensureSpaceExists();
19430
+ this.assertSignInCurrent(generation);
19390
19431
  return clientSession;
19391
19432
  }
19392
19433
  /**
@@ -20029,17 +20070,24 @@ import {
20029
20070
  ServiceContext
20030
20071
  } from "@tinycloud/sdk-core";
20031
20072
  var DelegatedAccess = class {
20032
- constructor(session, delegation, host, invoke2, invokeAny2, telemetry) {
20073
+ /**
20074
+ * @param lifetime - Optional owner of this access: its `fetch` is used for
20075
+ * requests and the service context is registered with `track`, so the
20076
+ * owner can cancel and retire the access (TinyCloudNode does this when its
20077
+ * session is replaced or signed out).
20078
+ */
20079
+ constructor(session, delegation, host, invoke2, invokeAny2, telemetry, lifetime) {
20033
20080
  this.session = session;
20034
20081
  this._delegation = delegation;
20035
20082
  this.host = host;
20036
- this._serviceContext = new ServiceContext({
20083
+ const context = new ServiceContext({
20037
20084
  invoke: invoke2,
20038
20085
  invokeAny: invokeAny2,
20039
- fetch: globalThis.fetch.bind(globalThis),
20086
+ fetch: lifetime?.fetch ?? globalThis.fetch.bind(globalThis),
20040
20087
  hosts: [host],
20041
20088
  telemetry
20042
20089
  });
20090
+ this._serviceContext = lifetime ? lifetime.track(context) : context;
20043
20091
  const prefix = this._delegation.path.replace(/\/$/, "");
20044
20092
  this._kv = new KVService({ prefix });
20045
20093
  this._kv.initialize(this._serviceContext);
@@ -20623,6 +20671,19 @@ function exactVerifiedRecap(verified) {
20623
20671
  caveats: cloneRecapCaveats(entry.caveats)
20624
20672
  }));
20625
20673
  }
20674
+ function createDefaultSessionKey(wasm) {
20675
+ const manager = wasm.createSessionManager();
20676
+ let keyId = "default";
20677
+ let jwkStr = manager.jwk(keyId);
20678
+ if (!jwkStr) {
20679
+ keyId = manager.createSessionKey(keyId);
20680
+ jwkStr = manager.jwk(keyId);
20681
+ }
20682
+ if (!jwkStr) {
20683
+ throw new Error("Failed to get session key JWK");
20684
+ }
20685
+ return { manager, keyId, jwk: JSON.parse(jwkStr) };
20686
+ }
20626
20687
  var ServiceGraphLifetime = class {
20627
20688
  constructor(invokeFn, invokeAnyFn, fetchFn, observeContext) {
20628
20689
  this.invokeFn = invokeFn;
@@ -20672,9 +20733,13 @@ var ServiceGraphLifetime = class {
20672
20733
  this.contexts.clear();
20673
20734
  }
20674
20735
  }
20736
+ /** Aborted when the graph retires: cancels requests made for its session. */
20737
+ get signal() {
20738
+ return this.abortController.signal;
20739
+ }
20675
20740
  assertActive() {
20676
20741
  if (this.retired) {
20677
- throw new Error("Service graph has been retired by session replacement.");
20742
+ throw new Error("Service graph has been retired: its session was replaced or signed out.");
20678
20743
  }
20679
20744
  }
20680
20745
  combineSignals(signal) {
@@ -20976,6 +21041,16 @@ var _TinyCloudNode = class _TinyCloudNode {
20976
21041
  * so it must outlast a slow but healthy sync (9–18 s on prod 1.20.2, TC-998).
20977
21042
  */
20978
21043
  this.accountRegistryDeadlineMs = 3e4;
21044
+ /**
21045
+ * Incremented by every sign-out. A sign-in or restore records it when it
21046
+ * starts and does not establish its session once it has changed.
21047
+ */
21048
+ this.sessionEpoch = 0;
21049
+ /**
21050
+ * Numbers sign-in and restore attempts. A cancelled attempt cleans up only
21051
+ * while it is still the latest, so it never ends a newer session.
21052
+ */
21053
+ this.latestSessionAttempt = 0;
20979
21054
  /**
20980
21055
  * True when the last signIn() detected an interactive signer and skipped
20981
21056
  * client-side bootstrap. Apps can read this to know whether bootstrap was
@@ -21109,54 +21184,17 @@ var _TinyCloudNode = class _TinyCloudNode {
21109
21184
  "wasmBindings must be provided in config. Import from '@tinycloud/node-sdk' (not '/core') for automatic Node.js defaults."
21110
21185
  );
21111
21186
  }
21112
- this.sessionManager = this.wasmBindings.createSessionManager();
21113
- const defaultKeyId = "default";
21114
- let jwkStr = this.sessionManager.jwk(defaultKeyId);
21115
- if (jwkStr) {
21116
- this.sessionKeyId = defaultKeyId;
21117
- } else {
21118
- this.sessionKeyId = this.sessionManager.createSessionKey(defaultKeyId);
21119
- jwkStr = this.sessionManager.jwk(this.sessionKeyId);
21120
- }
21121
- if (!jwkStr) {
21122
- throw new Error("Failed to get session key JWK");
21123
- }
21124
- this.sessionKeyJwk = JSON.parse(jwkStr);
21187
+ const sessionKey = createDefaultSessionKey(this.wasmBindings);
21188
+ this.sessionManager = sessionKey.manager;
21189
+ this.sessionKeyId = sessionKey.keyId;
21190
+ this.sessionKeyJwk = sessionKey.jwk;
21125
21191
  this._serviceGraph = this.createServiceGraphLifetime();
21126
21192
  this._capabilityRegistry = new CapabilityKeyRegistry();
21127
21193
  this._keyProvider = new WasmKeyProvider({
21128
21194
  sessionManager: this.sessionManager
21129
21195
  });
21130
21196
  this.notificationHandler = config.notificationHandler ?? new SilentNotificationHandler();
21131
- const receiveOnlyGraph = this._serviceGraph;
21132
- this._sharingService = new SharingService({
21133
- hosts: [this.config.host],
21134
- // session: undefined - not needed for receive()
21135
- invoke: receiveOnlyGraph.invoke,
21136
- fetch: receiveOnlyGraph.fetch,
21137
- assertActive: () => receiveOnlyGraph.assertActive(),
21138
- keyProvider: this._keyProvider,
21139
- registry: this._capabilityRegistry,
21140
- createDelegationWasm: (params) => this.createDelegationWrapper(params),
21141
- computeCid: (data, codec) => {
21142
- if (!this.wasmBindings.computeCid) throw new Error("computeCid is unavailable");
21143
- return this.wasmBindings.computeCid(data, codec);
21144
- },
21145
- // delegationManager: undefined - not needed for receive()
21146
- createKVService: (config2) => {
21147
- const prefix = config2.pathPrefix?.replace(/\/$/, "");
21148
- const kvService = new KVService2({ prefix });
21149
- const kvContext = receiveOnlyGraph.track(new ServiceContext2({
21150
- invoke: config2.invoke,
21151
- fetch: config2.fetch ?? globalThis.fetch.bind(globalThis),
21152
- hosts: config2.hosts,
21153
- telemetry: this.config.telemetry
21154
- }));
21155
- kvContext.setSession(config2.session);
21156
- kvService.initialize(kvContext);
21157
- return kvService;
21158
- }
21159
- });
21197
+ this._sharingService = this.createReceiveOnlySharing();
21160
21198
  if (config.signer) {
21161
21199
  this.signer = config.signer;
21162
21200
  this.setupAuth(config);
@@ -21298,6 +21336,42 @@ var _TinyCloudNode = class _TinyCloudNode {
21298
21336
  get siweDomain() {
21299
21337
  return this.config.domain ?? "app.tinycloud.xyz";
21300
21338
  }
21339
+ /**
21340
+ * SharingService for receive-only access (no session required), bound to the
21341
+ * current service graph. This lets session-only users receive sharing links
21342
+ * without signIn(); full capabilities (generate) are added after signIn().
21343
+ */
21344
+ createReceiveOnlySharing() {
21345
+ const receiveOnlyGraph = this._serviceGraph;
21346
+ return new SharingService({
21347
+ hosts: [this.config.host],
21348
+ // session: undefined - not needed for receive()
21349
+ invoke: receiveOnlyGraph.invoke,
21350
+ fetch: receiveOnlyGraph.fetch,
21351
+ assertActive: () => receiveOnlyGraph.assertActive(),
21352
+ keyProvider: this._keyProvider,
21353
+ registry: this._capabilityRegistry,
21354
+ createDelegationWasm: (params) => this.createDelegationWrapper(params),
21355
+ computeCid: (data, codec) => {
21356
+ if (!this.wasmBindings.computeCid) throw new Error("computeCid is unavailable");
21357
+ return this.wasmBindings.computeCid(data, codec);
21358
+ },
21359
+ // delegationManager: undefined - not needed for receive()
21360
+ createKVService: (config) => {
21361
+ const prefix = config.pathPrefix?.replace(/\/$/, "");
21362
+ const kvService = new KVService2({ prefix });
21363
+ const kvContext = receiveOnlyGraph.track(new ServiceContext2({
21364
+ invoke: config.invoke,
21365
+ fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
21366
+ hosts: config.hosts,
21367
+ telemetry: this.config.telemetry
21368
+ }));
21369
+ kvContext.setSession(config.session);
21370
+ kvService.initialize(kvContext);
21371
+ return kvService;
21372
+ }
21373
+ });
21374
+ }
21301
21375
  /**
21302
21376
  * Set up authorization handler and TinyCloud instance.
21303
21377
  * @internal
@@ -21557,6 +21631,102 @@ var _TinyCloudNode = class _TinyCloudNode {
21557
21631
  get restorableSession() {
21558
21632
  return this.currentTinyCloudSession();
21559
21633
  }
21634
+ /**
21635
+ * Sign out and end the session on this instance.
21636
+ *
21637
+ * Every service built for the session (and every `useDelegation()` result)
21638
+ * is retired and the session key is replaced before anything is awaited, so
21639
+ * nothing captured from the session can sign once sign-out has started.
21640
+ * Pending work is aborted and the persisted session is cleared from the
21641
+ * configured session storage. Afterwards session accessors return
21642
+ * `undefined` and service calls fail with "Not signed in" until
21643
+ * {@link signIn} or {@link restoreSession}, which wait for a sign-out in
21644
+ * progress.
21645
+ */
21646
+ signOut() {
21647
+ this.signOutInFlight ?? (this.signOutInFlight = this.endSession().finally(() => {
21648
+ this.signOutInFlight = void 0;
21649
+ }));
21650
+ return this.signOutInFlight;
21651
+ }
21652
+ async endSession() {
21653
+ const leavingAddress = this.address ?? this._restoredTcSession?.address;
21654
+ this.teardownSession();
21655
+ try {
21656
+ await this.accountRegistryTail;
21657
+ if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
21658
+ } finally {
21659
+ if (leavingAddress) await this.clearPersistedSessionFor(leavingAddress);
21660
+ }
21661
+ }
21662
+ /**
21663
+ * Synchronously drop every piece of in-memory session authority and
21664
+ * invalidate sign-ins and restores still in flight (see `sessionEpoch`).
21665
+ */
21666
+ teardownSession() {
21667
+ this.sessionEpoch++;
21668
+ this.pendingAccountRegistrySync?.controller.abort();
21669
+ const oldGraph = this._serviceGraph;
21670
+ const oldCore = this.tc;
21671
+ const sessionKey = createDefaultSessionKey(this.wasmBindings);
21672
+ this.sessionManager = sessionKey.manager;
21673
+ this.sessionKeyId = sessionKey.keyId;
21674
+ this.sessionKeyJwk = sessionKey.jwk;
21675
+ this.auth?.resetSession(
21676
+ sessionKey.manager,
21677
+ sessionKey.keyId,
21678
+ this.explicitHost ? [this.explicitHost] : void 0
21679
+ );
21680
+ this.config.host = this.explicitHost ?? DEFAULT_HOST;
21681
+ this._address = void 0;
21682
+ this._chainId = 1;
21683
+ this._restoredTcSession = void 0;
21684
+ this._restoredNodeFeatures = void 0;
21685
+ this._activeServiceSession = void 0;
21686
+ this._serviceContext = void 0;
21687
+ this._kv = void 0;
21688
+ this._sql = void 0;
21689
+ this._duckdb = void 0;
21690
+ this._hooks = void 0;
21691
+ this._vault = void 0;
21692
+ this._encryption = void 0;
21693
+ this._baseVaults.clear();
21694
+ this._baseSecrets.clear();
21695
+ this._secrets.clear();
21696
+ this._account = void 0;
21697
+ this._publicKV = void 0;
21698
+ this._delegationManager = void 0;
21699
+ this._spaceService = void 0;
21700
+ this.runtimePermissionGrants = [];
21701
+ this._recapOperationsCache = void 0;
21702
+ this.confirmedHostedSpaceIds.clear();
21703
+ this.hostedOwnedSpaceIds.clear();
21704
+ this.cancelOwnedSpaceHosts();
21705
+ this.storageFull.clear();
21706
+ this._bootstrapSkipped = false;
21707
+ this._bootstrapStatus = { skipped: false };
21708
+ this.replicationDelegateSession = void 0;
21709
+ this.replicationScopeError = void 0;
21710
+ this._serviceGraph = this.createServiceGraphLifetime();
21711
+ oldGraph.retire();
21712
+ this.tc = this.auth ? new TinyCloud(this.auth, {
21713
+ invokeAny: this.invokeAnyWithRuntimePermissions,
21714
+ telemetry: this.config.telemetry
21715
+ }) : null;
21716
+ oldCore?.retireServices();
21717
+ this._capabilityRegistry = new CapabilityKeyRegistry();
21718
+ this._keyProvider = new WasmKeyProvider({ sessionManager: this.sessionManager });
21719
+ this._sharingService = this.createReceiveOnlySharing();
21720
+ }
21721
+ async clearPersistedSessionFor(address) {
21722
+ await (this.auth ? this.auth.clearPersistedSession(address) : this.config.sessionStorage?.clear(address));
21723
+ }
21724
+ /** Throws when a sign-out started after `epoch` was read. */
21725
+ assertSessionEpoch(epoch, operation) {
21726
+ if (epoch !== this.sessionEpoch) {
21727
+ throw new Error(`${operation} was cancelled by sign-out.`);
21728
+ }
21729
+ }
21560
21730
  /**
21561
21731
  * Sign in and create a new session.
21562
21732
  * This creates the user's space if it doesn't exist.
@@ -21570,11 +21740,28 @@ var _TinyCloudNode = class _TinyCloudNode {
21570
21740
  "Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
21571
21741
  );
21572
21742
  }
21743
+ const epoch = this.sessionEpoch;
21744
+ const attempt = ++this.latestSessionAttempt;
21745
+ await this.signOutInFlight;
21746
+ try {
21747
+ await this.establishSignIn(epoch, options);
21748
+ } catch (error) {
21749
+ if (epoch !== this.sessionEpoch && attempt === this.latestSessionAttempt) {
21750
+ await this.endSession();
21751
+ }
21752
+ throw error;
21753
+ }
21754
+ }
21755
+ async establishSignIn(epoch, options) {
21756
+ if (!this.signer || !this.tc) {
21757
+ throw new Error("Not signed in. Call signIn() first.");
21758
+ }
21573
21759
  this.pendingAccountRegistrySync?.controller.abort();
21574
21760
  await this.accountRegistryTail;
21575
21761
  await this.wasmBindings.ensureInitialized?.();
21576
21762
  const address = canonicalizeAddress2(await this.signer.getAddress());
21577
21763
  const chainId = await this.signer.getChainId();
21764
+ this.assertSessionEpoch(epoch, "Sign-in");
21578
21765
  if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
21579
21766
  this._address = address;
21580
21767
  this._chainId = chainId;
@@ -21593,6 +21780,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21593
21780
  this.confirmedHostedSpaceIds.clear();
21594
21781
  this.hostedOwnedSpaceIds.clear();
21595
21782
  await this.tc.signIn(options);
21783
+ this.assertSessionEpoch(epoch, "Sign-in");
21596
21784
  this.cancelOwnedSpaceHosts();
21597
21785
  this.syncResolvedHostFromAuth();
21598
21786
  for (const spaceId of this.auth.lastActivationActivatedSpaceIds ?? []) {
@@ -21601,6 +21789,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21601
21789
  this.replicationDelegateSession = void 0;
21602
21790
  this.replicationScopeError = void 0;
21603
21791
  if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
21792
+ this.assertSessionEpoch(epoch, "Sign-in");
21604
21793
  const oldGraph = this._serviceGraph;
21605
21794
  this._serviceGraph = this.createServiceGraphLifetime();
21606
21795
  oldGraph.retire();
@@ -21611,15 +21800,19 @@ var _TinyCloudNode = class _TinyCloudNode {
21611
21800
  this.sessionKeyJwk = signedInSession.jwk;
21612
21801
  }
21613
21802
  await this.initializeServices();
21803
+ this.assertSessionEpoch(epoch, "Sign-in");
21614
21804
  const primarySession = this.currentTinyCloudSession();
21615
21805
  if (primarySession) {
21616
21806
  this.registerPrimarySessionGrant(primarySession);
21617
21807
  }
21618
21808
  const bootstrapped = await this.bootstrapAccountIfNeeded();
21809
+ this.assertSessionEpoch(epoch, "Sign-in");
21619
21810
  await this.ensureRequestedEncryptionNetworks();
21811
+ this.assertSessionEpoch(epoch, "Sign-in");
21620
21812
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
21621
21813
  await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(SECRETS_SPACE2));
21622
21814
  }
21815
+ this.assertSessionEpoch(epoch, "Sign-in");
21623
21816
  if (!bootstrapped) {
21624
21817
  this.scheduleAccountRegistrySync();
21625
21818
  }
@@ -21801,6 +21994,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21801
21994
  throw new Error("Account bootstrap requires a TinyCloud host");
21802
21995
  }
21803
21996
  const auth = this.auth;
21997
+ const graph = this._serviceGraph;
21804
21998
  const sessions = /* @__PURE__ */ new Map();
21805
21999
  const rawAbilitiesBySpace = /* @__PURE__ */ new Map();
21806
22000
  const warnings = [];
@@ -21809,6 +22003,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21809
22003
  const canReusePrimaryBootstrapSession = primarySession?.spaceId === defaultSpaceId && auth.capabilityRequest === BOOTSTRAP_SESSION_REQUESTS.default;
21810
22004
  for (const step of steps) {
21811
22005
  if (step.kind !== "session") continue;
22006
+ graph.assertActive();
21812
22007
  if (step.space === "default" && canReusePrimaryBootstrapSession && primarySession) {
21813
22008
  sessions.set(step.space, primarySession);
21814
22009
  continue;
@@ -21836,6 +22031,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21836
22031
  }
21837
22032
  for (const step of steps) {
21838
22033
  if (step.kind !== "host") continue;
22034
+ graph.assertActive();
21839
22035
  const hosted = await auth.hostOwnedSpaceResult(step.spaceId, "bootstrap-host");
21840
22036
  if (!hosted.success) {
21841
22037
  throw Object.assign(
@@ -21847,11 +22043,16 @@ var _TinyCloudNode = class _TinyCloudNode {
21847
22043
  await new Promise((resolve) => setTimeout(resolve, 100));
21848
22044
  for (const step of steps) {
21849
22045
  if (step.kind !== "activate") continue;
22046
+ graph.assertActive();
21850
22047
  const session = sessions.get(step.space);
21851
22048
  if (!session) {
21852
22049
  throw new Error(`Missing bootstrap session for ${step.space}`);
21853
22050
  }
21854
- const activated = await activateSessionWithHost2(host, session.delegationHeader);
22051
+ const activated = await activateSessionWithHost2(host, session.delegationHeader, graph.signal).catch((error) => {
22052
+ graph.assertActive();
22053
+ throw error;
22054
+ });
22055
+ graph.assertActive();
21855
22056
  if (!activated.success || activated.skipped?.includes(step.spaceId)) {
21856
22057
  throw Object.assign(
21857
22058
  new Error(
@@ -21867,6 +22068,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21867
22068
  );
21868
22069
  }
21869
22070
  for (const step of steps) {
22071
+ graph.assertActive();
21870
22072
  if (step.kind === "account-index-schema") {
21871
22073
  const ensured = await this.account.index.ensure();
21872
22074
  if (!ensured.ok) {
@@ -22642,6 +22844,9 @@ var _TinyCloudNode = class _TinyCloudNode {
22642
22844
  * @param sessionData - The stored delegation data from the browser flow
22643
22845
  */
22644
22846
  async restoreSession(sessionData) {
22847
+ const epoch = this.sessionEpoch;
22848
+ const attempt = ++this.latestSessionAttempt;
22849
+ await this.signOutInFlight;
22645
22850
  await this.wasmBindings.ensureInitialized?.();
22646
22851
  let restoredJwk;
22647
22852
  try {
@@ -22831,6 +23036,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22831
23036
  });
22832
23037
  this.pendingAccountRegistrySync?.controller.abort();
22833
23038
  await this.accountRegistryTail;
23039
+ this.assertSessionEpoch(epoch, "Session restore");
22834
23040
  const oldGraph = this._serviceGraph;
22835
23041
  const oldCore = this.tc;
22836
23042
  this.sessionManager = stagedManager;
@@ -22876,6 +23082,12 @@ var _TinyCloudNode = class _TinyCloudNode {
22876
23082
  this.replicationDelegateSession = compactReplicationSession;
22877
23083
  this.replicationScopeError = replicationScopeError;
22878
23084
  if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
23085
+ if (epoch !== this.sessionEpoch) {
23086
+ oldGraph.retire();
23087
+ oldCore?.retireServices?.();
23088
+ if (attempt === this.latestSessionAttempt) await this.endSession();
23089
+ this.assertSessionEpoch(epoch, "Session restore");
23090
+ }
22879
23091
  oldGraph.retire();
22880
23092
  if (replicationScopeError) {
22881
23093
  stagedGraph.serviceContext.emit("replication.state", {
@@ -22886,6 +23098,10 @@ var _TinyCloudNode = class _TinyCloudNode {
22886
23098
  }
22887
23099
  await this.bindReplicationRuntime(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
22888
23100
  oldCore?.retireServices?.();
23101
+ if (epoch !== this.sessionEpoch) {
23102
+ if (attempt === this.latestSessionAttempt) await this.endSession();
23103
+ this.assertSessionEpoch(epoch, "Session restore");
23104
+ }
22889
23105
  }
22890
23106
  stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
22891
23107
  if (!session?.siwe) return { cache: void 0, grants: [] };
@@ -24744,6 +24960,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24744
24960
  * matching service calls and downstream `delegateTo()` calls can use it.
24745
24961
  */
24746
24962
  async useRuntimeDelegation(delegation) {
24963
+ const graph = this._serviceGraph;
24747
24964
  const session = this.currentTinyCloudSession();
24748
24965
  if (!session) {
24749
24966
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
@@ -24762,9 +24979,11 @@ var _TinyCloudNode = class _TinyCloudNode {
24762
24979
  try {
24763
24980
  activateResult = await activateSessionWithHost2(
24764
24981
  targetHost,
24765
- delegation.delegationHeader
24982
+ delegation.delegationHeader,
24983
+ graph.signal
24766
24984
  );
24767
24985
  } catch (cause) {
24986
+ graph.assertActive();
24768
24987
  throw Object.assign(
24769
24988
  new Error("The TinyCloud host could not be reached to activate the delegation."),
24770
24989
  { code: "ACTIVATION_TRANSPORT_FAILED", cause }
@@ -24784,6 +25003,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24784
25003
  { cause: activateResult }
24785
25004
  );
24786
25005
  }
25006
+ graph.assertActive();
24787
25007
  this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
24788
25008
  (grant) => grant.delegation.cid !== delegation.cid
24789
25009
  );
@@ -24800,6 +25020,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24800
25020
  if (!Array.isArray(permissions) || permissions.length === 0) {
24801
25021
  throw new Error("grantRuntimePermissions requires a non-empty permissions array");
24802
25022
  }
25023
+ const graph = this._serviceGraph;
24803
25024
  const session = this.currentTinyCloudSession();
24804
25025
  if (!session) {
24805
25026
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
@@ -24880,14 +25101,19 @@ var _TinyCloudNode = class _TinyCloudNode {
24880
25101
  }
24881
25102
  })();
24882
25103
  const signature2 = await this.signer.signMessage(prepared.siwe);
25104
+ graph.assertActive();
24883
25105
  const delegatedSession = this.wasmBindings.completeSessionSetup({
24884
25106
  ...prepared,
24885
25107
  signature: signature2
24886
25108
  });
24887
25109
  const activateResult = await activateSessionWithHost2(
24888
25110
  this.config.host,
24889
- delegatedSession.delegationHeader
24890
- );
25111
+ delegatedSession.delegationHeader,
25112
+ graph.signal
25113
+ ).catch((error) => {
25114
+ graph.assertActive();
25115
+ throw error;
25116
+ });
24891
25117
  if (!activateResult.success) {
24892
25118
  if (rawForDelegation.some((entry) => this.isRawRevocationPermissionEntry(entry)) && activateResult.status === 401 && /Cannot find parent delegation/i.test(activateResult.error ?? "")) {
24893
25119
  throw Object.assign(
@@ -24908,6 +25134,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24908
25134
  expiresAt,
24909
25135
  { siwe: prepared.siwe, signature: signature2 }
24910
25136
  );
25137
+ graph.assertActive();
24911
25138
  this.runtimePermissionGrants.push({
24912
25139
  session: {
24913
25140
  delegationHeader: delegatedSession.delegationHeader,
@@ -26371,6 +26598,7 @@ var _TinyCloudNode = class _TinyCloudNode {
26371
26598
  * @returns A DelegatedAccess instance for performing operations
26372
26599
  */
26373
26600
  async useDelegation(delegation) {
26601
+ const graph = this._serviceGraph;
26374
26602
  const delegationHeader = delegation.delegationHeader;
26375
26603
  const targetHost = delegation.host ?? this.config.host;
26376
26604
  if (this.isSessionOnly) {
@@ -26412,7 +26640,8 @@ var _TinyCloudNode = class _TinyCloudNode {
26412
26640
  targetHost,
26413
26641
  this.wasmBindings.invoke,
26414
26642
  this.wasmBindings.invokeAny,
26415
- this.config.telemetry
26643
+ this.config.telemetry,
26644
+ graph
26416
26645
  );
26417
26646
  }
26418
26647
  const mySession = this.auth?.tinyCloudSession;
@@ -26436,14 +26665,19 @@ var _TinyCloudNode = class _TinyCloudNode {
26436
26665
  ...Object.keys(rawAbilities).length > 0 ? { rawAbilities } : {}
26437
26666
  });
26438
26667
  const signature2 = await this.signer.signMessage(prepared.siwe);
26668
+ graph.assertActive();
26439
26669
  const invokerSession = this.wasmBindings.completeSessionSetup({
26440
26670
  ...prepared,
26441
26671
  signature: signature2
26442
26672
  });
26443
26673
  const activateResult = await activateSessionWithHost2(
26444
26674
  targetHost,
26445
- invokerSession.delegationHeader
26446
- );
26675
+ invokerSession.delegationHeader,
26676
+ graph.signal
26677
+ ).catch((error) => {
26678
+ graph.assertActive();
26679
+ throw error;
26680
+ });
26447
26681
  if (!activateResult.success) {
26448
26682
  throw Object.assign(
26449
26683
  new Error(`Failed to activate delegated session: ${describeHostFailure2(activateResult)}`),
@@ -26462,6 +26696,7 @@ var _TinyCloudNode = class _TinyCloudNode {
26462
26696
  siwe: prepared.siwe,
26463
26697
  signature: signature2
26464
26698
  };
26699
+ graph.assertActive();
26465
26700
  this.trackReceivedDelegation(delegation, jwk);
26466
26701
  this.installRuntimeGrantFromServiceSession(
26467
26702
  delegation,
@@ -26480,7 +26715,8 @@ var _TinyCloudNode = class _TinyCloudNode {
26480
26715
  targetHost,
26481
26716
  this.wasmBindings.invoke,
26482
26717
  this.wasmBindings.invokeAny,
26483
- this.config.telemetry
26718
+ this.config.telemetry,
26719
+ graph
26484
26720
  );
26485
26721
  }
26486
26722
  /**