decane-connect-kit 2.23.0 → 2.24.1

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
@@ -37,6 +37,7 @@ __export(errors_exports, {
37
37
  CallSequenceError: () => CallSequenceError,
38
38
  DecaneConnectError: () => DecaneConnectError,
39
39
  ExportNotAuthorizedError: () => ExportNotAuthorizedError,
40
+ InvalidAccessTokenError: () => InvalidAccessTokenError,
40
41
  InvalidApiKeyError: () => InvalidApiKeyError,
41
42
  InvalidPinError: () => InvalidPinError,
42
43
  InvalidRecoveryFileError: () => InvalidRecoveryFileError,
@@ -59,7 +60,7 @@ __export(errors_exports, {
59
60
  WrongPasswordError: () => WrongPasswordError,
60
61
  WrongUnlockPasswordError: () => WrongUnlockPasswordError
61
62
  });
62
- var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
63
+ var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, InvalidAccessTokenError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
63
64
  var init_errors = __esm({
64
65
  "src/social/errors.ts"() {
65
66
  "use strict";
@@ -164,6 +165,11 @@ var init_errors = __esm({
164
165
  super("WRONG_UNLOCK_PASSWORD", "That password did not unlock this device");
165
166
  }
166
167
  };
168
+ InvalidAccessTokenError = class extends DecaneConnectError {
169
+ constructor(reason) {
170
+ super("INVALID_ACCESS_TOKEN", `That access token cannot open a session: ${reason}`);
171
+ }
172
+ };
167
173
  UnlockTokenRejectedError = class extends DecaneConnectError {
168
174
  constructor() {
169
175
  super("UNLOCK_TOKEN_REJECTED", "This device's unlock token is no longer valid \u2014 sign in normally to re-enable password unlock");
@@ -221,6 +227,7 @@ __export(index_exports, {
221
227
  DecaneMark: () => DecaneMark,
222
228
  DecaneWordmark: () => DecaneWordmark,
223
229
  ExportNotAuthorizedError: () => ExportNotAuthorizedError,
230
+ InvalidAccessTokenError: () => InvalidAccessTokenError,
224
231
  InvalidApiKeyError: () => InvalidApiKeyError,
225
232
  InvalidPinError: () => InvalidPinError,
226
233
  InvalidRecoveryFileError: () => InvalidRecoveryFileError,
@@ -2355,6 +2362,20 @@ function b64ToBytes(b644) {
2355
2362
  return Uint8Array.from(atob(b644), (c) => c.charCodeAt(0));
2356
2363
  }
2357
2364
 
2365
+ // src/social/accessToken.ts
2366
+ function decodeAccessToken(token) {
2367
+ try {
2368
+ const payload = token.split(".")[1];
2369
+ if (!payload) return null;
2370
+ const json = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
2371
+ if (typeof json.uid !== "string" || json.uid === "") return null;
2372
+ if (typeof json.exp !== "number" || !Number.isFinite(json.exp)) return null;
2373
+ return { userId: json.uid, expiresAt: json.exp * 1e3 };
2374
+ } catch {
2375
+ return null;
2376
+ }
2377
+ }
2378
+
2358
2379
  // src/social/storage.ts
2359
2380
  var import_idb = require("idb");
2360
2381
  var DB_NAME = "decane-connect-kit";
@@ -3211,6 +3232,8 @@ function loginWithKingsChatPopup(opts) {
3211
3232
 
3212
3233
  // src/social/signers/evm.ts
3213
3234
  var import_ethers2 = require("ethers");
3235
+ init_errors();
3236
+ var DECODE = (bytes) => new TextDecoder().decode(bytes);
3214
3237
  async function signEvmTransaction(tee, chain, tx, jwt) {
3215
3238
  const resolved = await (0, import_ethers2.resolveProperties)(tx);
3216
3239
  const unsigned = import_ethers2.Transaction.from(resolved).unsignedSerialized;
@@ -3219,7 +3242,44 @@ async function signEvmTransaction(tee, chain, tx, jwt) {
3219
3242
  return tee.sign({ chain, kind: "transaction", payload, txHash }, jwt);
3220
3243
  }
3221
3244
  async function signEvmMessage(tee, chain, message, jwt) {
3222
- return tee.sign({ chain, kind: "message", payload: message }, jwt);
3245
+ return tee.sign({ chain, kind: "message", payload: messageAsString(message) }, jwt);
3246
+ }
3247
+ function messageAsString(message) {
3248
+ if (typeof message === "string") return message;
3249
+ if (!isValidUtf8(message)) {
3250
+ throw new CallSequenceError(
3251
+ "signMessage: the message bytes are not valid UTF-8. The signer hashes text; pass a string, or UTF-8 bytes."
3252
+ );
3253
+ }
3254
+ return DECODE(message);
3255
+ }
3256
+ function isValidUtf8(b) {
3257
+ let i = 0;
3258
+ while (i < b.length) {
3259
+ const c = b[i];
3260
+ let n = 0;
3261
+ if (c < 128) n = 0;
3262
+ else if (c >= 194 && c <= 223) n = 1;
3263
+ else if (c >= 224 && c <= 239) n = 2;
3264
+ else if (c >= 240 && c <= 244) n = 3;
3265
+ else return false;
3266
+ for (let k = 1; k <= n; k++) {
3267
+ const t = b[i + k];
3268
+ if (t === void 0 || (t & 192) !== 128) return false;
3269
+ }
3270
+ if (n === 2) {
3271
+ const c1 = b[i + 1];
3272
+ if (c === 224 && c1 < 160) return false;
3273
+ if (c === 237 && c1 >= 160) return false;
3274
+ }
3275
+ if (n === 3) {
3276
+ const c1 = b[i + 1];
3277
+ if (c === 240 && c1 < 144) return false;
3278
+ if (c === 244 && c1 >= 144) return false;
3279
+ }
3280
+ i += n + 1;
3281
+ }
3282
+ return true;
3223
3283
  }
3224
3284
  async function signEvmAuthorization(tee, chainId, address, nonce, jwt) {
3225
3285
  const serialized = await tee.sign(
@@ -3749,8 +3809,10 @@ async function createDecaneConnect(config) {
3749
3809
  const durationMs = (config.sessionDurationMinutes ?? DEFAULT_SESSION_MINUTES) * 6e4;
3750
3810
  const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
3751
3811
  const rpName = config.rpName ?? "Decane Connect";
3752
- const PERSIST_KEY = makePersistKey(config.apiKey);
3753
- const LAST_USER_KEY = makeLastUserKey(config.apiKey);
3812
+ const PERSIST_KEY = makePersistKey(config.appId);
3813
+ const LAST_USER_KEY = makeLastUserKey(config.appId);
3814
+ const LEGACY_PERSIST_KEY = makePersistKey(config.apiKey);
3815
+ const LEGACY_LAST_USER_KEY = makeLastUserKey(config.apiKey);
3754
3816
  const teeUrl = config.teeUrl ?? DEFAULT_TEE_URL;
3755
3817
  const events = new EventEmitter();
3756
3818
  const api = new ApiClient(baseUrl, config.apiKey, config.appId);
@@ -3763,7 +3825,14 @@ async function createDecaneConnect(config) {
3763
3825
  );
3764
3826
  let auth = null;
3765
3827
  let persisted = loadPersisted(PERSIST_KEY);
3766
- let lastUserId = loadLastUser(LAST_USER_KEY);
3828
+ if (!persisted && LEGACY_PERSIST_KEY !== PERSIST_KEY) {
3829
+ persisted = loadPersisted(LEGACY_PERSIST_KEY);
3830
+ if (persisted) {
3831
+ savePersisted(PERSIST_KEY, persisted);
3832
+ clearPersisted(LEGACY_PERSIST_KEY);
3833
+ }
3834
+ }
3835
+ let lastUserId = loadLastUser(LAST_USER_KEY) ?? (LEGACY_LAST_USER_KEY !== LAST_USER_KEY ? loadLastUser(LEGACY_LAST_USER_KEY) : null);
3767
3836
  let ethProvider = null;
3768
3837
  const persistSession = config.persistSession !== false;
3769
3838
  const deferDeviceProtection = config.deferDeviceProtection === true;
@@ -3826,12 +3895,13 @@ async function createDecaneConnect(config) {
3826
3895
  })
3827
3896
  ]).finally(() => clearTimeout(timer));
3828
3897
  }
3829
- const SESSION_KEY = sessionPersistKey(config.apiKey);
3898
+ const SESSION_KEY = sessionPersistKey(config.appId);
3899
+ const LEGACY_SESSION_KEY = sessionPersistKey(config.apiKey);
3830
3900
  function persistLiveSession() {
3831
- if (!persistSession) return;
3901
+ if (!persistSession || !auth) return;
3832
3902
  const handle = session.getHandle();
3833
- const expiresAt = session.expiresAtTimestamp();
3834
- if (!handle || !auth || !expiresAt) return;
3903
+ const expiresAt = handle ? session.expiresAtTimestamp() : decodeAccessToken(auth.jwt)?.expiresAt ?? null;
3904
+ if (!expiresAt) return;
3835
3905
  saveSession(SESSION_KEY, {
3836
3906
  handle,
3837
3907
  jwt: auth.jwt,
@@ -3843,11 +3913,23 @@ async function createDecaneConnect(config) {
3843
3913
  function dropLiveSession() {
3844
3914
  clearSessionPersist(SESSION_KEY);
3845
3915
  }
3916
+ let accessTokenExpiry = null;
3917
+ function scheduleAccessTokenExpiry(jwt, expiresAt) {
3918
+ if (accessTokenExpiry) clearTimeout(accessTokenExpiry);
3919
+ accessTokenExpiry = setTimeout(() => {
3920
+ accessTokenExpiry = null;
3921
+ if (auth?.jwt !== jwt || session.isActive()) return;
3922
+ auth = null;
3923
+ dropLiveSession();
3924
+ events.emit("session-expired");
3925
+ }, Math.max(0, expiresAt - Date.now()));
3926
+ }
3846
3927
  if (persistSession) {
3847
- const saved = loadSession(SESSION_KEY);
3848
- if (saved?.handle && saved.expiresAt > Date.now() + 5e3) {
3928
+ const saved = loadSession(SESSION_KEY) ?? (LEGACY_SESSION_KEY !== SESSION_KEY ? loadSession(LEGACY_SESSION_KEY) : null);
3929
+ if (saved && saved.expiresAt > Date.now() + 5e3) {
3849
3930
  try {
3850
- session.resume(saved.handle, saved.addresses, saved.expiresAt);
3931
+ if (saved.handle) session.resume(saved.handle, saved.addresses, saved.expiresAt);
3932
+ else scheduleAccessTokenExpiry(saved.jwt, saved.expiresAt);
3851
3933
  auth = { jwt: saved.jwt, userId: saved.userId, addresses: saved.addresses };
3852
3934
  persisted = { userId: saved.userId, addresses: saved.addresses };
3853
3935
  } catch {
@@ -3856,6 +3938,7 @@ async function createDecaneConnect(config) {
3856
3938
  } else if (saved) {
3857
3939
  dropLiveSession();
3858
3940
  }
3941
+ if (LEGACY_SESSION_KEY !== SESSION_KEY) clearSessionPersist(LEGACY_SESSION_KEY);
3859
3942
  }
3860
3943
  events.on("session-expired", () => dropLiveSession());
3861
3944
  async function passwordSetup() {
@@ -4573,6 +4656,27 @@ async function createDecaneConnect(config) {
4573
4656
  const result = await api.authExternal(token, opts?.providerId);
4574
4657
  return handleAuthResult(result);
4575
4658
  },
4659
+ async connectWithAccessToken(token) {
4660
+ const claims = decodeAccessToken(token);
4661
+ if (!claims) throw new InvalidAccessTokenError("not a Decane access token");
4662
+ if (claims.expiresAt <= Date.now() + 5e3) throw new InvalidAccessTokenError("it has expired");
4663
+ const raw = await api.getAddresses(token);
4664
+ if (!raw.evm || !raw.solana) throw new InvalidAccessTokenError("this account holds no wallet yet");
4665
+ const addresses = { evm: raw.evm, solana: raw.solana, tron: raw.tron ?? evmToTronAddress2(raw.evm) };
4666
+ if (session.isActive()) session.end();
4667
+ auth = { jwt: token, userId: claims.userId, addresses };
4668
+ persisted = { userId: claims.userId, addresses };
4669
+ savePersisted(PERSIST_KEY, persisted);
4670
+ events.emit("connected");
4671
+ persistLiveSession();
4672
+ scheduleAccessTokenExpiry(token, claims.expiresAt);
4673
+ const stored = await storage.getShare(claims.userId);
4674
+ return {
4675
+ addresses,
4676
+ isNewUser: false,
4677
+ ...stored?.profile ? { profile: stored.profile } : {}
4678
+ };
4679
+ },
4576
4680
  async connectWithEmail(email) {
4577
4681
  await startEmailAuth(api, email);
4578
4682
  return { codeRequired: true };
@@ -4819,8 +4923,7 @@ async function createDecaneConnect(config) {
4819
4923
  return bufferToB64u(sig);
4820
4924
  }
4821
4925
  if (req.chain.startsWith("tron:")) {
4822
- const msg = typeof req.message === "string" ? req.message : new TextDecoder().decode(req.message);
4823
- return tee.sign({ chain: req.chain, kind: "message", payload: msg }, auth?.jwt);
4926
+ return tee.sign({ chain: req.chain, kind: "message", payload: messageAsString(req.message) }, auth?.jwt);
4824
4927
  }
4825
4928
  throw new CallSequenceError(`Unsupported chain: ${req.chain}`);
4826
4929
  },
@@ -7558,6 +7661,18 @@ function DecaneKit({ config, children }) {
7558
7661
  throw e;
7559
7662
  }
7560
7663
  }, [clearPhase]);
7664
+ const connectWithAccessToken = (0, import_react5.useCallback)(async (token) => {
7665
+ const sdk = socialRef.current;
7666
+ if (!sdk) throw new Error("Social sign-in not configured");
7667
+ try {
7668
+ const result = await sdk.connectWithAccessToken(token);
7669
+ applyResult(result);
7670
+ setSelectorOpen(false);
7671
+ } catch (e) {
7672
+ clearPhase();
7673
+ throw e;
7674
+ }
7675
+ }, [clearPhase]);
7561
7676
  const customAuthConfig = config?.social?.customAuth;
7562
7677
  const connectWithCustomAuth = (0, import_react5.useCallback)(async () => {
7563
7678
  if (!customAuthConfig) throw new Error("Custom auth not configured");
@@ -7750,6 +7865,7 @@ function DecaneKit({ config, children }) {
7750
7865
  connectWithPhone,
7751
7866
  verifyPhoneCode: verifyPhoneCode2,
7752
7867
  connectWithToken,
7868
+ connectWithAccessToken,
7753
7869
  customAuthLabel: customAuthConfig?.label,
7754
7870
  connectWithCustomAuth: customAuthConfig ? connectWithCustomAuth : void 0,
7755
7871
  socialUnlock,
@@ -7865,6 +7981,7 @@ function useSocialAuth() {
7865
7981
  connectWithPhone,
7866
7982
  verifyPhoneCode: verifyPhoneCode2,
7867
7983
  connectWithToken,
7984
+ connectWithAccessToken,
7868
7985
  customAuthLabel,
7869
7986
  connectWithCustomAuth,
7870
7987
  signInWithPasskey: ctxSignInWithPasskey,
@@ -7973,6 +8090,18 @@ function useSocialAuth() {
7973
8090
  setTokenLoading(false);
7974
8091
  }
7975
8092
  }, [connectWithToken]);
8093
+ const signInWithAccessToken = (0, import_react6.useCallback)(async (token) => {
8094
+ setTokenLoading(true);
8095
+ setError(null);
8096
+ try {
8097
+ await connectWithAccessToken(token);
8098
+ } catch (e) {
8099
+ setError(e.message);
8100
+ throw e;
8101
+ } finally {
8102
+ setTokenLoading(false);
8103
+ }
8104
+ }, [connectWithAccessToken]);
7976
8105
  const signInWithCustomAuth = (0, import_react6.useCallback)(async () => {
7977
8106
  if (!connectWithCustomAuth) throw new Error("Custom auth not configured");
7978
8107
  setCustomLoading(true);
@@ -8055,6 +8184,7 @@ function useSocialAuth() {
8055
8184
  sendPhoneCode,
8056
8185
  confirmPhoneCode,
8057
8186
  signInWithToken,
8187
+ signInWithAccessToken,
8058
8188
  signInWithCustomAuth: connectWithCustomAuth ? signInWithCustomAuth : void 0,
8059
8189
  signInWithPasskey,
8060
8190
  unlock,
@@ -8604,6 +8734,7 @@ function useSignTypedData() {
8604
8734
  DecaneMark,
8605
8735
  DecaneWordmark,
8606
8736
  ExportNotAuthorizedError,
8737
+ InvalidAccessTokenError,
8607
8738
  InvalidApiKeyError,
8608
8739
  InvalidPinError,
8609
8740
  InvalidRecoveryFileError,