@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 CHANGED
@@ -30,22 +30,41 @@ import type { SpokPayChatOptions, CreateConversationOptions, Conversation, GetCo
30
30
  * });
31
31
  * ```
32
32
  *
33
- * @example Receive staff replies via webhook
33
+ * @example Convex integration reactive chat with webhooks
34
34
  * ```ts
35
- * // In your webhook handler
36
- * const body = await req.text();
37
- * const isValid = await SpokPayChat.verifyWebhookSignature({
38
- * payload: body,
39
- * signature: req.headers.get("x-spokpay-signature")!,
40
- * secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
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
- * if (isValid) {
44
- * const event = JSON.parse(body);
45
- * if (event.type === "message.created") {
46
- * // Staff replied show the message to the customer
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 isValid = await SpokPayChat.verifyWebhookSignature({
73
- * payload: rawBody,
74
- * signature: req.headers.get("x-spokpay-signature")!,
75
- * secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
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 Receive staff replies via webhook
34
+ * @example Convex integration reactive chat with webhooks
35
35
  * ```ts
36
- * // In your webhook handler
37
- * const body = await req.text();
38
- * const isValid = await SpokPayChat.verifyWebhookSignature({
39
- * payload: body,
40
- * signature: req.headers.get("x-spokpay-signature")!,
41
- * secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
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
- * if (isValid) {
45
- * const event = JSON.parse(body);
46
- * if (event.type === "message.created") {
47
- * // Staff replied show the message to the customer
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 isValid = await SpokPayChat.verifyWebhookSignature({
78
- * payload: rawBody,
79
- * signature: req.headers.get("x-spokpay-signature")!,
80
- * secret: process.env.SPOKPAY_WEBHOOK_SECRET!,
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
- /** Data included in message webhook events. */
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
- /** Data included in conversation webhook events. */
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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spokpay/chat",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "SpokPay Chat SDK — add live chat conversations to your app",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",