@proveanything/smartlinks-auth-ui 0.5.6 → 0.5.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.js CHANGED
@@ -11513,15 +11513,18 @@ class AuthAPI {
11513
11513
  return smartlinks__namespace.authKit.verifyMagicLink(this.clientId, token);
11514
11514
  }
11515
11515
  // ============= WhatsApp =============
11516
- async sendWhatsApp(redirectUrl, phoneNumber, reply, prefillMessage) {
11516
+ async sendWhatsApp(redirectUrl, phoneNumber, reply, prefillMessage, contactData) {
11517
11517
  // phoneNumber is optional — backend extracts it from the inbound WhatsApp webhook.
11518
11518
  // `reply`, when provided, is sent back to the user via WhatsApp on successful verification.
11519
11519
  // `prefillMessage` customizes the message body pre-filled in the wa.me link.
11520
+ // `contactData` is upserted onto the contact when the backend creates/links it,
11521
+ // so the returned session.user is fully formed (name, email, custom fields, etc.).
11520
11522
  return smartlinks__namespace.authKit.sendWhatsApp(this.clientId, {
11521
11523
  phoneNumber: phoneNumber ?? '',
11522
11524
  redirectUrl,
11523
11525
  ...(reply ? { reply } : {}),
11524
11526
  ...(prefillMessage ? { prefillMessage } : {}),
11527
+ ...(contactData ? { contactData } : {}),
11525
11528
  });
11526
11529
  }
11527
11530
  async exchangeWhatsAppSession(token, sessionKey) {
@@ -12713,6 +12716,44 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12713
12716
  }
12714
12717
  }
12715
12718
  }, [token, user, isVerified, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
12719
+ // Apply a session refresh from SDK calls that rotate the bearer token
12720
+ // (e.g. authKit.updateProfile returns { token, ...profile }). Without this
12721
+ // the persisted token still decodes to the OLD claims, so a page refresh
12722
+ // would resurrect stale displayName / email / phone values.
12723
+ const applySessionRefresh = React.useCallback(async (payload) => {
12724
+ const nextToken = payload.token;
12725
+ const nextUser = user
12726
+ ? { ...user, ...(payload.user || {}) }
12727
+ : (payload.user && payload.user.uid ? payload.user : null);
12728
+ const nextAccountData = payload.accountData ?? accountData;
12729
+ if (nextToken && !proxyMode) {
12730
+ // Match the 7-day lifetime used by login()/refreshToken() — backend mints fresh JWTs with the same TTL.
12731
+ await tokenStorage.saveToken(nextToken, Date.now() + 7 * 24 * 60 * 60 * 1000);
12732
+ if (nextUser)
12733
+ await tokenStorage.saveUser(nextUser);
12734
+ if (payload.accountData)
12735
+ await tokenStorage.saveAccountData(payload.accountData);
12736
+ }
12737
+ if (nextToken)
12738
+ setToken(nextToken);
12739
+ if (nextUser)
12740
+ setUser(nextUser);
12741
+ if (payload.accountData)
12742
+ setAccountData(nextAccountData);
12743
+ // Refresh contact too — the backend's account/contact unification means
12744
+ // the contact record may now reflect the new displayName/email/phone.
12745
+ if (collectionId && shouldSyncContacts) {
12746
+ try {
12747
+ const fresh = await smartlinks__namespace.contact.publicGetMine(collectionId);
12748
+ if (fresh?.contact)
12749
+ setContact(fresh.contact);
12750
+ }
12751
+ catch {
12752
+ // Non-fatal
12753
+ }
12754
+ }
12755
+ notifyAuthStateChange('TOKEN_REFRESH', nextUser, nextToken ?? token, nextAccountData, accountInfo, isVerified, contact, contactId);
12756
+ }, [proxyMode, user, accountData, accountInfo, isVerified, contact, contactId, collectionId, shouldSyncContacts, token, notifyAuthStateChange]);
12716
12757
  // Online/offline event listener for auto-retry verification
12717
12758
  React.useEffect(() => {
12718
12759
  if (proxyMode)
@@ -12790,6 +12831,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12790
12831
  clearAccountCache,
12791
12832
  onAuthStateChange,
12792
12833
  retryVerification,
12834
+ applySessionRefresh,
12793
12835
  };
12794
12836
  return jsxRuntime.jsx(AuthContext.Provider, { value: value, children: children });
12795
12837
  };
@@ -14291,8 +14333,17 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14291
14333
  });
