@nextxai/sdk 0.2.0 → 0.3.0-beta.31

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.
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.0";
1
+ export declare const VERSION = "0.3.0-beta.31";
@@ -1 +1 @@
1
- export const VERSION = "0.2.0";
1
+ export const VERSION = "0.3.0-beta.31";
@@ -0,0 +1,92 @@
1
+ /** Hai lần giao cách nhau quá ngưỡng này (giây) bị coi là phát lại. */
2
+ export declare const DEFAULT_TOLERANCE_SEC = 300;
3
+ /** Tổng đài viên trong dữ liệu công khai. */
4
+ export interface WebhookAgent {
5
+ id: string;
6
+ userId: string;
7
+ name: string | null;
8
+ email: string | null;
9
+ extension: string;
10
+ }
11
+ /** `data.object` của các sự kiện `call.*` — cùng hình dạng với `GET /api/v1/call/partner/calls/{id}`. */
12
+ export interface WebhookCall {
13
+ callId: string;
14
+ /** 1 = ringing · 2 = answered · 3 = completed/missed · 4 = recording_ready · 5 = summary_ready — sắp xếp khi tới lệch thứ tự. */
15
+ sequence: number;
16
+ /** queued · initiated · ringing · in-progress · completed · busy · no-answer · failed · canceled · abandoned */
17
+ status: string;
18
+ direction: "inbound" | "outbound";
19
+ /** E.164 khi chuẩn hoá được. */
20
+ from: string;
21
+ to: string;
22
+ agent: WebhookAgent | null;
23
+ queueId: string | null;
24
+ queueName: string | null;
25
+ startedAt: string | null;
26
+ answeredAt: string | null;
27
+ endedAt: string | null;
28
+ durationSec: number | null;
29
+ talkSec: number | null;
30
+ waitSec: number | null;
31
+ /** Mã Q.850 (16 = gác bình thường, 17 = bận, 19 = không trả lời, 41 = lỗi tạm thời…). */
32
+ hangupCause: number | null;
33
+ hangupCauseName: string | null;
34
+ /** `contactRef` bạn gửi lúc gọi ra — trả lại nguyên văn. */
35
+ contactRef: string | null;
36
+ /** `metadata` bạn gửi lúc gọi ra — trả lại nguyên văn. */
37
+ metadata: Record<string, string> | null;
38
+ crmContactId: string | null;
39
+ hasRecording: boolean;
40
+ hasSummary: boolean;
41
+ /** Tóm tắt AI — có từ sự kiện `call.summary_ready` (null trước đó). */
42
+ summary: string | null;
43
+ }
44
+ /** `data.object` của `agent.status_changed`. */
45
+ export interface WebhookAgentStatus {
46
+ agent: WebhookAgent;
47
+ from: string;
48
+ to: string;
49
+ reason: string | null;
50
+ changedAt: string;
51
+ }
52
+ export interface WebhookObjectMap {
53
+ "call.ringing": WebhookCall;
54
+ "call.answered": WebhookCall;
55
+ "call.completed": WebhookCall;
56
+ "call.missed": WebhookCall;
57
+ "call.recording_ready": WebhookCall;
58
+ "call.summary_ready": WebhookCall;
59
+ "agent.status_changed": WebhookAgentStatus;
60
+ }
61
+ interface Envelope<K extends string, T> {
62
+ id: string;
63
+ type: K;
64
+ api_version: string;
65
+ created_at: string;
66
+ workspace_id: string;
67
+ livemode: boolean;
68
+ data: {
69
+ object: T;
70
+ };
71
+ metadata?: Record<string, unknown>;
72
+ }
73
+ /** Sự kiện đã kiểm chữ ký. Thu hẹp kiểu bằng `event.type`. Loại sự kiện khác (CRM, Bán hàng…) có `data.object` là `unknown`. */
74
+ export type WebhookEvent = {
75
+ [K in keyof WebhookObjectMap]: Envelope<K, WebhookObjectMap[K]>;
76
+ }[keyof WebhookObjectMap] | Envelope<string & {}, unknown>;
77
+ export declare class WebhookVerificationError extends Error {
78
+ constructor(message: string);
79
+ }
80
+ type HeaderBag = Headers | Record<string, string | string[] | undefined>;
81
+ /** Ký một thân webhook — dùng để viết test cho phần nhận webhook của bạn. */
82
+ export declare function signWebhook(secret: string, id: string, timestamp: number, body: string | Buffer): string;
83
+ /**
84
+ * Kiểm `webhook-id` / `webhook-timestamp` / `webhook-signature` rồi trả sự kiện đã parse.
85
+ * Ném {@link WebhookVerificationError} khi thiếu header, chữ ký sai, hoặc lệch giờ quá `toleranceSec`.
86
+ * Dùng `webhook-id` (cũng là `event.id`) để bỏ qua lần giao trùng — NextX giao lại khi bạn không trả 2xx.
87
+ */
88
+ export declare function verifyWebhook(body: string | Buffer | Uint8Array, headers: HeaderBag, secret: string, options?: {
89
+ toleranceSec?: number;
90
+ now?: Date;
91
+ }): WebhookEvent;
92
+ export {};
@@ -0,0 +1,75 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * Kiểm chữ ký webhook NextX theo chuẩn mở Standard Webhooks (https://www.standardwebhooks.com) — chỉ chạy trên máy chủ.
4
+ *
5
+ * import { verifyWebhook } from "@nextxai/sdk/webhooks";
6
+ *
7
+ * app.post("/nextx/webhook", express.raw({ type: "application/json" }), (req, res) => {
8
+ * const event = verifyWebhook(req.body, req.headers, process.env.NEXTX_WEBHOOK_SECRET!);
9
+ * if (event.type === "call.completed") saveCall(event.data.object);
10
+ * res.sendStatus(204);
11
+ * });
12
+ *
13
+ * Phải truyền THÂN GỐC (Buffer/chuỗi đúng từng byte) — JSON đã parse rồi stringify lại sẽ lệch chữ ký.
14
+ * Bí mật dạng `whsec_…` lấy ở developers.nextx.vn › Webhooks (xoay khoá ⇒ header có nhiều chữ ký, khớp một là đủ).
15
+ */
16
+ import { createHmac, timingSafeEqual } from "node:crypto";
17
+ /** Hai lần giao cách nhau quá ngưỡng này (giây) bị coi là phát lại. */
18
+ export const DEFAULT_TOLERANCE_SEC = 300;
19
+ export class WebhookVerificationError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "WebhookVerificationError";
23
+ }
24
+ }
25
+ function header(headers, name) {
26
+ if (typeof headers.get === "function")
27
+ return headers.get(name) ?? undefined;
28
+ const bag = headers;
29
+ const hit = Object.keys(bag).find((k) => k.toLowerCase() === name);
30
+ const v = hit === undefined ? undefined : bag[hit];
31
+ return Array.isArray(v) ? v[0] : v;
32
+ }
33
+ function keyOf(secret) {
34
+ if (!secret?.startsWith("whsec_"))
35
+ throw new WebhookVerificationError("secret phải có dạng whsec_…");
36
+ const key = Buffer.from(secret.slice(6), "base64");
37
+ if (key.length < 24 || key.length > 64)
38
+ throw new WebhookVerificationError("secret không hợp lệ");
39
+ return key;
40
+ }
41
+ /** Ký một thân webhook — dùng để viết test cho phần nhận webhook của bạn. */
42
+ export function signWebhook(secret, id, timestamp, body) {
43
+ const mac = createHmac("sha256", keyOf(secret));
44
+ mac.update(`${id}.${timestamp}.`);
45
+ mac.update(body);
46
+ return `v1,${mac.digest("base64")}`;
47
+ }
48
+ /**
49
+ * Kiểm `webhook-id` / `webhook-timestamp` / `webhook-signature` rồi trả sự kiện đã parse.
50
+ * Ném {@link WebhookVerificationError} khi thiếu header, chữ ký sai, hoặc lệch giờ quá `toleranceSec`.
51
+ * Dùng `webhook-id` (cũng là `event.id`) để bỏ qua lần giao trùng — NextX giao lại khi bạn không trả 2xx.
52
+ */
53
+ export function verifyWebhook(body, headers, secret, options = {}) {
54
+ const id = header(headers, "webhook-id");
55
+ const ts = header(headers, "webhook-timestamp");
56
+ const sig = header(headers, "webhook-signature");
57
+ if (!id || !ts || !sig)
58
+ throw new WebhookVerificationError("thiếu header webhook-id / webhook-timestamp / webhook-signature");
59
+ const timestamp = Number(ts);
60
+ const now = Math.floor((options.now ?? new Date()).getTime() / 1000);
61
+ if (!Number.isInteger(timestamp) || Math.abs(now - timestamp) > (options.toleranceSec ?? DEFAULT_TOLERANCE_SEC))
62
+ throw new WebhookVerificationError("webhook-timestamp lệch quá ngưỡng");
63
+ const raw = typeof body === "string" ? Buffer.from(body, "utf8") : Buffer.from(body);
64
+ const expected = Buffer.from(signWebhook(secret, id, timestamp, raw));
65
+ const ok = sig
66
+ .split(" ")
67
+ .filter(Boolean)
68
+ .some((s) => {
69
+ const candidate = Buffer.from(s);
70
+ return candidate.length === expected.length && timingSafeEqual(candidate, expected);
71
+ });
72
+ if (!ok)
73
+ throw new WebhookVerificationError("chữ ký không khớp");
74
+ return JSON.parse(raw.toString("utf8"));
75
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextxai/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-beta.31",
4
4
  "description": "Official Node.js and TypeScript client for the NextX API",
5
5
  "keywords": [
6
6
  "nextx",
@@ -45,6 +45,16 @@
45
45
  "default": "./dist/cjs/phone.js"
46
46
  }
47
47
  },
48
+ "./webhooks": {
49
+ "import": {
50
+ "types": "./dist/esm/webhooks.d.ts",
51
+ "default": "./dist/esm/webhooks.js"
52
+ },
53
+ "require": {
54
+ "types": "./dist/cjs/webhooks.d.ts",
55
+ "default": "./dist/cjs/webhooks.js"
56
+ }
57
+ },
48
58
  "./package.json": "./package.json"
49
59
  },
50
60
  "files": [
@@ -74,6 +84,6 @@
74
84
  "typescript": "5.6.3"
75
85
  },
76
86
  "nextx": {
77
- "specSha256": "93951d8099fbb163713c6b6b8bdd5787b812e39f2573b76d2d3d8773c04b1f45"
87
+ "specSha256": "28c08235e384d4d75ec3c9f455a6a2fd67ed74bc3d33ae7e8bb009a8bd2de393"
78
88
  }
79
89
  }