@frontera-sdk/chat 1.52.43 → 1.52.45
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/package.json +6 -2
- package/src/public-token.ts +79 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontera-sdk/chat",
|
|
3
|
-
"version": "1.52.
|
|
3
|
+
"version": "1.52.45",
|
|
4
4
|
"description": "Typed client and React hooks for chatting with a Frontera agent from your own application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"frontera",
|
|
@@ -50,6 +50,10 @@
|
|
|
50
50
|
"types": "./src/token-session.ts",
|
|
51
51
|
"import": "./src/token-session.ts"
|
|
52
52
|
},
|
|
53
|
+
"./public-token": {
|
|
54
|
+
"types": "./src/public-token.ts",
|
|
55
|
+
"import": "./src/public-token.ts"
|
|
56
|
+
},
|
|
53
57
|
"./callback-page": {
|
|
54
58
|
"types": "./src/callback-page.ts",
|
|
55
59
|
"import": "./src/callback-page.ts"
|
|
@@ -77,7 +81,7 @@
|
|
|
77
81
|
"smoke": "bun run scripts/smoke.ts"
|
|
78
82
|
},
|
|
79
83
|
"dependencies": {
|
|
80
|
-
"@frontera-sdk/core": "1.52.
|
|
84
|
+
"@frontera-sdk/core": "1.52.45"
|
|
81
85
|
},
|
|
82
86
|
"peerDependencies": {
|
|
83
87
|
"@ai-sdk/react": "^3.0.0",
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start a chat for an anonymous visitor of a public website, from the browser.
|
|
3
|
+
*
|
|
4
|
+
* A public website key (`fpk_…`) is made to sit in page JavaScript: it reaches
|
|
5
|
+
* one Agent, only from the origins listed on it, and only to mint a short-lived
|
|
6
|
+
* token that chats and does nothing else. So, unlike the server exchange in
|
|
7
|
+
* `auth.ts`, this runs where the visitor is and needs no backend of your own.
|
|
8
|
+
*
|
|
9
|
+
* The platform names the visitor, not the page. What comes back with the first
|
|
10
|
+
* token is a visitor pass; this keeps it in `localStorage` and shows it at the
|
|
11
|
+
* next mint, so a returning visitor gets their conversations back. Storage can
|
|
12
|
+
* be unavailable (a private window, blocked site data) — the chat then still
|
|
13
|
+
* works, and each page load is simply a new visitor.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { errorFromResponse } from '@frontera-sdk/core/errors'
|
|
17
|
+
|
|
18
|
+
import type { ChatToken } from './types'
|
|
19
|
+
|
|
20
|
+
export interface PublicChatTokenOptions {
|
|
21
|
+
/** Your deployment's API origin, for example `https://api.acme.example`. */
|
|
22
|
+
apiBaseUrl: string
|
|
23
|
+
/** The Agent's public website key, `fpk_…`. */
|
|
24
|
+
publishableKey: string
|
|
25
|
+
/** Test seam. Defaults to the global. */
|
|
26
|
+
fetchImpl?: typeof fetch
|
|
27
|
+
/** Test seam. Defaults to `localStorage`, when there is one. */
|
|
28
|
+
storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One pass per key, so two Agents on one site never share a visitor. */
|
|
32
|
+
export function visitorPassStorageKey(publishableKey: string): string {
|
|
33
|
+
return `frontera:visitor:${publishableKey.slice(0, 12)}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function defaultStorage(): PublicChatTokenOptions['storage'] {
|
|
37
|
+
try {
|
|
38
|
+
return typeof localStorage === 'undefined' ? null : localStorage
|
|
39
|
+
} catch {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function read(storage: PublicChatTokenOptions['storage'], key: string): string | null {
|
|
45
|
+
try {
|
|
46
|
+
return storage?.getItem(key) ?? null
|
|
47
|
+
} catch {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function write(storage: PublicChatTokenOptions['storage'], key: string, value: string): void {
|
|
53
|
+
try {
|
|
54
|
+
storage?.setItem(key, value)
|
|
55
|
+
} catch {
|
|
56
|
+
// Full or blocked: the next visit is a new visitor, nothing more.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function mintPublicChatToken(options: PublicChatTokenOptions): Promise<ChatToken> {
|
|
61
|
+
const storage = options.storage === undefined ? defaultStorage() : options.storage
|
|
62
|
+
const passKey = visitorPassStorageKey(options.publishableKey)
|
|
63
|
+
const visitorPass = read(storage, passKey)
|
|
64
|
+
|
|
65
|
+
const response = await (options.fetchImpl ?? fetch)(`${options.apiBaseUrl}/v1/chat-tokens/public`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: { 'content-type': 'application/json' },
|
|
68
|
+
// Never `include`: this call carries the key, not anyone's cookies.
|
|
69
|
+
credentials: 'omit',
|
|
70
|
+
body: JSON.stringify({ publishableKey: options.publishableKey, ...(visitorPass ? { visitorPass } : {}) }),
|
|
71
|
+
})
|
|
72
|
+
const body = (await response.json().catch(() => null)) as
|
|
73
|
+
| { data?: { token: string; expiresAt: string; visitorPass?: string } }
|
|
74
|
+
| null
|
|
75
|
+
if (!response.ok || !body?.data) throw errorFromResponse(response.status, body)
|
|
76
|
+
|
|
77
|
+
if (body.data.visitorPass) write(storage, passKey, body.data.visitorPass)
|
|
78
|
+
return { token: body.data.token, expiresAt: body.data.expiresAt }
|
|
79
|
+
}
|