decane-connect-kit 2.2.7 → 2.2.8

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
@@ -2293,6 +2293,22 @@ var TeeClient = class {
2293
2293
  sessionExpiresIn() {
2294
2294
  return this.sessionExpiresInSec;
2295
2295
  }
2296
+ /** The opaque session handle, so a live session can be persisted and resumed after a reload. */
2297
+ getHandle() {
2298
+ return this.handle;
2299
+ }
2300
+ /**
2301
+ * Adopt a handle from a persisted session (page reload) instead of opening a
2302
+ * new one. The session was already attested when first opened, and the handle
2303
+ * is bound to that CVM session — a swapped CVM would simply not recognise it —
2304
+ * so re-attestation is skipped. If the handle has expired server-side, the
2305
+ * first sign() 401s and clears it, falling back to a normal reconnect.
2306
+ */
2307
+ adoptHandle(handle, assertionPerSignature) {
2308
+ this.handle = handle;
2309
+ this.assertionPerSignature = assertionPerSignature;
2310
+ this.attested = true;
2311
+ }
2296
2312
  async ensureAttested() {
2297
2313
  if (this.attested) return;
2298
2314
  const nonce = crypto.getRandomValues(new Uint8Array(16));
@@ -2473,9 +2489,33 @@ var SessionManager = class {
2473
2489
  this.solanaAddress = null;
2474
2490
  this.expiresAt = null;
2475
2491
  this.timeoutId = null;
2476
- if (typeof window !== "undefined") {
2477
- window.addEventListener("pagehide", () => this.end());
2478
- }
2492
+ }
2493
+ setExpiryTimer(effectiveMs) {
2494
+ if (this.timeoutId) clearTimeout(this.timeoutId);
2495
+ this.timeoutId = setTimeout(() => {
2496
+ this.end();
2497
+ this.events.emit("session-expired");
2498
+ }, Math.max(0, effectiveMs));
2499
+ }
2500
+ /** The live session handle, for persisting a resumable session across reloads. */
2501
+ getHandle() {
2502
+ return this.tee?.getHandle() ?? null;
2503
+ }
2504
+ /**
2505
+ * Resume a persisted session without re-authenticating: adopt the handle,
2506
+ * addresses, and expiry from storage. No key material or prompt involved — the
2507
+ * TEE still holds the shares behind this handle. If the handle turns out to be
2508
+ * dead (server restarted, TTL passed), the next sign() fails and the caller
2509
+ * reconnects; nothing here can sign on its own.
2510
+ */
2511
+ resume(handle, addresses, expiresAtMs) {
2512
+ this.tee = new TeeClient(this.teeUrl);
2513
+ this.tee.adoptHandle(handle, this.requireAssertionPerSignature);
2514
+ this.evmAddress = addresses.evm;
2515
+ this.solanaAddress = addresses.solana;
2516
+ this.expiresAt = expiresAtMs;
2517
+ this.setExpiryTimer(expiresAtMs - Date.now());
2518
+ this.events.emit("session-started");
2479
2519
  }
2480
2520
  /**
2481
2521
  * Opens the TEE session and resolves which addresses this wallet really has.
@@ -3060,6 +3100,29 @@ function clearPersisted(key) {
3060
3100
  } catch {
3061
3101
  }
3062
3102
  }
3103
+ function sessionPersistKey(apiKey) {
3104
+ return `decane:sess:${apiKey.slice(0, 20)}`;
3105
+ }
3106
+ function loadSession(key) {
3107
+ try {
3108
+ const raw = typeof sessionStorage !== "undefined" ? sessionStorage.getItem(key) : null;
3109
+ return raw ? JSON.parse(raw) : null;
3110
+ } catch {
3111
+ return null;
3112
+ }
3113
+ }
3114
+ function saveSession(key, s) {
3115
+ try {
3116
+ sessionStorage.setItem(key, JSON.stringify(s));
3117
+ } catch {
3118
+ }
3119
+ }
3120
+ function clearSessionPersist(key) {
3121
+ try {
3122
+ sessionStorage.removeItem(key);
3123
+ } catch {
3124
+ }
3125
+ }
3063
3126
  async function createDecaneConnect(config) {
3064
3127
  if (!config.appId) throw new Error("decane-connect-kit: appId is required");
3065
3128
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
@@ -3082,6 +3145,39 @@ async function createDecaneConnect(config) {
3082
3145
  );
3083
3146
  let auth = null;
3084
3147
  let persisted = loadPersisted(PERSIST_KEY);
3148
+ const persistSession = config.persistSession !== false;
3149
+ const SESSION_KEY = sessionPersistKey(config.apiKey);
3150
+ function persistLiveSession() {
3151
+ if (!persistSession) return;
3152
+ const handle = session.getHandle();
3153
+ const expiresAt = session.expiresAtTimestamp();
3154
+ if (!handle || !auth || !expiresAt) return;
3155
+ saveSession(SESSION_KEY, {
3156
+ handle,
3157
+ jwt: auth.jwt,
3158
+ userId: auth.userId,
3159
+ addresses: auth.addresses,
3160
+ expiresAt
3161
+ });
3162
+ }
3163
+ function dropLiveSession() {
3164
+ clearSessionPersist(SESSION_KEY);
3165
+ }
3166
+ if (persistSession) {
3167
+ const saved = loadSession(SESSION_KEY);
3168
+ if (saved?.handle && saved.expiresAt > Date.now() + 5e3) {
3169
+ try {
3170
+ session.resume(saved.handle, saved.addresses, saved.expiresAt);
3171
+ auth = { jwt: saved.jwt, userId: saved.userId, addresses: saved.addresses };
3172
+ persisted = { userId: saved.userId, addresses: saved.addresses };
3173
+ } catch {
3174
+ dropLiveSession();
3175
+ }
3176
+ } else if (saved) {
3177
+ dropLiveSession();
3178
+ }
3179
+ }
3180
+ events.on("session-expired", () => dropLiveSession());
3085
3181
  async function pinSetup() {
3086
3182
  if (!config.promptPin) throw new PromptPinNotConfiguredError();
3087
3183
  const pin = await config.promptPin();
@@ -3365,6 +3461,7 @@ async function createDecaneConnect(config) {
3365
3461
  persisted = { userId, addresses };
3366
3462
  savePersisted(PERSIST_KEY, persisted);
3367
3463
  events.emit("connected");
3464
+ persistLiveSession();
3368
3465
  return { addresses, isNewUser: false };
3369
3466
  }
3370
3467
  async function handleAuthResult(result) {
@@ -3380,6 +3477,7 @@ async function createDecaneConnect(config) {
3380
3477
  persisted = { userId: result.userId, addresses };
3381
3478
  savePersisted(PERSIST_KEY, persisted);
3382
3479
  events.emit("connected");
3480
+ persistLiveSession();
3383
3481
  return { addresses, isNewUser: result.isNewUser };
3384
3482
  }
3385
3483
  const client = {
@@ -3574,6 +3672,7 @@ async function createDecaneConnect(config) {
3574
3672
  auth = null;
3575
3673
  persisted = null;
3576
3674
  clearPersisted(PERSIST_KEY);
3675
+ dropLiveSession();
3577
3676
  events.emit("disconnected");
3578
3677
  },
3579
3678
  getAddresses() {
@@ -5610,7 +5709,15 @@ function DecaneKit({ config, children }) {
5610
5709
  sdk.on("wallet-unlocking", () => setSocialAuth((p) => ({ ...p, phase: "unlocking" })));
5611
5710
  sdk.on("connected", () => setSocialAuth((p) => ({ ...p, phase: null })));
5612
5711
  const saved = sdk.getAddresses();
5613
- if (saved) {
5712
+ if (sdk.isUnlocked()) {
5713
+ setSocialAuth((p) => ({
5714
+ ...p,
5715
+ addresses: saved,
5716
+ isUnlocked: true,
5717
+ needsReconnect: false,
5718
+ sessionExpiresAt: sdk.sessionExpiresAt()
5719
+ }));
5720
+ } else if (saved) {
5614
5721
  setSocialAuth((p) => ({ ...p, addresses: saved, isUnlocked: false, needsReconnect: true }));
5615
5722
  }
5616
5723
  sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {