decane-connect-kit 2.9.1 → 2.11.0

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
@@ -1126,9 +1126,14 @@ interface SocialAuthState {
1126
1126
  * Who the provider said the user is — `{ name?, email?, picture? }`.
1127
1127
  *
1128
1128
  * `null` until a sign-in returns one, and stays null for email sign-in and
1129
- * for providers the user withheld a profile from. It is not persisted across
1130
- * reloads: the backend stores no profile, so a restored session has an
1131
- * address but no name. Copy it somewhere durable if you need it to survive.
1129
+ * for providers the user withheld a profile from.
1130
+ *
1131
+ * The backend stores no profile, so this cannot be re-fetched — but whatever
1132
+ * a sign-in does reveal is kept beside this device's share, and a passkey
1133
+ * login hands it back. A passkey sign-in therefore carries the same name and
1134
+ * email the original Google sign-in did. A session merely restored from a
1135
+ * reload still has an address and no name, so copy it somewhere durable if
1136
+ * you need it in that state.
1132
1137
  */
1133
1138
  profile: AuthProfile | null;
1134
1139
  isUnlocked: boolean;
@@ -1222,6 +1227,7 @@ declare function useSocialAuth(): {
1222
1227
  } | null;
1223
1228
  sessionExpiresAt: number | null;
1224
1229
  canUsePasskey: boolean;
1230
+ profile: AuthProfile | null;
1225
1231
  phase: "creating" | "unlocking" | null;
1226
1232
  isCreatingWallet: boolean;
1227
1233
  needsReconnect: boolean;
package/dist/index.d.ts CHANGED
@@ -1126,9 +1126,14 @@ interface SocialAuthState {
1126
1126
  * Who the provider said the user is — `{ name?, email?, picture? }`.
1127
1127
  *
1128
1128
  * `null` until a sign-in returns one, and stays null for email sign-in and
1129
- * for providers the user withheld a profile from. It is not persisted across
1130
- * reloads: the backend stores no profile, so a restored session has an
1131
- * address but no name. Copy it somewhere durable if you need it to survive.
1129
+ * for providers the user withheld a profile from.
1130
+ *
1131
+ * The backend stores no profile, so this cannot be re-fetched — but whatever
1132
+ * a sign-in does reveal is kept beside this device's share, and a passkey
1133
+ * login hands it back. A passkey sign-in therefore carries the same name and
1134
+ * email the original Google sign-in did. A session merely restored from a
1135
+ * reload still has an address and no name, so copy it somewhere durable if
1136
+ * you need it in that state.
1132
1137
  */
1133
1138
  profile: AuthProfile | null;
1134
1139
  isUnlocked: boolean;
@@ -1222,6 +1227,7 @@ declare function useSocialAuth(): {
1222
1227
  } | null;
1223
1228
  sessionExpiresAt: number | null;
1224
1229
  canUsePasskey: boolean;
1230
+ profile: AuthProfile | null;
1225
1231
  phase: "creating" | "unlocking" | null;
1226
1232
  isCreatingWallet: boolean;
1227
1233
  needsReconnect: boolean;
package/dist/index.js CHANGED
@@ -2195,6 +2195,15 @@ var Storage = class {
2195
2195
  async getShare(userId) {
2196
2196
  return (await this._db).get(STORE, userId) ?? null;
2197
2197
  }
2198
+ /**
2199
+ * Every share this browser holds. Needed because signing out clears the
2200
+ * remembered identity while deliberately leaving the share behind, so the
2201
+ * userId that keys it is no longer known and the share can only be found by
2202
+ * enumeration. See passkeyDeviceCandidate().
2203
+ */
2204
+ async listShares() {
2205
+ return (await this._db).getAll(STORE);
2206
+ }
2198
2207
  async putShare(share) {
2199
2208
  await (await this._db).put(STORE, share);
2200
2209
  }
@@ -3948,7 +3957,25 @@ async function createDecaneConnect(config) {
3948
3957
  }
3949
3958
  async function passkeyLoginCandidate() {
3950
3959
  if (!persisted?.userId || !isPasskeySupported()) return null;
3951
- const stored = await storage.getShare(persisted.userId);
3960
+ return usablePasskeyShare(await storage.getShare(persisted.userId));
3961
+ }
3962
+ async function passkeyDeviceCandidate() {
3963
+ if (!isPasskeySupported()) return null;
3964
+ if (persisted?.userId) return passkeyLoginCandidate();
3965
+ const candidates = (await storage.listShares()).map(usablePasskeyShare).filter(Boolean);
3966
+ return candidates.length === 1 ? candidates[0] : null;
3967
+ }
3968
+ async function rememberProfile(userId, profile) {
3969
+ const incoming = Object.fromEntries(Object.entries(profile).filter(([, v]) => v));
3970
+ if (!Object.keys(incoming).length) return;
3971
+ try {
3972
+ const stored = await storage.getShare(userId);
3973
+ if (!stored) return;
3974
+ await storage.putShare({ ...stored, profile: { ...stored.profile, ...incoming } });
3975
+ } catch {
3976
+ }
3977
+ }
3978
+ function usablePasskeyShare(stored) {
3952
3979
  if (!stored || stored.unlockMethod !== "passkey") return null;
3953
3980
  if (!stored.credentialId || !stored.prfSalt) return null;
3954
3981
  return stored;
@@ -3985,7 +4012,11 @@ async function createDecaneConnect(config) {
3985
4012
  savePersisted(PERSIST_KEY, persisted);
3986
4013
  events.emit("connected");
3987
4014
  persistLiveSession();
3988
- return { addresses, isNewUser: false };
4015
+ return {
4016
+ addresses,
4017
+ isNewUser: false,
4018
+ ...stored.profile ? { profile: stored.profile } : {}
4019
+ };
3989
4020
  }
3990
4021
  async function handleAuthResult(result) {
3991
4022
  let addresses;
@@ -4001,6 +4032,7 @@ async function createDecaneConnect(config) {
4001
4032
  savePersisted(PERSIST_KEY, persisted);
4002
4033
  events.emit("connected");
4003
4034
  persistLiveSession();
4035
+ if (result.profile) await rememberProfile(result.userId, result.profile);
4004
4036
  return {
4005
4037
  addresses,
4006
4038
  isNewUser: result.isNewUser,
@@ -4107,10 +4139,10 @@ async function createDecaneConnect(config) {
4107
4139
  return handleAuthResult(result);
4108
4140
  },
4109
4141
  async canSignInWithPasskey() {
4110
- return await passkeyLoginCandidate() !== null;
4142
+ return await passkeyDeviceCandidate() !== null;
4111
4143
  },
4112
4144
  async signInWithPasskey() {
4113
- const candidate = await passkeyLoginCandidate();
4145
+ const candidate = await passkeyDeviceCandidate();
4114
4146
  if (!candidate) throw new NewDeviceError();
4115
4147
  return passkeyLogin(candidate);
4116
4148
  },
@@ -6694,7 +6726,11 @@ function DecaneKit({ config, children }) {
6694
6726
  "session-expired",
6695
6727
  () => setSocialAuth((p) => ({ ...p, isUnlocked: false, sessionExpiresAt: null }))
6696
6728
  );
6697
- sdk.on("disconnected", () => setSocialAuth(DEFAULT_SOCIAL));
6729
+ sdk.on("disconnected", () => {
6730
+ setSocialAuth(DEFAULT_SOCIAL);
6731
+ sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
6732
+ });
6733
+ });
6698
6734
  sdk.on("wallet-creating", () => setSocialAuth((p) => ({ ...p, phase: "creating" })));
6699
6735
  sdk.on("wallet-unlocking", () => setSocialAuth((p) => ({ ...p, phase: "unlocking" })));
6700
6736
  sdk.on("connected", () => setSocialAuth((p) => ({ ...p, phase: null })));
@@ -7210,6 +7246,11 @@ function useSocialAuth() {
7210
7246
  sessionExpiresAt: socialAuth.sessionExpiresAt,
7211
7247
  // True when this device can skip the full sign-in and resume with a passkey.
7212
7248
  canUsePasskey: socialAuth.canUsePasskey,
7249
+ // Who the provider said the user is — { name?, email?, picture? }, or null.
7250
+ // A passkey sign-in returns the profile recorded on this device when the
7251
+ // user first signed in, so it carries the same name and email a Google
7252
+ // sign-in does rather than coming back anonymous.
7253
+ profile: socialAuth.profile,
7213
7254
  // Long-running work in progress, for driving your own status UI.
7214
7255
  phase: socialAuth.phase,
7215
7256
  isCreatingWallet: socialAuth.phase === "creating",