@cstar.help/js 0.1.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 +202 -0
- package/dist/chat/index.cjs +238 -0
- package/dist/chat/index.d.cts +172 -0
- package/dist/chat/index.d.ts +172 -0
- package/dist/chat/index.js +213 -0
- package/dist/index.cjs +364 -0
- package/dist/index.d.cts +519 -0
- package/dist/index.d.ts +519 -0
- package/dist/index.js +331 -0
- package/dist/library/index.cjs +97 -0
- package/dist/library/index.d.cts +82 -0
- package/dist/library/index.d.ts +82 -0
- package/dist/library/index.js +72 -0
- package/dist/webhook/index.cjs +84 -0
- package/dist/webhook/index.d.cts +163 -0
- package/dist/webhook/index.d.ts +163 -0
- package/dist/webhook/index.js +62 -0
- package/package.json +85 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** Configuration for the ChatClient. */
|
|
2
|
+
interface ChatConfig {
|
|
3
|
+
/** Team slug (e.g., 'acme'). Used to identify the team. */
|
|
4
|
+
teamSlug: string;
|
|
5
|
+
/** Override the base URL (defaults to https://app.cstar.help). */
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
/** Supabase project URL for realtime subscriptions (optional). */
|
|
8
|
+
supabaseUrl?: string;
|
|
9
|
+
/** Supabase anon key for realtime subscriptions (optional). */
|
|
10
|
+
supabaseAnonKey?: string;
|
|
11
|
+
/** Polling interval in ms when Supabase Realtime is not available (defaults to 3000). */
|
|
12
|
+
pollingInterval?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Parameters for identifying a customer via HMAC signature verification. */
|
|
15
|
+
interface IdentifyParams {
|
|
16
|
+
/** Customer ID from your system. */
|
|
17
|
+
externalId: string;
|
|
18
|
+
/** Customer email address. */
|
|
19
|
+
email: string;
|
|
20
|
+
/** Customer display name. */
|
|
21
|
+
name?: string;
|
|
22
|
+
/** Avatar URL (not included in signature). */
|
|
23
|
+
avatarUrl?: string;
|
|
24
|
+
/** Custom metadata (not included in signature). */
|
|
25
|
+
metadata?: Record<string, {
|
|
26
|
+
value: string;
|
|
27
|
+
agentVisible: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
/** Unix timestamp (seconds). Used for replay protection. */
|
|
30
|
+
timestamp: number;
|
|
31
|
+
}
|
|
32
|
+
/** Identity verification response. */
|
|
33
|
+
interface IdentifyResult {
|
|
34
|
+
verified: true;
|
|
35
|
+
customer: {
|
|
36
|
+
id: string;
|
|
37
|
+
externalId: string;
|
|
38
|
+
email: string;
|
|
39
|
+
name: string;
|
|
40
|
+
};
|
|
41
|
+
sessionToken: string;
|
|
42
|
+
expiresAt: string;
|
|
43
|
+
}
|
|
44
|
+
/** A customer conversation (maps to a ticket). */
|
|
45
|
+
interface Conversation {
|
|
46
|
+
id: string;
|
|
47
|
+
ticketNumber: string;
|
|
48
|
+
subject: string;
|
|
49
|
+
status: string;
|
|
50
|
+
messageCount: number;
|
|
51
|
+
createdAt: string;
|
|
52
|
+
updatedAt: string;
|
|
53
|
+
closedAt: string | null;
|
|
54
|
+
}
|
|
55
|
+
/** Paginated conversation list response. */
|
|
56
|
+
interface ConversationListResponse {
|
|
57
|
+
conversations: Conversation[];
|
|
58
|
+
cursor: string | null;
|
|
59
|
+
hasMore: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** A message in a conversation. */
|
|
62
|
+
interface WidgetMessage {
|
|
63
|
+
id: string;
|
|
64
|
+
content: string;
|
|
65
|
+
sender: 'customer' | 'agent';
|
|
66
|
+
sender_name: string;
|
|
67
|
+
created_at: string;
|
|
68
|
+
read_at: string | null;
|
|
69
|
+
attachments: unknown[];
|
|
70
|
+
}
|
|
71
|
+
/** Parameters for listing conversations. */
|
|
72
|
+
interface ConversationListParams {
|
|
73
|
+
limit?: number;
|
|
74
|
+
cursor?: string;
|
|
75
|
+
}
|
|
76
|
+
/** Parameters for creating a new conversation. */
|
|
77
|
+
interface ConversationCreateParams {
|
|
78
|
+
subject: string;
|
|
79
|
+
message?: string;
|
|
80
|
+
}
|
|
81
|
+
/** Typing indicator payload from realtime subscription. */
|
|
82
|
+
interface TypingPayload {
|
|
83
|
+
agent_id: string;
|
|
84
|
+
agent_name: string;
|
|
85
|
+
is_typing: boolean;
|
|
86
|
+
}
|
|
87
|
+
/** Callback for message events. */
|
|
88
|
+
type MessageCallback = (message: WidgetMessage) => void;
|
|
89
|
+
/** Callback for typing indicator events. */
|
|
90
|
+
type TypingCallback = (payload: TypingPayload) => void;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Customer-facing chat client for the cStar widget API.
|
|
94
|
+
*
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const chat = new ChatClient({ teamSlug: 'acme' });
|
|
97
|
+
* await chat.identify(
|
|
98
|
+
* { externalId: 'usr_123', email: 'jane@co.com', name: 'Jane', timestamp: Date.now() / 1000 },
|
|
99
|
+
* 'hmac_signature_hex'
|
|
100
|
+
* );
|
|
101
|
+
* const { conversations } = await chat.conversations.list();
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
declare class ChatClient {
|
|
105
|
+
private readonly baseUrl;
|
|
106
|
+
private readonly teamSlug;
|
|
107
|
+
private readonly pollingInterval;
|
|
108
|
+
private sessionToken;
|
|
109
|
+
private teamId;
|
|
110
|
+
private pollingTimers;
|
|
111
|
+
/** Sub-resource for managing conversations. */
|
|
112
|
+
readonly conversations: ConversationsApi;
|
|
113
|
+
/** Sub-resource for managing messages. */
|
|
114
|
+
readonly messages: MessagesApi;
|
|
115
|
+
constructor(config: ChatConfig);
|
|
116
|
+
/**
|
|
117
|
+
* Identify a customer using HMAC signature verification.
|
|
118
|
+
* Must be called before accessing conversations or messages.
|
|
119
|
+
*
|
|
120
|
+
* @param customer - Customer identity fields
|
|
121
|
+
* @param signature - HMAC-SHA256 hex signature of the signed fields
|
|
122
|
+
* @param testMode - Whether to use test mode (relaxed timestamp validation)
|
|
123
|
+
*/
|
|
124
|
+
identify(customer: IdentifyParams, signature: string, testMode?: boolean): Promise<IdentifyResult>;
|
|
125
|
+
/**
|
|
126
|
+
* Subscribe to new messages on a conversation.
|
|
127
|
+
* Falls back to polling when Supabase Realtime is not available.
|
|
128
|
+
*
|
|
129
|
+
* @returns Unsubscribe function
|
|
130
|
+
*/
|
|
131
|
+
onMessage(ticketId: string, callback: MessageCallback): () => void;
|
|
132
|
+
/**
|
|
133
|
+
* Subscribe to typing indicator events.
|
|
134
|
+
* Requires Supabase Realtime — returns a no-op if unavailable.
|
|
135
|
+
*
|
|
136
|
+
* @returns Unsubscribe function
|
|
137
|
+
*/
|
|
138
|
+
onTyping(_ticketId: string, _callback: TypingCallback): () => void;
|
|
139
|
+
/** Disconnect all subscriptions and clean up. */
|
|
140
|
+
disconnect(): void;
|
|
141
|
+
/** @internal Make an authenticated request to the widget API. */
|
|
142
|
+
fetch<T>(path: string, options: {
|
|
143
|
+
method: string;
|
|
144
|
+
body?: unknown;
|
|
145
|
+
query?: Record<string, string | undefined>;
|
|
146
|
+
}): Promise<T>;
|
|
147
|
+
/** @internal Get the stored team ID for requests that need it. */
|
|
148
|
+
getTeamId(): string | null;
|
|
149
|
+
/** @internal Set team ID from identify response or other source. */
|
|
150
|
+
setTeamId(teamId: string): void;
|
|
151
|
+
private buildUrl;
|
|
152
|
+
}
|
|
153
|
+
/** Conversations sub-resource on ChatClient. */
|
|
154
|
+
declare class ConversationsApi {
|
|
155
|
+
private readonly chat;
|
|
156
|
+
constructor(chat: ChatClient);
|
|
157
|
+
/** List conversations for the identified customer. */
|
|
158
|
+
list(params?: ConversationListParams): Promise<ConversationListResponse>;
|
|
159
|
+
/** Create a new conversation (ticket). */
|
|
160
|
+
create(params: ConversationCreateParams): Promise<Conversation>;
|
|
161
|
+
}
|
|
162
|
+
/** Messages sub-resource on ChatClient. */
|
|
163
|
+
declare class MessagesApi {
|
|
164
|
+
private readonly chat;
|
|
165
|
+
constructor(chat: ChatClient);
|
|
166
|
+
/** List messages for a conversation. */
|
|
167
|
+
list(ticketId: string): Promise<WidgetMessage[]>;
|
|
168
|
+
/** Send a message to a conversation. */
|
|
169
|
+
send(ticketId: string, content: string): Promise<WidgetMessage>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export { ChatClient, type ChatConfig, type Conversation, type ConversationCreateParams, type ConversationListParams, type ConversationListResponse, type IdentifyParams, type IdentifyResult, type MessageCallback, type TypingCallback, type WidgetMessage };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// src/chat/index.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://app.cstar.help";
|
|
3
|
+
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
4
|
+
var ChatClient = class {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.sessionToken = null;
|
|
7
|
+
this.teamId = null;
|
|
8
|
+
this.pollingTimers = /* @__PURE__ */ new Map();
|
|
9
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
10
|
+
this.teamSlug = config.teamSlug;
|
|
11
|
+
this.pollingInterval = config.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
|
|
12
|
+
this.conversations = new ConversationsApi(this);
|
|
13
|
+
this.messages = new MessagesApi(this);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Identify a customer using HMAC signature verification.
|
|
17
|
+
* Must be called before accessing conversations or messages.
|
|
18
|
+
*
|
|
19
|
+
* @param customer - Customer identity fields
|
|
20
|
+
* @param signature - HMAC-SHA256 hex signature of the signed fields
|
|
21
|
+
* @param testMode - Whether to use test mode (relaxed timestamp validation)
|
|
22
|
+
*/
|
|
23
|
+
async identify(customer, signature, testMode = false) {
|
|
24
|
+
const response = await this.fetch(
|
|
25
|
+
"/api/widget/verify-identity",
|
|
26
|
+
{
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: {
|
|
29
|
+
teamSlug: this.teamSlug,
|
|
30
|
+
testMode,
|
|
31
|
+
customer,
|
|
32
|
+
signature
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
this.sessionToken = response.data.sessionToken;
|
|
37
|
+
return response.data;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Subscribe to new messages on a conversation.
|
|
41
|
+
* Falls back to polling when Supabase Realtime is not available.
|
|
42
|
+
*
|
|
43
|
+
* @returns Unsubscribe function
|
|
44
|
+
*/
|
|
45
|
+
onMessage(ticketId, callback) {
|
|
46
|
+
let lastMessageId = null;
|
|
47
|
+
const poll = async () => {
|
|
48
|
+
try {
|
|
49
|
+
const messages = await this.messages.list(ticketId);
|
|
50
|
+
if (messages.length > 0) {
|
|
51
|
+
const latest = messages[messages.length - 1];
|
|
52
|
+
if (lastMessageId && latest.id !== lastMessageId) {
|
|
53
|
+
const lastIdx = messages.findIndex((m) => m.id === lastMessageId);
|
|
54
|
+
const newMessages = lastIdx >= 0 ? messages.slice(lastIdx + 1) : [latest];
|
|
55
|
+
for (const msg of newMessages) {
|
|
56
|
+
callback(msg);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
lastMessageId = latest.id;
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
poll();
|
|
65
|
+
const timer = setInterval(poll, this.pollingInterval);
|
|
66
|
+
this.pollingTimers.set(ticketId, timer);
|
|
67
|
+
return () => {
|
|
68
|
+
clearInterval(timer);
|
|
69
|
+
this.pollingTimers.delete(ticketId);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to typing indicator events.
|
|
74
|
+
* Requires Supabase Realtime — returns a no-op if unavailable.
|
|
75
|
+
*
|
|
76
|
+
* @returns Unsubscribe function
|
|
77
|
+
*/
|
|
78
|
+
onTyping(_ticketId, _callback) {
|
|
79
|
+
return () => {
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Disconnect all subscriptions and clean up. */
|
|
83
|
+
disconnect() {
|
|
84
|
+
for (const timer of this.pollingTimers.values()) {
|
|
85
|
+
clearInterval(timer);
|
|
86
|
+
}
|
|
87
|
+
this.pollingTimers.clear();
|
|
88
|
+
this.sessionToken = null;
|
|
89
|
+
}
|
|
90
|
+
/** @internal Make an authenticated request to the widget API. */
|
|
91
|
+
async fetch(path, options) {
|
|
92
|
+
const url = this.buildUrl(path, options.query);
|
|
93
|
+
const headers = {
|
|
94
|
+
"Content-Type": "application/json"
|
|
95
|
+
};
|
|
96
|
+
if (this.sessionToken) {
|
|
97
|
+
headers["Authorization"] = `Bearer ${this.sessionToken}`;
|
|
98
|
+
}
|
|
99
|
+
const response = await globalThis.fetch(url, {
|
|
100
|
+
method: options.method,
|
|
101
|
+
headers,
|
|
102
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
|
|
106
|
+
throw new Error(errorBody.error?.message ?? `Widget API error: ${response.status}`);
|
|
107
|
+
}
|
|
108
|
+
return response.json();
|
|
109
|
+
}
|
|
110
|
+
/** @internal Get the stored team ID for requests that need it. */
|
|
111
|
+
getTeamId() {
|
|
112
|
+
return this.teamId;
|
|
113
|
+
}
|
|
114
|
+
/** @internal Set team ID from identify response or other source. */
|
|
115
|
+
setTeamId(teamId) {
|
|
116
|
+
this.teamId = teamId;
|
|
117
|
+
}
|
|
118
|
+
buildUrl(path, query) {
|
|
119
|
+
const base = `${this.baseUrl}${path}`;
|
|
120
|
+
if (!query) return base;
|
|
121
|
+
const params = new URLSearchParams();
|
|
122
|
+
for (const [key, value] of Object.entries(query)) {
|
|
123
|
+
if (value !== void 0) params.set(key, value);
|
|
124
|
+
}
|
|
125
|
+
const qs = params.toString();
|
|
126
|
+
return qs ? `${base}?${qs}` : base;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var ConversationsApi = class {
|
|
130
|
+
constructor(chat) {
|
|
131
|
+
this.chat = chat;
|
|
132
|
+
}
|
|
133
|
+
/** List conversations for the identified customer. */
|
|
134
|
+
async list(params) {
|
|
135
|
+
const teamId = this.chat.getTeamId();
|
|
136
|
+
const response = await this.chat.fetch(
|
|
137
|
+
"/api/widget/conversations",
|
|
138
|
+
{
|
|
139
|
+
method: "GET",
|
|
140
|
+
query: {
|
|
141
|
+
teamId: teamId ?? void 0,
|
|
142
|
+
limit: params?.limit?.toString(),
|
|
143
|
+
cursor: params?.cursor
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
return response.data;
|
|
148
|
+
}
|
|
149
|
+
/** Create a new conversation (ticket). */
|
|
150
|
+
async create(params) {
|
|
151
|
+
const teamId = this.chat.getTeamId();
|
|
152
|
+
const response = await this.chat.fetch(
|
|
153
|
+
"/api/widget/tickets",
|
|
154
|
+
{
|
|
155
|
+
method: "POST",
|
|
156
|
+
body: {
|
|
157
|
+
teamId,
|
|
158
|
+
subject: params.subject,
|
|
159
|
+
message: params.message
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
return {
|
|
164
|
+
id: response.data.id,
|
|
165
|
+
ticketNumber: "",
|
|
166
|
+
subject: response.data.subject,
|
|
167
|
+
status: response.data.status,
|
|
168
|
+
messageCount: params.message ? 1 : 0,
|
|
169
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
170
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
171
|
+
closedAt: null
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
var MessagesApi = class {
|
|
176
|
+
constructor(chat) {
|
|
177
|
+
this.chat = chat;
|
|
178
|
+
}
|
|
179
|
+
/** List messages for a conversation. */
|
|
180
|
+
async list(ticketId) {
|
|
181
|
+
const teamId = this.chat.getTeamId();
|
|
182
|
+
const response = await this.chat.fetch(
|
|
183
|
+
"/api/widget/messages",
|
|
184
|
+
{
|
|
185
|
+
method: "GET",
|
|
186
|
+
query: {
|
|
187
|
+
teamId: teamId ?? void 0,
|
|
188
|
+
ticketId
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
return response.data.messages;
|
|
193
|
+
}
|
|
194
|
+
/** Send a message to a conversation. */
|
|
195
|
+
async send(ticketId, content) {
|
|
196
|
+
const teamId = this.chat.getTeamId();
|
|
197
|
+
const response = await this.chat.fetch(
|
|
198
|
+
"/api/widget/messages",
|
|
199
|
+
{
|
|
200
|
+
method: "POST",
|
|
201
|
+
body: {
|
|
202
|
+
teamId,
|
|
203
|
+
ticketId,
|
|
204
|
+
content
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
return response.data.message;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
export {
|
|
212
|
+
ChatClient
|
|
213
|
+
};
|