decane-connect-kit 2.2.6 → 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.d.cts CHANGED
@@ -328,6 +328,13 @@ interface DecaneConnectConfig {
328
328
  rpName?: string;
329
329
  rpcUrls?: Record<string, string>;
330
330
  teeUrl?: string;
331
+ /**
332
+ * Persist the live session (handle + JWT) in sessionStorage so a page RELOAD
333
+ * silently resumes it — no repeat passkey/sign-in. Survives reloads, cleared
334
+ * on tab close. Default true. Set false to require a fresh unlock on every
335
+ * page load (stricter, but the reload friction that motivated this returns).
336
+ */
337
+ persistSession?: boolean;
331
338
  /** Offer to save a recovery-share file at signup. Default true. Never blocks onboarding. */
332
339
  offerRecoveryAtSignup?: boolean;
333
340
  /** Ask the user whether they want a recovery share and, if so, a password for it. */
package/dist/index.d.ts CHANGED
@@ -328,6 +328,13 @@ interface DecaneConnectConfig {
328
328
  rpName?: string;
329
329
  rpcUrls?: Record<string, string>;
330
330
  teeUrl?: string;
331
+ /**
332
+ * Persist the live session (handle + JWT) in sessionStorage so a page RELOAD
333
+ * silently resumes it — no repeat passkey/sign-in. Survives reloads, cleared
334
+ * on tab close. Default true. Set false to require a fresh unlock on every
335
+ * page load (stricter, but the reload friction that motivated this returns).
336
+ */
337
+ persistSession?: boolean;
331
338
  /** Offer to save a recovery-share file at signup. Default true. Never blocks onboarding. */
332
339
  offerRecoveryAtSignup?: boolean;
333
340
  /** Ask the user whether they want a recovery share and, if so, a password for it. */
package/dist/index.js CHANGED
@@ -2222,6 +2222,22 @@ var TeeClient = class {
2222
2222
  sessionExpiresIn() {
2223
2223
  return this.sessionExpiresInSec;
2224
2224
  }
2225
+ /** The opaque session handle, so a live session can be persisted and resumed after a reload. */
2226
+ getHandle() {
2227
+ return this.handle;
2228
+ }
2229
+ /**
2230
+ * Adopt a handle from a persisted session (page reload) instead of opening a
2231
+ * new one. The session was already attested when first opened, and the handle
2232
+ * is bound to that CVM session — a swapped CVM would simply not recognise it —
2233
+ * so re-attestation is skipped. If the handle has expired server-side, the
2234
+ * first sign() 401s and clears it, falling back to a normal reconnect.
2235
+ */
2236
+ adoptHandle(handle, assertionPerSignature) {
2237
+ this.handle = handle;
2238
+ this.assertionPerSignature = assertionPerSignature;
2239
+ this.attested = true;
2240
+ }
2225
2241
  async ensureAttested() {
2226
2242
  if (this.attested) return;
2227
2243
  const nonce = crypto.getRandomValues(new Uint8Array(16));
@@ -2402,9 +2418,33 @@ var SessionManager = class {
2402
2418
  this.solanaAddress = null;
2403
2419
  this.expiresAt = null;
2404
2420
  this.timeoutId = null;
2405
- if (typeof window !== "undefined") {
2406
- window.addEventListener("pagehide", () => this.end());
2407
- }
2421
+ }
2422
+ setExpiryTimer(effectiveMs) {
2423
+ if (this.timeoutId) clearTimeout(this.timeoutId);
2424
+ this.timeoutId = setTimeout(() => {
2425
+ this.end();
2426
+ this.events.emit("session-expired");
2427
+ }, Math.max(0, effectiveMs));
2428
+ }
2429
+ /** The live session handle, for persisting a resumable session across reloads. */
2430
+ getHandle() {
2431
+ return this.tee?.getHandle() ?? null;
2432
+ }
2433
+ /**
2434
+ * Resume a persisted session without re-authenticating: adopt the handle,
2435
+ * addresses, and expiry from storage. No key material or prompt involved — the
2436
+ * TEE still holds the shares behind this handle. If the handle turns out to be
2437
+ * dead (server restarted, TTL passed), the next sign() fails and the caller
2438
+ * reconnects; nothing here can sign on its own.
2439
+ */
2440
+ resume(handle, addresses, expiresAtMs) {
2441
+ this.tee = new TeeClient(this.teeUrl);
2442
+ this.tee.adoptHandle(handle, this.requireAssertionPerSignature);
2443
+ this.evmAddress = addresses.evm;
2444
+ this.solanaAddress = addresses.solana;
2445
+ this.expiresAt = expiresAtMs;
2446
+ this.setExpiryTimer(expiresAtMs - Date.now());
2447
+ this.events.emit("session-started");
2408
2448
  }
2409
2449
  /**
2410
2450
  * Opens the TEE session and resolves which addresses this wallet really has.
@@ -2965,7 +3005,7 @@ function formatBalanceDisplay(value) {
2965
3005
  // src/social/client.ts
2966
3006
  var DEFAULT_API_BASE = "https://backend.decane.app";
2967
3007
  var DEFAULT_TEE_URL = "https://4405742df08d19abc549a05ade2a7154d98bd87f-8443.dstack-pha-prod9.phala.network";
2968
- var DEFAULT_SESSION_MINUTES = 30;
3008
+ var DEFAULT_SESSION_MINUTES = 120;
2969
3009
  function makePersistKey(apiKey) {
2970
3010
  return `decane:social:${apiKey.slice(0, 20)}`;
2971
3011
  }
@@ -2989,6 +3029,29 @@ function clearPersisted(key) {
2989
3029
  } catch {
2990
3030
  }
2991
3031
  }
3032
+ function sessionPersistKey(apiKey) {
3033
+ return `decane:sess:${apiKey.slice(0, 20)}`;
3034
+ }
3035
+ function loadSession(key) {
3036
+ try {
3037
+ const raw = typeof sessionStorage !== "undefined" ? sessionStorage.getItem(key) : null;
3038
+ return raw ? JSON.parse(raw) : null;
3039
+ } catch {
3040
+ return null;
3041
+ }
3042
+ }
3043
+ function saveSession(key, s) {
3044
+ try {
3045
+ sessionStorage.setItem(key, JSON.stringify(s));
3046
+ } catch {
3047
+ }
3048
+ }
3049
+ function clearSessionPersist(key) {
3050
+ try {
3051
+ sessionStorage.removeItem(key);
3052
+ } catch {
3053
+ }
3054
+ }
2992
3055
  async function createDecaneConnect(config) {
2993
3056
  if (!config.appId) throw new Error("decane-connect-kit: appId is required");
2994
3057
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
@@ -3011,6 +3074,39 @@ async function createDecaneConnect(config) {
3011
3074
  );
3012
3075
  let auth = null;
3013
3076
  let persisted = loadPersisted(PERSIST_KEY);
3077
+ const persistSession = config.persistSession !== false;
3078
+ const SESSION_KEY = sessionPersistKey(config.apiKey);
3079
+ function persistLiveSession() {
3080
+ if (!persistSession) return;
3081
+ const handle = session.getHandle();
3082
+ const expiresAt = session.expiresAtTimestamp();
3083
+ if (!handle || !auth || !expiresAt) return;
3084
+ saveSession(SESSION_KEY, {
3085
+ handle,
3086
+ jwt: auth.jwt,
3087
+ userId: auth.userId,
3088
+ addresses: auth.addresses,
3089
+ expiresAt
3090
+ });
3091
+ }
3092
+ function dropLiveSession() {
3093
+ clearSessionPersist(SESSION_KEY);
3094
+ }
3095
+ if (persistSession) {
3096
+ const saved = loadSession(SESSION_KEY);
3097
+ if (saved?.handle && saved.expiresAt > Date.now() + 5e3) {
3098
+ try {
3099
+ session.resume(saved.handle, saved.addresses, saved.expiresAt);
3100
+ auth = { jwt: saved.jwt, userId: saved.userId, addresses: saved.addresses };
3101
+ persisted = { userId: saved.userId, addresses: saved.addresses };
3102
+ } catch {
3103
+ dropLiveSession();
3104
+ }
3105
+ } else if (saved) {
3106
+ dropLiveSession();
3107
+ }
3108
+ }
3109
+ events.on("session-expired", () => dropLiveSession());
3014
3110
  async function pinSetup() {
3015
3111
  if (!config.promptPin) throw new PromptPinNotConfiguredError();
3016
3112
  const pin = await config.promptPin();
@@ -3294,6 +3390,7 @@ async function createDecaneConnect(config) {
3294
3390
  persisted = { userId, addresses };
3295
3391
  savePersisted(PERSIST_KEY, persisted);
3296
3392
  events.emit("connected");
3393
+ persistLiveSession();
3297
3394
  return { addresses, isNewUser: false };
3298
3395
  }
3299
3396
  async function handleAuthResult(result) {
@@ -3309,6 +3406,7 @@ async function createDecaneConnect(config) {
3309
3406
  persisted = { userId: result.userId, addresses };
3310
3407
  savePersisted(PERSIST_KEY, persisted);
3311
3408
  events.emit("connected");
3409
+ persistLiveSession();
3312
3410
  return { addresses, isNewUser: result.isNewUser };
3313
3411
  }
3314
3412
  const client = {
@@ -3503,6 +3601,7 @@ async function createDecaneConnect(config) {
3503
3601
  auth = null;
3504
3602
  persisted = null;
3505
3603
  clearPersisted(PERSIST_KEY);
3604
+ dropLiveSession();
3506
3605
  events.emit("disconnected");
3507
3606
  },
3508
3607
  getAddresses() {
@@ -5546,7 +5645,15 @@ function DecaneKit({ config, children }) {
5546
5645
  sdk.on("wallet-unlocking", () => setSocialAuth((p) => ({ ...p, phase: "unlocking" })));
5547
5646
  sdk.on("connected", () => setSocialAuth((p) => ({ ...p, phase: null })));
5548
5647
  const saved = sdk.getAddresses();
5549
- if (saved) {
5648
+ if (sdk.isUnlocked()) {
5649
+ setSocialAuth((p) => ({
5650
+ ...p,
5651
+ addresses: saved,
5652
+ isUnlocked: true,
5653
+ needsReconnect: false,
5654
+ sessionExpiresAt: sdk.sessionExpiresAt()
5655
+ }));
5656
+ } else if (saved) {
5550
5657
  setSocialAuth((p) => ({ ...p, addresses: saved, isUnlocked: false, needsReconnect: true }));
5551
5658
  }
5552
5659
  sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {