@chatlychat/sdk-node 0.0.0-stage → 0.2.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.
@@ -0,0 +1,183 @@
1
+ import { listable } from './pagination.js';
2
+ export function contactsResource(t) {
3
+ return {
4
+ ...listable(t, '/v1/contacts'),
5
+ get: (id) => t.get(`/v1/contacts/${encodeURIComponent(id)}`),
6
+ upsert: (body) => t.post('/v1/contacts', body),
7
+ /** Up to the API's documented batch ceiling per call. One idempotency key covers the batch. */
8
+ bulkUpsert: (contacts) => t.post('/v1/contacts/bulk', { contacts }),
9
+ merge: (id, body) => t.post(`/v1/contacts/${encodeURIComponent(id)}/merge`, body),
10
+ /**
11
+ * Add ONE tag. The route's body is `{ tag }`, singular.
12
+ *
13
+ * Kept plural in the cross-language manifest (`contacts.add_tags`) because
14
+ * eight SDKs have locked that name, but the wire shape is one tag per
15
+ * call: loop for several. This sent `{ tags: [...] }`, which zod dropped,
16
+ * so it returned 200 and added nothing.
17
+ */
18
+ addTags: (id, tag) => t.post(`/v1/contacts/${encodeURIComponent(id)}/tags`, { tag }),
19
+ removeTag: (id, tag) => t.del(`/v1/contacts/${encodeURIComponent(id)}/tags/${encodeURIComponent(tag)}`),
20
+ /**
21
+ * Opt the contact out of outbound email, or back in.
22
+ *
23
+ * `unsubscribed` is required — there is no default, and omitting it is a
24
+ * 400. Honoured across campaigns and workflows.
25
+ */
26
+ unsubscribe: (id, body) => t.post(`/v1/contacts/${encodeURIComponent(id)}/unsubscribe`, body),
27
+ /** The contact's timeline: conversations, events, tickets, in one ordered read. */
28
+ journey: (id, query = {}) => t.get(`/v1/contacts/${encodeURIComponent(id)}/journey`, query),
29
+ };
30
+ }
31
+ export function conversationsResource(t) {
32
+ return {
33
+ ...listable(t, '/v1/conversations'),
34
+ get: (id) => t.get(`/v1/conversations/${encodeURIComponent(id)}`),
35
+ /**
36
+ * Read a thread before replying to it.
37
+ *
38
+ * The MCP server has exposed this as `get_messages` since it shipped and
39
+ * the SDK did not, so an integrator writing "read the conversation, then
40
+ * decide" against the SDK had to hand-roll the request while the same
41
+ * capability sat behind a tool call.
42
+ */
43
+ messages: (conversationId, query = {}) => t.get(`/v1/conversations/${encodeURIComponent(conversationId)}/messages`, query),
44
+ send: (body) => t.post(`/v1/conversations/${encodeURIComponent(body.conversationId)}/messages`, body),
45
+ /** Open conversations with no agent reply yet — the queue a bot or a rota works from. */
46
+ unattended: (query = {}) => t.get('/v1/conversations/unattended', query),
47
+ /** Is a human or a bot driving this thread right now. */
48
+ agentHandling: (id) => t.get(`/v1/conversations/${encodeURIComponent(id)}/agent-handling`),
49
+ /** Hand the thread to a bot, or take it back. `{ state, reason? }`. */
50
+ setAgentHandling: (id, body) => t.put(`/v1/conversations/${encodeURIComponent(id)}/agent-handling`, body),
51
+ /** Drafts the AI produced for this thread, newest first. */
52
+ generatedReplies: (id, query = {}) => t.get(`/v1/conversations/${encodeURIComponent(id)}/generated-replies`, query),
53
+ /** Ask for a draft now. Returns the draft; it is not sent until you send it. */
54
+ generateReply: (id, body = {}) => t.post(`/v1/conversations/${encodeURIComponent(id)}/generated-replies`, body),
55
+ scheduledMessages: (id, query = {}) => t.get(`/v1/conversations/${encodeURIComponent(id)}/scheduled-messages`, query),
56
+ scheduleMessage: (id, body) => t.post(`/v1/conversations/${encodeURIComponent(id)}/scheduled-messages`, body),
57
+ cancelScheduled: (scheduledId) => t.del(`/v1/conversations/scheduled-messages/${encodeURIComponent(scheduledId)}`),
58
+ };
59
+ }
60
+ export function ticketsResource(t) {
61
+ return {
62
+ ...listable(t, '/v1/tickets'),
63
+ comments: (id, query = {}) => t.get(`/v1/tickets/${encodeURIComponent(id)}/comments`, query),
64
+ /** The ticket auto-created from a conversation, if there is one. */
65
+ forConversation: (conversationId) => t.get(`/v1/tickets/for-conversation/${encodeURIComponent(conversationId)}`),
66
+ queues: () => t.get('/v1/tickets/queues'),
67
+ /** `id` is the WINNER; the body names the loser. */
68
+ merge: (id, body) => t.post(`/v1/tickets/${encodeURIComponent(id)}/merge`, body),
69
+ split: (id, body) => t.post(`/v1/tickets/${encodeURIComponent(id)}/split`, body),
70
+ /** One status, assignee or queue change applied to many tickets under one key. */
71
+ bulk: (body) => t.post('/v1/tickets/bulk', body),
72
+ };
73
+ }
74
+ export function macrosResource(t) {
75
+ return {
76
+ ...listable(t, '/v1/macros'),
77
+ create: (body) => t.post('/v1/macros', body),
78
+ update: (id, body) => t.patch(`/v1/macros/${encodeURIComponent(id)}`, body),
79
+ delete: (id) => t.del(`/v1/macros/${encodeURIComponent(id)}`),
80
+ };
81
+ }
82
+ export function kbResource(t) {
83
+ return {
84
+ ...listable(t, '/v1/kb/articles'),
85
+ /** One article by slug — the same slug the help centre serves it under. */
86
+ article: (slug) => t.get(`/v1/kb/articles/${encodeURIComponent(slug)}`),
87
+ collections: () => t.get('/v1/kb/collections'),
88
+ };
89
+ }
90
+ export function searchResource(t) {
91
+ return {
92
+ /** Full-text across contacts, conversations, messages, tickets and the KB. */
93
+ query: (q, opts = {}) => t.get('/v1/search', { q, ...opts }),
94
+ /**
95
+ * Help-centre articles only.
96
+ *
97
+ * Sends `kinds=kb` as a repeated PLAIN key. It used to send `kinds[]=kb`,
98
+ * and `searchQuerySchema` is an object schema, so zod stripped the
99
+ * bracketed key and the search ran UNNARROWED — this method returned
100
+ * contacts, conversations, messages and tickets with a 200 and nothing to
101
+ * indicate the filter had been ignored. The MCP server's `search_kb` tool
102
+ * and the dashboard sent the same spelling. The schema now folds
103
+ * `kinds[]` and a scalar `kinds` as well, but the plain repeated key is
104
+ * the form to send.
105
+ */
106
+ kb: (q, opts = {}) => t.get('/v1/search', { q, kinds: ['kb'], ...opts }),
107
+ };
108
+ }
109
+ export function eventsResource(t) {
110
+ const page = listable(t, '/v1/cdp/events');
111
+ return {
112
+ track: (input) => t.post('/v1/cdp/events', input),
113
+ /**
114
+ * Read back what was tracked — the same `/v1/cdp/events` collection
115
+ * `track` writes to, cursor-paginated newest-first.
116
+ *
117
+ * The SDK could write events and never read them, so "did my tracking
118
+ * call actually land?" had no answer outside the dashboard. Pass
119
+ * `contactId` to scope the stream to one person.
120
+ */
121
+ list: (query = {}) => page.list(query),
122
+ listAll: page.listAll,
123
+ };
124
+ }
125
+ export function webhooksResource(t) {
126
+ return {
127
+ ...listable(t, '/v1/webhooks/deliveries'),
128
+ /** One delivery, with its request, response and attempt history. */
129
+ delivery: (id) => t.get(`/v1/webhooks/deliveries/${encodeURIComponent(id)}`),
130
+ /** Send it again. The receiver sees the same payload with a fresh signature. */
131
+ redeliver: (id) => t.post(`/v1/webhooks/deliveries/${encodeURIComponent(id)}/redeliver`),
132
+ };
133
+ }
134
+ export function reportsResource(t) {
135
+ return {
136
+ overview: (query = {}) => t.get('/v1/reports/overview', query),
137
+ agents: (query = {}) => t.get('/v1/reports/agents', query),
138
+ conversationsByDay: (query = {}) => t.get('/v1/reports/conversations-by-day', query),
139
+ sla: (query = {}) => t.get('/v1/reports/sla', query),
140
+ };
141
+ }
142
+ export function channelsResource(t) {
143
+ return {
144
+ list: (query = {}) => t.get('/v1/channels', query),
145
+ get: (id) => t.get(`/v1/channels/${encodeURIComponent(id)}`),
146
+ };
147
+ }
148
+ export function billingResource(t) {
149
+ return {
150
+ /** The plan catalogue this deployment can actually sell. */
151
+ plans: () => t.get('/v1/billing/plans'),
152
+ /** Current period usage, including AI spend against the workspace ceiling. */
153
+ usage: (query = {}) => t.get('/v1/billing/usage', query),
154
+ };
155
+ }
156
+ export function workspaceResource(t) {
157
+ return {
158
+ settings: () => t.get('/v1/workspace/settings'),
159
+ labels: () => t.get('/v1/labels'),
160
+ inboxes: () => t.get('/v1/inboxes'),
161
+ /**
162
+ * One team's roster. The only team read an API key can make.
163
+ *
164
+ * The workspace-wide `GET /v1/teams` list carries no `@AllowApiKey()` —
165
+ * it reads a calling user id, and a key is not a person — so it answers
166
+ * every key with 403. This manifest listed it anyway for a day, because
167
+ * the script that derived the key-accessible surface took the nearest
168
+ * `@AllowApiKey()` in either direction and credited the list route with
169
+ * the decorator belonging to this one. Get a team id from a workflow, an
170
+ * inbox's `settings.routingTeamId`, or a conversation's `teamId`.
171
+ */
172
+ teamMembers: (teamId) => t.get(`/v1/teams/${encodeURIComponent(teamId)}/members`),
173
+ /**
174
+ * The workspace's skill taxonomy — the distinct labels routing matches on.
175
+ *
176
+ * `GET /v1/agents` has no handler at all; the agents controller serves
177
+ * only `/agents/skills` and `/agents/{userId}/skills`. There is no
178
+ * key-accessible way to list the people with a seat here.
179
+ */
180
+ agentSkills: () => t.get('/v1/agents/skills'),
181
+ };
182
+ }
183
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /** Production. Never a dev or example host — see the note in {@link Transport}. */
2
+ export declare const DEFAULT_API_URL = "https://api.chatlychat.com";
3
+ /** The package version, stamped into `User-Agent` so our logs can see SDK age. */
4
+ export declare const SDK_VERSION = "0.2.0";
5
+ export interface RetryPolicy {
6
+ /** Total attempts, including the first. `1` disables retrying. */
7
+ readonly attempts: number;
8
+ /** First backoff, doubled per attempt. Jitter is applied on top. */
9
+ readonly baseDelayMs: number;
10
+ /** Ceiling for a single backoff, before jitter. */
11
+ readonly maxDelayMs: number;
12
+ }
13
+ export declare const DEFAULT_RETRY: RetryPolicy;
14
+ export interface TransportOptions {
15
+ readonly apiKey: string;
16
+ readonly apiUrl?: string;
17
+ readonly timeoutMs?: number;
18
+ readonly fetchFn?: typeof fetch;
19
+ readonly retry?: Partial<RetryPolicy>;
20
+ /** Test seam. Defaults to a real timer; a test passes a no-op. */
21
+ readonly sleepFn?: (ms: number) => Promise<void>;
22
+ }
23
+ export type Query = Record<string, string | number | boolean | undefined | ReadonlyArray<string>>;
24
+ /**
25
+ * One HTTP concern, in one place: auth, idempotency, retry, error mapping.
26
+ *
27
+ * Resource namespaces hold no HTTP logic at all — they build a path and a
28
+ * body and hand both here. That is what makes the retry policy and the error
29
+ * model uniform across 55 routes instead of per-method.
30
+ */
31
+ export declare class Transport {
32
+ private readonly base;
33
+ private readonly headers;
34
+ private readonly timeoutMs;
35
+ private readonly fetchFn;
36
+ private readonly retry;
37
+ private readonly sleepFn;
38
+ /**
39
+ * When set, every write uses this exact `Idempotency-Key` instead of a fresh
40
+ * UUID. Set by `ChatlyClient.withIdempotencyKey`, which returns a client
41
+ * sharing this transport's configuration with a pinned key.
42
+ */
43
+ readonly pinnedIdempotencyKey: string | null;
44
+ constructor(opts: TransportOptions, pinnedIdempotencyKey?: string | null);
45
+ /** A copy of this transport with a pinned idempotency key. */
46
+ withPinnedKey(key: string): Transport;
47
+ get<T>(path: string, query?: Query): Promise<T>;
48
+ post<T>(path: string, body?: unknown, query?: Query): Promise<T>;
49
+ patch<T>(path: string, body?: unknown, query?: Query): Promise<T>;
50
+ put<T>(path: string, body?: unknown, query?: Query): Promise<T>;
51
+ del<T>(path: string, query?: Query): Promise<T>;
52
+ /**
53
+ * The escape hatch, and the one method every other method goes through.
54
+ *
55
+ * 85 of the 140 key-accessible routes have no typed method here. Without a
56
+ * public way in, reaching one means abandoning the SDK and hand-rolling
57
+ * HTTP with a bearer token — losing the retry policy, the error model and
58
+ * the idempotency contract in the process.
59
+ */
60
+ send<T>(method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE', path: string, query?: Query, body?: unknown): Promise<T>;
61
+ private attempt;
62
+ }
63
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAYA,mFAAmF;AACnF,eAAO,MAAM,eAAe,+BAA+B,CAAC;AAE5D,kFAAkF;AAClF,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,eAAO,MAAM,aAAa,EAAE,WAAkE,CAAC;AAE/F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACtC,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAED,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;AAElG;;;;;;GAMG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD;;;;OAIG;IACH,SAAgB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEjC,IAAI,EAAE,gBAAgB,EAAE,oBAAoB,GAAE,MAAM,GAAG,IAAW;IA6BrF,8DAA8D;IACvD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;IAM/B,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,KAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IAInD,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,GAAE,KAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IAIpE,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,GAAE,KAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IAIrE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,GAAE,KAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IAInE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,KAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhE;;;;;;;OAOG;IACU,IAAI,CAAC,CAAC,EACjB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,EACnD,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,KAAU,EACjB,IAAI,CAAC,EAAE,OAAO,GACb,OAAO,CAAC,CAAC,CAAC;YAqDC,OAAO;CA8CtB"}
@@ -0,0 +1,178 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { ChatlyApiError, ChatlyRateLimitError, ChatlyServerError, ChatlyTransportError, errorForResponse, parseRetryAfter, retryAfterHeader, } from './errors.js';
3
+ /** Production. Never a dev or example host — see the note in {@link Transport}. */
4
+ export const DEFAULT_API_URL = 'https://api.chatlychat.com';
5
+ /** The package version, stamped into `User-Agent` so our logs can see SDK age. */
6
+ export const SDK_VERSION = '0.2.0';
7
+ export const DEFAULT_RETRY = { attempts: 3, baseDelayMs: 250, maxDelayMs: 4_000 };
8
+ /**
9
+ * One HTTP concern, in one place: auth, idempotency, retry, error mapping.
10
+ *
11
+ * Resource namespaces hold no HTTP logic at all — they build a path and a
12
+ * body and hand both here. That is what makes the retry policy and the error
13
+ * model uniform across 55 routes instead of per-method.
14
+ */
15
+ export class Transport {
16
+ base;
17
+ headers;
18
+ timeoutMs;
19
+ fetchFn;
20
+ retry;
21
+ sleepFn;
22
+ /**
23
+ * When set, every write uses this exact `Idempotency-Key` instead of a fresh
24
+ * UUID. Set by `ChatlyClient.withIdempotencyKey`, which returns a client
25
+ * sharing this transport's configuration with a pinned key.
26
+ */
27
+ pinnedIdempotencyKey;
28
+ constructor(opts, pinnedIdempotencyKey = null) {
29
+ if (typeof opts.apiKey !== 'string' || opts.apiKey.length === 0) {
30
+ throw new TypeError('ChatlyClient: apiKey is required and must be a non-empty string');
31
+ }
32
+ /*
33
+ * Production, not a dev hostname.
34
+ *
35
+ * This was `https://api.livechat.local`, which resolves nowhere — so a
36
+ * developer who followed the published docs and omitted `apiUrl` got an
37
+ * SDK whose every call failed, and the docs page carried a warning
38
+ * telling each reader to override it. Reported by a customer who
39
+ * integrated from the docs alone, not caught here, because every test in
40
+ * this package passes an explicit `apiUrl`. The same defect shipped in
41
+ * the Kotlin and Swift SDKs as `api.chatly.example`.
42
+ */
43
+ this.base = (opts.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, '');
44
+ this.headers = new Headers({
45
+ authorization: `Bearer ${opts.apiKey}`,
46
+ 'content-type': 'application/json',
47
+ accept: 'application/json',
48
+ 'user-agent': `chatly-sdk-node/${SDK_VERSION}`,
49
+ });
50
+ this.timeoutMs = opts.timeoutMs ?? 15_000;
51
+ this.fetchFn = opts.fetchFn ?? globalThis.fetch;
52
+ this.retry = { ...DEFAULT_RETRY, ...opts.retry };
53
+ this.sleepFn = opts.sleepFn ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
54
+ this.pinnedIdempotencyKey = pinnedIdempotencyKey;
55
+ }
56
+ /** A copy of this transport with a pinned idempotency key. */
57
+ withPinnedKey(key) {
58
+ const clone = Object.create(Transport.prototype);
59
+ Object.assign(clone, this, { pinnedIdempotencyKey: key });
60
+ return clone;
61
+ }
62
+ async get(path, query = {}) {
63
+ return this.send('GET', path, query, undefined);
64
+ }
65
+ async post(path, body, query = {}) {
66
+ return this.send('POST', path, query, body);
67
+ }
68
+ async patch(path, body, query = {}) {
69
+ return this.send('PATCH', path, query, body);
70
+ }
71
+ async put(path, body, query = {}) {
72
+ return this.send('PUT', path, query, body);
73
+ }
74
+ async del(path, query = {}) {
75
+ return this.send('DELETE', path, query, undefined);
76
+ }
77
+ /**
78
+ * The escape hatch, and the one method every other method goes through.
79
+ *
80
+ * 85 of the 140 key-accessible routes have no typed method here. Without a
81
+ * public way in, reaching one means abandoning the SDK and hand-rolling
82
+ * HTTP with a bearer token — losing the retry policy, the error model and
83
+ * the idempotency contract in the process.
84
+ */
85
+ async send(method, path, query = {}, body) {
86
+ const url = new URL(`${this.base}${path}`);
87
+ for (const [key, value] of Object.entries(query)) {
88
+ if (value === undefined) {
89
+ continue;
90
+ }
91
+ if (Array.isArray(value)) {
92
+ // Repeated keys stay repeated: `kinds=kb&kinds=ticket`. Joining them
93
+ // with a comma produces one parameter whose value is not a member of
94
+ // the enum — which is what the MCP server's `search` tool did.
95
+ for (const item of value) {
96
+ url.searchParams.append(key, item);
97
+ }
98
+ }
99
+ else {
100
+ url.searchParams.set(key, String(value));
101
+ }
102
+ }
103
+ const isWrite = method !== 'GET';
104
+ /*
105
+ * One key for the whole call, retries included.
106
+ *
107
+ * Minting a fresh key per ATTEMPT would defeat the header's only purpose:
108
+ * attempt 2 of a POST that actually succeeded upstream would be accepted
109
+ * as a new operation and double-write. Minting it per CALL means a retry
110
+ * is recognised as the same logical operation and replayed.
111
+ */
112
+ const idempotencyKey = isWrite ? (this.pinnedIdempotencyKey ?? randomUUID()) : null;
113
+ let lastError;
114
+ for (let attempt = 1; attempt <= this.retry.attempts; attempt++) {
115
+ try {
116
+ return await this.attempt(url, method, body, idempotencyKey);
117
+ }
118
+ catch (err) {
119
+ lastError = err;
120
+ const retryable = err instanceof ChatlyTransportError ||
121
+ err instanceof ChatlyRateLimitError ||
122
+ err instanceof ChatlyServerError;
123
+ if (!retryable || attempt === this.retry.attempts) {
124
+ throw err;
125
+ }
126
+ const advised = err instanceof ChatlyApiError && err.retryAfter !== null ? err.retryAfter * 1000 : null;
127
+ const backoff = Math.min(this.retry.baseDelayMs * 2 ** (attempt - 1), this.retry.maxDelayMs);
128
+ // Jitter: a fleet of workers retrying in lockstep is a second outage.
129
+ const jittered = backoff * (0.5 + Math.random() / 2);
130
+ await this.sleepFn(advised ?? jittered);
131
+ }
132
+ }
133
+ throw lastError;
134
+ }
135
+ async attempt(url, method, body, idempotencyKey) {
136
+ const ctrl = new AbortController();
137
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
138
+ try {
139
+ const headers = new Headers(this.headers);
140
+ if (idempotencyKey !== null) {
141
+ headers.set('idempotency-key', idempotencyKey);
142
+ }
143
+ let res;
144
+ try {
145
+ res = await this.fetchFn(url.toString(), {
146
+ method,
147
+ headers,
148
+ signal: ctrl.signal,
149
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
150
+ });
151
+ }
152
+ catch (cause) {
153
+ const why = ctrl.signal.aborted ? `timed out after ${String(this.timeoutMs)}ms` : 'failed';
154
+ throw new ChatlyTransportError(`Chatly request ${why}: ${method} ${url.pathname}`, cause);
155
+ }
156
+ if (!res.ok) {
157
+ throw errorForResponse({
158
+ status: res.status,
159
+ body: await res.text(),
160
+ requestId: res.headers.get('x-request-id'),
161
+ retryAfter: parseRetryAfter(retryAfterHeader(res.headers), Date.now()),
162
+ });
163
+ }
164
+ if (res.status === 204) {
165
+ return undefined;
166
+ }
167
+ const text = await res.text();
168
+ if (text.length === 0) {
169
+ return undefined;
170
+ }
171
+ return JSON.parse(text);
172
+ }
173
+ finally {
174
+ clearTimeout(timer);
175
+ }
176
+ }
177
+ }
178
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /** Freshness window for `t`, in seconds. Matches what the docs recommend. */
2
+ export declare const WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = 300;
3
+ export interface VerifyWebhookSignatureInput {
4
+ /**
5
+ * The EXACT bytes we POSTed. Pass the raw body — re-serialising parsed JSON
6
+ * reorders keys and changes whitespace, and the digest will not match.
7
+ */
8
+ body: string | Uint8Array;
9
+ /** The `x-livechat-signature` header value: `t=<unix seconds>,v1=<hex>`. */
10
+ signatureHeader: string;
11
+ /** The endpoint's `whsec_…` signing secret. */
12
+ secret: string;
13
+ /**
14
+ * How far `t` may be from now before the delivery is treated as a replay.
15
+ * Defaults to {@link WEBHOOK_SIGNATURE_TOLERANCE_SECONDS}; pass `Infinity`
16
+ * only when re-verifying an archived delivery.
17
+ */
18
+ toleranceSeconds?: number;
19
+ /** Escape hatch for tests. Unix seconds. */
20
+ nowSeconds?: number;
21
+ }
22
+ /**
23
+ * Verify the signature header on a webhook delivery from the platform.
24
+ *
25
+ * `v1` is HMAC-SHA256 over `` `${t}.${body}` `` — the timestamp is bound INTO
26
+ * the digest, which is what makes the replay window enforceable at all.
27
+ *
28
+ * This used to HMAC the body ALONE, so it rejected every genuine delivery the
29
+ * platform sent: the producer has always signed `${t}.${body}` and our own
30
+ * published documentation has always described that construction. Anyone
31
+ * following either the SDK or the docs got a hard "invalid signature" on 100%
32
+ * of real traffic. The wire format is unchanged — only this verifier was wrong.
33
+ */
34
+ export declare function verifyWebhookSignature(input: VerifyWebhookSignatureInput): boolean;
35
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,eAAO,MAAM,mCAAmC,MAAM,CAAC;AAEvD,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B,4EAA4E;IAC5E,eAAe,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAiClF"}
@@ -0,0 +1,47 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ /** Freshness window for `t`, in seconds. Matches what the docs recommend. */
3
+ export const WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = 300;
4
+ /**
5
+ * Verify the signature header on a webhook delivery from the platform.
6
+ *
7
+ * `v1` is HMAC-SHA256 over `` `${t}.${body}` `` — the timestamp is bound INTO
8
+ * the digest, which is what makes the replay window enforceable at all.
9
+ *
10
+ * This used to HMAC the body ALONE, so it rejected every genuine delivery the
11
+ * platform sent: the producer has always signed `${t}.${body}` and our own
12
+ * published documentation has always described that construction. Anyone
13
+ * following either the SDK or the docs got a hard "invalid signature" on 100%
14
+ * of real traffic. The wire format is unchanged — only this verifier was wrong.
15
+ */
16
+ export function verifyWebhookSignature(input) {
17
+ const parts = new Map();
18
+ for (const part of input.signatureHeader.split(',')) {
19
+ const eq = part.indexOf('=');
20
+ if (eq > 0) {
21
+ parts.set(part.slice(0, eq).trim(), part.slice(eq + 1).trim());
22
+ }
23
+ }
24
+ const rawTs = parts.get('t');
25
+ const tag = parts.get('v1');
26
+ if (rawTs === undefined || tag === undefined) {
27
+ return false;
28
+ }
29
+ const issuedAt = Number.parseInt(rawTs, 10);
30
+ if (!Number.isFinite(issuedAt)) {
31
+ return false;
32
+ }
33
+ const tolerance = input.toleranceSeconds ?? WEBHOOK_SIGNATURE_TOLERANCE_SECONDS;
34
+ const now = input.nowSeconds ?? Math.floor(Date.now() / 1000);
35
+ if (Math.abs(now - issuedAt) > tolerance) {
36
+ return false;
37
+ }
38
+ const hmac = createHmac('sha256', input.secret);
39
+ hmac.update(`${rawTs}.`);
40
+ hmac.update(typeof input.body === 'string' ? Buffer.from(input.body, 'utf8') : input.body);
41
+ const a = Buffer.from(tag, 'hex');
42
+ const b = Buffer.from(hmac.digest('hex'), 'hex');
43
+ // Length check first: timingSafeEqual throws on a length mismatch, and a
44
+ // thrown verifier is an accepted webhook in a handler that forgot try/catch.
45
+ return a.length === b.length && timingSafeEqual(a, b);
46
+ }
47
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mCAAmC,GAAG,GAAG,CAAC;AAsBvD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAkC;IACvE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YACX,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,gBAAgB,IAAI,mCAAmC,CAAC;IAChF,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9D,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,QAAQ,CAAC,GAAG,SAAS,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IACzB,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3F,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IACjD,yEAAyE;IACzE,6EAA6E;IAC7E,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "@chatlychat/sdk-node",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Official Node.js SDK for the Chatly API — typed REST client, webhook signature verification, and the widget identity hash.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/chatlychat/chatly-sdk-node#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/chatlychat/chatly-sdk-node.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/chatlychat/chatly-sdk-node/issues"
13
+ },
14
+ "keywords": [
15
+ "chatly",
16
+ "chat",
17
+ "messaging",
18
+ "support",
19
+ "webhooks",
20
+ "sdk",
21
+ "server"
22
+ ],
23
+ "author": "Chatly",
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "default": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md",
38
+ "LICENSE",
39
+ "CHANGELOG.md"
40
+ ],
41
+ "sideEffects": false,
42
+ "publishConfig": {
43
+ "access": "public",
44
+ "registry": "https://registry.npmjs.org/",
45
+ "provenance": true
46
+ },
47
+ "engines": {
48
+ "node": ">=18"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "20.14.10",
52
+ "typescript": "5.6.2",
53
+ "vitest": "2.1.2",
54
+ "@livechat/config": "0.1.0",
55
+ "@livechat/shared": "0.1.0"
56
+ },
57
+ "dependencies": {},
58
+ "scripts": {
59
+ "build": "tsc -p tsconfig.build.json",
60
+ "typecheck": "tsc -p tsconfig.json --noEmit",
61
+ "lint": "eslint src --max-warnings=0",
62
+ "test": "vitest run"
63
+ }
6
64
  }