@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/dist/outbox.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MessageContent } from './protocol/index.js';
|
|
2
|
+
export interface OutboxItem {
|
|
3
|
+
clientMsgId: string;
|
|
4
|
+
content: MessageContent;
|
|
5
|
+
ts: number;
|
|
6
|
+
}
|
|
7
|
+
/** Persists not-yet-acknowledged outgoing messages to localStorage, keyed by
|
|
8
|
+
* guest token, so a page reload during a connectivity drop doesn't silently
|
|
9
|
+
* lose what the user typed (the "WhatsApp" guarantee: your message is queued
|
|
10
|
+
* until it's confirmed sent, even across app restarts). */
|
|
11
|
+
export declare class PersistentOutbox {
|
|
12
|
+
private readonly key;
|
|
13
|
+
constructor(token: string);
|
|
14
|
+
/** All pending items, oldest first, with stale (>7d) entries dropped. */
|
|
15
|
+
load(): OutboxItem[];
|
|
16
|
+
add(item: OutboxItem): void;
|
|
17
|
+
/** Remove an item once it's been acknowledged by the server. */
|
|
18
|
+
remove(clientMsgId: string): void;
|
|
19
|
+
private save;
|
|
20
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ActionId, ProfileId, TenantId } from './ids.js';
|
|
2
|
+
export type ActionAudience = 'guest' | 'agent' | 'both';
|
|
3
|
+
export type ActionSurface = 'toolbar' | 'inline' | 'quick_reply';
|
|
4
|
+
export interface ActionInputField {
|
|
5
|
+
name: string;
|
|
6
|
+
label: string;
|
|
7
|
+
type: 'text' | 'number' | 'date' | 'select';
|
|
8
|
+
required?: boolean;
|
|
9
|
+
options?: string[];
|
|
10
|
+
}
|
|
11
|
+
export type TerminalEffect = {
|
|
12
|
+
type: 'webhook';
|
|
13
|
+
url: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: 'state_transition';
|
|
16
|
+
target: 'conversation' | 'subject';
|
|
17
|
+
toState: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'bot';
|
|
20
|
+
} | {
|
|
21
|
+
type: 'builtin';
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
export type ActionEffect = TerminalEffect | {
|
|
25
|
+
type: 'form';
|
|
26
|
+
fields: ActionInputField[];
|
|
27
|
+
then: TerminalEffect;
|
|
28
|
+
};
|
|
29
|
+
export type ActionResult = {
|
|
30
|
+
kind: 'system_message';
|
|
31
|
+
template?: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'card';
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'state_badge';
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'none';
|
|
38
|
+
};
|
|
39
|
+
export interface ActionDef {
|
|
40
|
+
id: ActionId;
|
|
41
|
+
label: string;
|
|
42
|
+
icon?: string;
|
|
43
|
+
confirm?: boolean;
|
|
44
|
+
audience: ActionAudience;
|
|
45
|
+
surface: ActionSurface;
|
|
46
|
+
availableInStates?: string[];
|
|
47
|
+
effect: ActionEffect;
|
|
48
|
+
result: ActionResult;
|
|
49
|
+
}
|
|
50
|
+
export interface ManifestAction {
|
|
51
|
+
id: ActionId;
|
|
52
|
+
label: string;
|
|
53
|
+
icon?: string;
|
|
54
|
+
confirm?: boolean;
|
|
55
|
+
audience: ActionAudience;
|
|
56
|
+
surface: ActionSurface;
|
|
57
|
+
availableInStates?: string[];
|
|
58
|
+
input?: ActionInputField[];
|
|
59
|
+
}
|
|
60
|
+
/** Project an internal action to its client-safe manifest form. */
|
|
61
|
+
export declare function toManifestAction(a: ActionDef): ManifestAction;
|
|
62
|
+
/** Operating hours slot: 0=Sun … 6=Sat, times in "HH:MM" 24h local. */
|
|
63
|
+
export interface OperatingHoursSlot {
|
|
64
|
+
day: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
65
|
+
open: string;
|
|
66
|
+
close: string;
|
|
67
|
+
}
|
|
68
|
+
export interface BehaviorProfile {
|
|
69
|
+
id: ProfileId;
|
|
70
|
+
tenantId: TenantId;
|
|
71
|
+
name: string;
|
|
72
|
+
actions: ActionDef[];
|
|
73
|
+
defaults: {
|
|
74
|
+
greeting?: string;
|
|
75
|
+
theme?: {
|
|
76
|
+
accent: string;
|
|
77
|
+
};
|
|
78
|
+
e2e?: boolean;
|
|
79
|
+
persona?: string;
|
|
80
|
+
/** Paid-tier flag: when true, hides the "Powered by Relay" footer in the widget. */
|
|
81
|
+
whiteLabel?: boolean;
|
|
82
|
+
/** White-label: serve/embed the widget from this hostname (e.g.
|
|
83
|
+
* "chat.acmeco.com"). Allowed automatically as a CORS origin for the
|
|
84
|
+
* control-plane API so the widget works from the custom domain. */
|
|
85
|
+
customDomain?: string;
|
|
86
|
+
};
|
|
87
|
+
states: string[];
|
|
88
|
+
initialState: string;
|
|
89
|
+
version: number;
|
|
90
|
+
welcomeMessage?: string;
|
|
91
|
+
operatingHours?: OperatingHoursSlot[];
|
|
92
|
+
offlineMessage?: string;
|
|
93
|
+
/** Base64-encoded ECDSA P-256 SPKI public key. When set, guest tokens must be
|
|
94
|
+
* signed JWTs — unsigned opaque tokens are rejected. */
|
|
95
|
+
guestPublicKey?: string;
|
|
96
|
+
createdAt: number;
|
|
97
|
+
updatedAt: number;
|
|
98
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ClientFrame, ServerFrame } from './frames.js';
|
|
2
|
+
export type AnyFrame = ClientFrame | ServerFrame;
|
|
3
|
+
/** True if a decoded frame is one a client is allowed to send. The server uses
|
|
4
|
+
* this to reject server-only frame types before dispatch, so a malicious or
|
|
5
|
+
* buggy client can't reach an unexpected handler path. */
|
|
6
|
+
export declare function isClientFrame(frame: AnyFrame): frame is ClientFrame;
|
|
7
|
+
/** Encode a frame to a binary msgpack payload for the wire. */
|
|
8
|
+
export declare function encodeFrame(frame: AnyFrame): Uint8Array;
|
|
9
|
+
/** Decode a binary payload into a frame. Returns null on any malformed input or
|
|
10
|
+
* anything lacking a string `type`, so a bad frame can never crash the handler
|
|
11
|
+
* — the boundary validates `type` before trusting the rest. */
|
|
12
|
+
export declare function decodeFrame(bytes: Uint8Array): AnyFrame | null;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ConversationId, MessageId, ProfileId, SubjectId, TenantId, UserId } from './ids.js';
|
|
2
|
+
import type { ActionId } from './ids.js';
|
|
3
|
+
export interface CardField {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
/** A reference to an action a card/quick-reply can invoke. */
|
|
8
|
+
export interface InlineActionRef {
|
|
9
|
+
actionId: ActionId;
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
export type MessageContent = {
|
|
13
|
+
kind: 'text';
|
|
14
|
+
text: string;
|
|
15
|
+
enc?: boolean;
|
|
16
|
+
iv?: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'attachment';
|
|
19
|
+
url: string;
|
|
20
|
+
mime: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
size?: number;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'card';
|
|
25
|
+
title?: string;
|
|
26
|
+
body?: string;
|
|
27
|
+
fields?: CardField[];
|
|
28
|
+
actions?: InlineActionRef[];
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'form';
|
|
31
|
+
prompt: string;
|
|
32
|
+
actionId: ActionId;
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'system';
|
|
35
|
+
event: string;
|
|
36
|
+
data?: Record<string, string | number | boolean>;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'appointment';
|
|
39
|
+
title: string;
|
|
40
|
+
startIso: string;
|
|
41
|
+
endIso: string;
|
|
42
|
+
location?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
googleUrl: string;
|
|
45
|
+
icalUrl: string;
|
|
46
|
+
confirmed?: boolean;
|
|
47
|
+
};
|
|
48
|
+
export type SenderRole = 'guest' | 'agent' | 'system' | 'bot';
|
|
49
|
+
export interface Message {
|
|
50
|
+
id: MessageId;
|
|
51
|
+
conversationId: ConversationId;
|
|
52
|
+
seq: number;
|
|
53
|
+
senderId: UserId;
|
|
54
|
+
senderRole: SenderRole;
|
|
55
|
+
content: MessageContent;
|
|
56
|
+
ts: number;
|
|
57
|
+
replyToId?: MessageId;
|
|
58
|
+
editedAt?: number;
|
|
59
|
+
deletedAt?: number;
|
|
60
|
+
reactions?: Record<string, UserId[]>;
|
|
61
|
+
internal?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface Conversation {
|
|
64
|
+
id: ConversationId;
|
|
65
|
+
tenantId: TenantId;
|
|
66
|
+
profileId: ProfileId;
|
|
67
|
+
subjectId?: SubjectId;
|
|
68
|
+
guestId: UserId;
|
|
69
|
+
participants: UserId[];
|
|
70
|
+
assignedAgentId?: UserId;
|
|
71
|
+
aiActive?: boolean;
|
|
72
|
+
state: string;
|
|
73
|
+
firstResponseAt?: number;
|
|
74
|
+
csat?: number;
|
|
75
|
+
lastSeq: number;
|
|
76
|
+
tags?: string[];
|
|
77
|
+
/** Live sentiment of the guest's most recent message (best-effort, async). */
|
|
78
|
+
sentiment?: 'positive' | 'neutral' | 'frustrated';
|
|
79
|
+
/** -1 (very frustrated) .. +1 (very positive); paired with `sentiment`. */
|
|
80
|
+
sentimentScore?: number;
|
|
81
|
+
/** Set once an SLA-breach escalation macro has fired, so it only fires once. */
|
|
82
|
+
slaEscalatedAt?: number;
|
|
83
|
+
/** Page URL where the widget was open when the conversation started. */
|
|
84
|
+
pageUrl?: string;
|
|
85
|
+
/** Browser tab title at conversation start — gives agents context. */
|
|
86
|
+
pageTitle?: string;
|
|
87
|
+
createdAt: number;
|
|
88
|
+
updatedAt: number;
|
|
89
|
+
}
|
|
90
|
+
export interface AnnotationPoint {
|
|
91
|
+
x: number;
|
|
92
|
+
y: number;
|
|
93
|
+
}
|
|
94
|
+
export interface AnnotationStroke {
|
|
95
|
+
id: string;
|
|
96
|
+
points: AnnotationPoint[];
|
|
97
|
+
color: string;
|
|
98
|
+
width: number;
|
|
99
|
+
by: UserId;
|
|
100
|
+
}
|
|
101
|
+
export interface Subject {
|
|
102
|
+
id: SubjectId;
|
|
103
|
+
tenantId: TenantId;
|
|
104
|
+
title: string;
|
|
105
|
+
state: string;
|
|
106
|
+
fields: Record<string, string | number | boolean>;
|
|
107
|
+
createdAt: number;
|
|
108
|
+
updatedAt: number;
|
|
109
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import type { ConnectionId, ConversationId, MessageId, ProfileId, SubjectId, UserId } from './ids.js';
|
|
2
|
+
import type { Conversation, Message, MessageContent, Subject, AnnotationStroke } from './entities.js';
|
|
3
|
+
import type { ManifestAction } from './actions.js';
|
|
4
|
+
export type ErrorCode = 'UNAUTHORIZED' | 'FORBIDDEN' | 'NOT_FOUND' | 'BAD_REQUEST' | 'RATE_LIMITED' | 'PAYLOAD_TOO_LARGE' | 'CONFLICT' | 'INTERNAL';
|
|
5
|
+
export type ClientFrame = {
|
|
6
|
+
type: 'auth';
|
|
7
|
+
token: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'open';
|
|
10
|
+
conversationId?: ConversationId;
|
|
11
|
+
subjectId?: SubjectId;
|
|
12
|
+
profileId?: ProfileId;
|
|
13
|
+
pageUrl?: string;
|
|
14
|
+
pageTitle?: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'send';
|
|
17
|
+
conversationId: ConversationId;
|
|
18
|
+
clientMsgId: string;
|
|
19
|
+
content: MessageContent;
|
|
20
|
+
replyToId?: MessageId;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'sync';
|
|
23
|
+
conversationId: ConversationId;
|
|
24
|
+
sinceSeq: number;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'history';
|
|
27
|
+
conversationId: ConversationId;
|
|
28
|
+
beforeSeq: number;
|
|
29
|
+
limit?: number;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'read';
|
|
32
|
+
conversationId: ConversationId;
|
|
33
|
+
seq: number;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'typing';
|
|
36
|
+
conversationId: ConversationId;
|
|
37
|
+
isTyping: boolean;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'react';
|
|
40
|
+
conversationId: ConversationId;
|
|
41
|
+
messageId: MessageId;
|
|
42
|
+
emoji: string;
|
|
43
|
+
remove?: boolean;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'edit';
|
|
46
|
+
conversationId: ConversationId;
|
|
47
|
+
messageId: MessageId;
|
|
48
|
+
content: MessageContent;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'delete';
|
|
51
|
+
conversationId: ConversationId;
|
|
52
|
+
messageId: MessageId;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'invoke';
|
|
55
|
+
conversationId: ConversationId;
|
|
56
|
+
actionId: string;
|
|
57
|
+
clientInvokeId: string;
|
|
58
|
+
inputs?: Record<string, unknown>;
|
|
59
|
+
} | {
|
|
60
|
+
type: 'assign';
|
|
61
|
+
conversationId: ConversationId;
|
|
62
|
+
agentId: UserId | null;
|
|
63
|
+
} | {
|
|
64
|
+
type: 'tag';
|
|
65
|
+
conversationId: ConversationId;
|
|
66
|
+
tag: string;
|
|
67
|
+
remove?: boolean;
|
|
68
|
+
} | {
|
|
69
|
+
type: 'note';
|
|
70
|
+
conversationId: ConversationId;
|
|
71
|
+
clientMsgId: string;
|
|
72
|
+
text: string;
|
|
73
|
+
} | {
|
|
74
|
+
type: 'agent_status';
|
|
75
|
+
status: 'online' | 'away' | 'offline';
|
|
76
|
+
} | {
|
|
77
|
+
type: 'annotate';
|
|
78
|
+
conversationId: ConversationId;
|
|
79
|
+
stroke: Omit<AnnotationStroke, 'by'>;
|
|
80
|
+
} | {
|
|
81
|
+
type: 'annotate_clear';
|
|
82
|
+
conversationId: ConversationId;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'pubkey';
|
|
85
|
+
conversationId: ConversationId;
|
|
86
|
+
key: string;
|
|
87
|
+
} | {
|
|
88
|
+
type: 'uploadPrekeys';
|
|
89
|
+
identityKey: string;
|
|
90
|
+
signedPrekey: string;
|
|
91
|
+
signedPrekeyId: string;
|
|
92
|
+
signature: string;
|
|
93
|
+
oneTimePrekeys: string[];
|
|
94
|
+
} | {
|
|
95
|
+
type: 'fetchPrekey';
|
|
96
|
+
targetUserId: UserId;
|
|
97
|
+
} | {
|
|
98
|
+
type: 'ping';
|
|
99
|
+
};
|
|
100
|
+
export type ServerFrame = {
|
|
101
|
+
type: 'authed';
|
|
102
|
+
userId: UserId;
|
|
103
|
+
connectionId: ConnectionId;
|
|
104
|
+
} | {
|
|
105
|
+
type: 'opened';
|
|
106
|
+
conversation: Conversation;
|
|
107
|
+
subject?: Subject;
|
|
108
|
+
annotations?: AnnotationStroke[];
|
|
109
|
+
} | {
|
|
110
|
+
type: 'manifest';
|
|
111
|
+
conversationId: ConversationId;
|
|
112
|
+
actions: ManifestAction[];
|
|
113
|
+
version: number;
|
|
114
|
+
name?: string;
|
|
115
|
+
theme?: {
|
|
116
|
+
accent: string;
|
|
117
|
+
};
|
|
118
|
+
e2e?: boolean;
|
|
119
|
+
offline?: boolean;
|
|
120
|
+
offlineMessage?: string;
|
|
121
|
+
whiteLabel?: boolean;
|
|
122
|
+
} | {
|
|
123
|
+
type: 'message';
|
|
124
|
+
message: Message;
|
|
125
|
+
} | {
|
|
126
|
+
type: 'ack';
|
|
127
|
+
clientMsgId: string;
|
|
128
|
+
messageId: MessageId;
|
|
129
|
+
seq: number;
|
|
130
|
+
ts: number;
|
|
131
|
+
} | {
|
|
132
|
+
type: 'delivered';
|
|
133
|
+
conversationId: ConversationId;
|
|
134
|
+
seq: number;
|
|
135
|
+
to: UserId;
|
|
136
|
+
} | {
|
|
137
|
+
type: 'read';
|
|
138
|
+
conversationId: ConversationId;
|
|
139
|
+
seq: number;
|
|
140
|
+
by: UserId;
|
|
141
|
+
} | {
|
|
142
|
+
type: 'sync';
|
|
143
|
+
conversationId: ConversationId;
|
|
144
|
+
messages: Message[];
|
|
145
|
+
} | {
|
|
146
|
+
type: 'history';
|
|
147
|
+
conversationId: ConversationId;
|
|
148
|
+
messages: Message[];
|
|
149
|
+
hasMore: boolean;
|
|
150
|
+
} | {
|
|
151
|
+
type: 'typing';
|
|
152
|
+
conversationId: ConversationId;
|
|
153
|
+
userId: UserId;
|
|
154
|
+
isTyping: boolean;
|
|
155
|
+
} | {
|
|
156
|
+
type: 'reaction';
|
|
157
|
+
conversationId: ConversationId;
|
|
158
|
+
messageId: MessageId;
|
|
159
|
+
emoji: string;
|
|
160
|
+
by: UserId;
|
|
161
|
+
removed: boolean;
|
|
162
|
+
} | {
|
|
163
|
+
type: 'edited';
|
|
164
|
+
conversationId: ConversationId;
|
|
165
|
+
messageId: MessageId;
|
|
166
|
+
content: MessageContent;
|
|
167
|
+
editedAt: number;
|
|
168
|
+
} | {
|
|
169
|
+
type: 'deleted';
|
|
170
|
+
conversationId: ConversationId;
|
|
171
|
+
messageId: MessageId;
|
|
172
|
+
ts: number;
|
|
173
|
+
} | {
|
|
174
|
+
type: 'state';
|
|
175
|
+
conversationId: ConversationId;
|
|
176
|
+
state: string;
|
|
177
|
+
} | {
|
|
178
|
+
type: 'assigned';
|
|
179
|
+
conversationId: ConversationId;
|
|
180
|
+
agentId: UserId | null;
|
|
181
|
+
} | {
|
|
182
|
+
type: 'tagged';
|
|
183
|
+
conversationId: ConversationId;
|
|
184
|
+
tag: string;
|
|
185
|
+
removed: boolean;
|
|
186
|
+
} | {
|
|
187
|
+
type: 'visitor_count';
|
|
188
|
+
count: number;
|
|
189
|
+
} | {
|
|
190
|
+
type: 'agent_status_changed';
|
|
191
|
+
agentId: UserId;
|
|
192
|
+
status: 'online' | 'away' | 'offline';
|
|
193
|
+
} | {
|
|
194
|
+
type: 'sentiment';
|
|
195
|
+
conversationId: ConversationId;
|
|
196
|
+
label: 'positive' | 'neutral' | 'frustrated';
|
|
197
|
+
score: number;
|
|
198
|
+
} | {
|
|
199
|
+
type: 'annotation';
|
|
200
|
+
conversationId: ConversationId;
|
|
201
|
+
stroke: AnnotationStroke;
|
|
202
|
+
} | {
|
|
203
|
+
type: 'annotation_clear';
|
|
204
|
+
conversationId: ConversationId;
|
|
205
|
+
by: UserId;
|
|
206
|
+
} | {
|
|
207
|
+
type: 'subjectState';
|
|
208
|
+
subjectId: SubjectId;
|
|
209
|
+
state: string;
|
|
210
|
+
} | {
|
|
211
|
+
type: 'presence';
|
|
212
|
+
conversationId: ConversationId;
|
|
213
|
+
userId: UserId;
|
|
214
|
+
status: 'online' | 'offline';
|
|
215
|
+
lastSeen?: number;
|
|
216
|
+
} | {
|
|
217
|
+
type: 'invoked';
|
|
218
|
+
clientInvokeId: string;
|
|
219
|
+
ok: boolean;
|
|
220
|
+
error?: string;
|
|
221
|
+
} | {
|
|
222
|
+
type: 'error';
|
|
223
|
+
code: ErrorCode;
|
|
224
|
+
message: string;
|
|
225
|
+
} | {
|
|
226
|
+
type: 'peerkey';
|
|
227
|
+
conversationId: ConversationId;
|
|
228
|
+
userId: UserId;
|
|
229
|
+
key: string;
|
|
230
|
+
} | {
|
|
231
|
+
type: 'prekeyBundle';
|
|
232
|
+
targetUserId: UserId;
|
|
233
|
+
bundle: {
|
|
234
|
+
identityKey: string;
|
|
235
|
+
signedPrekey: string;
|
|
236
|
+
signedPrekeyId: string;
|
|
237
|
+
signature: string;
|
|
238
|
+
oneTimePrekey?: string;
|
|
239
|
+
} | null;
|
|
240
|
+
} | {
|
|
241
|
+
type: 'pong';
|
|
242
|
+
};
|
|
243
|
+
/** Limits referenced by both ends so validation stays consistent. */
|
|
244
|
+
export declare const LIMITS: {
|
|
245
|
+
readonly MAX_TEXT_LEN: 8000;
|
|
246
|
+
readonly MAX_HISTORY_LIMIT: 100;
|
|
247
|
+
readonly DEFAULT_HISTORY: 50;
|
|
248
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type Brand<T, B extends string> = T & {
|
|
2
|
+
readonly __brand: B;
|
|
3
|
+
};
|
|
4
|
+
export type TenantId = Brand<string, 'TenantId'>;
|
|
5
|
+
export type UserId = Brand<string, 'UserId'>;
|
|
6
|
+
export type ConversationId = Brand<string, 'ConversationId'>;
|
|
7
|
+
export type SubjectId = Brand<string, 'SubjectId'>;
|
|
8
|
+
export type MessageId = Brand<string, 'MessageId'>;
|
|
9
|
+
export type ActionId = Brand<string, 'ActionId'>;
|
|
10
|
+
export type ProfileId = Brand<string, 'ProfileId'>;
|
|
11
|
+
export type ConnectionId = Brand<string, 'ConnectionId'>;
|
|
12
|
+
export declare const asTenantId: (s: string) => TenantId;
|
|
13
|
+
export declare const asUserId: (s: string) => UserId;
|
|
14
|
+
export declare const asConversationId: (s: string) => ConversationId;
|
|
15
|
+
export declare const asSubjectId: (s: string) => SubjectId;
|
|
16
|
+
export declare const asMessageId: (s: string) => MessageId;
|
|
17
|
+
export declare const asActionId: (s: string) => ActionId;
|
|
18
|
+
export declare const asProfileId: (s: string) => ProfileId;
|
|
19
|
+
export declare const asConnectionId: (s: string) => ConnectionId;
|
|
20
|
+
/** Anonymous guests live under this tenant; they bypass cross-tenant scoping but
|
|
21
|
+
* are still gated by conversation membership. */
|
|
22
|
+
export declare const ANONYMOUS_TENANT: TenantId;
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type MountOptions } from './index.js';
|
|
2
|
+
interface BaseProps {
|
|
3
|
+
/** Relay WebSocket URL — set as NEXT_PUBLIC_RELAY_WS_URL in .env — required */
|
|
4
|
+
url: string;
|
|
5
|
+
/** Your Relay profile ID — set as NEXT_PUBLIC_RELAY_PROFILE_ID in .env — required */
|
|
6
|
+
profileId: string;
|
|
7
|
+
/** Your logged-in user's stable ID. When omitted the widget automatically
|
|
8
|
+
* assigns a persistent anonymous ID from localStorage — no login required. */
|
|
9
|
+
userId?: string;
|
|
10
|
+
/** Shown to agents instead of the raw user ID */
|
|
11
|
+
userName?: string;
|
|
12
|
+
/** Shown to agents so they can follow up by email */
|
|
13
|
+
userEmail?: string;
|
|
14
|
+
/** Avatar URL shown in the widget header and dashboard */
|
|
15
|
+
userAvatar?: string;
|
|
16
|
+
/** Brand colour hex, e.g. "#1a56db". Defaults to the profile theme colour. */
|
|
17
|
+
accent?: string;
|
|
18
|
+
/** Render as a floating launcher button instead of inline */
|
|
19
|
+
launcher?: boolean;
|
|
20
|
+
/** Launcher position — default 'bottom-right' */
|
|
21
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
22
|
+
/** Pre-set reply chips shown above the input */
|
|
23
|
+
quickReplies?: string[];
|
|
24
|
+
/** Container height when rendered inline. Default: '100%' */
|
|
25
|
+
height?: string;
|
|
26
|
+
/** i18n string overrides for non-English sites */
|
|
27
|
+
i18n?: MountOptions['i18n'];
|
|
28
|
+
/** ISO language code for auto-translating incoming messages */
|
|
29
|
+
translateLang?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ChatWidgetProps extends BaseProps {
|
|
32
|
+
/** Optional context card shown at the top of the chat
|
|
33
|
+
* (e.g. the support ticket, booking, or order being discussed) */
|
|
34
|
+
contextTitle?: string;
|
|
35
|
+
contextSubtitle?: string;
|
|
36
|
+
contextStatus?: string;
|
|
37
|
+
/** Show the WhatsApp-style thread list as the home screen.
|
|
38
|
+
* Use when users can have more than one thread on this profile
|
|
39
|
+
* (e.g. one thread per support ticket, one per booking). */
|
|
40
|
+
showChatList?: boolean;
|
|
41
|
+
/** Stable item ID — pins this conversation to a specific item/thread.
|
|
42
|
+
* When showChatList is also true, the widget opens this thread immediately
|
|
43
|
+
* but the user can navigate back to the full list. */
|
|
44
|
+
subjectId?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* General-purpose support chat widget. Only `url` and `profileId` are required.
|
|
48
|
+
* All other props are optional — anonymous guests work without any configuration.
|
|
49
|
+
*
|
|
50
|
+
* @example Basic support chat
|
|
51
|
+
* ```tsx
|
|
52
|
+
* <ChatWidget
|
|
53
|
+
* url={process.env.NEXT_PUBLIC_RELAY_WS_URL}
|
|
54
|
+
* profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}
|
|
55
|
+
* userId={session?.user.id}
|
|
56
|
+
* userName={session?.user.name}
|
|
57
|
+
* userEmail={session?.user.email}
|
|
58
|
+
* />
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @example Multi-thread (tickets, bookings, orders)
|
|
62
|
+
* ```tsx
|
|
63
|
+
* <ChatWidget
|
|
64
|
+
* url={...} profileId={...}
|
|
65
|
+
* showChatList
|
|
66
|
+
* subjectId={`ticket_${ticket.id}`}
|
|
67
|
+
* contextTitle={ticket.title}
|
|
68
|
+
* contextStatus={ticket.status}
|
|
69
|
+
* userId={session?.user.id}
|
|
70
|
+
* />
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function ChatWidget({ url, profileId, userId, userName, userEmail, userAvatar, contextTitle, contextSubtitle, contextStatus, showChatList, subjectId, accent, launcher, position, quickReplies, height, i18n, translateLang, }: ChatWidgetProps): JSX.Element;
|
|
74
|
+
export interface MarketplaceChatProps extends BaseProps {
|
|
75
|
+
/** Unique ID for this listing — each listing gets its own thread.
|
|
76
|
+
* Omit for a general (non-item-specific) conversation. */
|
|
77
|
+
listingId?: string;
|
|
78
|
+
/** Shown at the top of the chat — e.g. "2019 Toyota Camry SE" */
|
|
79
|
+
listingTitle?: string;
|
|
80
|
+
/** One-line detail — e.g. "45,000 km · Automatic" */
|
|
81
|
+
listingMeta?: string;
|
|
82
|
+
/** Price shown as a tag — e.g. 12500 → "$12,500" */
|
|
83
|
+
listingPrice?: number;
|
|
84
|
+
/** Status badge — e.g. "Available", "Sold", "Pending" */
|
|
85
|
+
listingStatus?: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Marketplace chat — one thread per listing, guests can switch between all
|
|
89
|
+
* their active threads via a WhatsApp-style list. Only `url` and `profileId`
|
|
90
|
+
* are required. All other props are optional.
|
|
91
|
+
*
|
|
92
|
+
* @example Used car listing
|
|
93
|
+
* ```tsx
|
|
94
|
+
* <MarketplaceChat
|
|
95
|
+
* url={process.env.NEXT_PUBLIC_RELAY_WS_URL}
|
|
96
|
+
* profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}
|
|
97
|
+
* listingId={car.id}
|
|
98
|
+
* listingTitle={car.title}
|
|
99
|
+
* listingPrice={car.price}
|
|
100
|
+
* listingMeta={`${car.mileage.toLocaleString()} km · ${car.transmission}`}
|
|
101
|
+
* listingStatus="Available"
|
|
102
|
+
* userId={session?.user.id}
|
|
103
|
+
* userName={session?.user.name}
|
|
104
|
+
* userEmail={session?.user.email}
|
|
105
|
+
* />
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export declare function MarketplaceChat({ url, profileId, listingId, listingTitle, listingMeta, listingPrice, listingStatus, userId, userName, userEmail, userAvatar, accent, launcher, position, quickReplies, height, i18n, translateLang, }: MarketplaceChatProps): JSX.Element;
|
|
109
|
+
export {};
|