@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/api.d.ts +7 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/components/AccountManagement.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.esm.js +141 -37
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +141 -37
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
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
|
-
|
|
14295
|
-
|
|
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
|
-
//
|
|
14316
|
-
//
|
|
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
|
-
|
|
14742
|
-
|
|
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 (!
|
|
14782
|
-
setError(
|
|
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
|
-
|
|
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 (!
|
|
14829
|
-
setError(
|
|
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(
|
|
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 (!
|
|
14854
|
-
setError(
|
|
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(
|
|
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 (!
|
|
14886
|
-
setError(
|
|
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(
|
|
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 (!
|
|
14907
|
-
setError(
|
|
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(
|
|
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 (!
|
|
14932
|
-
setError(
|
|
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(
|
|
15050
|
+
await smartlinks__namespace.authKit.deleteAccount(resolvedClientId, deletePassword, deleteConfirmText);
|
|
14947
15051
|
setSuccess('Account deleted successfully');
|
|
14948
15052
|
await auth.logout();
|
|
14949
15053
|
}
|