14292
14334
  // Resolve outbound prefill message: per-call prop wins, then AuthKit default.
14293
14335
  const prefillMessage = whatsappPrefillMessage ?? config?.whatsappPrefillMessage;
14294
- const result = await api.sendWhatsApp(getRedirectUrl(), undefined, reply, prefillMessage);
14295
- whatsappSendRef.current = { token: result.token, sessionKey: result.sessionKey };
14336
+ // Pass collected signup details as contactData so the backend creates the
14337
+ // contact with name/customFields already attached and returns a fully
14338
+ // formed session.user — no follow-up updateProfile call needed.
14339
+ const trimmedName = displayName?.trim() || undefined;
14340
+ const contactData = trimmedName ? { name: trimmedName } : undefined;
14341
+ const result = await api.sendWhatsApp(getRedirectUrl(), undefined, reply, prefillMessage, contactData);
14342
+ whatsappSendRef.current = {
14343
+ token: result.token,
14344
+ sessionKey: result.sessionKey,
14345
+ displayName: trimmedName,
14346
+ };
14296
14347
  return result;
14297
14348
  }
14298
14349
  catch (err) {
@@ -14311,9 +14362,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14311
14362
  const target = status.redirectUrl || getRedirectUrl();
14312
14363
  setAuthSuccess(true);
14313
14364
  setSuccessMessage('WhatsApp verified! Signing you in…');
14314
- // Exchange the verified WhatsApp proof for a real Auth Kit session token,
14315
- // so the user is fully logged in (not just verified) and any same-tab
14316
- // continuation flows (claim, bid, etc.) pick up an authenticated session.
14365
+ // Exchange the verified WhatsApp proof for a real Auth Kit session token.
14366
+ // The backend has already created/updated the contact using contactData
14367
+ // sent at sendWhatsApp time, so session.user is fully formed.
14317
14368
  const send = whatsappSendRef.current;
14318
14369
  if (send?.sessionKey) {
14319
14370
  try {
@@ -14656,6 +14707,32 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14656
14707
  const [profile, setProfile] = React.useState(null);
14657
14708
  const [error, setError] = React.useState();
14658
14709
  const [success, setSuccess] = React.useState();
14710
+ const [resolvedClientId, setResolvedClientId] = React.useState(clientId);
14711
+ // Keep prop in sync
14712
+ React.useEffect(() => {
14713
+ if (clientId)
14714
+ setResolvedClientId(clientId);
14715
+ }, [clientId]);
14716
+ // If no clientId was provided, try to resolve the collection's default Auth Kit.
14717
+ // This supports "portal mode" where the host app only knows the collectionId.
14718
+ React.useEffect(() => {
14719
+ if (clientId || !collectionId)
14720
+ return;
14721
+ let cancelled = false;
14722
+ (async () => {
14723
+ try {
14724
+ const collection = await smartlinks__namespace.collection.get(collectionId);
14725
+ const defaultId = collection?.defaultAuthKitId;
14726
+ if (!cancelled && defaultId)
14727
+ setResolvedClientId(defaultId);
14728
+ }
14729
+ catch {
14730
+ // Non-fatal — handlers will surface a friendly error if still missing
14731
+ }
14732
+ })();
14733
+ return () => { cancelled = true; };
14734
+ }, [clientId, collectionId]);
14735
+ const NO_CLIENT_ID_MSG = 'This account section is not connected to an Auth Kit yet. Pass a clientId prop, or set a default Auth Kit on the collection in the admin console.';
14659
14736
  // Schema state — now uses SDK type directly
14660
14737
  const [schema, setSchema] = React.useState(null);
14661
14738
  const [schemaLoading, setSchemaLoading] = React.useState(false);
@@ -14727,26 +14804,29 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14727
14804
  setLoading(true);
14728
14805
  setError(undefined);
14729
14806
  try {
14807
+ // Pull contact first so we can fall back to fields the auth user object
14808
+ // may not carry (e.g. WhatsApp phone numbers, displayName, email).
14809
+ let contactData = auth.contact;
14810
+ if (collectionId && !contactData) {
14811
+ try {
14812
+ contactData = (await auth.getContact?.()) || null;
14813
+ }
14814
+ catch { /* non-fatal */ }
14815
+ }
14816
+ const contactAny = contactData;
14730
14817
  const profileData = {
14731
14818
  uid: auth.user?.uid || '',
14732
- email: auth.user?.email,
14733
- displayName: auth.user?.displayName,
14734
- phoneNumber: auth.user?.phoneNumber,
14819
+ email: auth.user?.email || contactAny?.email,
14820
+ displayName: auth.user?.displayName || contactAny?.displayName || contactAny?.name,
14821
+ phoneNumber: auth.user?.phoneNumber || contactAny?.phone || contactAny?.phoneNumber,
14735
14822
  photoURL: auth.user?.photoURL,
14736
14823
  emailVerified: true,
14737
14824
  accountData: auth.accountData || {},
14738
14825
  };
14739
14826
  setProfile(profileData);
14740
14827
  setDisplayName(profileData.displayName || '');
14741
- // Load contact custom fields if collectionId provided
14742
- if (collectionId && auth.contact) {
14743
- setCustomFieldValues(auth.contact.customFields || {});
14744
- }
14745
- else if (collectionId) {
14746
- const contact = await auth.getContact?.();
14747
- if (contact?.customFields) {
14748
- setCustomFieldValues(contact.customFields);
14749
- }
14828
+ if (collectionId && contactData?.customFields) {
14829
+ setCustomFieldValues(contactData.customFields);
14750
14830
  }
14751
14831
  }
14752
14832
  catch (err) {
@@ -14778,19 +14858,34 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14778
14858
  const editableCustomFields = getEditableFields(schema, customFieldValues).filter(f => !['email', 'displayName', 'phone', 'phoneNumber'].includes(f.key));
14779
14859
  const handleUpdateProfile = async (e) => {
14780
14860
  e.preventDefault();
14781
- if (!clientId) {
14782
- setError('Client ID is required');
14861
+ if (!resolvedClientId) {
14862
+ setError(NO_CLIENT_ID_MSG);
14783
14863
  return;
14784
14864
  }
14785
14865
  setLoading(true);
14786
14866
  setError(undefined);
14787
14867
  setSuccess(undefined);
14788
14868
  try {
14789
- await smartlinks__namespace.authKit.updateProfile(clientId, { displayName });
14869
+ // SDK 1.13.17+: updateProfile returns a fresh bearer token with refreshed
14870
+ // claims (displayName/photoURL). We MUST persist the new token, otherwise
14871
+ // a page refresh would decode the old token and resurrect stale values.
14872
+ // Cast: older @proveanything/smartlinks type defs typed this as UserProfile
14873
+ // (no `token`). The runtime always returns the rotated token in 1.13.17+.
14874
+ const updated = await smartlinks__namespace.authKit.updateProfile(resolvedClientId, { displayName });
14875
+ await auth.applySessionRefresh({
14876
+ token: updated.token,
14877
+ user: {
14878
+ displayName: updated.displayName ?? displayName,
14879
+ email: updated.email,
14880
+ phoneNumber: updated.phoneNumber ?? undefined,
14881
+ photoURL: updated.photoURL ?? undefined,
14882
+ },
14883
+ accountData: updated.accountData,
14884
+ });
14790
14885
  setSuccess('Profile updated successfully!');
14791
14886
  setEditingSection(null);
14792
14887
  if (profile) {
14793
- setProfile({ ...profile, displayName });
14888
+ setProfile({ ...profile, displayName: updated.displayName ?? displayName });
14794
14889
  }
14795
14890
  }
14796
14891
  catch (err) {
@@ -14825,8 +14920,8 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14825
14920
  };
14826
14921
  const handleChangeEmail = async (e) => {
14827
14922
  e.preventDefault();
14828
- if (!clientId) {
14829
- setError('Client ID is required');
14923
+ if (!resolvedClientId) {
14924
+ setError(NO_CLIENT_ID_MSG);
14830
14925
  return;
14831
14926
  }
14832
14927
  setLoading(true);
@@ -14834,7 +14929,11 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14834
14929
  setSuccess(undefined);
14835
14930
  try {
14836
14931
  const redirectUrl = window.location.href;
14837
- await smartlinks__namespace.authKit.changeEmail(clientId, newEmail, emailPassword, redirectUrl);
14932
+ const res = await smartlinks__namespace.authKit.changeEmail(resolvedClientId, newEmail, emailPassword, redirectUrl);
14933
+ // SDK may rotate bearer token on email change — persist if present.
14934
+ if (res?.token) {
14935
+ await auth.applySessionRefresh({ token: res.token });
14936
+ }
14838
14937
  setSuccess('Email change requested. Please check your new email for verification.');
14839
14938
  setEditingSection(null);
14840
14939
  setNewEmail('');
@@ -14850,8 +14949,8 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14850
14949
  };
14851
14950
  const handleChangePassword = async (e) => {
14852
14951
  e.preventDefault();
14853
- if (!clientId) {
14854
- setError('Client ID is required');
14952
+ if (!resolvedClientId) {
14953
+ setError(NO_CLIENT_ID_MSG);
14855
14954
  return;
14856
14955
  }
14857
14956
  if (newPassword !== confirmPassword) {
@@ -14866,7 +14965,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14866
14965
  setError(undefined);
14867
14966
  setSuccess(undefined);
14868
14967
  try {
14869
- await smartlinks__namespace.authKit.changePassword(clientId, currentPassword, newPassword);
14968
+ await smartlinks__namespace.authKit.changePassword(resolvedClientId, currentPassword, newPassword);
14870
14969
  setSuccess('Password changed successfully!');
14871
14970
  setEditingSection(null);
14872
14971
  setCurrentPassword('');
@@ -14882,14 +14981,14 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14882
14981
  }
14883
14982
  };
14884
14983
  const handleSendPhoneCode = async () => {
14885
- if (!clientId) {
14886
- setError('Client ID is required');
14984
+ if (!resolvedClientId) {
14985
+ setError(NO_CLIENT_ID_MSG);
14887
14986
  return;
14888
14987
  }
14889
14988
  setLoading(true);
14890
14989
  setError(undefined);
14891
14990
  try {
14892
- await smartlinks__namespace.authKit.sendPhoneCode(clientId, newPhone);
14991
+ await smartlinks__namespace.authKit.sendPhoneCode(resolvedClientId, newPhone);
14893
14992
  setPhoneCodeSent(true);
14894
14993
  setSuccess('Verification code sent to your phone');
14895
14994
  }
@@ -14903,15 +15002,20 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14903
15002
  };
14904
15003
  const handleUpdatePhone = async (e) => {
14905
15004
  e.preventDefault();
14906
- if (!clientId) {
14907
- setError('Client ID is required');
15005
+ if (!resolvedClientId) {
15006
+ setError(NO_CLIENT_ID_MSG);
14908
15007
  return;
14909
15008
  }
14910
15009
  setLoading(true);
14911
15010
  setError(undefined);
14912
15011
  setSuccess(undefined);
14913
15012
  try {
14914
- await smartlinks__namespace.authKit.updatePhone(clientId, newPhone, phoneCode);
15013
+ const res = await smartlinks__namespace.authKit.updatePhone(resolvedClientId, newPhone, phoneCode);
15014
+ // Phone change rotates the bearer token (phoneNumber is a JWT claim).
15015
+ await auth.applySessionRefresh({
15016
+ token: res?.token,
15017
+ user: { phoneNumber: newPhone },
15018
+ });
14915
15019
  setSuccess('Phone number updated successfully!');
14916
15020
  setEditingSection(null);
14917
15021
  setNewPhone('');
@@ -14928,8 +15032,8 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14928
15032
  }
14929
15033
  };
14930
15034
  const handleDeleteAccount = async () => {
14931
- if (!clientId) {
14932
- setError('Client ID is required');
15035
+ if (!resolvedClientId) {
15036
+ setError(NO_CLIENT_ID_MSG);
14933
15037
  return;
14934
15038
  }
14935
15039
  if (deleteConfirmText !== 'DELETE') {
@@ -14943,7 +15047,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14943
15047
  setLoading(true);
14944
15048
  setError(undefined);
14945
15049
  try {
14946
- await smartlinks__namespace.authKit.deleteAccount(clientId, deletePassword, deleteConfirmText);
15050
+ await smartlinks__namespace.authKit.deleteAccount(resolvedClientId, deletePassword, deleteConfirmText);
14947
15051
  setSuccess('Account deleted successfully');
14948
15052
  await auth.logout();
14949
15053
  }