@paramms/chat-widget 0.1.0 → 1.0.0
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/README.md +138 -12
- package/dist/connection.d.ts +48 -0
- package/dist/crypto.d.ts +69 -0
- package/dist/e2e.d.ts +75 -0
- package/dist/history.d.ts +14 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +2597 -0
- package/dist/index.js.map +1 -0
- package/dist/outbox.d.ts +20 -0
- package/dist/protocol/actions.d.ts +98 -0
- package/dist/protocol/codec.d.ts +12 -0
- package/dist/protocol/entities.d.ts +109 -0
- package/dist/protocol/frames.d.ts +248 -0
- package/dist/protocol/ids.d.ts +22 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/react.d.ts +109 -0
- package/dist/react.js +137 -0
- package/dist/react.js.map +1 -0
- package/dist/renderer.d.ts +159 -0
- package/dist/store.d.ts +48 -0
- package/package.json +24 -1
- package/build-preview.js +0 -136
- package/index.html +0 -37
- package/src/__tests__/chatlist.test.ts +0 -133
- package/src/__tests__/connection.test.ts +0 -163
- package/src/__tests__/crypto.test.ts +0 -28
- package/src/__tests__/history.test.ts +0 -91
- package/src/__tests__/ime.test.ts +0 -93
- package/src/__tests__/render.test.ts +0 -58
- package/src/__tests__/render_new.test.ts +0 -441
- package/src/__tests__/store.test.ts +0 -86
- package/src/__tests__/x3dh.test.ts +0 -204
- package/src/connection.ts +0 -133
- package/src/crypto.ts +0 -252
- package/src/e2e.ts +0 -161
- package/src/history.ts +0 -43
- package/src/index.ts +0 -380
- package/src/outbox.ts +0 -58
- package/src/protocol/actions.ts +0 -114
- package/src/protocol/codec.ts +0 -35
- package/src/protocol/entities.ts +0 -104
- package/src/protocol/frames.ts +0 -86
- package/src/protocol/ids.ts +0 -27
- package/src/protocol/index.ts +0 -5
- package/src/react.tsx +0 -37
- package/src/renderer.ts +0 -906
- package/src/store.ts +0 -207
- package/tsconfig.json +0 -33
- package/vercel.json +0 -22
- package/vite.config.ts +0 -26
- package/vitest.config.ts +0 -2
package/README.md
CHANGED
|
@@ -1,23 +1,149 @@
|
|
|
1
1
|
# @paramms/chat-widget
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
(ws/reconnect/outbox/cursor) + thin `Renderer`, wired by `mount()`.
|
|
3
|
+
Real-time embeddable chat widget for the Relay platform. Drop into any website with a single script tag — no build step required.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
## Install
|
|
7
6
|
|
|
8
|
-
## Run
|
|
9
7
|
```bash
|
|
10
|
-
npm install
|
|
11
|
-
npm run dev # Vite demo at http://localhost:5173/ (expects the server on ws://localhost:3000)
|
|
12
|
-
npm run build # tsc → dist/ (library: importable by the dashboard)
|
|
13
|
-
npm run build:bundle # vite → standalone browser bundle
|
|
14
|
-
npm test # vitest (store, connection, jsdom render)
|
|
8
|
+
npm install @paramms/chat-widget
|
|
15
9
|
```
|
|
16
10
|
|
|
17
|
-
##
|
|
11
|
+
## Quick start — CDN (no npm, no build)
|
|
12
|
+
|
|
18
13
|
```html
|
|
14
|
+
<div id="chat"></div>
|
|
19
15
|
<script type="module">
|
|
20
|
-
import { mount } from '
|
|
21
|
-
mount({
|
|
16
|
+
import { mount } from 'https://relay.paramms.com/index.js'
|
|
17
|
+
mount({
|
|
18
|
+
el: document.getElementById('chat'),
|
|
19
|
+
url: 'wss://api.paramms.com/ws',
|
|
20
|
+
profileId: 'YOUR_PROFILE_ID',
|
|
21
|
+
})
|
|
22
22
|
</script>
|
|
23
23
|
```
|
|
24
|
+
|
|
25
|
+
## React / Next.js
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
'use client'
|
|
29
|
+
import { ChatWidget } from '@paramms/chat-widget/react'
|
|
30
|
+
|
|
31
|
+
export default function SupportChat() {
|
|
32
|
+
return (
|
|
33
|
+
<ChatWidget
|
|
34
|
+
url="wss://api.paramms.com/ws"
|
|
35
|
+
profileId="YOUR_PROFILE_ID"
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Identified users (no separate login needed)
|
|
42
|
+
|
|
43
|
+
Pass your own user's ID as the token and their details via `user`. Anonymous users work without any configuration.
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<ChatWidget
|
|
47
|
+
url="wss://api.paramms.com/ws"
|
|
48
|
+
profileId="YOUR_PROFILE_ID"
|
|
49
|
+
token={currentUser.id} // your own stable user ID — ties history across devices
|
|
50
|
+
user={{
|
|
51
|
+
name: currentUser.name, // shown to agents instead of opaque ID
|
|
52
|
+
email: currentUser.email, // agents can follow up even after disconnect
|
|
53
|
+
avatar: currentUser.avatarUrl,
|
|
54
|
+
meta: {
|
|
55
|
+
plan: currentUser.plan,
|
|
56
|
+
accountId: currentUser.id,
|
|
57
|
+
},
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Cryptographically verified identity (Tier 4)
|
|
63
|
+
|
|
64
|
+
For marketplaces and financial services — the guest's identity is verified against your ECDSA key so it cannot be spoofed.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// 1. Generate a key pair (once, server-side)
|
|
68
|
+
// openssl ecparam -genkey -name prime256v1 -noout | openssl pkcs8 -topk8 -nocrypt -out private.pem
|
|
69
|
+
// openssl ec -in private.pem -pubout | base64 -w0 → paste in Dashboard → Domain → Guest identity linking
|
|
70
|
+
|
|
71
|
+
// 2. Sign a token per user (your backend)
|
|
72
|
+
import { createSign } from 'node:crypto'
|
|
73
|
+
const hdr = Buffer.from(JSON.stringify({ alg: 'ES256', typ: 'JWT' })).toString('base64url')
|
|
74
|
+
const pay = Buffer.from(JSON.stringify({ sub: userId, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600 })).toString('base64url')
|
|
75
|
+
const sig = createSign('SHA256').update(`${hdr}.${pay}`).sign(privateKey, 'base64url')
|
|
76
|
+
const signedToken = `${hdr}.${pay}.${sig}`
|
|
77
|
+
|
|
78
|
+
// 3. Pass to widget — server verifies the signature automatically
|
|
79
|
+
<ChatWidget url="..." profileId="..." token={signedToken} />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Marketplace / multi-item (one thread per listing)
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<ChatWidget
|
|
86
|
+
url="wss://api.paramms.com/ws"
|
|
87
|
+
profileId="YOUR_PROFILE_ID"
|
|
88
|
+
subjectId={`car_${listing.id}`} // one conversation per item
|
|
89
|
+
showChatList={true} // guest can switch between their threads
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Launcher (floating button)
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<ChatWidget
|
|
97
|
+
url="wss://api.paramms.com/ws"
|
|
98
|
+
profileId="YOUR_PROFILE_ID"
|
|
99
|
+
launcher={true}
|
|
100
|
+
position="bottom-right"
|
|
101
|
+
accent="#4F63F5"
|
|
102
|
+
/>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Internationalisation
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<ChatWidget
|
|
109
|
+
url="wss://api.paramms.com/ws"
|
|
110
|
+
profileId="YOUR_PROFILE_ID"
|
|
111
|
+
i18n={{
|
|
112
|
+
placeholder: 'Écrivez un message…',
|
|
113
|
+
send: 'Envoyer',
|
|
114
|
+
offline: 'Nous sommes hors ligne pour l\'instant',
|
|
115
|
+
poweredBy: '', // empty string hides the footer
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
RTL is detected automatically for Arabic, Hebrew, Persian and Urdu browsers.
|
|
121
|
+
|
|
122
|
+
## All options
|
|
123
|
+
|
|
124
|
+
| Option | Type | Default | Description |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `el` | `HTMLElement` | required | Mount target |
|
|
127
|
+
| `url` | `string` | required | WebSocket URL (`wss://...`) |
|
|
128
|
+
| `profileId` | `string` | required | Domain profile ID |
|
|
129
|
+
| `token` | `string` | auto-generated | Guest identity token — pass your user's stable ID to tie history across devices |
|
|
130
|
+
| `user` | `UserInfo` | — | Name, email, avatar, custom metadata — shown to agents |
|
|
131
|
+
| `subjectId` | `string` | — | Item ID for marketplace mode |
|
|
132
|
+
| `showChatList` | `boolean` | `false` | Show conversation switcher in header |
|
|
133
|
+
| `accent` | `string` | `#f5713c` | Brand colour (hex) |
|
|
134
|
+
| `launcher` | `boolean` | `false` | Render as floating button |
|
|
135
|
+
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Launcher position |
|
|
136
|
+
| `translateLang` | `string` | — | Auto-translate incoming messages (ISO language code) |
|
|
137
|
+
| `quickReplies` | `string[]` | — | Pre-set reply chips shown above input |
|
|
138
|
+
| `i18n` | `I18nStrings` | English | UI string overrides |
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm install
|
|
144
|
+
npm run dev # Vite preview at http://localhost:5174/
|
|
145
|
+
npm run build # tsc → dist/
|
|
146
|
+
npm run build:bundle # Vite → standalone CDN bundle
|
|
147
|
+
npm test # 75 tests via vitest
|
|
148
|
+
npm run typecheck
|
|
149
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ClientFrame, type ServerFrame } from './protocol/index.js';
|
|
2
|
+
export interface SocketLike {
|
|
3
|
+
binaryType: string;
|
|
4
|
+
send(data: Uint8Array): void;
|
|
5
|
+
close(): void;
|
|
6
|
+
onopen: (() => void) | null;
|
|
7
|
+
onclose: (() => void) | null;
|
|
8
|
+
onerror: (() => void) | null;
|
|
9
|
+
onmessage: ((ev: {
|
|
10
|
+
data: ArrayBuffer;
|
|
11
|
+
}) => void) | null;
|
|
12
|
+
}
|
|
13
|
+
export type SocketFactory = (url: string) => SocketLike;
|
|
14
|
+
export interface ConnectionOptions {
|
|
15
|
+
url: string;
|
|
16
|
+
token: string;
|
|
17
|
+
open: Extract<ClientFrame, {
|
|
18
|
+
type: 'open';
|
|
19
|
+
}>;
|
|
20
|
+
onFrame: (frame: ServerFrame) => void;
|
|
21
|
+
getCursor: () => number;
|
|
22
|
+
onStatusChange?: (status: 'connecting' | 'open' | 'reconnecting') => void;
|
|
23
|
+
socketFactory?: SocketFactory;
|
|
24
|
+
backoffBaseMs?: number;
|
|
25
|
+
backoffMaxMs?: number;
|
|
26
|
+
maxOutbox?: number;
|
|
27
|
+
}
|
|
28
|
+
export declare class ConnectionManager {
|
|
29
|
+
private readonly opts;
|
|
30
|
+
private socket;
|
|
31
|
+
private state;
|
|
32
|
+
private authed;
|
|
33
|
+
private attempt;
|
|
34
|
+
private outbox;
|
|
35
|
+
private stopped;
|
|
36
|
+
private timer;
|
|
37
|
+
constructor(opts: ConnectionOptions);
|
|
38
|
+
connect(): void;
|
|
39
|
+
/** Queue a frame; sent immediately if open, else flushed on (re)connect.
|
|
40
|
+
* The outbox is bounded so a prolonged outage can't grow memory without limit
|
|
41
|
+
* — oldest queued frames are dropped past the cap. */
|
|
42
|
+
send(frame: ClientFrame): void;
|
|
43
|
+
close(): void;
|
|
44
|
+
private handle;
|
|
45
|
+
private flush;
|
|
46
|
+
private raw;
|
|
47
|
+
private onClosed;
|
|
48
|
+
}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end encryption primitives (Web Crypto): ECDH P-256 for key agreement
|
|
3
|
+
* + AES-GCM for message content. The server only ever relays public keys and
|
|
4
|
+
* stores ciphertext — it cannot read messages.
|
|
5
|
+
*
|
|
6
|
+
* Scope/limitations (honest): this secures a *live 1:1* session — the guest and
|
|
7
|
+
* one agent exchange public keys while both are connected, then messages between
|
|
8
|
+
* them are encrypted. True asynchronous E2E (encrypting to an offline party)
|
|
9
|
+
* needs a prekey/X3DH scheme, which is out of scope here. When E2E is on, the
|
|
10
|
+
* AI assistant cannot read the room (by design).
|
|
11
|
+
*/
|
|
12
|
+
export interface KeyPair {
|
|
13
|
+
publicKey: CryptoKey;
|
|
14
|
+
privateKey: CryptoKey;
|
|
15
|
+
}
|
|
16
|
+
export declare function generateKeyPair(): Promise<KeyPair>;
|
|
17
|
+
/** Export a public key to a compact base64 string (raw, 65 bytes for P-256). */
|
|
18
|
+
export declare function exportPublicKey(key: CryptoKey): Promise<string>;
|
|
19
|
+
/** Derive the shared AES-GCM key from our private key + the peer's public key. */
|
|
20
|
+
export declare function deriveSharedKey(privateKey: CryptoKey, peerPublicKeyB64: string): Promise<CryptoKey>;
|
|
21
|
+
export interface Ciphertext {
|
|
22
|
+
ct: string;
|
|
23
|
+
iv: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function encrypt(key: CryptoKey, plaintext: string): Promise<Ciphertext>;
|
|
26
|
+
export declare function decrypt(key: CryptoKey, ct: string, iv: string): Promise<string>;
|
|
27
|
+
/** Persist/restore our keypair across reloads (so prior ciphertext stays readable). */
|
|
28
|
+
export declare function loadOrCreateKeyPair(storageKey: string): Promise<KeyPair>;
|
|
29
|
+
/** Sign a prekey public key bytes using ECDSA P-256 SHA-256.
|
|
30
|
+
* The signingKey must be an ECDSA P-256 private key (not ECDH).
|
|
31
|
+
* In the full X3DH setup the identity key pair contains both an ECDH key
|
|
32
|
+
* (for DH) and an ECDSA key (for signing). We keep them separate here. */
|
|
33
|
+
export declare function signPrekey(signingPrivateKey: CryptoKey, spkPublicKey: CryptoKey): Promise<string>;
|
|
34
|
+
/** Verify an SPK signature. verifyPublicKey must be an ECDSA P-256 public key. */
|
|
35
|
+
export declare function verifyPrekeySignature(verifyPublicKeyB64: string, spkPublicKeyB64: string, signatureB64: string): Promise<boolean>;
|
|
36
|
+
/** A full identity keypair for X3DH: ECDH key for DH computations + ECDSA key
|
|
37
|
+
* for signing prekeys. The two key objects share the same P-256 curve but have
|
|
38
|
+
* different usages, so Web Crypto treats them separately. */
|
|
39
|
+
export interface IdentityKeyPair {
|
|
40
|
+
ecdhKP: KeyPair;
|
|
41
|
+
ecdsaKP: {
|
|
42
|
+
publicKey: CryptoKey;
|
|
43
|
+
privateKey: CryptoKey;
|
|
44
|
+
};
|
|
45
|
+
/** The ECDH public key exported as base64 — used as the X3DH identity key. */
|
|
46
|
+
publicKeyB64: string;
|
|
47
|
+
/** The ECDSA public key exported as base64 — used for SPK signature verification. */
|
|
48
|
+
sigPublicKeyB64: string;
|
|
49
|
+
}
|
|
50
|
+
/** Generate a full X3DH identity keypair (ECDH + ECDSA on the same P-256 curve). */
|
|
51
|
+
export declare function generateIdentityKeyPair(): Promise<IdentityKeyPair>;
|
|
52
|
+
/** Load or generate an identity keypair, persisting both components. */
|
|
53
|
+
export declare function loadOrCreateIdentityKeyPair(storageKey: string): Promise<IdentityKeyPair>;
|
|
54
|
+
export interface X3DHBundle {
|
|
55
|
+
identityKey: string;
|
|
56
|
+
signedPrekey: string;
|
|
57
|
+
signedPrekeyId: string;
|
|
58
|
+
signature: string;
|
|
59
|
+
oneTimePrekey?: string;
|
|
60
|
+
}
|
|
61
|
+
/** X3DH sender side: derive a shared key from the recipient's prekey bundle.
|
|
62
|
+
* Returns the shared AES-GCM key and the ephemeral public key to transmit. */
|
|
63
|
+
export declare function x3dhSend(senderIK: KeyPair, recipientBundle: X3DHBundle): Promise<{
|
|
64
|
+
sharedKey: CryptoKey;
|
|
65
|
+
ephemeralPublicKey: string;
|
|
66
|
+
}>;
|
|
67
|
+
/** X3DH recipient side: rederive the shared key from an init message.
|
|
68
|
+
* Returns the shared AES-GCM key. */
|
|
69
|
+
export declare function x3dhReceive(recipientIK: KeyPair, recipientSPK: KeyPair, senderIKb64: string, ephemeralKeyB64: string, recipientOPK?: KeyPair): Promise<CryptoKey>;
|
package/dist/e2e.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { MessageContent, UserId } from './protocol/index.js';
|
|
2
|
+
import type { ServerFrame } from './protocol/index.js';
|
|
3
|
+
import { type X3DHBundle } from './crypto.js';
|
|
4
|
+
/**
|
|
5
|
+
* Per-user E2E session. Supports two modes:
|
|
6
|
+
*
|
|
7
|
+
* LIVE (original): Both parties are online. ECDH P-256 key exchange via the
|
|
8
|
+
* `pubkey`/`peerkey` frames. Instant but requires both parties to be connected.
|
|
9
|
+
*
|
|
10
|
+
* ASYNC (X3DH): The sender encrypts to the recipient's prekey bundle while the
|
|
11
|
+
* recipient is offline. Uses X3DH (Extended Triple DH) with identity keys,
|
|
12
|
+
* signed prekeys, and one-time prekeys. The recipient derives the same shared
|
|
13
|
+
* key from the init message when they come online.
|
|
14
|
+
*
|
|
15
|
+
* Both modes produce an AES-GCM 256 shared key for message encryption.
|
|
16
|
+
*/
|
|
17
|
+
export declare class E2ESession {
|
|
18
|
+
private readonly storageKey;
|
|
19
|
+
private kp?;
|
|
20
|
+
private shared?;
|
|
21
|
+
private identityKP;
|
|
22
|
+
private signedPreKP;
|
|
23
|
+
private signedPrekeyId;
|
|
24
|
+
private readonly otpKeys;
|
|
25
|
+
private x3dhShared?;
|
|
26
|
+
private pendingX3DH;
|
|
27
|
+
constructor(storageKey: string);
|
|
28
|
+
get ready(): boolean;
|
|
29
|
+
/** Live ECDH mode: Generate/restore our keypair and return our public key to publish. */
|
|
30
|
+
begin(): Promise<string>;
|
|
31
|
+
/** Live ECDH mode: A peer published their key — derive the shared secret. */
|
|
32
|
+
onPeerKey(peerKeyB64: string): Promise<void>;
|
|
33
|
+
/** X3DH: Generate identity key, signed prekey, and OTP prekeys.
|
|
34
|
+
* Returns the upload frame payload the caller should send to the server. */
|
|
35
|
+
initX3DH(): Promise<{
|
|
36
|
+
identityKey: string;
|
|
37
|
+
signedPrekey: string;
|
|
38
|
+
signedPrekeyId: string;
|
|
39
|
+
signature: string;
|
|
40
|
+
oneTimePrekeys: string[];
|
|
41
|
+
}>;
|
|
42
|
+
/** X3DH sender: given a recipient's prekey bundle, derive the shared key and
|
|
43
|
+
* return the init message fields to embed in the first encrypted message. */
|
|
44
|
+
x3dhSendTo(bundle: X3DHBundle): Promise<{
|
|
45
|
+
ephemeralKey: string;
|
|
46
|
+
spkId: string;
|
|
47
|
+
usedOTP: boolean;
|
|
48
|
+
senderIK: string;
|
|
49
|
+
}>;
|
|
50
|
+
/** X3DH recipient: given an init message's sender IK + EK + SPK ID, derive the shared key. */
|
|
51
|
+
x3dhReceiveFrom(senderIKb64: string, ephemeralKeyB64: string, spkId: string): Promise<void>;
|
|
52
|
+
/** Flush pending X3DH derivation after initX3DH() completes. */
|
|
53
|
+
flushPendingX3DH(): Promise<void>;
|
|
54
|
+
/** Encrypt outgoing text into a wire content object. For X3DH init messages,
|
|
55
|
+
* the caller should pass x3dhInit fields to embed in the content. */
|
|
56
|
+
sealText(text: string, x3dhInit?: {
|
|
57
|
+
ephemeralKey: string;
|
|
58
|
+
spkId: string;
|
|
59
|
+
senderIK: string;
|
|
60
|
+
}): Promise<MessageContent>;
|
|
61
|
+
/** Decrypt one content object if it is encrypted (otherwise pass through). */
|
|
62
|
+
private openContent;
|
|
63
|
+
/** Decrypt any encrypted message content carried by an incoming frame, in place. */
|
|
64
|
+
openFrame(frame: ServerFrame): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
/** X3DH init fields embedded in a text MessageContent (as extra properties).
|
|
67
|
+
* Present only on the very first message from a sender to an offline peer. */
|
|
68
|
+
export interface X3DHInitFields {
|
|
69
|
+
x3dhEK: string;
|
|
70
|
+
x3dhSPK: string;
|
|
71
|
+
x3dhIK: string;
|
|
72
|
+
}
|
|
73
|
+
export declare function extractX3DHInit(content: MessageContent): X3DHInitFields | null;
|
|
74
|
+
export { type X3DHBundle } from './crypto.js';
|
|
75
|
+
export { type UserId };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ChatStore } from './store.js';
|
|
2
|
+
import type { ConversationId } from './protocol/index.js';
|
|
3
|
+
import type { Renderer } from './renderer.js';
|
|
4
|
+
/** Derive the HTTP(S) base origin from a ws(s):// URL. */
|
|
5
|
+
export declare function httpBaseFromWsUrl(wsUrl: string): string;
|
|
6
|
+
/** Initial history restore on conversation open.
|
|
7
|
+
*
|
|
8
|
+
* Fetches the latest PAGE messages and sets up scroll-triggered loading for
|
|
9
|
+
* older messages via IntersectionObserver on a sentinel at the top of the
|
|
10
|
+
* scroll container. No buttons — scrolling up loads more automatically.
|
|
11
|
+
*
|
|
12
|
+
* Returns a cleanup function — call it when the conversation is closed to
|
|
13
|
+
* disconnect the observer and prevent stale updates. */
|
|
14
|
+
export declare function restoreHistory(wsUrl: string, token: string, conversationId: ConversationId, store: ChatStore, renderer: Renderer): Promise<void>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { asConversationId } from './protocol/index.js';
|
|
2
|
+
import { type WidgetConfig } from './renderer.js';
|
|
3
|
+
export interface UserInfo {
|
|
4
|
+
/** Display name shown in the conversation (e.g. "Sarah Chen"). */
|
|
5
|
+
name?: string;
|
|
6
|
+
/** Email address — passed as conversation metadata for agent context. */
|
|
7
|
+
email?: string;
|
|
8
|
+
/** Avatar URL — shown as the guest's avatar in both widget and dashboard. */
|
|
9
|
+
avatar?: string;
|
|
10
|
+
/** Any custom key/value metadata to attach to the conversation
|
|
11
|
+
* (e.g. plan tier, account ID, page URL). Shown to agents in the sidebar. */
|
|
12
|
+
meta?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export interface MountOptions {
|
|
15
|
+
el: HTMLElement;
|
|
16
|
+
url: string;
|
|
17
|
+
profileId: string;
|
|
18
|
+
subjectId?: string;
|
|
19
|
+
token?: string;
|
|
20
|
+
subject?: WidgetConfig['subject'];
|
|
21
|
+
quickReplies?: string[];
|
|
22
|
+
accent?: string;
|
|
23
|
+
/** If set, shows a 🌐 translate button on incoming messages that translates
|
|
24
|
+
* them into this language (ISO code or language name) via the server's
|
|
25
|
+
* /translate endpoint. Omit to disable the feature. */
|
|
26
|
+
translateLang?: string;
|
|
27
|
+
/** If true, mount as a floating launcher button that opens/closes the chat */
|
|
28
|
+
launcher?: boolean;
|
|
29
|
+
/** Position of the launcher button: default 'bottom-right' */
|
|
30
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
31
|
+
/** If true, shows a 💬 button in the header that opens a list of the
|
|
32
|
+
* guest's other conversations (e.g. a marketplace where each item gets
|
|
33
|
+
* its own subjectId/thread) and lets them switch between them.
|
|
34
|
+
* Off by default — single-conversation embeds don't need this. */
|
|
35
|
+
showChatList?: boolean;
|
|
36
|
+
/** Optional user info for identified users. When provided, the name/email/
|
|
37
|
+
* avatar are shown to agents in the dashboard instead of the anonymous ID.
|
|
38
|
+
* The token still controls identity — this is display metadata only.
|
|
39
|
+
* Anonymous users (no token, no user) remain fully anonymous. */
|
|
40
|
+
user?: UserInfo;
|
|
41
|
+
/** i18n: override UI strings. All keys are optional — omitted keys fall
|
|
42
|
+
* back to English defaults. */
|
|
43
|
+
i18n?: {
|
|
44
|
+
placeholder?: string;
|
|
45
|
+
send?: string;
|
|
46
|
+
offline?: string;
|
|
47
|
+
poweredBy?: string;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface WidgetHandle {
|
|
51
|
+
close(): void;
|
|
52
|
+
}
|
|
53
|
+
export declare function mount(opts: MountOptions): WidgetHandle;
|
|
54
|
+
export { ChatStore } from './store.js';
|
|
55
|
+
export { ConnectionManager } from './connection.js';
|
|
56
|
+
export { Renderer, type ChatListEntry } from './renderer.js';
|
|
57
|
+
export { asConversationId };
|
|
58
|
+
export { E2ESession, extractX3DHInit, type X3DHBundle } from './e2e.js';
|
|
59
|
+
export { PersistentOutbox, type OutboxItem } from './outbox.js';
|
|
60
|
+
export { restoreHistory, httpBaseFromWsUrl } from './history.js';
|