@spokpay/chat 0.1.0 → 0.1.1
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/client.d.ts +99 -18
- package/dist/client.js +99 -18
- package/dist/types.d.ts +74 -3
- package/dist/types.js +16 -1
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -30,22 +30,41 @@ import type { SpokPayChatOptions, CreateConversationOptions, Conversation, GetCo
|
|
|
30
30
|
* });
|
|
31
31
|
* ```
|
|
32
32
|
*
|
|
33
|
-
* @example
|
|
33
|
+
* @example Convex integration — reactive chat with webhooks
|
|
34
34
|
* ```ts
|
|
35
|
-
* //
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
35
|
+
* // convex/http.ts — Receive SpokPay chat webhooks
|
|
36
|
+
* import { SpokPayChat, type ChatWebhookPayload } from "@spokpay/chat";
|
|
37
|
+
*
|
|
38
|
+
* http.route({
|
|
39
|
+
* path: "/webhooks/spokpay-chat",
|
|
40
|
+
* method: "POST",
|
|
41
|
+
* handler: httpAction(async (ctx, req) => {
|
|
42
|
+
* const body = await req.text();
|
|
43
|
+
* const signature = req.headers.get("x-spokpay-signature")!;
|
|
44
|
+
*
|
|
45
|
+
* const isValid = await SpokPayChat.verifyWebhookSignature({
|
|
46
|
+
* payload: body,
|
|
47
|
+
* signature,
|
|
48
|
+
* secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
|
|
49
|
+
* });
|
|
50
|
+
* if (!isValid) return new Response("Invalid signature", { status: 401 });
|
|
51
|
+
*
|
|
52
|
+
* const event: ChatWebhookPayload = JSON.parse(body);
|
|
53
|
+
* if (event.type === "message.created") {
|
|
54
|
+
* await ctx.runMutation(internal.chat.handleStaffReply, {
|
|
55
|
+
* conversationId: event.data.conversationId,
|
|
56
|
+
* content: event.data.content,
|
|
57
|
+
* authorName: event.data.authorDisplayName,
|
|
58
|
+
* });
|
|
59
|
+
* }
|
|
60
|
+
* return new Response("OK");
|
|
61
|
+
* }),
|
|
41
62
|
* });
|
|
42
63
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* displayMessage(event.data.content, event.data.authorDisplayName);
|
|
48
|
-
* }
|
|
64
|
+
* // React component — auto-updates when webhook triggers the mutation
|
|
65
|
+
* function ChatPage({ conversationId }: { conversationId: string }) {
|
|
66
|
+
* const messages = useQuery(api.chat.listMessages, { conversationId });
|
|
67
|
+
* // messages reactively updates when staff replies via the webhook
|
|
49
68
|
* }
|
|
50
69
|
* ```
|
|
51
70
|
*/
|
|
@@ -54,8 +73,45 @@ export declare class SpokPayChat {
|
|
|
54
73
|
private readonly baseUrl;
|
|
55
74
|
/**
|
|
56
75
|
* Client for managing conversations and messages.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* // Create a conversation
|
|
80
|
+
* const conv = await chat.conversations.create({
|
|
81
|
+
* type: "order_support",
|
|
82
|
+
* customer: { externalId: "cust_123", name: "João" },
|
|
83
|
+
* subject: "Help with my order",
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* // Send a message
|
|
87
|
+
* await chat.conversations.sendMessage({
|
|
88
|
+
* conversationId: conv.id,
|
|
89
|
+
* content: "When will my order arrive?",
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* // List messages
|
|
93
|
+
* const { messages } = await chat.conversations.listMessages({
|
|
94
|
+
* conversationId: conv.id,
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // Close when resolved
|
|
98
|
+
* await chat.conversations.close({ id: conv.id });
|
|
99
|
+
* ```
|
|
57
100
|
*/
|
|
58
101
|
readonly conversations: ConversationsClient;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a new SpokPay Chat client.
|
|
104
|
+
*
|
|
105
|
+
* @param options - Client configuration (API key and base URL).
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const chat = new SpokPayChat({
|
|
110
|
+
* apiKey: process.env.SPOKPAY_API_KEY!,
|
|
111
|
+
* baseUrl: process.env.SPOKPAY_BASE_URL!,
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
59
115
|
constructor(options: SpokPayChatOptions);
|
|
60
116
|
/**
|
|
61
117
|
* Verify a webhook signature to ensure the request came from SpokPay.
|
|
@@ -64,15 +120,40 @@ export declare class SpokPayChat {
|
|
|
64
120
|
* Works in all JavaScript runtimes: Node.js 18+, Deno, Cloudflare Workers,
|
|
65
121
|
* Vercel Edge, and Convex.
|
|
66
122
|
*
|
|
123
|
+
* **Important:** Always verify signatures before processing webhook payloads.
|
|
124
|
+
* Use the raw request body string — do not parse it before verification.
|
|
125
|
+
*
|
|
67
126
|
* @param options - The raw payload, signature header, and your webhook secret.
|
|
68
127
|
* @returns `true` if the signature is valid, `false` otherwise.
|
|
69
128
|
*
|
|
70
|
-
* @example
|
|
129
|
+
* @example Node.js / Express
|
|
130
|
+
* ```ts
|
|
131
|
+
* app.post("/webhooks/spokpay-chat", express.raw({ type: "*\/*" }), async (req, res) => {
|
|
132
|
+
* const isValid = await SpokPayChat.verifyWebhookSignature({
|
|
133
|
+
* payload: req.body.toString(),
|
|
134
|
+
* signature: req.headers["x-spokpay-signature"] as string,
|
|
135
|
+
* secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
|
|
136
|
+
* });
|
|
137
|
+
*
|
|
138
|
+
* if (!isValid) return res.status(401).send("Invalid signature");
|
|
139
|
+
*
|
|
140
|
+
* const event = JSON.parse(req.body.toString());
|
|
141
|
+
* // Handle event...
|
|
142
|
+
* res.sendStatus(200);
|
|
143
|
+
* });
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @example Convex httpAction
|
|
71
147
|
* ```ts
|
|
72
|
-
* const
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
148
|
+
* const handler = httpAction(async (ctx, req) => {
|
|
149
|
+
* const body = await req.text();
|
|
150
|
+
* const isValid = await SpokPayChat.verifyWebhookSignature({
|
|
151
|
+
* payload: body,
|
|
152
|
+
* signature: req.headers.get("x-spokpay-signature")!,
|
|
153
|
+
* secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
|
|
154
|
+
* });
|
|
155
|
+
* if (!isValid) return new Response("Invalid", { status: 401 });
|
|
156
|
+
* // Process the webhook...
|
|
76
157
|
* });
|
|
77
158
|
* ```
|
|
78
159
|
*/
|
package/dist/client.js
CHANGED
|
@@ -31,22 +31,41 @@ const DEFAULT_BASE_URL = "https://your-convex-deployment.convex.site";
|
|
|
31
31
|
* });
|
|
32
32
|
* ```
|
|
33
33
|
*
|
|
34
|
-
* @example
|
|
34
|
+
* @example Convex integration — reactive chat with webhooks
|
|
35
35
|
* ```ts
|
|
36
|
-
* //
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
36
|
+
* // convex/http.ts — Receive SpokPay chat webhooks
|
|
37
|
+
* import { SpokPayChat, type ChatWebhookPayload } from "@spokpay/chat";
|
|
38
|
+
*
|
|
39
|
+
* http.route({
|
|
40
|
+
* path: "/webhooks/spokpay-chat",
|
|
41
|
+
* method: "POST",
|
|
42
|
+
* handler: httpAction(async (ctx, req) => {
|
|
43
|
+
* const body = await req.text();
|
|
44
|
+
* const signature = req.headers.get("x-spokpay-signature")!;
|
|
45
|
+
*
|
|
46
|
+
* const isValid = await SpokPayChat.verifyWebhookSignature({
|
|
47
|
+
* payload: body,
|
|
48
|
+
* signature,
|
|
49
|
+
* secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
|
|
50
|
+
* });
|
|
51
|
+
* if (!isValid) return new Response("Invalid signature", { status: 401 });
|
|
52
|
+
*
|
|
53
|
+
* const event: ChatWebhookPayload = JSON.parse(body);
|
|
54
|
+
* if (event.type === "message.created") {
|
|
55
|
+
* await ctx.runMutation(internal.chat.handleStaffReply, {
|
|
56
|
+
* conversationId: event.data.conversationId,
|
|
57
|
+
* content: event.data.content,
|
|
58
|
+
* authorName: event.data.authorDisplayName,
|
|
59
|
+
* });
|
|
60
|
+
* }
|
|
61
|
+
* return new Response("OK");
|
|
62
|
+
* }),
|
|
42
63
|
* });
|
|
43
64
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* displayMessage(event.data.content, event.data.authorDisplayName);
|
|
49
|
-
* }
|
|
65
|
+
* // React component — auto-updates when webhook triggers the mutation
|
|
66
|
+
* function ChatPage({ conversationId }: { conversationId: string }) {
|
|
67
|
+
* const messages = useQuery(api.chat.listMessages, { conversationId });
|
|
68
|
+
* // messages reactively updates when staff replies via the webhook
|
|
50
69
|
* }
|
|
51
70
|
* ```
|
|
52
71
|
*/
|
|
@@ -55,8 +74,45 @@ export class SpokPayChat {
|
|
|
55
74
|
baseUrl;
|
|
56
75
|
/**
|
|
57
76
|
* Client for managing conversations and messages.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* // Create a conversation
|
|
81
|
+
* const conv = await chat.conversations.create({
|
|
82
|
+
* type: "order_support",
|
|
83
|
+
* customer: { externalId: "cust_123", name: "João" },
|
|
84
|
+
* subject: "Help with my order",
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* // Send a message
|
|
88
|
+
* await chat.conversations.sendMessage({
|
|
89
|
+
* conversationId: conv.id,
|
|
90
|
+
* content: "When will my order arrive?",
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* // List messages
|
|
94
|
+
* const { messages } = await chat.conversations.listMessages({
|
|
95
|
+
* conversationId: conv.id,
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* // Close when resolved
|
|
99
|
+
* await chat.conversations.close({ id: conv.id });
|
|
100
|
+
* ```
|
|
58
101
|
*/
|
|
59
102
|
conversations;
|
|
103
|
+
/**
|
|
104
|
+
* Creates a new SpokPay Chat client.
|
|
105
|
+
*
|
|
106
|
+
* @param options - Client configuration (API key and base URL).
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* const chat = new SpokPayChat({
|
|
111
|
+
* apiKey: process.env.SPOKPAY_API_KEY!,
|
|
112
|
+
* baseUrl: process.env.SPOKPAY_BASE_URL!,
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
60
116
|
constructor(options) {
|
|
61
117
|
this.apiKey = options.apiKey;
|
|
62
118
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
@@ -69,15 +125,40 @@ export class SpokPayChat {
|
|
|
69
125
|
* Works in all JavaScript runtimes: Node.js 18+, Deno, Cloudflare Workers,
|
|
70
126
|
* Vercel Edge, and Convex.
|
|
71
127
|
*
|
|
128
|
+
* **Important:** Always verify signatures before processing webhook payloads.
|
|
129
|
+
* Use the raw request body string — do not parse it before verification.
|
|
130
|
+
*
|
|
72
131
|
* @param options - The raw payload, signature header, and your webhook secret.
|
|
73
132
|
* @returns `true` if the signature is valid, `false` otherwise.
|
|
74
133
|
*
|
|
75
|
-
* @example
|
|
134
|
+
* @example Node.js / Express
|
|
135
|
+
* ```ts
|
|
136
|
+
* app.post("/webhooks/spokpay-chat", express.raw({ type: "*\/*" }), async (req, res) => {
|
|
137
|
+
* const isValid = await SpokPayChat.verifyWebhookSignature({
|
|
138
|
+
* payload: req.body.toString(),
|
|
139
|
+
* signature: req.headers["x-spokpay-signature"] as string,
|
|
140
|
+
* secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
|
|
141
|
+
* });
|
|
142
|
+
*
|
|
143
|
+
* if (!isValid) return res.status(401).send("Invalid signature");
|
|
144
|
+
*
|
|
145
|
+
* const event = JSON.parse(req.body.toString());
|
|
146
|
+
* // Handle event...
|
|
147
|
+
* res.sendStatus(200);
|
|
148
|
+
* });
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* @example Convex httpAction
|
|
76
152
|
* ```ts
|
|
77
|
-
* const
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
153
|
+
* const handler = httpAction(async (ctx, req) => {
|
|
154
|
+
* const body = await req.text();
|
|
155
|
+
* const isValid = await SpokPayChat.verifyWebhookSignature({
|
|
156
|
+
* payload: body,
|
|
157
|
+
* signature: req.headers.get("x-spokpay-signature")!,
|
|
158
|
+
* secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
|
|
159
|
+
* });
|
|
160
|
+
* if (!isValid) return new Response("Invalid", { status: 401 });
|
|
161
|
+
* // Process the webhook...
|
|
81
162
|
* });
|
|
82
163
|
* ```
|
|
83
164
|
*/
|
package/dist/types.d.ts
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Configuration options for the {@link SpokPayChat} client.
|
|
3
3
|
*
|
|
4
|
+
* The API key prefix determines the environment:
|
|
5
|
+
* - `spk_live_*` — Production (real conversations routed to staff)
|
|
6
|
+
* - `spk_test_*` — Development/Sandbox (test conversations, no notifications)
|
|
7
|
+
*
|
|
4
8
|
* @example
|
|
5
9
|
* ```ts
|
|
6
10
|
* import { SpokPayChat } from "@spokpay/chat";
|
|
7
11
|
*
|
|
12
|
+
* // Production
|
|
8
13
|
* const chat = new SpokPayChat({
|
|
9
14
|
* apiKey: "spk_live_abc123...",
|
|
10
15
|
* baseUrl: "https://your-deployment.convex.site",
|
|
11
16
|
* });
|
|
17
|
+
*
|
|
18
|
+
* // Sandbox / Testing
|
|
19
|
+
* const chatTest = new SpokPayChat({
|
|
20
|
+
* apiKey: "spk_test_abc123...",
|
|
21
|
+
* baseUrl: "https://your-deployment.convex.site",
|
|
22
|
+
* });
|
|
12
23
|
* ```
|
|
13
24
|
*/
|
|
14
25
|
export interface SpokPayChatOptions {
|
|
15
26
|
/**
|
|
16
27
|
* Your SpokPay API key.
|
|
28
|
+
* - `spk_live_*` keys target **production** (real conversations routed to staff).
|
|
29
|
+
* - `spk_test_*` keys target **SpokPay sandbox** (test conversations, no notifications).
|
|
30
|
+
*
|
|
17
31
|
* Generate keys from the Plugin page in your SpokPay store dashboard.
|
|
18
32
|
*/
|
|
19
33
|
apiKey: string;
|
|
@@ -229,31 +243,73 @@ export interface ChatWebhookPayload {
|
|
|
229
243
|
/** Unix timestamp (ms) when the event was created. */
|
|
230
244
|
createdAt: number;
|
|
231
245
|
}
|
|
232
|
-
/**
|
|
246
|
+
/**
|
|
247
|
+
* Data included in message webhook events (`message.created`, `message.updated`, `message.deleted`).
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```ts
|
|
251
|
+
* const event: ChatWebhookPayload = JSON.parse(body);
|
|
252
|
+
* if (event.type === "message.created") {
|
|
253
|
+
* const msg = event.data as WebhookMessageData;
|
|
254
|
+
* console.log(`${msg.authorDisplayName}: ${msg.content}`);
|
|
255
|
+
* }
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
233
258
|
export interface WebhookMessageData {
|
|
259
|
+
/** Unique message ID. */
|
|
234
260
|
id: string;
|
|
261
|
+
/** The conversation this message belongs to. */
|
|
235
262
|
conversationId: string;
|
|
263
|
+
/** Message text content. */
|
|
236
264
|
content: string;
|
|
265
|
+
/** Who sent this message (e.g., `"staff"`, `"ai"`, `"bot"`). */
|
|
237
266
|
authorType: string;
|
|
267
|
+
/** Display name of the author. */
|
|
238
268
|
authorDisplayName?: string;
|
|
269
|
+
/** Your external ID for the author, if provided. */
|
|
239
270
|
authorExternalId?: string;
|
|
271
|
+
/** File attachments included with the message. */
|
|
240
272
|
attachments: Array<{
|
|
273
|
+
/** Filename. */
|
|
241
274
|
filename: string;
|
|
275
|
+
/** MIME content type. */
|
|
242
276
|
contentType?: string;
|
|
277
|
+
/** File size in bytes. */
|
|
243
278
|
size?: number;
|
|
279
|
+
/** Direct URL to the file. */
|
|
244
280
|
url?: string;
|
|
245
281
|
}>;
|
|
282
|
+
/** Unix timestamp (ms) when the message was sent. */
|
|
246
283
|
createdAt: number;
|
|
247
284
|
}
|
|
248
|
-
/**
|
|
285
|
+
/**
|
|
286
|
+
* Data included in conversation webhook events (`conversation.closed`).
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```ts
|
|
290
|
+
* const event: ChatWebhookPayload = JSON.parse(body);
|
|
291
|
+
* if (event.type === "conversation.closed") {
|
|
292
|
+
* const conv = event.data as WebhookConversationData;
|
|
293
|
+
* console.log(`Conversation ${conv.id} closed at ${new Date(conv.closedAt!)}`);
|
|
294
|
+
* }
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
249
297
|
export interface WebhookConversationData {
|
|
298
|
+
/** Unique SpokPay conversation ID. */
|
|
250
299
|
id: string;
|
|
300
|
+
/** The type of conversation (e.g., `"order_support"`, `"general"`). */
|
|
251
301
|
type: string;
|
|
302
|
+
/** Current status of the conversation (e.g., `"closed"`). */
|
|
252
303
|
status: string;
|
|
304
|
+
/** Your external ID, if provided when creating the conversation. */
|
|
253
305
|
externalId?: string;
|
|
306
|
+
/** Conversation subject or title. */
|
|
254
307
|
subject?: string;
|
|
308
|
+
/** Arbitrary metadata as a JSON string, if provided. */
|
|
255
309
|
metadata?: string;
|
|
310
|
+
/** Unix timestamp (ms) when the conversation was created. */
|
|
256
311
|
createdAt: number;
|
|
312
|
+
/** Unix timestamp (ms) when the conversation was closed. */
|
|
257
313
|
closedAt?: number;
|
|
258
314
|
}
|
|
259
315
|
/**
|
|
@@ -270,7 +326,22 @@ export interface VerifyWebhookOptions {
|
|
|
270
326
|
secret: string;
|
|
271
327
|
}
|
|
272
328
|
/**
|
|
273
|
-
* Error thrown when the SpokPay API returns a non-2xx response.
|
|
329
|
+
* Error thrown when the SpokPay Chat API returns a non-2xx response.
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```ts
|
|
333
|
+
* import { SpokPayChatApiError } from "@spokpay/chat";
|
|
334
|
+
*
|
|
335
|
+
* try {
|
|
336
|
+
* await chat.conversations.get({ id: "invalid_id" });
|
|
337
|
+
* } catch (error) {
|
|
338
|
+
* if (error instanceof SpokPayChatApiError) {
|
|
339
|
+
* console.error(error.message); // "Conversation not found"
|
|
340
|
+
* console.error(error.statusCode); // 404
|
|
341
|
+
* console.error(error.body); // Raw API response body
|
|
342
|
+
* }
|
|
343
|
+
* }
|
|
344
|
+
* ```
|
|
274
345
|
*/
|
|
275
346
|
export declare class SpokPayChatApiError extends Error {
|
|
276
347
|
/** HTTP status code from the API response. */
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Error thrown when the SpokPay API returns a non-2xx response.
|
|
2
|
+
* Error thrown when the SpokPay Chat API returns a non-2xx response.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { SpokPayChatApiError } from "@spokpay/chat";
|
|
7
|
+
*
|
|
8
|
+
* try {
|
|
9
|
+
* await chat.conversations.get({ id: "invalid_id" });
|
|
10
|
+
* } catch (error) {
|
|
11
|
+
* if (error instanceof SpokPayChatApiError) {
|
|
12
|
+
* console.error(error.message); // "Conversation not found"
|
|
13
|
+
* console.error(error.statusCode); // 404
|
|
14
|
+
* console.error(error.body); // Raw API response body
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
3
18
|
*/
|
|
4
19
|
export class SpokPayChatApiError extends Error {
|
|
5
20
|
/** HTTP status code from the API response. */
|