@proveanything/smartlinks 1.13.14 → 1.13.16
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/authKit.d.ts +1 -1
- package/dist/api/authKit.js +1 -1
- package/dist/docs/API_SUMMARY.md +17 -2
- package/dist/docs/auth-kit.md +18 -1
- package/dist/docs/building-react-components.md +2 -0
- package/dist/docs/overview.md +1 -0
- package/dist/docs/portal-auth-broadcast.md +160 -0
- package/dist/openapi.yaml +24 -1
- package/dist/types/authKit.d.ts +11 -0
- package/docs/API_SUMMARY.md +17 -2
- package/docs/auth-kit.md +18 -1
- package/docs/building-react-components.md +2 -0
- package/docs/overview.md +1 -0
- package/docs/portal-auth-broadcast.md +160 -0
- package/openapi.yaml +24 -1
- package/package.json +1 -1
package/dist/api/authKit.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export declare namespace authKit {
|
|
|
29
29
|
function verifyPhoneCode(clientId: string, phoneNumber: string, code: string): Promise<PhoneVerifyResponse>;
|
|
30
30
|
/** Send a WhatsApp verification deep-link (public). */
|
|
31
31
|
function sendWhatsApp(clientId: string, body?: SendWhatsAppRequest): Promise<SendWhatsAppResponse>;
|
|
32
|
-
/** Manually verify WhatsApp token if inbound webhook path is unavailable (public). */
|
|
32
|
+
/** Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback). */
|
|
33
33
|
function verifyWhatsApp(clientId: string, token: string, phoneNumber: string): Promise<VerifyWhatsAppResponse>;
|
|
34
34
|
/** Poll WhatsApp verification status for a token (public). */
|
|
35
35
|
function getWhatsAppStatus(clientId: string, token: string): Promise<WhatsAppStatusResponse>;
|
package/dist/api/authKit.js
CHANGED
|
@@ -51,7 +51,7 @@ export var authKit;
|
|
|
51
51
|
return post(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/send`, body);
|
|
52
52
|
}
|
|
53
53
|
authKit.sendWhatsApp = sendWhatsApp;
|
|
54
|
-
/** Manually verify WhatsApp token if inbound webhook path is unavailable (public). */
|
|
54
|
+
/** Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback). */
|
|
55
55
|
async function verifyWhatsApp(clientId, token, phoneNumber) {
|
|
56
56
|
return post(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/verify`, { token, phoneNumber });
|
|
57
57
|
}
|
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.16 | Generated: 2026-05-15T19:36:34.712Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -3043,12 +3043,27 @@ interface WhatsAppReplyOptions {
|
|
|
3043
3043
|
}
|
|
3044
3044
|
```
|
|
3045
3045
|
|
|
3046
|
+
**WhatsAppContactData** (interface)
|
|
3047
|
+
```typescript
|
|
3048
|
+
interface WhatsAppContactData {
|
|
3049
|
+
name?: string
|
|
3050
|
+
firstName?: string
|
|
3051
|
+
lastName?: string
|
|
3052
|
+
displayName?: string
|
|
3053
|
+
email?: string
|
|
3054
|
+
source?: string
|
|
3055
|
+
customFields?: Record<string, unknown>
|
|
3056
|
+
externalIds?: Record<string, unknown>
|
|
3057
|
+
}
|
|
3058
|
+
```
|
|
3059
|
+
|
|
3046
3060
|
**SendWhatsAppRequest** (interface)
|
|
3047
3061
|
```typescript
|
|
3048
3062
|
interface SendWhatsAppRequest {
|
|
3049
3063
|
redirectUrl?: string
|
|
3050
3064
|
prefillMessage?: string
|
|
3051
3065
|
reply?: WhatsAppReplyOptions
|
|
3066
|
+
contactData?: WhatsAppContactData
|
|
3052
3067
|
}
|
|
3053
3068
|
```
|
|
3054
3069
|
|
|
@@ -8229,7 +8244,7 @@ Verify phone verification code (public).
|
|
|
8229
8244
|
Send a WhatsApp verification deep-link (public).
|
|
8230
8245
|
|
|
8231
8246
|
**verifyWhatsApp**(clientId: string, token: string, phoneNumber: string) → `Promise<VerifyWhatsAppResponse>`
|
|
8232
|
-
Manually verify WhatsApp token if inbound webhook path is unavailable (public).
|
|
8247
|
+
Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
|
|
8233
8248
|
|
|
8234
8249
|
**getWhatsAppStatus**(clientId: string, token: string) → `Promise<WhatsAppStatusResponse>`
|
|
8235
8250
|
Poll WhatsApp verification status for a token (public).
|
package/dist/docs/auth-kit.md
CHANGED
|
@@ -82,6 +82,8 @@ const session = await authKit.verifyPhoneCode(clientId, '+61400000000', '123456'
|
|
|
82
82
|
|
|
83
83
|
Use these flows when you want low-friction verification before or without full account sign-in.
|
|
84
84
|
|
|
85
|
+
WhatsApp verification is token-first. The user does not type their phone number in your app for this flow; phone ownership is proven by the inbound WhatsApp sender number.
|
|
86
|
+
|
|
85
87
|
```ts
|
|
86
88
|
import { authKit } from '@proveanything/smartlinks';
|
|
87
89
|
|
|
@@ -92,6 +94,12 @@ const wa = await authKit.sendWhatsApp(clientId);
|
|
|
92
94
|
// const wa = await authKit.sendWhatsApp(clientId, {
|
|
93
95
|
// redirectUrl: 'https://app.example.com/checkout/continue',
|
|
94
96
|
// prefillMessage: 'Please let me bid in this auction. Code: {{token}}',
|
|
97
|
+
// contactData: {
|
|
98
|
+
// name: 'Jane Doe',
|
|
99
|
+
// email: 'jane@example.com',
|
|
100
|
+
// source: 'auction-checkout',
|
|
101
|
+
// customFields: { agreedToTerms: true },
|
|
102
|
+
// },
|
|
95
103
|
// reply: {
|
|
96
104
|
// cta: {
|
|
97
105
|
// body: "You're verified and ready to bid.",
|
|
@@ -112,7 +120,7 @@ if (status.status === 'verified' && wa.sessionKey) {
|
|
|
112
120
|
// session.token can be used as the authenticated bearer token
|
|
113
121
|
}
|
|
114
122
|
|
|
115
|
-
// Optional fallback path if webhook confirmation is unavailable
|
|
123
|
+
// Optional legacy fallback path if webhook confirmation is unavailable
|
|
116
124
|
await authKit.verifyWhatsApp(clientId, wa.token, '+447911123456');
|
|
117
125
|
|
|
118
126
|
// 2) Or send SMS click-to-verify link
|
|
@@ -126,6 +134,13 @@ await authKit.sendSmsVerify(clientId, {
|
|
|
126
134
|
await authKit.verifySms(clientId, '<token>', '+447911123456');
|
|
127
135
|
```
|
|
128
136
|
|
|
137
|
+
`contactData` is optional and is useful when you collect name/email before the customer switches to WhatsApp.
|
|
138
|
+
|
|
139
|
+
- Auth Kit stores `contactData` on the verification token metadata first.
|
|
140
|
+
- Contact details are written to durable contact storage only after WhatsApp verification succeeds.
|
|
141
|
+
- If the user abandons before verification, no contact is created.
|
|
142
|
+
- `contactData` must not include phone; the verified inbound WhatsApp sender number is always authoritative.
|
|
143
|
+
|
|
129
144
|
### Contact bootstrap / durable identity
|
|
130
145
|
|
|
131
146
|
After verification, upsert contact identity and store `contactId` on downstream records (raffle ticket, bid, claim intent).
|
|
@@ -183,6 +198,8 @@ const session = await authKit.exchangeWhatsAppSession(clientId, wa.token, wa.ses
|
|
|
183
198
|
`sessionKey` is returned by `sendWhatsApp` and is used to mitigate token replay from contexts that did not initiate the browser flow.
|
|
184
199
|
|
|
185
200
|
> **Note:** `redirectUrl` is optional. WhatsApp tokens are short hex strings (16 chars) for better UX.
|
|
201
|
+
>
|
|
202
|
+
> **Legacy note:** `verifyWhatsApp` is for older phone-bound token flows. Prefer inbound WhatsApp token confirmation plus status polling for new implementations.
|
|
186
203
|
|
|
187
204
|
### Google OAuth
|
|
188
205
|
|
|
@@ -309,6 +309,7 @@ Now that you understand the core concepts, see the implementation guides:
|
|
|
309
309
|
- **[mpa.md](./mpa.md)** — Multi-page app architecture (optional)
|
|
310
310
|
- **[iframe-responder.md](./iframe-responder.md)** — Parent-side iframe integration
|
|
311
311
|
- **[portal-back-button.md](./portal-back-button.md)** — Embedded-app "up" navigation and portal back-button behaviour
|
|
312
|
+
- **[portal-auth-broadcast.md](./portal-auth-broadcast.md)** — Publishing custom auth flows to the portal
|
|
312
313
|
|
|
313
314
|
---
|
|
314
315
|
|
|
@@ -318,4 +319,5 @@ Now that you understand the core concepts, see the implementation guides:
|
|
|
318
319
|
- [ai.md](./ai.md) — AI assistant integration
|
|
319
320
|
- [deep-link-discovery.md](./deep-link-discovery.md) — Deep linking patterns
|
|
320
321
|
- [portal-back-button.md](./portal-back-button.md) — Portal shell back-button behavior for hierarchical embeds
|
|
322
|
+
- [portal-auth-broadcast.md](./portal-auth-broadcast.md) — Custom authentication and session broadcast
|
|
321
323
|
- [manifests.md](./manifests.md) — App manifest configuration
|
package/dist/docs/overview.md
CHANGED
|
@@ -74,6 +74,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
74
74
|
| **AI Guide Template** | `docs/ai-guide-template.md` | Template for creating `public/ai-guide.md` — customise per app |
|
|
75
75
|
| **Forms** | `docs/forms.md` | Form definitions, schema-driven rendering, submission patterns |
|
|
76
76
|
| **Auth Kit** | `docs/auth-kit.md` | End-user sign-in: email/password, magic links, phone OTP, Google OAuth |
|
|
77
|
+
| **Portal Auth Broadcast** | `docs/portal-auth-broadcast.md` | Publishing custom auth flows to the portal; syncing sessions across containers and iframes |
|
|
77
78
|
| **App Records Pattern** | `docs/app-records-pattern.md` | Standard pattern for per-product/facet/variant/batch admin + public widget UIs |
|
|
78
79
|
| **UI Utils** | `docs/ui-utils.md` | `@proveanything/smartlinks-utils-ui` — React shells, hooks, and primitives for records-based apps |
|
|
79
80
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Publishing Auth State to the Portal
|
|
2
|
+
|
|
3
|
+
> **For sub-app authors.** This guide explains how to broadcast authentication state changes to the portal so the header, account UI, and sibling apps stay in sync.
|
|
4
|
+
|
|
5
|
+
A SmartLinks micro-app may need to run its own custom authentication flow —
|
|
6
|
+
typical cases: an auction app that calls a bidder API, a competition app
|
|
7
|
+
that exchanges a magic code for a session, a partner SSO. When that flow
|
|
8
|
+
completes the app holds a `token` + `user` of its own, and the **portal**
|
|
9
|
+
needs to know about it so the header, the account UI, and every sibling
|
|
10
|
+
app pick up the new session.
|
|
11
|
+
|
|
12
|
+
This doc describes the contract the portal framework already implements.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Container / Widget Apps (Same React Tree)
|
|
17
|
+
|
|
18
|
+
These run inside the portal's React context and share its `AuthProvider`.
|
|
19
|
+
Call `useAuth()` directly:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { useAuth } from '@proveanything/smartlinks-auth-ui';
|
|
23
|
+
|
|
24
|
+
function BidButton() {
|
|
25
|
+
const { login, logout, isAuthenticated, user } = useAuth();
|
|
26
|
+
|
|
27
|
+
async function placeBid() {
|
|
28
|
+
if (!isAuthenticated) {
|
|
29
|
+
// Run your own auth flow…
|
|
30
|
+
const { token, user, expiresAt } = await api.signInBidder(...);
|
|
31
|
+
// Publish to the portal — this is the *only* call you need:
|
|
32
|
+
await login(token, user, { source: 'auction' }, /* isNewUser */ false, expiresAt);
|
|
33
|
+
}
|
|
34
|
+
await api.placeBid(...);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return <button onClick={placeBid}>Bid</button>;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`useAuth().login(token, user, accountData?, isNewUser?, expiresAt?)` will:
|
|
42
|
+
|
|
43
|
+
- Persist the session (cookie/storage as configured).
|
|
44
|
+
- Call `setBearerToken(token)` on the SDK so subsequent API calls are
|
|
45
|
+
authenticated.
|
|
46
|
+
- Re-render every consumer of `useAuth()` / `useSafeAuth()` —
|
|
47
|
+
including the portal header.
|
|
48
|
+
|
|
49
|
+
`useAuth().logout()` clears all of the above.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Iframe Apps (Cross-Origin)
|
|
54
|
+
|
|
55
|
+
Iframe apps don't share React context. Use the SDK's `authKit` helper —
|
|
56
|
+
it posts the framework-recognised messages on `window.parent`:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { authKit } from '@proveanything/smartlinks';
|
|
60
|
+
|
|
61
|
+
// After your custom flow succeeds:
|
|
62
|
+
await authKit.publishLogin({
|
|
63
|
+
token, // string — your API's bearer token
|
|
64
|
+
user: { // mirrors AuthUser
|
|
65
|
+
uid: 'usr_123',
|
|
66
|
+
email: 'bidder@example.com',
|
|
67
|
+
displayName: 'Jane Bidder',
|
|
68
|
+
},
|
|
69
|
+
accountData: { tier: 'gold' }, // optional, free-form
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// On sign-out:
|
|
73
|
+
await authKit.publishLogout();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Raw postMessage (fallback)
|
|
77
|
+
|
|
78
|
+
If you can't use the helper (e.g. you're outside the SDK), post the raw
|
|
79
|
+
messages from the iframe to its parent. The portal's `IframeResponder`
|
|
80
|
+
listens for these:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// LOGIN
|
|
84
|
+
window.parent.postMessage({
|
|
85
|
+
_smartlinksIframeMessage: true,
|
|
86
|
+
type: 'smartlinks:authkit:login',
|
|
87
|
+
payload: {
|
|
88
|
+
token: '<bearer>',
|
|
89
|
+
user: { uid: 'usr_123', email: 'bidder@example.com', displayName: 'Jane' },
|
|
90
|
+
accountData: { /* optional */ },
|
|
91
|
+
},
|
|
92
|
+
}, '*');
|
|
93
|
+
|
|
94
|
+
// LOGOUT
|
|
95
|
+
window.parent.postMessage({
|
|
96
|
+
_smartlinksIframeMessage: true,
|
|
97
|
+
type: 'smartlinks:authkit:logout',
|
|
98
|
+
payload: {},
|
|
99
|
+
}, '*');
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The portal-framework's `AppIframeRenderer` translates these into:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
PortalAuthProvider.login(token, user, accountData);
|
|
106
|
+
// or
|
|
107
|
+
PortalAuthProvider.logout();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
— the **same** call the built-in `AuthModal` makes when the user logs in
|
|
111
|
+
through the standard portal UI.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## What the Portal Does on Receipt
|
|
116
|
+
|
|
117
|
+
1. Updates the `IframeResponder` cache so future context syncs carry the
|
|
118
|
+
user.
|
|
119
|
+
2. Calls `PortalAuthProvider`'s bridged `login` / `logout`.
|
|
120
|
+
3. `setBearerToken` runs against the parent SDK instance — your API
|
|
121
|
+
calls are now authenticated.
|
|
122
|
+
4. The portal header avatar swaps in (or out).
|
|
123
|
+
5. Every other container/widget/iframe app observes the new session via
|
|
124
|
+
its own `useAuth()` / context-refresh.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Anti-Patterns
|
|
129
|
+
|
|
130
|
+
❌ Storing your own auth token in `localStorage` and ignoring the portal —
|
|
131
|
+
the user will see a logged-out header above your "logged in" app.
|
|
132
|
+
|
|
133
|
+
❌ Posting `smartlinks-auth-login` (with a hyphen). The correct event
|
|
134
|
+
name is `smartlinks:authkit:login` (colon-separated, namespaced).
|
|
135
|
+
|
|
136
|
+
❌ Calling `setBearerToken` directly without notifying the portal —
|
|
137
|
+
your token will work until the portal next refreshes auth, then be
|
|
138
|
+
wiped.
|
|
139
|
+
|
|
140
|
+
❌ Implementing logout by just clearing your own state. Always call
|
|
141
|
+
`useAuth().logout()` (container/widget) or `authKit.publishLogout()`
|
|
142
|
+
(iframe) so the whole portal session ends cleanly.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Reading the Current Portal Session
|
|
147
|
+
|
|
148
|
+
You don't need to broadcast just to *read* the session — that comes for
|
|
149
|
+
free:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
// Container / widget apps
|
|
153
|
+
import { useAuth } from '@proveanything/smartlinks-auth-ui';
|
|
154
|
+
const { user, token, isAuthenticated } = useAuth();
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// Iframe apps — the IframeResponder caches the parent's user under
|
|
159
|
+
// `cache.user`. Read it via the SDK's standard hooks/helpers.
|
|
160
|
+
```
|
package/dist/openapi.yaml
CHANGED
|
@@ -8589,7 +8589,7 @@ paths:
|
|
|
8589
8589
|
post:
|
|
8590
8590
|
tags:
|
|
8591
8591
|
- authKit
|
|
8592
|
-
summary: Manually verify WhatsApp token if inbound webhook path is unavailable (public).
|
|
8592
|
+
summary: Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
|
|
8593
8593
|
operationId: authKit_verifyWhatsApp
|
|
8594
8594
|
security: []
|
|
8595
8595
|
parameters:
|
|
@@ -17915,6 +17915,27 @@ components:
|
|
|
17915
17915
|
$ref: "#/components/schemas/WhatsAppReplyCta"
|
|
17916
17916
|
text:
|
|
17917
17917
|
type: string
|
|
17918
|
+
WhatsAppContactData:
|
|
17919
|
+
type: object
|
|
17920
|
+
properties:
|
|
17921
|
+
name:
|
|
17922
|
+
type: string
|
|
17923
|
+
firstName:
|
|
17924
|
+
type: string
|
|
17925
|
+
lastName:
|
|
17926
|
+
type: string
|
|
17927
|
+
displayName:
|
|
17928
|
+
type: string
|
|
17929
|
+
email:
|
|
17930
|
+
type: string
|
|
17931
|
+
source:
|
|
17932
|
+
type: string
|
|
17933
|
+
customFields:
|
|
17934
|
+
type: object
|
|
17935
|
+
additionalProperties: true
|
|
17936
|
+
externalIds:
|
|
17937
|
+
type: object
|
|
17938
|
+
additionalProperties: true
|
|
17918
17939
|
SendWhatsAppRequest:
|
|
17919
17940
|
type: object
|
|
17920
17941
|
properties:
|
|
@@ -17924,6 +17945,8 @@ components:
|
|
|
17924
17945
|
type: string
|
|
17925
17946
|
reply:
|
|
17926
17947
|
$ref: "#/components/schemas/WhatsAppReplyOptions"
|
|
17948
|
+
contactData:
|
|
17949
|
+
$ref: "#/components/schemas/WhatsAppContactData"
|
|
17927
17950
|
SendWhatsAppResponse:
|
|
17928
17951
|
type: object
|
|
17929
17952
|
properties:
|
package/dist/types/authKit.d.ts
CHANGED
|
@@ -90,10 +90,21 @@ export interface WhatsAppReplyOptions {
|
|
|
90
90
|
/** Option C: plain-text fallback */
|
|
91
91
|
text?: string;
|
|
92
92
|
}
|
|
93
|
+
export interface WhatsAppContactData {
|
|
94
|
+
name?: string;
|
|
95
|
+
firstName?: string;
|
|
96
|
+
lastName?: string;
|
|
97
|
+
displayName?: string;
|
|
98
|
+
email?: string;
|
|
99
|
+
source?: string;
|
|
100
|
+
customFields?: Record<string, unknown>;
|
|
101
|
+
externalIds?: Record<string, unknown>;
|
|
102
|
+
}
|
|
93
103
|
export interface SendWhatsAppRequest {
|
|
94
104
|
redirectUrl?: string;
|
|
95
105
|
prefillMessage?: string;
|
|
96
106
|
reply?: WhatsAppReplyOptions;
|
|
107
|
+
contactData?: WhatsAppContactData;
|
|
97
108
|
}
|
|
98
109
|
export interface SendWhatsAppResponse {
|
|
99
110
|
waLink: string;
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.16 | Generated: 2026-05-15T19:36:34.712Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -3043,12 +3043,27 @@ interface WhatsAppReplyOptions {
|
|
|
3043
3043
|
}
|
|
3044
3044
|
```
|
|
3045
3045
|
|
|
3046
|
+
**WhatsAppContactData** (interface)
|
|
3047
|
+
```typescript
|
|
3048
|
+
interface WhatsAppContactData {
|
|
3049
|
+
name?: string
|
|
3050
|
+
firstName?: string
|
|
3051
|
+
lastName?: string
|
|
3052
|
+
displayName?: string
|
|
3053
|
+
email?: string
|
|
3054
|
+
source?: string
|
|
3055
|
+
customFields?: Record<string, unknown>
|
|
3056
|
+
externalIds?: Record<string, unknown>
|
|
3057
|
+
}
|
|
3058
|
+
```
|
|
3059
|
+
|
|
3046
3060
|
**SendWhatsAppRequest** (interface)
|
|
3047
3061
|
```typescript
|
|
3048
3062
|
interface SendWhatsAppRequest {
|
|
3049
3063
|
redirectUrl?: string
|
|
3050
3064
|
prefillMessage?: string
|
|
3051
3065
|
reply?: WhatsAppReplyOptions
|
|
3066
|
+
contactData?: WhatsAppContactData
|
|
3052
3067
|
}
|
|
3053
3068
|
```
|
|
3054
3069
|
|
|
@@ -8229,7 +8244,7 @@ Verify phone verification code (public).
|
|
|
8229
8244
|
Send a WhatsApp verification deep-link (public).
|
|
8230
8245
|
|
|
8231
8246
|
**verifyWhatsApp**(clientId: string, token: string, phoneNumber: string) → `Promise<VerifyWhatsAppResponse>`
|
|
8232
|
-
Manually verify WhatsApp token if inbound webhook path is unavailable (public).
|
|
8247
|
+
Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
|
|
8233
8248
|
|
|
8234
8249
|
**getWhatsAppStatus**(clientId: string, token: string) → `Promise<WhatsAppStatusResponse>`
|
|
8235
8250
|
Poll WhatsApp verification status for a token (public).
|
package/docs/auth-kit.md
CHANGED
|
@@ -82,6 +82,8 @@ const session = await authKit.verifyPhoneCode(clientId, '+61400000000', '123456'
|
|
|
82
82
|
|
|
83
83
|
Use these flows when you want low-friction verification before or without full account sign-in.
|
|
84
84
|
|
|
85
|
+
WhatsApp verification is token-first. The user does not type their phone number in your app for this flow; phone ownership is proven by the inbound WhatsApp sender number.
|
|
86
|
+
|
|
85
87
|
```ts
|
|
86
88
|
import { authKit } from '@proveanything/smartlinks';
|
|
87
89
|
|
|
@@ -92,6 +94,12 @@ const wa = await authKit.sendWhatsApp(clientId);
|
|
|
92
94
|
// const wa = await authKit.sendWhatsApp(clientId, {
|
|
93
95
|
// redirectUrl: 'https://app.example.com/checkout/continue',
|
|
94
96
|
// prefillMessage: 'Please let me bid in this auction. Code: {{token}}',
|
|
97
|
+
// contactData: {
|
|
98
|
+
// name: 'Jane Doe',
|
|
99
|
+
// email: 'jane@example.com',
|
|
100
|
+
// source: 'auction-checkout',
|
|
101
|
+
// customFields: { agreedToTerms: true },
|
|
102
|
+
// },
|
|
95
103
|
// reply: {
|
|
96
104
|
// cta: {
|
|
97
105
|
// body: "You're verified and ready to bid.",
|
|
@@ -112,7 +120,7 @@ if (status.status === 'verified' && wa.sessionKey) {
|
|
|
112
120
|
// session.token can be used as the authenticated bearer token
|
|
113
121
|
}
|
|
114
122
|
|
|
115
|
-
// Optional fallback path if webhook confirmation is unavailable
|
|
123
|
+
// Optional legacy fallback path if webhook confirmation is unavailable
|
|
116
124
|
await authKit.verifyWhatsApp(clientId, wa.token, '+447911123456');
|
|
117
125
|
|
|
118
126
|
// 2) Or send SMS click-to-verify link
|
|
@@ -126,6 +134,13 @@ await authKit.sendSmsVerify(clientId, {
|
|
|
126
134
|
await authKit.verifySms(clientId, '<token>', '+447911123456');
|
|
127
135
|
```
|
|
128
136
|
|
|
137
|
+
`contactData` is optional and is useful when you collect name/email before the customer switches to WhatsApp.
|
|
138
|
+
|
|
139
|
+
- Auth Kit stores `contactData` on the verification token metadata first.
|
|
140
|
+
- Contact details are written to durable contact storage only after WhatsApp verification succeeds.
|
|
141
|
+
- If the user abandons before verification, no contact is created.
|
|
142
|
+
- `contactData` must not include phone; the verified inbound WhatsApp sender number is always authoritative.
|
|
143
|
+
|
|
129
144
|
### Contact bootstrap / durable identity
|
|
130
145
|
|
|
131
146
|
After verification, upsert contact identity and store `contactId` on downstream records (raffle ticket, bid, claim intent).
|
|
@@ -183,6 +198,8 @@ const session = await authKit.exchangeWhatsAppSession(clientId, wa.token, wa.ses
|
|
|
183
198
|
`sessionKey` is returned by `sendWhatsApp` and is used to mitigate token replay from contexts that did not initiate the browser flow.
|
|
184
199
|
|
|
185
200
|
> **Note:** `redirectUrl` is optional. WhatsApp tokens are short hex strings (16 chars) for better UX.
|
|
201
|
+
>
|
|
202
|
+
> **Legacy note:** `verifyWhatsApp` is for older phone-bound token flows. Prefer inbound WhatsApp token confirmation plus status polling for new implementations.
|
|
186
203
|
|
|
187
204
|
### Google OAuth
|
|
188
205
|
|
|
@@ -309,6 +309,7 @@ Now that you understand the core concepts, see the implementation guides:
|
|
|
309
309
|
- **[mpa.md](./mpa.md)** — Multi-page app architecture (optional)
|
|
310
310
|
- **[iframe-responder.md](./iframe-responder.md)** — Parent-side iframe integration
|
|
311
311
|
- **[portal-back-button.md](./portal-back-button.md)** — Embedded-app "up" navigation and portal back-button behaviour
|
|
312
|
+
- **[portal-auth-broadcast.md](./portal-auth-broadcast.md)** — Publishing custom auth flows to the portal
|
|
312
313
|
|
|
313
314
|
---
|
|
314
315
|
|
|
@@ -318,4 +319,5 @@ Now that you understand the core concepts, see the implementation guides:
|
|
|
318
319
|
- [ai.md](./ai.md) — AI assistant integration
|
|
319
320
|
- [deep-link-discovery.md](./deep-link-discovery.md) — Deep linking patterns
|
|
320
321
|
- [portal-back-button.md](./portal-back-button.md) — Portal shell back-button behavior for hierarchical embeds
|
|
322
|
+
- [portal-auth-broadcast.md](./portal-auth-broadcast.md) — Custom authentication and session broadcast
|
|
321
323
|
- [manifests.md](./manifests.md) — App manifest configuration
|
package/docs/overview.md
CHANGED
|
@@ -74,6 +74,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
74
74
|
| **AI Guide Template** | `docs/ai-guide-template.md` | Template for creating `public/ai-guide.md` — customise per app |
|
|
75
75
|
| **Forms** | `docs/forms.md` | Form definitions, schema-driven rendering, submission patterns |
|
|
76
76
|
| **Auth Kit** | `docs/auth-kit.md` | End-user sign-in: email/password, magic links, phone OTP, Google OAuth |
|
|
77
|
+
| **Portal Auth Broadcast** | `docs/portal-auth-broadcast.md` | Publishing custom auth flows to the portal; syncing sessions across containers and iframes |
|
|
77
78
|
| **App Records Pattern** | `docs/app-records-pattern.md` | Standard pattern for per-product/facet/variant/batch admin + public widget UIs |
|
|
78
79
|
| **UI Utils** | `docs/ui-utils.md` | `@proveanything/smartlinks-utils-ui` — React shells, hooks, and primitives for records-based apps |
|
|
79
80
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Publishing Auth State to the Portal
|
|
2
|
+
|
|
3
|
+
> **For sub-app authors.** This guide explains how to broadcast authentication state changes to the portal so the header, account UI, and sibling apps stay in sync.
|
|
4
|
+
|
|
5
|
+
A SmartLinks micro-app may need to run its own custom authentication flow —
|
|
6
|
+
typical cases: an auction app that calls a bidder API, a competition app
|
|
7
|
+
that exchanges a magic code for a session, a partner SSO. When that flow
|
|
8
|
+
completes the app holds a `token` + `user` of its own, and the **portal**
|
|
9
|
+
needs to know about it so the header, the account UI, and every sibling
|
|
10
|
+
app pick up the new session.
|
|
11
|
+
|
|
12
|
+
This doc describes the contract the portal framework already implements.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Container / Widget Apps (Same React Tree)
|
|
17
|
+
|
|
18
|
+
These run inside the portal's React context and share its `AuthProvider`.
|
|
19
|
+
Call `useAuth()` directly:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { useAuth } from '@proveanything/smartlinks-auth-ui';
|
|
23
|
+
|
|
24
|
+
function BidButton() {
|
|
25
|
+
const { login, logout, isAuthenticated, user } = useAuth();
|
|
26
|
+
|
|
27
|
+
async function placeBid() {
|
|
28
|
+
if (!isAuthenticated) {
|
|
29
|
+
// Run your own auth flow…
|
|
30
|
+
const { token, user, expiresAt } = await api.signInBidder(...);
|
|
31
|
+
// Publish to the portal — this is the *only* call you need:
|
|
32
|
+
await login(token, user, { source: 'auction' }, /* isNewUser */ false, expiresAt);
|
|
33
|
+
}
|
|
34
|
+
await api.placeBid(...);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return <button onClick={placeBid}>Bid</button>;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`useAuth().login(token, user, accountData?, isNewUser?, expiresAt?)` will:
|
|
42
|
+
|
|
43
|
+
- Persist the session (cookie/storage as configured).
|
|
44
|
+
- Call `setBearerToken(token)` on the SDK so subsequent API calls are
|
|
45
|
+
authenticated.
|
|
46
|
+
- Re-render every consumer of `useAuth()` / `useSafeAuth()` —
|
|
47
|
+
including the portal header.
|
|
48
|
+
|
|
49
|
+
`useAuth().logout()` clears all of the above.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Iframe Apps (Cross-Origin)
|
|
54
|
+
|
|
55
|
+
Iframe apps don't share React context. Use the SDK's `authKit` helper —
|
|
56
|
+
it posts the framework-recognised messages on `window.parent`:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { authKit } from '@proveanything/smartlinks';
|
|
60
|
+
|
|
61
|
+
// After your custom flow succeeds:
|
|
62
|
+
await authKit.publishLogin({
|
|
63
|
+
token, // string — your API's bearer token
|
|
64
|
+
user: { // mirrors AuthUser
|
|
65
|
+
uid: 'usr_123',
|
|
66
|
+
email: 'bidder@example.com',
|
|
67
|
+
displayName: 'Jane Bidder',
|
|
68
|
+
},
|
|
69
|
+
accountData: { tier: 'gold' }, // optional, free-form
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// On sign-out:
|
|
73
|
+
await authKit.publishLogout();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Raw postMessage (fallback)
|
|
77
|
+
|
|
78
|
+
If you can't use the helper (e.g. you're outside the SDK), post the raw
|
|
79
|
+
messages from the iframe to its parent. The portal's `IframeResponder`
|
|
80
|
+
listens for these:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// LOGIN
|
|
84
|
+
window.parent.postMessage({
|
|
85
|
+
_smartlinksIframeMessage: true,
|
|
86
|
+
type: 'smartlinks:authkit:login',
|
|
87
|
+
payload: {
|
|
88
|
+
token: '<bearer>',
|
|
89
|
+
user: { uid: 'usr_123', email: 'bidder@example.com', displayName: 'Jane' },
|
|
90
|
+
accountData: { /* optional */ },
|
|
91
|
+
},
|
|
92
|
+
}, '*');
|
|
93
|
+
|
|
94
|
+
// LOGOUT
|
|
95
|
+
window.parent.postMessage({
|
|
96
|
+
_smartlinksIframeMessage: true,
|
|
97
|
+
type: 'smartlinks:authkit:logout',
|
|
98
|
+
payload: {},
|
|
99
|
+
}, '*');
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The portal-framework's `AppIframeRenderer` translates these into:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
PortalAuthProvider.login(token, user, accountData);
|
|
106
|
+
// or
|
|
107
|
+
PortalAuthProvider.logout();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
— the **same** call the built-in `AuthModal` makes when the user logs in
|
|
111
|
+
through the standard portal UI.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## What the Portal Does on Receipt
|
|
116
|
+
|
|
117
|
+
1. Updates the `IframeResponder` cache so future context syncs carry the
|
|
118
|
+
user.
|
|
119
|
+
2. Calls `PortalAuthProvider`'s bridged `login` / `logout`.
|
|
120
|
+
3. `setBearerToken` runs against the parent SDK instance — your API
|
|
121
|
+
calls are now authenticated.
|
|
122
|
+
4. The portal header avatar swaps in (or out).
|
|
123
|
+
5. Every other container/widget/iframe app observes the new session via
|
|
124
|
+
its own `useAuth()` / context-refresh.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Anti-Patterns
|
|
129
|
+
|
|
130
|
+
❌ Storing your own auth token in `localStorage` and ignoring the portal —
|
|
131
|
+
the user will see a logged-out header above your "logged in" app.
|
|
132
|
+
|
|
133
|
+
❌ Posting `smartlinks-auth-login` (with a hyphen). The correct event
|
|
134
|
+
name is `smartlinks:authkit:login` (colon-separated, namespaced).
|
|
135
|
+
|
|
136
|
+
❌ Calling `setBearerToken` directly without notifying the portal —
|
|
137
|
+
your token will work until the portal next refreshes auth, then be
|
|
138
|
+
wiped.
|
|
139
|
+
|
|
140
|
+
❌ Implementing logout by just clearing your own state. Always call
|
|
141
|
+
`useAuth().logout()` (container/widget) or `authKit.publishLogout()`
|
|
142
|
+
(iframe) so the whole portal session ends cleanly.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Reading the Current Portal Session
|
|
147
|
+
|
|
148
|
+
You don't need to broadcast just to *read* the session — that comes for
|
|
149
|
+
free:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
// Container / widget apps
|
|
153
|
+
import { useAuth } from '@proveanything/smartlinks-auth-ui';
|
|
154
|
+
const { user, token, isAuthenticated } = useAuth();
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// Iframe apps — the IframeResponder caches the parent's user under
|
|
159
|
+
// `cache.user`. Read it via the SDK's standard hooks/helpers.
|
|
160
|
+
```
|
package/openapi.yaml
CHANGED
|
@@ -8589,7 +8589,7 @@ paths:
|
|
|
8589
8589
|
post:
|
|
8590
8590
|
tags:
|
|
8591
8591
|
- authKit
|
|
8592
|
-
summary: Manually verify WhatsApp token if inbound webhook path is unavailable (public).
|
|
8592
|
+
summary: Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
|
|
8593
8593
|
operationId: authKit_verifyWhatsApp
|
|
8594
8594
|
security: []
|
|
8595
8595
|
parameters:
|
|
@@ -17915,6 +17915,27 @@ components:
|
|
|
17915
17915
|
$ref: "#/components/schemas/WhatsAppReplyCta"
|
|
17916
17916
|
text:
|
|
17917
17917
|
type: string
|
|
17918
|
+
WhatsAppContactData:
|
|
17919
|
+
type: object
|
|
17920
|
+
properties:
|
|
17921
|
+
name:
|
|
17922
|
+
type: string
|
|
17923
|
+
firstName:
|
|
17924
|
+
type: string
|
|
17925
|
+
lastName:
|
|
17926
|
+
type: string
|
|
17927
|
+
displayName:
|
|
17928
|
+
type: string
|
|
17929
|
+
email:
|
|
17930
|
+
type: string
|
|
17931
|
+
source:
|
|
17932
|
+
type: string
|
|
17933
|
+
customFields:
|
|
17934
|
+
type: object
|
|
17935
|
+
additionalProperties: true
|
|
17936
|
+
externalIds:
|
|
17937
|
+
type: object
|
|
17938
|
+
additionalProperties: true
|
|
17918
17939
|
SendWhatsAppRequest:
|
|
17919
17940
|
type: object
|
|
17920
17941
|
properties:
|
|
@@ -17924,6 +17945,8 @@ components:
|
|
|
17924
17945
|
type: string
|
|
17925
17946
|
reply:
|
|
17926
17947
|
$ref: "#/components/schemas/WhatsAppReplyOptions"
|
|
17948
|
+
contactData:
|
|
17949
|
+
$ref: "#/components/schemas/WhatsAppContactData"
|
|
17927
17950
|
SendWhatsAppResponse:
|
|
17928
17951
|
type: object
|
|
17929
17952
|
properties:
|