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

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.cjs CHANGED
@@ -18494,6 +18494,13 @@ function cloneAbilitiesMap(actions) {
18494
18494
  var NodeUserAuthorization = class {
18495
18495
  constructor(config) {
18496
18496
  this.activeSessionKeyId = "default";
18497
+ /** Incremented by resetSession; a sign-in started earlier publishes nothing. */
18498
+ this.sessionGeneration = 0;
18499
+ /**
18500
+ * Sign-in saves run one at a time, in order, each together with its own
18501
+ * cancellation cleanup, so no later write can land between the two.
18502
+ */
18503
+ this.persistenceQueue = Promise.resolve();
18497
18504
  this.extensions = [];
18498
18505
  this._nodeFeatures = [];
18499
18506
  this._nodeFeaturesKnown = false;
@@ -18624,6 +18631,27 @@ var NodeUserAuthorization = class {
18624
18631
  this.tinycloudHosts = [...hosts];
18625
18632
  }
18626
18633
  }
18634
+ /**
18635
+ * @internal End the session: adopt a fresh session manager whose active key
18636
+ * is `sessionKeyId`, and return to the constructed state.
18637
+ */
18638
+ resetSession(sessionManager, sessionKeyId, hosts) {
18639
+ this.sessionGeneration++;
18640
+ this.installRestoredSession(sessionManager, void 0, hosts);
18641
+ this.activeSessionKeyId = sessionKeyId;
18642
+ }
18643
+ /** Throws when the session was reset (signed out) after `generation` was read. */
18644
+ assertSignInCurrent(generation) {
18645
+ if (generation !== this.sessionGeneration) {
18646
+ throw new Error("Sign-in was cancelled by sign-out.");
18647
+ }
18648
+ }
18649
+ /** Runs `task` after every earlier queued persistence task has settled. */
18650
+ enqueuePersistence(task) {
18651
+ const run = this.persistenceQueue.then(task);
18652
+ this.persistenceQueue = run.catch(() => void 0);
18653
+ return run;
18654
+ }
18627
18655
  /** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
18628
18656
  installRestoredSession(sessionManager, session, hosts, nodeFeatures) {
18629
18657
  this.sessionManager = sessionManager;
@@ -19312,11 +19340,14 @@ var NodeUserAuthorization = class {
19312
19340
  * @param options - Optional per-call SIWE overrides for this sign-in only
19313
19341
  */
19314
19342
  async signIn(options) {
19315
- this._address = (0, import_sdk_core4.canonicalizeAddress)(await this.signer.getAddress());
19316
- this._chainId = await this.signer.getChainId();
19317
- const address = this._address;
19318
- const chainId = this._chainId;
19343
+ const generation = this.sessionGeneration;
19344
+ const address = (0, import_sdk_core4.canonicalizeAddress)(await this.signer.getAddress());
19345
+ const chainId = await this.signer.getChainId();
19346
+ this.assertSignInCurrent(generation);
19347
+ this._address = address;
19348
+ this._chainId = chainId;
19319
19349
  await this.resolveTinyCloudHostsForSignIn(address, chainId);
19350
+ this.assertSignInCurrent(generation);
19320
19351
  const keyId = `session-${Date.now()}`;
19321
19352
  this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
19322
19353
  this.activeSessionKeyId = keyId;
@@ -19349,6 +19380,7 @@ var NodeUserAuthorization = class {
19349
19380
  type: "siwe",
19350
19381
  purpose: "sign-in"
19351
19382
  });
19383
+ this.assertSignInCurrent(generation);
19352
19384
  const session = this.wasm.completeSessionSetup({
19353
19385
  ...prepared,
19354
19386
  signature: signature2
@@ -19393,18 +19425,27 @@ var NodeUserAuthorization = class {
19393
19425
  version: "1.0",
19394
19426
  tinycloudHosts: this.tinycloudHosts
19395
19427
  };
19396
- await this.sessionStorage.save(address, persistedData);
19428
+ await this.enqueuePersistence(async () => {
19429
+ await this.sessionStorage.save(address, persistedData);
19430
+ if (generation !== this.sessionGeneration) {
19431
+ await this.sessionStorage.clear(address);
19432
+ }
19433
+ });
19434
+ this.assertSignInCurrent(generation);
19397
19435
  this._session = clientSession;
19398
19436
  this._tinyCloudSession = tinyCloudSession;
19399
19437
  this._address = address;
19400
19438
  this._chainId = chainId;
19401
19439
  await this.refreshNodeInfo();
19440
+ this.assertSignInCurrent(generation);
19402
19441
  for (const ext of this.extensions) {
19403
19442
  if (ext.afterSignIn) {
19404
19443
  await ext.afterSignIn(clientSession);
19444
+ this.assertSignInCurrent(generation);
19405
19445
  }
19406
19446
  }
19407
19447
  await this.ensureSpaceExists();
19448
+ this.assertSignInCurrent(generation);
19408
19449
  return clientSession;
19409
19450
  }
19410
19451
  /**
@@ -20041,17 +20082,24 @@ var import_sdk_core5 = require("@tinycloud/sdk-core");
20041
20082
  // src/DelegatedAccess.ts
20042
20083
  var import_sdk_core6 = require("@tinycloud/sdk-core");
20043
20084
  var DelegatedAccess = class {
20044
- constructor(session, delegation, host, invoke2, invokeAny2, telemetry) {
20085
+ /**
20086
+ * @param lifetime - Optional owner of this access: its `fetch` is used for
20087
+ * requests and the service context is registered with `track`, so the
20088
+ * owner can cancel and retire the access (TinyCloudNode does this when its
20089
+ * session is replaced or signed out).
20090
+ */
20091
+ constructor(session, delegation, host, invoke2, invokeAny2, telemetry, lifetime) {
20045
20092
  this.session = session;
20046
20093
  this._delegation = delegation;
20047
20094
  this.host = host;
20048
- this._serviceContext = new import_sdk_core6.ServiceContext({
20095
+ const context = new import_sdk_core6.ServiceContext({
20049
20096
  invoke: invoke2,
20050
20097
  invokeAny: invokeAny2,
20051
- fetch: globalThis.fetch.bind(globalThis),
20098
+ fetch: lifetime?.fetch ?? globalThis.fetch.bind(globalThis),
20052
20099
  hosts: [host],
20053
20100
  telemetry
20054
20101
  });
20102
+ this._serviceContext = lifetime ? lifetime.track(context) : context;
20055
20103
  const prefix = this._delegation.path.replace(/\/$/, "");
20056
20104
  this._kv = new import_sdk_core6.KVService({ prefix });
20057
20105
  this._kv.initialize(this._serviceContext);
@@ -20625,6 +20673,19 @@ function exactVerifiedRecap(verified) {
20625
20673
  caveats: cloneRecapCaveats(entry.caveats)
20626
20674
  }));
20627
20675
  }
20676
+ function createDefaultSessionKey(wasm) {
20677
+ const manager = wasm.createSessionManager();
20678
+ let keyId = "default";
20679
+ let jwkStr = manager.jwk(keyId);
20680
+ if (!jwkStr) {
20681
+ keyId = manager.createSessionKey(keyId);
20682
+ jwkStr = manager.jwk(keyId);
20683
+ }
20684
+ if (!jwkStr) {
20685
+ throw new Error("Failed to get session key JWK");
20686
+ }
20687
+ return { manager, keyId, jwk: JSON.parse(jwkStr) };
20688
+ }
20628
20689
  var ServiceGraphLifetime = class {
20629
20690
  constructor(invokeFn, invokeAnyFn, fetchFn, observeContext) {
20630
20691
  this.invokeFn = invokeFn;
@@ -20674,9 +20735,13 @@ var ServiceGraphLifetime = class {
20674
20735
  this.contexts.clear();
20675
20736
  }
20676
20737
  }
20738
+ /** Aborted when the graph retires: cancels requests made for its session. */
20739
+ get signal() {
20740
+ return this.abortController.signal;
20741
+ }
20677
20742
  assertActive() {
20678
20743
  if (this.retired) {
20679
- throw new Error("Service graph has been retired by session replacement.");
20744
+ throw new Error("Service graph has been retired: its session was replaced or signed out.");
20680
20745
  }
20681
20746
  }
20682
20747
  combineSignals(signal) {
@@ -20978,6 +21043,16 @@ var _TinyCloudNode = class _TinyCloudNode {
20978
21043
  * so it must outlast a slow but healthy sync (9–18 s on prod 1.20.2, TC-998).
20979
21044
  */
20980
21045
  this.accountRegistryDeadlineMs = 3e4;
21046
+ /**
21047
+ * Incremented by every sign-out. A sign-in or restore records it when it
21048
+ * starts and does not establish its session once it has changed.
21049
+ */
21050
+ this.sessionEpoch = 0;
21051
+ /**
21052
+ * Numbers sign-in and restore attempts. A cancelled attempt cleans up only
21053
+ * while it is still the latest, so it never ends a newer session.
21054
+ */
21055
+ this.latestSessionAttempt = 0;
20981
21056
  /**
20982
21057
  * True when the last signIn() detected an interactive signer and skipped
20983
21058
  * client-side bootstrap. Apps can read this to know whether bootstrap was
@@ -21111,54 +21186,17 @@ var _TinyCloudNode = class _TinyCloudNode {
21111
21186
  "wasmBindings must be provided in config. Import from '@tinycloud/node-sdk' (not '/core') for automatic Node.js defaults."
21112
21187
  );
21113
21188
  }
21114
- this.sessionManager = this.wasmBindings.createSessionManager();
21115
- const defaultKeyId = "default";
21116
- let jwkStr = this.sessionManager.jwk(defaultKeyId);
21117
- if (jwkStr) {
21118
- this.sessionKeyId = defaultKeyId;
21119
- } else {
21120
- this.sessionKeyId = this.sessionManager.createSessionKey(defaultKeyId);
21121
- jwkStr = this.sessionManager.jwk(this.sessionKeyId);
21122
- }
21123
- if (!jwkStr) {
21124
- throw new Error("Failed to get session key JWK");
21125
- }
21126
- this.sessionKeyJwk = JSON.parse(jwkStr);
21189
+ const sessionKey = createDefaultSessionKey(this.wasmBindings);
21190
+ this.sessionManager = sessionKey.manager;
21191
+ this.sessionKeyId = sessionKey.keyId;
21192
+ this.sessionKeyJwk = sessionKey.jwk;
21127
21193
  this._serviceGraph = this.createServiceGraphLifetime();
21128
21194
  this._capabilityRegistry = new import_sdk_core9.CapabilityKeyRegistry();
21129
21195
  this._keyProvider = new WasmKeyProvider({
21130
21196
  sessionManager: this.sessionManager
21131
21197
  });
21132
21198
  this.notificationHandler = config.notificationHandler ?? new import_sdk_core9.SilentNotificationHandler();
21133
- const receiveOnlyGraph = this._serviceGraph;
21134
- this._sharingService = new import_sdk_core9.SharingService({
21135
- hosts: [this.config.host],
21136
- // session: undefined - not needed for receive()
21137
- invoke: receiveOnlyGraph.invoke,
21138
- fetch: receiveOnlyGraph.fetch,
21139
- assertActive: () => receiveOnlyGraph.assertActive(),
21140
- keyProvider: this._keyProvider,
21141
- registry: this._capabilityRegistry,
21142
- createDelegationWasm: (params) => this.createDelegationWrapper(params),
21143
- computeCid: (data, codec) => {
21144
- if (!this.wasmBindings.computeCid) throw new Error("computeCid is unavailable");
21145
- return this.wasmBindings.computeCid(data, codec);
21146
- },
21147
- // delegationManager: undefined - not needed for receive()
21148
- createKVService: (config2) => {
21149
- const prefix = config2.pathPrefix?.replace(/\/$/, "");
21150
- const kvService = new import_sdk_core9.KVService({ prefix });
21151
- const kvContext = receiveOnlyGraph.track(new import_sdk_core9.ServiceContext({
21152
- invoke: config2.invoke,
21153
- fetch: config2.fetch ?? globalThis.fetch.bind(globalThis),
21154
- hosts: config2.hosts,
21155
- telemetry: this.config.telemetry
21156
- }));
21157
- kvContext.setSession(config2.session);
21158
- kvService.initialize(kvContext);
21159
- return kvService;
21160
- }
21161
- });
21199
+ this._sharingService = this.createReceiveOnlySharing();
21162
21200
  if (config.signer) {
21163
21201
  this.signer = config.signer;
21164
21202
  this.setupAuth(config);
@@ -21300,6 +21338,42 @@ var _TinyCloudNode = class _TinyCloudNode {
21300
21338
  get siweDomain() {
21301
21339
  return this.config.domain ?? "app.tinycloud.xyz";
21302
21340
  }
21341
+ /**
21342
+ * SharingService for receive-only access (no session required), bound to the
21343
+ * current service graph. This lets session-only users receive sharing links
21344
+ * without signIn(); full capabilities (generate) are added after signIn().
21345
+ */
21346
+ createReceiveOnlySharing() {
21347
+ const receiveOnlyGraph = this._serviceGraph;
21348
+ return new import_sdk_core9.SharingService({
21349
+ hosts: [this.config.host],
21350
+ // session: undefined - not needed for receive()
21351
+ invoke: receiveOnlyGraph.invoke,
21352
+ fetch: receiveOnlyGraph.fetch,
21353
+ assertActive: () => receiveOnlyGraph.assertActive(),
21354
+ keyProvider: this._keyProvider,
21355
+ registry: this._capabilityRegistry,
21356
+ createDelegationWasm: (params) => this.createDelegationWrapper(params),
21357
+ computeCid: (data, codec) => {
21358
+ if (!this.wasmBindings.computeCid) throw new Error("computeCid is unavailable");
21359
+ return this.wasmBindings.computeCid(data, codec);
21360
+ },
21361
+ // delegationManager: undefined - not needed for receive()
21362
+ createKVService: (config) => {
21363
+ const prefix = config.pathPrefix?.replace(/\/$/, "");
21364
+ const kvService = new import_sdk_core9.KVService({ prefix });
21365
+ const kvContext = receiveOnlyGraph.track(new import_sdk_core9.ServiceContext({
21366
+ invoke: config.invoke,
21367
+ fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
21368
+ hosts: config.hosts,
21369
+ telemetry: this.config.telemetry
21370
+ }));
21371
+ kvContext.setSession(config.session);
21372
+ kvService.initialize(kvContext);
21373
+ return kvService;
21374
+ }
21375
+ });
21376
+ }
21303
21377
  /**
21304
21378
  * Set up authorization handler and TinyCloud instance.
21305
21379
  * @internal
@@ -21559,6 +21633,115 @@ var _TinyCloudNode = class _TinyCloudNode {
21559
21633
  get restorableSession() {
21560
21634
  return this.currentTinyCloudSession();
21561
21635
  }
21636
+ /**
21637
+ * Sign out and end the session on this instance.
21638
+ *
21639
+ * Every service built for the session (and every `useDelegation()` result)
21640
+ * is retired and the session key is replaced before anything is awaited, so
21641
+ * nothing captured from the session can sign once sign-out has started.
21642
+ * Pending work is aborted and the persisted session is cleared from the
21643
+ * configured session storage. Afterwards session accessors return
21644
+ * `undefined` and service calls fail with "Not signed in" until
21645
+ * {@link signIn} or {@link restoreSession}, which wait for a sign-out in
21646
+ * progress.
21647
+ */
21648
+ signOut() {
21649
+ this.signOutInFlight ?? (this.signOutInFlight = this.endSession().finally(() => {
21650
+ this.signOutInFlight = void 0;
21651
+ }));
21652
+ return this.signOutInFlight;
21653
+ }
21654
+ async endSession() {
21655
+ const leavingAddress = this.address ?? this._restoredTcSession?.address;
21656
+ const leavingAuth = this.auth;
21657
+ try {
21658
+ this.teardownSession();
21659
+ } finally {
21660
+ try {
21661
+ await this.accountRegistryTail;
21662
+ if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
21663
+ } finally {
21664
+ if (leavingAddress) {
21665
+ await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
21666
+ }
21667
+ }
21668
+ }
21669
+ }
21670
+ /**
21671
+ * Synchronously drop every piece of in-memory session authority and
21672
+ * invalidate sign-ins and restores still in flight (see `sessionEpoch`).
21673
+ */
21674
+ teardownSession() {
21675
+ this.sessionEpoch++;
21676
+ this.pendingAccountRegistrySync?.controller.abort();
21677
+ const oldGraph = this._serviceGraph;
21678
+ const oldCore = this.tc;
21679
+ const oldAuth = this.auth;
21680
+ this.auth = null;
21681
+ this.tc = null;
21682
+ this._address = void 0;
21683
+ this._restoredTcSession = void 0;
21684
+ this._activeServiceSession = void 0;
21685
+ oldGraph.retire();
21686
+ oldCore?.retireServices();
21687
+ oldAuth?.resetSession(
21688
+ this.sessionManager,
21689
+ this.sessionKeyId,
21690
+ this.explicitHost ? [this.explicitHost] : void 0
21691
+ );
21692
+ this.config.host = this.explicitHost ?? DEFAULT_HOST;
21693
+ this._chainId = 1;
21694
+ this._restoredNodeFeatures = void 0;
21695
+ this._activeServiceSession = void 0;
21696
+ this._serviceContext = void 0;
21697
+ this._kv = void 0;
21698
+ this._sql = void 0;
21699
+ this._duckdb = void 0;
21700
+ this._hooks = void 0;
21701
+ this._vault = void 0;
21702
+ this._encryption = void 0;
21703
+ this._baseVaults.clear();
21704
+ this._baseSecrets.clear();
21705
+ this._secrets.clear();
21706
+ this._account = void 0;
21707
+ this._publicKV = void 0;
21708
+ this._delegationManager = void 0;
21709
+ this._spaceService = void 0;
21710
+ this.runtimePermissionGrants = [];
21711
+ this._recapOperationsCache = void 0;
21712
+ this.confirmedHostedSpaceIds.clear();
21713
+ this.hostedOwnedSpaceIds.clear();
21714
+ this.cancelOwnedSpaceHosts();
21715
+ this.storageFull.clear();
21716
+ this._bootstrapSkipped = false;
21717
+ this._bootstrapStatus = { skipped: false };
21718
+ this.replicationDelegateSession = void 0;
21719
+ this.replicationScopeError = void 0;
21720
+ const sessionKey = createDefaultSessionKey(this.wasmBindings);
21721
+ this.sessionManager = sessionKey.manager;
21722
+ this.sessionKeyId = sessionKey.keyId;
21723
+ this.sessionKeyJwk = sessionKey.jwk;
21724
+ oldAuth?.resetSession(
21725
+ sessionKey.manager,
21726
+ sessionKey.keyId,
21727
+ this.explicitHost ? [this.explicitHost] : void 0
21728
+ );
21729
+ this._serviceGraph = this.createServiceGraphLifetime();
21730
+ this.auth = oldAuth;
21731
+ this.tc = this.auth ? new import_sdk_core9.TinyCloud(this.auth, {
21732
+ invokeAny: this.invokeAnyWithRuntimePermissions,
21733
+ telemetry: this.config.telemetry
21734
+ }) : null;
21735
+ this._capabilityRegistry = new import_sdk_core9.CapabilityKeyRegistry();
21736
+ this._keyProvider = new WasmKeyProvider({ sessionManager: this.sessionManager });
21737
+ this._sharingService = this.createReceiveOnlySharing();
21738
+ }
21739
+ /** Throws when a sign-out started after `epoch` was read. */
21740
+ assertSessionEpoch(epoch, operation) {
21741
+ if (epoch !== this.sessionEpoch) {
21742
+ throw new Error(`${operation} was cancelled by sign-out.`);
21743
+ }
21744
+ }
21562
21745
  /**
21563
21746
  * Sign in and create a new session.
21564
21747
  * This creates the user's space if it doesn't exist.
@@ -21572,11 +21755,28 @@ var _TinyCloudNode = class _TinyCloudNode {
21572
21755
  "Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
21573
21756
  );
21574
21757
  }
21758
+ const epoch = this.sessionEpoch;
21759
+ const attempt = ++this.latestSessionAttempt;
21760
+ await this.signOutInFlight;
21761
+ try {
21762
+ await this.establishSignIn(epoch, options);
21763
+ } catch (error) {
21764
+ if (epoch !== this.sessionEpoch && attempt === this.latestSessionAttempt) {
21765
+ await this.endSession();
21766
+ }
21767
+ throw error;
21768
+ }
21769
+ }
21770
+ async establishSignIn(epoch, options) {
21771
+ if (!this.signer || !this.tc) {
21772
+ throw new Error("Not signed in. Call signIn() first.");
21773
+ }
21575
21774
  this.pendingAccountRegistrySync?.controller.abort();
21576
21775
  await this.accountRegistryTail;
21577
21776
  await this.wasmBindings.ensureInitialized?.();
21578
21777
  const address = (0, import_sdk_core9.canonicalizeAddress)(await this.signer.getAddress());
21579
21778
  const chainId = await this.signer.getChainId();
21779
+ this.assertSessionEpoch(epoch, "Sign-in");
21580
21780
  if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
21581
21781
  this._address = address;
21582
21782
  this._chainId = chainId;
@@ -21595,6 +21795,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21595
21795
  this.confirmedHostedSpaceIds.clear();
21596
21796
  this.hostedOwnedSpaceIds.clear();
21597
21797
  await this.tc.signIn(options);
21798
+ this.assertSessionEpoch(epoch, "Sign-in");
21598
21799
  this.cancelOwnedSpaceHosts();
21599
21800
  this.syncResolvedHostFromAuth();
21600
21801
  for (const spaceId of this.auth.lastActivationActivatedSpaceIds ?? []) {
@@ -21603,6 +21804,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21603
21804
  this.replicationDelegateSession = void 0;
21604
21805
  this.replicationScopeError = void 0;
21605
21806
  if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
21807
+ this.assertSessionEpoch(epoch, "Sign-in");
21606
21808
  const oldGraph = this._serviceGraph;
21607
21809
  this._serviceGraph = this.createServiceGraphLifetime();
21608
21810
  oldGraph.retire();
@@ -21613,15 +21815,19 @@ var _TinyCloudNode = class _TinyCloudNode {
21613
21815
  this.sessionKeyJwk = signedInSession.jwk;
21614
21816
  }
21615
21817
  await this.initializeServices();
21818
+ this.assertSessionEpoch(epoch, "Sign-in");
21616
21819
  const primarySession = this.currentTinyCloudSession();
21617
21820
  if (primarySession) {
21618
21821
  this.registerPrimarySessionGrant(primarySession);
21619
21822
  }
21620
21823
  const bootstrapped = await this.bootstrapAccountIfNeeded();
21824
+ this.assertSessionEpoch(epoch, "Sign-in");
21621
21825
  await this.ensureRequestedEncryptionNetworks();
21826
+ this.assertSessionEpoch(epoch, "Sign-in");
21622
21827
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
21623
21828
  await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(import_sdk_core9.SECRETS_SPACE));
21624
21829
  }
21830
+ this.assertSessionEpoch(epoch, "Sign-in");
21625
21831
  if (!bootstrapped) {
21626
21832
  this.scheduleAccountRegistrySync();
21627
21833
  }
@@ -21803,6 +22009,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21803
22009
  throw new Error("Account bootstrap requires a TinyCloud host");
21804
22010
  }
21805
22011
  const auth = this.auth;
22012
+ const graph = this._serviceGraph;
21806
22013
  const sessions = /* @__PURE__ */ new Map();
21807
22014
  const rawAbilitiesBySpace = /* @__PURE__ */ new Map();
21808
22015
  const warnings = [];
@@ -21811,6 +22018,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21811
22018
  const canReusePrimaryBootstrapSession = primarySession?.spaceId === defaultSpaceId && auth.capabilityRequest === import_sdk_core9.BOOTSTRAP_SESSION_REQUESTS.default;
21812
22019
  for (const step of steps) {
21813
22020
  if (step.kind !== "session") continue;
22021
+ graph.assertActive();
21814
22022
  if (step.space === "default" && canReusePrimaryBootstrapSession && primarySession) {
21815
22023
  sessions.set(step.space, primarySession);
21816
22024
  continue;
@@ -21838,6 +22046,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21838
22046
  }
21839
22047
  for (const step of steps) {
21840
22048
  if (step.kind !== "host") continue;
22049
+ graph.assertActive();
21841
22050
  const hosted = await auth.hostOwnedSpaceResult(step.spaceId, "bootstrap-host");
21842
22051
  if (!hosted.success) {
21843
22052
  throw Object.assign(
@@ -21849,11 +22058,16 @@ var _TinyCloudNode = class _TinyCloudNode {
21849
22058
  await new Promise((resolve) => setTimeout(resolve, 100));
21850
22059
  for (const step of steps) {
21851
22060
  if (step.kind !== "activate") continue;
22061
+ graph.assertActive();
21852
22062
  const session = sessions.get(step.space);
21853
22063
  if (!session) {
21854
22064
  throw new Error(`Missing bootstrap session for ${step.space}`);
21855
22065
  }
21856
- const activated = await (0, import_sdk_core9.activateSessionWithHost)(host, session.delegationHeader);
22066
+ const activated = await (0, import_sdk_core9.activateSessionWithHost)(host, session.delegationHeader, graph.signal).catch((error) => {
22067
+ graph.assertActive();
22068
+ throw error;
22069
+ });
22070
+ graph.assertActive();
21857
22071
  if (!activated.success || activated.skipped?.includes(step.spaceId)) {
21858
22072
  throw Object.assign(
21859
22073
  new Error(
@@ -21869,6 +22083,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21869
22083
  );
21870
22084
  }
21871
22085
  for (const step of steps) {
22086
+ graph.assertActive();
21872
22087
  if (step.kind === "account-index-schema") {
21873
22088
  const ensured = await this.account.index.ensure();
21874
22089
  if (!ensured.ok) {
@@ -22644,6 +22859,9 @@ var _TinyCloudNode = class _TinyCloudNode {
22644
22859
  * @param sessionData - The stored delegation data from the browser flow
22645
22860
  */
22646
22861
  async restoreSession(sessionData) {
22862
+ const epoch = this.sessionEpoch;
22863
+ const attempt = ++this.latestSessionAttempt;
22864
+ await this.signOutInFlight;
22647
22865
  await this.wasmBindings.ensureInitialized?.();
22648
22866
  let restoredJwk;
22649
22867
  try {
@@ -22833,6 +23051,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22833
23051
  });
22834
23052
  this.pendingAccountRegistrySync?.controller.abort();
22835
23053
  await this.accountRegistryTail;
23054
+ this.assertSessionEpoch(epoch, "Session restore");
22836
23055
  const oldGraph = this._serviceGraph;
22837
23056
  const oldCore = this.tc;
22838
23057
  this.sessionManager = stagedManager;
@@ -22878,6 +23097,12 @@ var _TinyCloudNode = class _TinyCloudNode {
22878
23097
  this.replicationDelegateSession = compactReplicationSession;
22879
23098
  this.replicationScopeError = replicationScopeError;
22880
23099
  if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
23100
+ if (epoch !== this.sessionEpoch) {
23101
+ oldGraph.retire();
23102
+ oldCore?.retireServices?.();
23103
+ if (attempt === this.latestSessionAttempt) await this.endSession();
23104
+ this.assertSessionEpoch(epoch, "Session restore");
23105
+ }
22881
23106
  oldGraph.retire();
22882
23107
  if (replicationScopeError) {
22883
23108
  stagedGraph.serviceContext.emit("replication.state", {
@@ -22888,6 +23113,10 @@ var _TinyCloudNode = class _TinyCloudNode {
22888
23113
  }
22889
23114
  await this.bindReplicationRuntime(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
22890
23115
  oldCore?.retireServices?.();
23116
+ if (epoch !== this.sessionEpoch) {
23117
+ if (attempt === this.latestSessionAttempt) await this.endSession();
23118
+ this.assertSessionEpoch(epoch, "Session restore");
23119
+ }
22891
23120
  }
22892
23121
  stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
22893
23122
  if (!session?.siwe) return { cache: void 0, grants: [] };
@@ -24746,6 +24975,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24746
24975
  * matching service calls and downstream `delegateTo()` calls can use it.
24747
24976
  */
24748
24977
  async useRuntimeDelegation(delegation) {
24978
+ const graph = this._serviceGraph;
24749
24979
  const session = this.currentTinyCloudSession();
24750
24980
  if (!session) {
24751
24981
  throw new import_sdk_core9.SessionExpiredError(/* @__PURE__ */ new Date(0));
@@ -24764,9 +24994,11 @@ var _TinyCloudNode = class _TinyCloudNode {
24764
24994
  try {
24765
24995
  activateResult = await (0, import_sdk_core9.activateSessionWithHost)(
24766
24996
  targetHost,
24767
- delegation.delegationHeader
24997
+ delegation.delegationHeader,
24998
+ graph.signal
24768
24999
  );
24769
25000
  } catch (cause) {
25001
+ graph.assertActive();
24770
25002
  throw Object.assign(
24771
25003
  new Error("The TinyCloud host could not be reached to activate the delegation."),
24772
25004
  { code: "ACTIVATION_TRANSPORT_FAILED", cause }
@@ -24786,6 +25018,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24786
25018
  { cause: activateResult }
24787
25019
  );
24788
25020
  }
25021
+ graph.assertActive();
24789
25022
  this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
24790
25023
  (grant) => grant.delegation.cid !== delegation.cid
24791
25024
  );
@@ -24802,6 +25035,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24802
25035
  if (!Array.isArray(permissions) || permissions.length === 0) {
24803
25036
  throw new Error("grantRuntimePermissions requires a non-empty permissions array");
24804
25037
  }
25038
+ const graph = this._serviceGraph;
24805
25039
  const session = this.currentTinyCloudSession();
24806
25040
  if (!session) {
24807
25041
  throw new import_sdk_core9.SessionExpiredError(/* @__PURE__ */ new Date(0));
@@ -24882,14 +25116,19 @@ var _TinyCloudNode = class _TinyCloudNode {
24882
25116
  }
24883
25117
  })();
24884
25118
  const signature2 = await this.signer.signMessage(prepared.siwe);
25119
+ graph.assertActive();
24885
25120
  const delegatedSession = this.wasmBindings.completeSessionSetup({
24886
25121
  ...prepared,
24887
25122
  signature: signature2
24888
25123
  });
24889
25124
  const activateResult = await (0, import_sdk_core9.activateSessionWithHost)(
24890
25125
  this.config.host,
24891
- delegatedSession.delegationHeader
24892
- );
25126
+ delegatedSession.delegationHeader,
25127
+ graph.signal
25128
+ ).catch((error) => {
25129
+ graph.assertActive();
25130
+ throw error;
25131
+ });
24893
25132
  if (!activateResult.success) {
24894
25133
  if (rawForDelegation.some((entry) => this.isRawRevocationPermissionEntry(entry)) && activateResult.status === 401 && /Cannot find parent delegation/i.test(activateResult.error ?? "")) {
24895
25134
  throw Object.assign(
@@ -24910,6 +25149,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24910
25149
  expiresAt,
24911
25150
  { siwe: prepared.siwe, signature: signature2 }
24912
25151
  );
25152
+ graph.assertActive();
24913
25153
  this.runtimePermissionGrants.push({
24914
25154
  session: {
24915
25155
  delegationHeader: delegatedSession.delegationHeader,
@@ -26373,6 +26613,7 @@ var _TinyCloudNode = class _TinyCloudNode {
26373
26613
  * @returns A DelegatedAccess instance for performing operations
26374
26614
  */
26375
26615
  async useDelegation(delegation) {
26616
+ const graph = this._serviceGraph;
26376
26617
  const delegationHeader = delegation.delegationHeader;
26377
26618
  const targetHost = delegation.host ?? this.config.host;
26378
26619
  if (this.isSessionOnly) {
@@ -26414,7 +26655,8 @@ var _TinyCloudNode = class _TinyCloudNode {
26414
26655
  targetHost,
26415
26656
  this.wasmBindings.invoke,
26416
26657
  this.wasmBindings.invokeAny,
26417
- this.config.telemetry
26658
+ this.config.telemetry,
26659
+ graph
26418
26660
  );
26419
26661
  }
26420
26662
  const mySession = this.auth?.tinyCloudSession;
@@ -26438,14 +26680,19 @@ var _TinyCloudNode = class _TinyCloudNode {
26438
26680
  ...Object.keys(rawAbilities).length > 0 ? { rawAbilities } : {}
26439
26681
  });
26440
26682
  const signature2 = await this.signer.signMessage(prepared.siwe);
26683
+ graph.assertActive();
26441
26684
  const invokerSession = this.wasmBindings.completeSessionSetup({
26442
26685
  ...prepared,
26443
26686
  signature: signature2
26444
26687
  });
26445
26688
  const activateResult = await (0, import_sdk_core9.activateSessionWithHost)(
26446
26689
  targetHost,
26447
- invokerSession.delegationHeader
26448
- );
26690
+ invokerSession.delegationHeader,
26691
+ graph.signal
26692
+ ).catch((error) => {
26693
+ graph.assertActive();
26694
+ throw error;
26695
+ });
26449
26696
  if (!activateResult.success) {
26450
26697
  throw Object.assign(
26451
26698
  new Error(`Failed to activate delegated session: ${describeHostFailure2(activateResult)}`),
@@ -26464,6 +26711,7 @@ var _TinyCloudNode = class _TinyCloudNode {
26464
26711
  siwe: prepared.siwe,
26465
26712
  signature: signature2
26466
26713
  };
26714
+ graph.assertActive();
26467
26715
  this.trackReceivedDelegation(delegation, jwk);
26468
26716
  this.installRuntimeGrantFromServiceSession(
26469
26717
  delegation,
@@ -26482,7 +26730,8 @@ var _TinyCloudNode = class _TinyCloudNode {
26482
26730
  targetHost,
26483
26731
  this.wasmBindings.invoke,
26484
26732
  this.wasmBindings.invokeAny,
26485
- this.config.telemetry
26733
+ this.config.telemetry,
26734
+ graph
26486
26735
  );
26487
26736
  }
26488
26737
  /**