@proveanything/smartlinks-auth-ui 0.5.15 → 0.5.17
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
|
@@ -12154,10 +12154,34 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12154
12154
|
}
|
|
12155
12155
|
});
|
|
12156
12156
|
}, []);
|
|
12157
|
-
// Sync contact to Smartlinks (non-blocking)
|
|
12157
|
+
// Sync contact to Smartlinks (non-blocking).
|
|
12158
|
+
// Strategy: call publicGetMine() first — the "me" endpoint tells us if a
|
|
12159
|
+
// contact already exists for this (org, userId) pair. If it does, we skip
|
|
12160
|
+
// the upsert entirely (the backend's "upsert" currently calls
|
|
12161
|
+
// prisma.contact.create() unconditionally and 400s on the unique
|
|
12162
|
+
// constraint, so we must not call it for returning users). Only when no
|
|
12163
|
+
// contact exists do we create one via publicUpsert.
|
|
12158
12164
|
const syncContact = React.useCallback(async (authUser, customFields) => {
|
|
12159
12165
|
if (!collectionId || !shouldSyncContacts)
|
|
12160
12166
|
return null;
|
|
12167
|
+
// 1. Look up existing contact via the "me" endpoint.
|
|
12168
|
+
try {
|
|
12169
|
+
const myContactResponse = await smartlinks__namespace.contact.publicGetMine(collectionId);
|
|
12170
|
+
const existing = myContactResponse?.contact;
|
|
12171
|
+
if (existing?.contactId) {
|
|
12172
|
+
if (!proxyMode) {
|
|
12173
|
+
await tokenStorage.saveContactId(existing.contactId);
|
|
12174
|
+
}
|
|
12175
|
+
setContact(existing);
|
|
12176
|
+
setContactId(existing.contactId);
|
|
12177
|
+
notifyAuthStateChange('CONTACT_SYNCED', authUser, token, accountData, accountInfo, isVerified, existing, existing.contactId);
|
|
12178
|
+
return existing.contactId;
|
|
12179
|
+
}
|
|
12180
|
+
}
|
|
12181
|
+
catch (lookupErr) {
|
|
12182
|
+
// Non-fatal — fall through to create.
|
|
12183
|
+
}
|
|
12184
|
+
// 2. No existing contact — create one.
|
|
12161
12185
|
try {
|
|
12162
12186
|
const result = await smartlinks__namespace.contact.publicUpsert(collectionId, {
|
|
12163
12187
|
userId: authUser.uid,
|
|
@@ -13108,8 +13132,48 @@ const useAuth = () => {
|
|
|
13108
13132
|
};
|
|
13109
13133
|
|
|
13110
13134
|
// VERSION: Update this when making changes to help identify which version is running
|
|
13111
|
-
const AUTH_UI_VERSION = '
|
|
13135
|
+
const AUTH_UI_VERSION = '47';
|
|
13112
13136
|
const LOG_PREFIX = `[SmartlinksAuthUI:v${AUTH_UI_VERSION}]`;
|
|
13137
|
+
const PERMANENT_WHATSAPP_EXCHANGE_ERROR_CODES = new Set([
|
|
13138
|
+
'TOKEN_ALREADY_USED',
|
|
13139
|
+
'SESSION_ALREADY_USED',
|
|
13140
|
+
'WHATSAPP_SESSION_ALREADY_USED',
|
|
13141
|
+
'INVALID_TOKEN',
|
|
13142
|
+
'TOKEN_EXPIRED',
|
|
13143
|
+
'INVALID_SESSION',
|
|
13144
|
+
'SESSION_EXPIRED',
|
|
13145
|
+
'SESSION_NOT_FOUND',
|
|
13146
|
+
]);
|
|
13147
|
+
const getExchangeErrorCode = (error) => {
|
|
13148
|
+
if (!error || typeof error !== 'object')
|
|
13149
|
+
return undefined;
|
|
13150
|
+
const err = error;
|
|
13151
|
+
return err.errorCode || err.details?.errorCode || err.details?.error || err.response?.data?.errorCode || err.response?.data?.error;
|
|
13152
|
+
};
|
|
13153
|
+
const getExchangeErrorStatus = (error) => {
|
|
13154
|
+
if (!error || typeof error !== 'object')
|
|
13155
|
+
return undefined;
|
|
13156
|
+
const err = error;
|
|
13157
|
+
return err.statusCode || err.status || err.response?.status;
|
|
13158
|
+
};
|
|
13159
|
+
const getExchangeErrorMessage = (error) => {
|
|
13160
|
+
if (error instanceof Error)
|
|
13161
|
+
return error.message || '';
|
|
13162
|
+
if (!error || typeof error !== 'object')
|
|
13163
|
+
return '';
|
|
13164
|
+
const err = error;
|
|
13165
|
+
return err.message || err.details?.message || err.response?.data?.message || '';
|
|
13166
|
+
};
|
|
13167
|
+
const isPermanentWhatsAppExchangeError = (error) => {
|
|
13168
|
+
const errorCode = getExchangeErrorCode(error)?.toUpperCase();
|
|
13169
|
+
if (errorCode && PERMANENT_WHATSAPP_EXCHANGE_ERROR_CODES.has(errorCode)) {
|
|
13170
|
+
return true;
|
|
13171
|
+
}
|
|
13172
|
+
const status = getExchangeErrorStatus(error);
|
|
13173
|
+
const message = getExchangeErrorMessage(error).toLowerCase();
|
|
13174
|
+
const looksPermanentMessage = /(already used|already been used|session.*used|expired|invalid|not found|consumed)/i.test(message);
|
|
13175
|
+
return looksPermanentMessage && [400, 401, 404, 409, 410].includes(status ?? -1);
|
|
13176
|
+
};
|
|
13113
13177
|
// Normalize malformed query strings where a second '?' is used instead of '&'.
|
|
13114
13178
|
// Some host platforms append "?mode=...&token=..." to a URL that already has a "?pageId=...",
|
|
13115
13179
|
// resulting in "?pageId=xxx?mode=resetPassword&token=yyy". Convert any extra '?' to '&'
|
|
@@ -14751,18 +14815,40 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14751
14815
|
try {
|
|
14752
14816
|
// No ensureAccount and no phone number — the backend creates/links the
|
|
14753
14817
|
// account from the inbound WhatsApp webhook (sender's number is the proof).
|
|
14754
|
-
// Resolve reply config: per-call prop wins,
|
|
14755
|
-
|
|
14818
|
+
// Resolve reply config: per-call prop wins, then AuthKit config, then a
|
|
14819
|
+
// sensible built-in default so the post-verification WhatsApp reply ALWAYS
|
|
14820
|
+
// carries a "Continue" CTA back to the app with the resume token in the URL.
|
|
14821
|
+
// Without this, a user verifying in WhatsApp on one device/browser can't
|
|
14822
|
+
// resume the session by tapping back into our app from the chat thread.
|
|
14756
14823
|
const effectiveRedirectUrl = getRedirectUrl();
|
|
14757
14824
|
const resumableRedirectUrl = appendWhatsAppResumeParams(effectiveRedirectUrl, '{{token}}');
|
|
14825
|
+
const friendlyClientName = (clientName && clientName.trim()) || 'this app';
|
|
14826
|
+
const defaultReplyConfig = {
|
|
14827
|
+
enabled: true,
|
|
14828
|
+
text: `You're verified! Tap below to continue signing in to ${friendlyClientName}.`,
|
|
14829
|
+
cta: {
|
|
14830
|
+
body: `You're verified! Tap below to continue signing in to ${friendlyClientName}.`,
|
|
14831
|
+
buttonLabel: 'Continue',
|
|
14832
|
+
buttonUrl: '{{returnUrl}}',
|
|
14833
|
+
},
|
|
14834
|
+
};
|
|
14835
|
+
const replyConfig = whatsappReply ??
|
|
14836
|
+
config?.whatsappReply ??
|
|
14837
|
+
defaultReplyConfig;
|
|
14758
14838
|
const reply = buildWhatsAppReply(replyConfig, {
|
|
14759
14839
|
name: displayName,
|
|
14760
14840
|
clientName,
|
|
14761
14841
|
returnUrl: resumableRedirectUrl,
|
|
14762
14842
|
clientId,
|
|
14763
14843
|
});
|
|
14764
|
-
// Resolve outbound prefill message: per-call prop wins, then AuthKit
|
|
14765
|
-
|
|
14844
|
+
// Resolve outbound prefill message: per-call prop wins, then AuthKit config,
|
|
14845
|
+
// then a sensible built-in default so the wa.me body is never blank and always
|
|
14846
|
+
// carries the verification token (the SDK appends {{token}} if missing, but we
|
|
14847
|
+
// also want friendly human context for the recipient).
|
|
14848
|
+
const defaultPrefill = `Hi! I'd like to sign in to ${friendlyClientName}. Code: {{token}}`;
|
|
14849
|
+
const prefillMessage = whatsappPrefillMessage ??
|
|
14850
|
+
config?.whatsappPrefillMessage ??
|
|
14851
|
+
defaultPrefill;
|
|
14766
14852
|
// Pass collected signup details as contactData so the backend creates the
|
|
14767
14853
|
// contact with name/customFields already attached and returns a fully
|
|
14768
14854
|
// formed session.user — no follow-up updateProfile call needed.
|
|
@@ -14811,6 +14897,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14811
14897
|
try {
|
|
14812
14898
|
await updatePendingWhatsAppSession({ exchangeStartedAt: Date.now() });
|
|
14813
14899
|
const session = await api.exchangeWhatsAppSession(send.token, send.sessionKey);
|
|
14900
|
+
const resultErrorMessage = getActionResultErrorMessage(session);
|
|
14901
|
+
if (resultErrorMessage) {
|
|
14902
|
+
throw Object.assign(new Error(resultErrorMessage), session);
|
|
14903
|
+
}
|
|
14814
14904
|
if (session?.token && session.user) {
|
|
14815
14905
|
await auth.login(session.token, session.user, session.accountData, true, getExpirationFromResponse(session));
|
|
14816
14906
|
if (!proxyMode) {
|
|
@@ -14823,6 +14913,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14823
14913
|
}
|
|
14824
14914
|
}
|
|
14825
14915
|
catch (err) {
|
|
14916
|
+
if (isPermanentWhatsAppExchangeError(err)) {
|
|
14917
|
+
log.warn('WhatsApp session exchange failed permanently; clearing stale pending session:', err);
|
|
14918
|
+
setAuthSuccess(false);
|
|
14919
|
+
setSuccessMessage(undefined);
|
|
14920
|
+
setError('This WhatsApp sign-in session has already been used or expired. Please start again.');
|
|
14921
|
+
setRestoredWhatsAppSend(null);
|
|
14922
|
+
await clearPendingWhatsAppSession();
|
|
14923
|
+
return true;
|
|
14924
|
+
}
|
|
14826
14925
|
const latestPending = await loadPendingWhatsAppSession();
|
|
14827
14926
|
if (!latestPending) {
|
|
14828
14927
|
return true;
|
|
@@ -14871,9 +14970,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14871
14970
|
color: config?.branding?.inheritHostStyles
|
|
14872
14971
|
? 'hsl(var(--foreground, 215 25% 15%))'
|
|
14873
14972
|
: (resolvedTheme === 'dark' ? '#f1f5f9' : 'inherit')
|
|
14874
|
-
}, children: successMessage?.includes('
|
|
14875
|
-
successMessage?.includes('
|
|
14876
|
-
|
|
14973
|
+
}, children: successMessage?.includes('WhatsApp') ? 'WhatsApp Verified!' :
|
|
14974
|
+
successMessage?.includes('Phone') ? 'Phone Verified!' :
|
|
14975
|
+
successMessage?.includes('Email verified') || successMessage?.includes('email verified') ? 'Email Verified!' :
|
|
14976
|
+
successMessage?.includes('Magic link') ? 'Check Your Email!' :
|
|
14977
|
+
successMessage?.includes('verified') ? 'Verified!' :
|
|
14978
|
+
mode === 'register' ? 'Account Created!' : 'Login Successful!' }), jsxRuntime.jsx("p", { style: {
|
|
14877
14979
|
color: config?.branding?.inheritHostStyles
|
|
14878
14980
|
? 'hsl(var(--muted-foreground, 215 15% 45%))'
|
|
14879
14981
|
: (resolvedTheme === 'dark' ? '#94a3b8' : '#6B7280'),
|