@clawpro/node 0.1.3 → 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.
package/dist/index.cjs CHANGED
@@ -25,7 +25,52 @@ __export(index_exports, {
25
25
  verifyWebhook: () => verifyWebhook
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/internal.ts
28
30
  var import_node_crypto = require("crypto");
31
+ function parseSignature(header) {
32
+ const parts = {};
33
+ for (const p of header.split(",")) {
34
+ const i = p.indexOf("=");
35
+ if (i > 0) parts[p.slice(0, i).trim()] = p.slice(i + 1).trim();
36
+ }
37
+ const t = Number(parts.t);
38
+ if (!Number.isFinite(t) || t <= 0 || !parts.v1) return null;
39
+ return { t, v1: parts.v1 };
40
+ }
41
+ var signingString = (t, payload) => `${t}.${payload}`;
42
+ var hmacHex = (secret, message) => (0, import_node_crypto.createHmac)("sha256", secret).update(message).digest("hex");
43
+ function timestampFresh(t, toleranceSec, nowMs) {
44
+ return toleranceSec <= 0 || Math.abs(nowMs / 1e3 - t) <= toleranceSec;
45
+ }
46
+ function timingSafeEqualStr(a, b) {
47
+ const ba = Buffer.from(a);
48
+ const bb = Buffer.from(b);
49
+ return ba.length === bb.length && (0, import_node_crypto.timingSafeEqual)(ba, bb);
50
+ }
51
+ function buildUrl(base, path, query) {
52
+ const url = new URL(base + path);
53
+ if (query) {
54
+ for (const [k, v] of Object.entries(query)) {
55
+ if (v !== void 0 && v !== null && v !== "") url.searchParams.set(k, String(v));
56
+ }
57
+ }
58
+ return url.toString();
59
+ }
60
+ var isIdempotent = (method) => method === "GET" || method === "DELETE" || method === "PUT";
61
+ var shouldRetryNetwork = (method, attempt, maxRetries) => isIdempotent(method) && attempt < maxRetries;
62
+ function shouldRetryStatus(method, status, attempt, maxRetries) {
63
+ const transient = status === 429 || status >= 500;
64
+ const allowed = status === 429 || isIdempotent(method);
65
+ return transient && allowed && attempt < maxRetries;
66
+ }
67
+ var backoffMs = (attempt, rand) => Math.round(250 * 2 ** attempt * (0.8 + rand() * 0.4));
68
+ function retryDelayMs(retryAfter, attempt, rand) {
69
+ const ra = Number(retryAfter);
70
+ return Number.isFinite(ra) && ra > 0 ? ra * 1e3 : backoffMs(attempt, rand);
71
+ }
72
+
73
+ // src/index.ts
29
74
  var ClawProError = class extends Error {
30
75
  status;
31
76
  code;
@@ -50,6 +95,8 @@ var ClawPro = class {
50
95
  fetchImpl;
51
96
  timeoutMs;
52
97
  maxRetries;
98
+ sleep;
99
+ rand;
53
100
  constructor(opts) {
54
101
  if (!opts?.apiKey) throw new Error("ClawPro: `apiKey` is required");
55
102
  this.apiKey = opts.apiKey;
@@ -59,6 +106,8 @@ var ClawPro = class {
59
106
  if (!this.fetchImpl) throw new Error("ClawPro: no fetch available \u2014 pass `fetch` (Node <18)");
60
107
  this.timeoutMs = opts.timeoutMs ?? 3e4;
61
108
  this.maxRetries = opts.maxRetries ?? 2;
109
+ this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
110
+ this.rand = opts.rand ?? Math.random;
62
111
  this.accounts = new AccountsResource(this);
63
112
  this.campaigns = new CampaignsResource(this);
64
113
  this.leads = new LeadsResource(this);
@@ -68,19 +117,13 @@ var ClawPro = class {
68
117
  }
69
118
  /** @internal */
70
119
  async request(method, path, opts = {}) {
71
- const url = new URL(this.base + path);
72
- if (opts.query) {
73
- for (const [k, v] of Object.entries(opts.query)) {
74
- if (v !== void 0 && v !== null && v !== "") url.searchParams.set(k, String(v));
75
- }
76
- }
77
- const idempotent = method === "GET" || method === "DELETE" || method === "PUT";
120
+ const url = buildUrl(this.base, path, opts.query);
78
121
  for (let attempt = 0; ; attempt++) {
79
122
  const ctrl = new AbortController();
80
123
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
81
124
  let res;
82
125
  try {
83
- res = await this.fetchImpl(url.toString(), {
126
+ res = await this.fetchImpl(url, {
84
127
  method,
85
128
  headers: {
86
129
  "X-API-Key": this.apiKey,
@@ -92,21 +135,18 @@ var ClawPro = class {
92
135
  });
93
136
  } catch (err) {
94
137
  clearTimeout(timer);
95
- if (idempotent && attempt < this.maxRetries) {
96
- await sleep(backoff(attempt));
138
+ if (shouldRetryNetwork(method, attempt, this.maxRetries)) {
139
+ await this.sleep(backoffMs(attempt, this.rand));
97
140
  continue;
98
141
  }
99
142
  throw new ClawProError(`Network error: ${err.message}`, { status: 0, code: "network_error" });
100
143
  }
101
144
  clearTimeout(timer);
102
- const requestId = res.headers.get("x-request-id") ?? void 0;
103
- const transient = res.status === 429 || res.status >= 500;
104
- const retryable = res.status === 429 || idempotent;
105
- if (transient && retryable && attempt < this.maxRetries) {
106
- const ra = Number(res.headers.get("retry-after"));
107
- await sleep(Number.isFinite(ra) && ra > 0 ? ra * 1e3 : backoff(attempt));
145
+ if (shouldRetryStatus(method, res.status, attempt, this.maxRetries)) {
146
+ await this.sleep(retryDelayMs(res.headers.get("retry-after"), attempt, this.rand));
108
147
  continue;
109
148
  }
149
+ const requestId = res.headers.get("x-request-id") ?? void 0;
110
150
  const text = await res.text();
111
151
  const data = text ? safeJson(text) : void 0;
112
152
  if (!res.ok) {
@@ -117,8 +157,6 @@ var ClawPro = class {
117
157
  }
118
158
  }
119
159
  };
120
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
121
- var backoff = (attempt) => Math.round(250 * 2 ** attempt * (0.8 + Math.random() * 0.4));
122
160
  function safeJson(text) {
123
161
  try {
124
162
  return JSON.parse(text);
@@ -166,6 +204,16 @@ var CampaignsResource = class {
166
204
  leads(id, opts = {}) {
167
205
  return this.c.request("GET", `/campaigns/${id}/leads`, { query: { status: opts.status } }).then((r) => r.leads);
168
206
  }
207
+ /** Push externally-sourced leads into a campaign (bring-your-own-scraper). Each
208
+ * carries the engagement context that feeds the DM. Deduped on username; up to
209
+ * 500/call. Pass `score: true` to route them through the ICP scorer first. */
210
+ addLeads(id, leads, opts = {}) {
211
+ return this.c.request(
212
+ "POST",
213
+ `/campaigns/${id}/leads`,
214
+ { body: leads, query: { score: opts.score ? "1" : void 0 } }
215
+ );
216
+ }
169
217
  /** Threads with at least one message (the unified inbox). */
170
218
  inbox(id) {
171
219
  return this.c.request("GET", `/campaigns/${id}/inbox`).then((r) => r.threads);
@@ -180,6 +228,11 @@ var LeadsResource = class {
180
228
  update(id, patch) {
181
229
  return this.c.request("PATCH", `/leads/${id}`, { body: patch }).then((r) => r.lead);
182
230
  }
231
+ /** Send a reply / follow-up to a lead in its existing thread (inbox-write —
232
+ * manage the conversation via API). Goes out promptly through the account. */
233
+ reply(id, text) {
234
+ return this.c.request("POST", `/leads/${id}/reply`, { body: { text } }).then((r) => r.message);
235
+ }
183
236
  };
184
237
  var WebhooksResource = class {
185
238
  constructor(c) {
@@ -250,19 +303,11 @@ var UsageResource = class {
250
303
  };
251
304
  function verifyWebhook(opts) {
252
305
  const { payload, signature, secret, toleranceSec = 300 } = opts;
253
- const parts = {};
254
- for (const p of signature.split(",")) {
255
- const i = p.indexOf("=");
256
- if (i > 0) parts[p.slice(0, i).trim()] = p.slice(i + 1).trim();
257
- }
258
- const t = Number(parts.t);
259
- const v1 = parts.v1;
260
- if (!t || !v1) return false;
261
- if (toleranceSec > 0 && Math.abs(Date.now() / 1e3 - t) > toleranceSec) return false;
262
- const expected = (0, import_node_crypto.createHmac)("sha256", secret).update(`${t}.${payload}`).digest("hex");
263
- const a = Buffer.from(expected);
264
- const b = Buffer.from(v1);
265
- return a.length === b.length && (0, import_node_crypto.timingSafeEqual)(a, b);
306
+ const parsed = parseSignature(signature);
307
+ if (!parsed) return false;
308
+ if (!timestampFresh(parsed.t, toleranceSec, Date.now())) return false;
309
+ const expected = hmacHex(secret, signingString(parsed.t, payload));
310
+ return timingSafeEqualStr(expected, parsed.v1);
266
311
  }
267
312
  // Annotate the CommonJS export names for ESM import in node:
268
313
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @clawpro/node — official SDK for the ClawPro Instagram outbound API.\n *\n * ```ts\n * import { ClawPro } from '@clawpro/node'\n * const clawpro = new ClawPro({ apiKey: process.env.CLAWPRO_API_KEY! })\n * const { accounts } = await clawpro.accounts.list()\n * ```\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n// ─────────────────────────────── Types ───────────────────────────────────────\n\nexport type AccountStatus = 'new' | 'warming' | 'active' | 'challenged' | 'banned' | 'disconnected';\nexport type ProxyType = 'mobile' | 'isp' | 'residential' | 'none';\n\nexport interface Account {\n id: string;\n username: string;\n status: AccountStatus;\n warmupTier?: number;\n dailyDmCap?: number;\n}\n\nexport type CampaignStatus = 'draft' | 'active' | 'paused';\n\nexport interface CampaignTarget {\n username: string;\n lastCheckedPostId: string | null;\n}\n\nexport interface Campaign {\n id: string;\n accountId: string;\n name: string;\n status: CampaignStatus;\n targets: CampaignTarget[];\n offer: string;\n icpCriteria: string;\n dailyDmTarget: number;\n}\n\nexport type LeadStatus =\n | 'discovered' | 'enriched' | 'scored' | 'qualified' | 'rejected'\n | 'queued' | 'dmed' | 'replied' | 'booked';\n\nexport interface Message {\n id: string;\n direction: 'out' | 'in';\n body: string;\n status: string;\n sentAt: string | null;\n createdAt: string;\n}\n\nexport interface Lead {\n id: string;\n igUsername: string;\n fullName: string | null;\n sourceTargetUsername: string;\n engagementType: 'like' | 'comment';\n commentText: string | null;\n score: number | null;\n scoreReason: string | null;\n status: LeadStatus;\n messages?: Message[];\n}\n\nexport type WebhookType = 'generic' | 'slack';\nexport type WebhookEvent =\n | 'reply.received' | 'dm.sent' | 'dm.failed'\n | 'lead.qualified' | 'lead.booked'\n | 'account.active' | 'account.challenged' | 'account.banned';\n\nexport interface Webhook {\n id: string;\n label: string;\n url: string;\n type: WebhookType;\n events: (WebhookEvent | '*')[];\n active: boolean;\n lastStatus: number | null;\n lastDeliveryAt: string | null;\n lastError: string | null;\n /** Present only on the create response (generic HMAC secret). */\n secret?: string;\n}\n\nexport interface WebhookDelivery {\n id: string;\n event: string;\n status: 'pending' | 'delivered' | 'dead';\n attempts: number;\n lastStatusCode: number | null;\n lastError: string | null;\n nextRetryAt: string | null;\n deliveredAt: string | null;\n createdAt: string;\n}\n\nexport type ApiKeyEnv = 'production' | 'development';\nexport type ApiKeyAccess = 'full' | 'read' | 'write';\n\nexport interface ApiKey {\n id: string;\n name: string;\n environment: ApiKeyEnv;\n access: ApiKeyAccess;\n keyPrefix: string;\n createdAt: string;\n expiresAt: string | null;\n lastUsedAt: string | null;\n}\n\nexport interface UsageSummary {\n callsThisMonth: number;\n calls24h: number;\n errors: number;\n avgLatencyMs: number;\n}\n\nexport interface RequestLog {\n id: string;\n requestId: string;\n method: string;\n path: string;\n status: number;\n latencyMs: number;\n error: string | null;\n createdAt: string;\n keyName: string | null;\n keyPrefix: string | null;\n}\n\n// ─────────────────────────────── Errors ──────────────────────────────────────\n\n/** Thrown for any non-2xx API response. Inspect `status` / `requestId`. */\nexport class ClawProError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId?: string;\n constructor(message: string, opts: { status: number; code?: string; requestId?: string }) {\n super(message);\n this.name = 'ClawProError';\n this.status = opts.status;\n this.code = opts.code ?? 'error';\n this.requestId = opts.requestId;\n }\n}\n\n// ─────────────────────────────── Client ──────────────────────────────────────\n\nexport interface ClawProOptions {\n /** A `sk_live_…` / `sk_test_…` key from the developer portal. Sent as `X-API-Key`. */\n apiKey: string;\n /** API origin (no trailing slash). Defaults to https://api.tryclawpro.com */\n baseUrl?: string;\n /** Override the fetch implementation (defaults to global fetch). */\n fetch?: typeof fetch;\n /** Per-request timeout in ms (default 30000). */\n timeoutMs?: number;\n /** Auto-retry transient failures (network, 429, 5xx) with exponential backoff.\n * Default 2. 429s honor the `Retry-After` header; only idempotent methods\n * (GET/DELETE/PUT) are retried on network/5xx. */\n maxRetries?: number;\n}\n\ntype Query = Record<string, string | number | boolean | undefined | null>;\n\nexport class ClawPro {\n readonly accounts: AccountsResource;\n readonly campaigns: CampaignsResource;\n readonly leads: LeadsResource;\n readonly webhooks: WebhooksResource;\n readonly keys: ApiKeysResource;\n readonly usage: UsageResource;\n\n private readonly apiKey: string;\n private readonly base: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly maxRetries: number;\n\n constructor(opts: ClawProOptions) {\n if (!opts?.apiKey) throw new Error('ClawPro: `apiKey` is required');\n this.apiKey = opts.apiKey;\n const origin = (opts.baseUrl ?? 'https://api.tryclawpro.com').replace(/\\/$/, '');\n this.base = `${origin}/api/instagram`;\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!this.fetchImpl) throw new Error('ClawPro: no fetch available — pass `fetch` (Node <18)');\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n this.maxRetries = opts.maxRetries ?? 2;\n\n this.accounts = new AccountsResource(this);\n this.campaigns = new CampaignsResource(this);\n this.leads = new LeadsResource(this);\n this.webhooks = new WebhooksResource(this);\n this.keys = new ApiKeysResource(this);\n this.usage = new UsageResource(this);\n }\n\n /** @internal */\n async request<T>(method: string, path: string, opts: { body?: unknown; query?: Query } = {}): Promise<T> {\n const url = new URL(this.base + path);\n if (opts.query) {\n for (const [k, v] of Object.entries(opts.query)) {\n if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, String(v));\n }\n }\n const idempotent = method === 'GET' || method === 'DELETE' || method === 'PUT';\n\n for (let attempt = 0; ; attempt++) {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url.toString(), {\n method,\n headers: {\n 'X-API-Key': this.apiKey,\n Accept: 'application/json',\n ...(opts.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n },\n body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,\n signal: ctrl.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n // Network/timeout: safe to retry only idempotent methods.\n if (idempotent && attempt < this.maxRetries) { await sleep(backoff(attempt)); continue; }\n throw new ClawProError(`Network error: ${(err as Error).message}`, { status: 0, code: 'network_error' });\n }\n clearTimeout(timer);\n\n const requestId = res.headers.get('x-request-id') ?? undefined;\n // Transient server-side: 429 (didn't process → retry any method) or 5xx (idempotent only).\n const transient = res.status === 429 || res.status >= 500;\n const retryable = res.status === 429 || idempotent;\n if (transient && retryable && attempt < this.maxRetries) {\n const ra = Number(res.headers.get('retry-after'));\n await sleep(Number.isFinite(ra) && ra > 0 ? ra * 1000 : backoff(attempt));\n continue;\n }\n\n const text = await res.text();\n const data = text ? safeJson(text) : undefined;\n if (!res.ok) {\n const message = (data && (data.message || data.error)) || `HTTP ${res.status}`;\n throw new ClawProError(message, { status: res.status, code: (data && data.error) || 'error', requestId });\n }\n return data as T;\n }\n }\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n/** Exponential backoff with jitter: ~250ms, ~500ms, ~1s … */\nconst backoff = (attempt: number) => Math.round((250 * 2 ** attempt) * (0.8 + Math.random() * 0.4));\n\nfunction safeJson(text: string): any {\n try { return JSON.parse(text); } catch { return { raw: text }; }\n}\n\n// ───────────────────────────── Resources ─────────────────────────────────────\n\nclass AccountsResource {\n constructor(private c: ClawPro) {}\n /** List connected sending accounts. */\n list() { return this.c.request<{ accounts: Account[] }>('GET', '/accounts').then((r) => r.accounts); }\n /** Connect a new sending account (a geo-matched mobile proxy is assigned). */\n create(input: { username: string; country?: string; proxyType?: ProxyType; proxyUrl?: string; timezone?: string }) {\n return this.c.request<{ account: Account }>('POST', '/accounts', { body: input }).then((r) => r.account);\n }\n /** Remove an account (cascades to its campaigns/leads/messages). */\n delete(id: string) { return this.c.request<{ ok: true }>('DELETE', `/accounts/${id}`); }\n}\n\nclass CampaignsResource {\n constructor(private c: ClawPro) {}\n list() { return this.c.request<{ campaigns: Campaign[] }>('GET', '/campaigns').then((r) => r.campaigns); }\n create(input: { accountId: string; name: string; targets: string[]; offer?: string; icpCriteria?: string; dailyDmTarget?: number }) {\n return this.c.request<{ campaign: Campaign }>('POST', '/campaigns', { body: input }).then((r) => r.campaign);\n }\n update(id: string, patch: Partial<{ status: CampaignStatus; targets: string[]; offer: string; icpCriteria: string; dailyDmTarget: number; name: string }>) {\n return this.c.request<{ campaign: Campaign }>('PATCH', `/campaigns/${id}`, { body: patch }).then((r) => r.campaign);\n }\n /** Kick a discovery/scoring/queue run for a campaign. */\n run(id: string) { return this.c.request<{ discovered: number; scored: number; queued: number }>('POST', `/campaigns/${id}/run`); }\n /** Leads for a campaign, optionally filtered by status. */\n leads(id: string, opts: { status?: LeadStatus } = {}) {\n return this.c.request<{ leads: Lead[] }>('GET', `/campaigns/${id}/leads`, { query: { status: opts.status } }).then((r) => r.leads);\n }\n /** Threads with at least one message (the unified inbox). */\n inbox(id: string) { return this.c.request<{ threads: Lead[] }>('GET', `/campaigns/${id}/inbox`).then((r) => r.threads); }\n}\n\nclass LeadsResource {\n constructor(private c: ClawPro) {}\n /** Advance a lead — e.g. `{ status: 'booked' }` once a call is set (fires `lead.booked`). */\n update(id: string, patch: { status: 'qualified' | 'rejected' | 'booked' }) {\n return this.c.request<{ lead: Lead }>('PATCH', `/leads/${id}`, { body: patch }).then((r) => r.lead);\n }\n}\n\nclass WebhooksResource {\n constructor(private c: ClawPro) {}\n /** The catalog of subscribable event types. */\n events() { return this.c.request<{ events: WebhookEvent[] }>('GET', '/webhooks/events').then((r) => r.events); }\n list() { return this.c.request<{ webhooks: Webhook[] }>('GET', '/webhooks').then((r) => r.webhooks); }\n /** Create an endpoint. The generic HMAC `secret` is returned once, here. */\n create(input: { url: string; type?: WebhookType; events?: (WebhookEvent | '*')[]; label?: string }) {\n return this.c.request<{ webhook: Webhook }>('POST', '/webhooks', { body: { type: 'generic', events: ['*'], ...input } }).then((r) => r.webhook);\n }\n delete(id: string) { return this.c.request<{ ok: true }>('DELETE', `/webhooks/${id}`); }\n /** Send a `webhook.test` delivery. */\n test(id: string) { return this.c.request<unknown>('POST', `/webhooks/${id}/test`); }\n deliveries(id: string) { return this.c.request<{ deliveries: WebhookDelivery[] }>('GET', `/webhooks/${id}/deliveries`).then((r) => r.deliveries); }\n}\n\nclass ApiKeysResource {\n constructor(private c: ClawPro) {}\n list() { return this.c.request<{ keys: ApiKey[] }>('GET', '/keys').then((r) => r.keys); }\n /** Returns `{ secret, key }` — the plaintext `secret` is shown only here. */\n create(input: { name: string; environment?: ApiKeyEnv; access?: ApiKeyAccess; expiresAt?: string | null }) {\n return this.c.request<{ secret: string; key: ApiKey }>('POST', '/keys', { body: input });\n }\n revoke(id: string) { return this.c.request<{ ok: true }>('DELETE', `/keys/${id}`); }\n}\n\nexport interface LogFilter {\n key?: string; method?: string; status?: '2xx' | '4xx' | '5xx';\n from?: string; to?: string; endpoint?: string; requestId?: string; search?: string;\n limit?: number; offset?: number;\n}\n\nclass UsageResource {\n constructor(private c: ClawPro) {}\n /** Calls this month / 24h / errors / avg latency. */\n summary() { return this.c.request<UsageSummary>('GET', '/usage'); }\n /** A single page of request logs (`limit` + `offset`). */\n logs(filter: LogFilter = {}) {\n return this.c.request<{ logs: RequestLog[] }>('GET', '/logs', { query: filter as Query }).then((r) => r.logs);\n }\n /** Auto-paginate every matching log:\n * `for await (const log of clawpro.usage.iterateLogs()) { … }` */\n async *iterateLogs(filter: Omit<LogFilter, 'offset'> = {}): AsyncGenerator<RequestLog, void, unknown> {\n const pageSize = filter.limit ?? 100;\n for (let offset = 0; ; offset += pageSize) {\n const page = await this.logs({ ...filter, limit: pageSize, offset });\n for (const row of page) yield row;\n if (page.length < pageSize) return;\n }\n }\n}\n\n// ─────────────────────────── Webhook verification ────────────────────────────\n\nexport interface VerifyWebhookOptions {\n /** The raw request body, exactly as received (string). */\n payload: string;\n /** The `X-Souk-Signature` header value (`t=…,v1=…`). */\n signature: string;\n /** The endpoint's signing secret (shown once on webhook creation). */\n secret: string;\n /** Reject signatures older than this many seconds (default 300; 0 disables). */\n toleranceSec?: number;\n}\n\n/** Verify a ClawPro webhook's HMAC signature (timing-safe). Returns true if valid.\n *\n * ```ts\n * if (!verifyWebhook({ payload: rawBody, signature: req.headers['x-souk-signature'], secret })) return res.sendStatus(400)\n * ```\n */\nexport function verifyWebhook(opts: VerifyWebhookOptions): boolean {\n const { payload, signature, secret, toleranceSec = 300 } = opts;\n const parts: Record<string, string> = {};\n for (const p of signature.split(',')) {\n const i = p.indexOf('=');\n if (i > 0) parts[p.slice(0, i).trim()] = p.slice(i + 1).trim();\n }\n const t = Number(parts.t);\n const v1 = parts.v1;\n if (!t || !v1) return false;\n if (toleranceSec > 0 && Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;\n const expected = createHmac('sha256', secret).update(`${t}.${payload}`).digest('hex');\n const a = Buffer.from(expected);\n const b = Buffer.from(v1);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,yBAA4C;AAgIrC,IAAM,eAAN,cAA2B,MAAM;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY,SAAiB,MAA6D;AACxF,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,KAAK,QAAQ;AACzB,SAAK,YAAY,KAAK;AAAA,EACxB;AACF;AAqBO,IAAM,UAAN,MAAc;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,MAAsB;AAChC,QAAI,CAAC,MAAM,OAAQ,OAAM,IAAI,MAAM,+BAA+B;AAClE,SAAK,SAAS,KAAK;AACnB,UAAM,UAAU,KAAK,WAAW,8BAA8B,QAAQ,OAAO,EAAE;AAC/E,SAAK,OAAO,GAAG,MAAM;AACrB,SAAK,YAAY,KAAK,SAAS,WAAW;AAC1C,QAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,4DAAuD;AAC5F,SAAK,YAAY,KAAK,aAAa;AACnC,SAAK,aAAa,KAAK,cAAc;AAErC,SAAK,WAAW,IAAI,iBAAiB,IAAI;AACzC,SAAK,YAAY,IAAI,kBAAkB,IAAI;AAC3C,SAAK,QAAQ,IAAI,cAAc,IAAI;AACnC,SAAK,WAAW,IAAI,iBAAiB,IAAI;AACzC,SAAK,OAAO,IAAI,gBAAgB,IAAI;AACpC,SAAK,QAAQ,IAAI,cAAc,IAAI;AAAA,EACrC;AAAA;AAAA,EAGA,MAAM,QAAW,QAAgB,MAAc,OAA0C,CAAC,GAAe;AACvG,UAAM,MAAM,IAAI,IAAI,KAAK,OAAO,IAAI;AACpC,QAAI,KAAK,OAAO;AACd,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AAC/C,YAAI,MAAM,UAAa,MAAM,QAAQ,MAAM,GAAI,KAAI,aAAa,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,MAClF;AAAA,IACF;AACA,UAAM,aAAa,WAAW,SAAS,WAAW,YAAY,WAAW;AAEzE,aAAS,UAAU,KAAK,WAAW;AACjC,YAAM,OAAO,IAAI,gBAAgB;AACjC,YAAM,QAAQ,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,SAAS;AAC3D,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,KAAK,UAAU,IAAI,SAAS,GAAG;AAAA,UACzC;AAAA,UACA,SAAS;AAAA,YACP,aAAa,KAAK;AAAA,YAClB,QAAQ;AAAA,YACR,GAAI,KAAK,SAAS,SAAY,EAAE,gBAAgB,mBAAmB,IAAI,CAAC;AAAA,UAC1E;AAAA,UACA,MAAM,KAAK,SAAS,SAAY,KAAK,UAAU,KAAK,IAAI,IAAI;AAAA,UAC5D,QAAQ,KAAK;AAAA,QACf,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,qBAAa,KAAK;AAElB,YAAI,cAAc,UAAU,KAAK,YAAY;AAAE,gBAAM,MAAM,QAAQ,OAAO,CAAC;AAAG;AAAA,QAAU;AACxF,cAAM,IAAI,aAAa,kBAAmB,IAAc,OAAO,IAAI,EAAE,QAAQ,GAAG,MAAM,gBAAgB,CAAC;AAAA,MACzG;AACA,mBAAa,KAAK;AAElB,YAAM,YAAY,IAAI,QAAQ,IAAI,cAAc,KAAK;AAErD,YAAM,YAAY,IAAI,WAAW,OAAO,IAAI,UAAU;AACtD,YAAM,YAAY,IAAI,WAAW,OAAO;AACxC,UAAI,aAAa,aAAa,UAAU,KAAK,YAAY;AACvD,cAAM,KAAK,OAAO,IAAI,QAAQ,IAAI,aAAa,CAAC;AAChD,cAAM,MAAM,OAAO,SAAS,EAAE,KAAK,KAAK,IAAI,KAAK,MAAO,QAAQ,OAAO,CAAC;AACxE;AAAA,MACF;AAEA,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,YAAM,OAAO,OAAO,SAAS,IAAI,IAAI;AACrC,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,UAAW,SAAS,KAAK,WAAW,KAAK,UAAW,QAAQ,IAAI,MAAM;AAC5E,cAAM,IAAI,aAAa,SAAS,EAAE,QAAQ,IAAI,QAAQ,MAAO,QAAQ,KAAK,SAAU,SAAS,UAAU,CAAC;AAAA,MAC1G;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,IAAM,QAAQ,CAAC,OAAe,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAExE,IAAM,UAAU,CAAC,YAAoB,KAAK,MAAO,MAAM,KAAK,WAAY,MAAM,KAAK,OAAO,IAAI,IAAI;AAElG,SAAS,SAAS,MAAmB;AACnC,MAAI;AAAE,WAAO,KAAK,MAAM,IAAI;AAAA,EAAG,QAAQ;AAAE,WAAO,EAAE,KAAK,KAAK;AAAA,EAAG;AACjE;AAIA,IAAM,mBAAN,MAAuB;AAAA,EACrB,YAAoB,GAAY;AAAZ;AAAA,EAAa;AAAA,EAAb;AAAA;AAAA,EAEpB,OAAO;AAAE,WAAO,KAAK,EAAE,QAAiC,OAAO,WAAW,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ;AAAA,EAAG;AAAA;AAAA,EAErG,OAAO,OAA4G;AACjH,WAAO,KAAK,EAAE,QAA8B,QAAQ,aAAa,EAAE,MAAM,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,EACzG;AAAA;AAAA,EAEA,OAAO,IAAY;AAAE,WAAO,KAAK,EAAE,QAAsB,UAAU,aAAa,EAAE,EAAE;AAAA,EAAG;AACzF;AAEA,IAAM,oBAAN,MAAwB;AAAA,EACtB,YAAoB,GAAY;AAAZ;AAAA,EAAa;AAAA,EAAb;AAAA,EACpB,OAAO;AAAE,WAAO,KAAK,EAAE,QAAmC,OAAO,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS;AAAA,EAAG;AAAA,EACzG,OAAO,OAA6H;AAClI,WAAO,KAAK,EAAE,QAAgC,QAAQ,cAAc,EAAE,MAAM,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ;AAAA,EAC7G;AAAA,EACA,OAAO,IAAY,OAAwI;AACzJ,WAAO,KAAK,EAAE,QAAgC,SAAS,cAAc,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ;AAAA,EACpH;AAAA;AAAA,EAEA,IAAI,IAAY;AAAE,WAAO,KAAK,EAAE,QAAgE,QAAQ,cAAc,EAAE,MAAM;AAAA,EAAG;AAAA;AAAA,EAEjI,MAAM,IAAY,OAAgC,CAAC,GAAG;AACpD,WAAO,KAAK,EAAE,QAA2B,OAAO,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,KAAK,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK;AAAA,EACnI;AAAA;AAAA,EAEA,MAAM,IAAY;AAAE,WAAO,KAAK,EAAE,QAA6B,OAAO,cAAc,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,EAAG;AAC1H;AAEA,IAAM,gBAAN,MAAoB;AAAA,EAClB,YAAoB,GAAY;AAAZ;AAAA,EAAa;AAAA,EAAb;AAAA;AAAA,EAEpB,OAAO,IAAY,OAAwD;AACzE,WAAO,KAAK,EAAE,QAAwB,SAAS,UAAU,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI;AAAA,EACpG;AACF;AAEA,IAAM,mBAAN,MAAuB;AAAA,EACrB,YAAoB,GAAY;AAAZ;AAAA,EAAa;AAAA,EAAb;AAAA;AAAA,EAEpB,SAAS;AAAE,WAAO,KAAK,EAAE,QAAoC,OAAO,kBAAkB,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM;AAAA,EAAG;AAAA,EAC/G,OAAO;AAAE,WAAO,KAAK,EAAE,QAAiC,OAAO,WAAW,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ;AAAA,EAAG;AAAA;AAAA,EAErG,OAAO,OAA6F;AAClG,WAAO,KAAK,EAAE,QAA8B,QAAQ,aAAa,EAAE,MAAM,EAAE,MAAM,WAAW,QAAQ,CAAC,GAAG,GAAG,GAAG,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,EAChJ;AAAA,EACA,OAAO,IAAY;AAAE,WAAO,KAAK,EAAE,QAAsB,UAAU,aAAa,EAAE,EAAE;AAAA,EAAG;AAAA;AAAA,EAEvF,KAAK,IAAY;AAAE,WAAO,KAAK,EAAE,QAAiB,QAAQ,aAAa,EAAE,OAAO;AAAA,EAAG;AAAA,EACnF,WAAW,IAAY;AAAE,WAAO,KAAK,EAAE,QAA2C,OAAO,aAAa,EAAE,aAAa,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU;AAAA,EAAG;AACpJ;AAEA,IAAM,kBAAN,MAAsB;AAAA,EACpB,YAAoB,GAAY;AAAZ;AAAA,EAAa;AAAA,EAAb;AAAA,EACpB,OAAO;AAAE,WAAO,KAAK,EAAE,QAA4B,OAAO,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI;AAAA,EAAG;AAAA;AAAA,EAExF,OAAO,OAAoG;AACzG,WAAO,KAAK,EAAE,QAAyC,QAAQ,SAAS,EAAE,MAAM,MAAM,CAAC;AAAA,EACzF;AAAA,EACA,OAAO,IAAY;AAAE,WAAO,KAAK,EAAE,QAAsB,UAAU,SAAS,EAAE,EAAE;AAAA,EAAG;AACrF;AAQA,IAAM,gBAAN,MAAoB;AAAA,EAClB,YAAoB,GAAY;AAAZ;AAAA,EAAa;AAAA,EAAb;AAAA;AAAA,EAEpB,UAAU;AAAE,WAAO,KAAK,EAAE,QAAsB,OAAO,QAAQ;AAAA,EAAG;AAAA;AAAA,EAElE,KAAK,SAAoB,CAAC,GAAG;AAC3B,WAAO,KAAK,EAAE,QAAgC,OAAO,SAAS,EAAE,OAAO,OAAgB,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI;AAAA,EAC9G;AAAA;AAAA;AAAA,EAGA,OAAO,YAAY,SAAoC,CAAC,GAA8C;AACpG,UAAM,WAAW,OAAO,SAAS;AACjC,aAAS,SAAS,KAAK,UAAU,UAAU;AACzC,YAAM,OAAO,MAAM,KAAK,KAAK,EAAE,GAAG,QAAQ,OAAO,UAAU,OAAO,CAAC;AACnE,iBAAW,OAAO,KAAM,OAAM;AAC9B,UAAI,KAAK,SAAS,SAAU;AAAA,IAC9B;AAAA,EACF;AACF;AAqBO,SAAS,cAAc,MAAqC;AACjE,QAAM,EAAE,SAAS,WAAW,QAAQ,eAAe,IAAI,IAAI;AAC3D,QAAM,QAAgC,CAAC;AACvC,aAAW,KAAK,UAAU,MAAM,GAAG,GAAG;AACpC,UAAM,IAAI,EAAE,QAAQ,GAAG;AACvB,QAAI,IAAI,EAAG,OAAM,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,EAAE,KAAK;AAAA,EAC/D;AACA,QAAM,IAAI,OAAO,MAAM,CAAC;AACxB,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,KAAK,CAAC,GAAI,QAAO;AACtB,MAAI,eAAe,KAAK,KAAK,IAAI,KAAK,IAAI,IAAI,MAAO,CAAC,IAAI,aAAc,QAAO;AAC/E,QAAM,eAAW,+BAAW,UAAU,MAAM,EAAE,OAAO,GAAG,CAAC,IAAI,OAAO,EAAE,EAAE,OAAO,KAAK;AACpF,QAAM,IAAI,OAAO,KAAK,QAAQ;AAC9B,QAAM,IAAI,OAAO,KAAK,EAAE;AACxB,SAAO,EAAE,WAAW,EAAE,cAAU,oCAAgB,GAAG,CAAC;AACtD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/internal.ts"],"sourcesContent":["/**\n * @clawpro/node — official SDK for the ClawPro Instagram outbound API.\n *\n * ```ts\n * import { ClawPro } from '@clawpro/node'\n * const clawpro = new ClawPro({ apiKey: process.env.CLAWPRO_API_KEY! })\n * const { accounts } = await clawpro.accounts.list()\n * ```\n */\n\nimport {\n parseSignature, timestampFresh, hmacHex, signingString, timingSafeEqualStr,\n buildUrl, shouldRetryNetwork, shouldRetryStatus, backoffMs, retryDelayMs,\n} from './internal';\n\n// ─────────────────────────────── Types ───────────────────────────────────────\n\nexport type AccountStatus = 'new' | 'warming' | 'active' | 'challenged' | 'banned' | 'disconnected';\nexport type ProxyType = 'mobile' | 'isp' | 'residential' | 'none';\n\nexport interface Account {\n id: string;\n username: string;\n status: AccountStatus;\n warmupTier?: number;\n dailyDmCap?: number;\n}\n\nexport type CampaignStatus = 'draft' | 'active' | 'paused';\n\nexport interface CampaignTarget {\n username: string;\n lastCheckedPostId: string | null;\n}\n\nexport interface Campaign {\n id: string;\n accountId: string;\n name: string;\n status: CampaignStatus;\n targets: CampaignTarget[];\n offer: string;\n icpCriteria: string;\n dailyDmTarget: number;\n}\n\nexport type LeadStatus =\n | 'discovered' | 'enriched' | 'scored' | 'qualified' | 'rejected'\n | 'queued' | 'dmed' | 'replied' | 'booked';\n\nexport interface Message {\n id: string;\n direction: 'out' | 'in';\n body: string;\n status: string;\n sentAt: string | null;\n createdAt: string;\n}\n\nexport interface Lead {\n id: string;\n igUsername: string;\n fullName: string | null;\n sourceTargetUsername: string;\n engagementType: 'like' | 'comment';\n commentText: string | null;\n score: number | null;\n scoreReason: string | null;\n status: LeadStatus;\n messages?: Message[];\n}\n\n/** A lead pushed in from your own scraper — who to DM + the engagement context. */\nexport interface IngestLead {\n username: string;\n engagedWith?: string;\n engagementType?: 'like' | 'comment';\n comment?: string | null;\n fullName?: string | null;\n}\n\nexport type WebhookType = 'generic' | 'slack';\nexport type WebhookEvent =\n | 'reply.received' | 'dm.sent' | 'dm.failed'\n | 'lead.qualified' | 'lead.booked'\n | 'account.active' | 'account.challenged' | 'account.banned';\n\nexport interface Webhook {\n id: string;\n label: string;\n url: string;\n type: WebhookType;\n events: (WebhookEvent | '*')[];\n active: boolean;\n lastStatus: number | null;\n lastDeliveryAt: string | null;\n lastError: string | null;\n /** Present only on the create response (generic HMAC secret). */\n secret?: string;\n}\n\nexport interface WebhookDelivery {\n id: string;\n event: string;\n status: 'pending' | 'delivered' | 'dead';\n attempts: number;\n lastStatusCode: number | null;\n lastError: string | null;\n nextRetryAt: string | null;\n deliveredAt: string | null;\n createdAt: string;\n}\n\nexport type ApiKeyEnv = 'production' | 'development';\nexport type ApiKeyAccess = 'full' | 'read' | 'write';\n\nexport interface ApiKey {\n id: string;\n name: string;\n environment: ApiKeyEnv;\n access: ApiKeyAccess;\n keyPrefix: string;\n createdAt: string;\n expiresAt: string | null;\n lastUsedAt: string | null;\n}\n\nexport interface UsageSummary {\n callsThisMonth: number;\n calls24h: number;\n errors: number;\n avgLatencyMs: number;\n}\n\nexport interface RequestLog {\n id: string;\n requestId: string;\n method: string;\n path: string;\n status: number;\n latencyMs: number;\n error: string | null;\n createdAt: string;\n keyName: string | null;\n keyPrefix: string | null;\n}\n\n// ─────────────────────────────── Errors ──────────────────────────────────────\n\n/** Thrown for any non-2xx API response. Inspect `status` / `requestId`. */\nexport class ClawProError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId?: string;\n constructor(message: string, opts: { status: number; code?: string; requestId?: string }) {\n super(message);\n this.name = 'ClawProError';\n this.status = opts.status;\n this.code = opts.code ?? 'error';\n this.requestId = opts.requestId;\n }\n}\n\n// ─────────────────────────────── Client ──────────────────────────────────────\n\nexport interface ClawProOptions {\n /** A `sk_live_…` / `sk_test_…` key from the developer portal. Sent as `X-API-Key`. */\n apiKey: string;\n /** API origin (no trailing slash). Defaults to https://api.tryclawpro.com */\n baseUrl?: string;\n /** Override the fetch implementation (defaults to global fetch). */\n fetch?: typeof fetch;\n /** Per-request timeout in ms (default 30000). */\n timeoutMs?: number;\n /** Auto-retry transient failures (network, 429, 5xx) with exponential backoff.\n * Default 2. 429s honor the `Retry-After` header; only idempotent methods\n * (GET/DELETE/PUT) are retried on network/5xx. */\n maxRetries?: number;\n /** Injected for determinism/testing — defaults to a real timer. */\n sleep?: (ms: number) => Promise<void>;\n /** Injected for determinism/testing — defaults to Math.random. */\n rand?: () => number;\n}\n\ntype Query = Record<string, string | number | boolean | undefined | null>;\n\nexport class ClawPro {\n readonly accounts: AccountsResource;\n readonly campaigns: CampaignsResource;\n readonly leads: LeadsResource;\n readonly webhooks: WebhooksResource;\n readonly keys: ApiKeysResource;\n readonly usage: UsageResource;\n\n private readonly apiKey: string;\n private readonly base: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly maxRetries: number;\n private readonly sleep: (ms: number) => Promise<void>;\n private readonly rand: () => number;\n\n constructor(opts: ClawProOptions) {\n if (!opts?.apiKey) throw new Error('ClawPro: `apiKey` is required');\n this.apiKey = opts.apiKey;\n const origin = (opts.baseUrl ?? 'https://api.tryclawpro.com').replace(/\\/$/, '');\n this.base = `${origin}/api/instagram`;\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!this.fetchImpl) throw new Error('ClawPro: no fetch available — pass `fetch` (Node <18)');\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n this.maxRetries = opts.maxRetries ?? 2;\n this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));\n this.rand = opts.rand ?? Math.random;\n\n this.accounts = new AccountsResource(this);\n this.campaigns = new CampaignsResource(this);\n this.leads = new LeadsResource(this);\n this.webhooks = new WebhooksResource(this);\n this.keys = new ApiKeysResource(this);\n this.usage = new UsageResource(this);\n }\n\n /** @internal */\n async request<T>(method: string, path: string, opts: { body?: unknown; query?: Query } = {}): Promise<T> {\n const url = buildUrl(this.base, path, opts.query);\n\n for (let attempt = 0; ; attempt++) {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method,\n headers: {\n 'X-API-Key': this.apiKey,\n Accept: 'application/json',\n ...(opts.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n },\n body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,\n signal: ctrl.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n if (shouldRetryNetwork(method, attempt, this.maxRetries)) {\n await this.sleep(backoffMs(attempt, this.rand));\n continue;\n }\n throw new ClawProError(`Network error: ${(err as Error).message}`, { status: 0, code: 'network_error' });\n }\n clearTimeout(timer);\n\n if (shouldRetryStatus(method, res.status, attempt, this.maxRetries)) {\n await this.sleep(retryDelayMs(res.headers.get('retry-after'), attempt, this.rand));\n continue;\n }\n\n const requestId = res.headers.get('x-request-id') ?? undefined;\n const text = await res.text();\n const data = text ? safeJson(text) : undefined;\n if (!res.ok) {\n const message = (data && (data.message || data.error)) || `HTTP ${res.status}`;\n throw new ClawProError(message, { status: res.status, code: (data && data.error) || 'error', requestId });\n }\n return data as T;\n }\n }\n}\n\nfunction safeJson(text: string): any {\n try { return JSON.parse(text); } catch { return { raw: text }; }\n}\n\n// ───────────────────────────── Resources ─────────────────────────────────────\n\nclass AccountsResource {\n constructor(private c: ClawPro) {}\n /** List connected sending accounts. */\n list() { return this.c.request<{ accounts: Account[] }>('GET', '/accounts').then((r) => r.accounts); }\n /** Connect a new sending account (a geo-matched mobile proxy is assigned). */\n create(input: { username: string; country?: string; proxyType?: ProxyType; proxyUrl?: string; timezone?: string }) {\n return this.c.request<{ account: Account }>('POST', '/accounts', { body: input }).then((r) => r.account);\n }\n /** Remove an account (cascades to its campaigns/leads/messages). */\n delete(id: string) { return this.c.request<{ ok: true }>('DELETE', `/accounts/${id}`); }\n}\n\nclass CampaignsResource {\n constructor(private c: ClawPro) {}\n list() { return this.c.request<{ campaigns: Campaign[] }>('GET', '/campaigns').then((r) => r.campaigns); }\n create(input: { accountId: string; name: string; targets: string[]; offer?: string; icpCriteria?: string; dailyDmTarget?: number }) {\n return this.c.request<{ campaign: Campaign }>('POST', '/campaigns', { body: input }).then((r) => r.campaign);\n }\n update(id: string, patch: Partial<{ status: CampaignStatus; targets: string[]; offer: string; icpCriteria: string; dailyDmTarget: number; name: string }>) {\n return this.c.request<{ campaign: Campaign }>('PATCH', `/campaigns/${id}`, { body: patch }).then((r) => r.campaign);\n }\n /** Kick a discovery/scoring/queue run for a campaign. */\n run(id: string) { return this.c.request<{ discovered: number; scored: number; queued: number }>('POST', `/campaigns/${id}/run`); }\n /** Leads for a campaign, optionally filtered by status. */\n leads(id: string, opts: { status?: LeadStatus } = {}) {\n return this.c.request<{ leads: Lead[] }>('GET', `/campaigns/${id}/leads`, { query: { status: opts.status } }).then((r) => r.leads);\n }\n /** Push externally-sourced leads into a campaign (bring-your-own-scraper). Each\n * carries the engagement context that feeds the DM. Deduped on username; up to\n * 500/call. Pass `score: true` to route them through the ICP scorer first. */\n addLeads(id: string, leads: IngestLead[], opts: { score?: boolean } = {}) {\n return this.c.request<{ created: number; skipped: number; total: number; status: string }>(\n 'POST', `/campaigns/${id}/leads`, { body: leads, query: { score: opts.score ? '1' : undefined } },\n );\n }\n /** Threads with at least one message (the unified inbox). */\n inbox(id: string) { return this.c.request<{ threads: Lead[] }>('GET', `/campaigns/${id}/inbox`).then((r) => r.threads); }\n}\n\nclass LeadsResource {\n constructor(private c: ClawPro) {}\n /** Advance a lead — e.g. `{ status: 'booked' }` once a call is set (fires `lead.booked`). */\n update(id: string, patch: { status: 'qualified' | 'rejected' | 'booked' }) {\n return this.c.request<{ lead: Lead }>('PATCH', `/leads/${id}`, { body: patch }).then((r) => r.lead);\n }\n /** Send a reply / follow-up to a lead in its existing thread (inbox-write —\n * manage the conversation via API). Goes out promptly through the account. */\n reply(id: string, text: string) {\n return this.c.request<{ message: unknown }>('POST', `/leads/${id}/reply`, { body: { text } }).then((r) => r.message);\n }\n}\n\nclass WebhooksResource {\n constructor(private c: ClawPro) {}\n /** The catalog of subscribable event types. */\n events() { return this.c.request<{ events: WebhookEvent[] }>('GET', '/webhooks/events').then((r) => r.events); }\n list() { return this.c.request<{ webhooks: Webhook[] }>('GET', '/webhooks').then((r) => r.webhooks); }\n /** Create an endpoint. The generic HMAC `secret` is returned once, here. */\n create(input: { url: string; type?: WebhookType; events?: (WebhookEvent | '*')[]; label?: string }) {\n return this.c.request<{ webhook: Webhook }>('POST', '/webhooks', { body: { type: 'generic', events: ['*'], ...input } }).then((r) => r.webhook);\n }\n delete(id: string) { return this.c.request<{ ok: true }>('DELETE', `/webhooks/${id}`); }\n /** Send a `webhook.test` delivery. */\n test(id: string) { return this.c.request<unknown>('POST', `/webhooks/${id}/test`); }\n deliveries(id: string) { return this.c.request<{ deliveries: WebhookDelivery[] }>('GET', `/webhooks/${id}/deliveries`).then((r) => r.deliveries); }\n}\n\nclass ApiKeysResource {\n constructor(private c: ClawPro) {}\n list() { return this.c.request<{ keys: ApiKey[] }>('GET', '/keys').then((r) => r.keys); }\n /** Returns `{ secret, key }` — the plaintext `secret` is shown only here. */\n create(input: { name: string; environment?: ApiKeyEnv; access?: ApiKeyAccess; expiresAt?: string | null }) {\n return this.c.request<{ secret: string; key: ApiKey }>('POST', '/keys', { body: input });\n }\n revoke(id: string) { return this.c.request<{ ok: true }>('DELETE', `/keys/${id}`); }\n}\n\nexport interface LogFilter {\n key?: string; method?: string; status?: '2xx' | '4xx' | '5xx';\n from?: string; to?: string; endpoint?: string; requestId?: string; search?: string;\n limit?: number; offset?: number;\n}\n\nclass UsageResource {\n constructor(private c: ClawPro) {}\n /** Calls this month / 24h / errors / avg latency. */\n summary() { return this.c.request<UsageSummary>('GET', '/usage'); }\n /** A single page of request logs (`limit` + `offset`). */\n logs(filter: LogFilter = {}) {\n return this.c.request<{ logs: RequestLog[] }>('GET', '/logs', { query: filter as Query }).then((r) => r.logs);\n }\n /** Auto-paginate every matching log:\n * `for await (const log of clawpro.usage.iterateLogs()) { … }` */\n async *iterateLogs(filter: Omit<LogFilter, 'offset'> = {}): AsyncGenerator<RequestLog, void, unknown> {\n const pageSize = filter.limit ?? 100;\n for (let offset = 0; ; offset += pageSize) {\n const page = await this.logs({ ...filter, limit: pageSize, offset });\n for (const row of page) yield row;\n if (page.length < pageSize) return;\n }\n }\n}\n\n// ─────────────────────────── Webhook verification ────────────────────────────\n\nexport interface VerifyWebhookOptions {\n /** The raw request body, exactly as received (string). */\n payload: string;\n /** The `X-Souk-Signature` header value (`t=…,v1=…`). */\n signature: string;\n /** The endpoint's signing secret (shown once on webhook creation). */\n secret: string;\n /** Reject signatures older than this many seconds (default 300; 0 disables). */\n toleranceSec?: number;\n}\n\n/** Verify a ClawPro webhook's HMAC signature (timing-safe). Returns true if valid.\n *\n * ```ts\n * if (!verifyWebhook({ payload: rawBody, signature: req.headers['x-souk-signature'], secret })) return res.sendStatus(400)\n * ```\n */\nexport function verifyWebhook(opts: VerifyWebhookOptions): boolean {\n const { payload, signature, secret, toleranceSec = 300 } = opts;\n const parsed = parseSignature(signature);\n if (!parsed) return false;\n if (!timestampFresh(parsed.t, toleranceSec, Date.now())) return false;\n const expected = hmacHex(secret, signingString(parsed.t, payload));\n return timingSafeEqualStr(expected, parsed.v1);\n}\n","/**\n * Pure, total helpers — the testable core of the SDK. No I/O, no throwing\n * (except the deliberately-effectful `hmacHex`, which is referentially\n * transparent: same inputs → same digest). The client (index.ts) is the thin\n * effectful shell that threads `fetch`, sleep, and randomness around these.\n */\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n// ── Webhook signatures ───────────────────────────────────────────────────────\nexport interface ParsedSignature { t: number; v1: string }\n\n/** Parse `t=<unix>,v1=<hex>`. Returns null for anything malformed (total). */\nexport function parseSignature(header: string): ParsedSignature | null {\n const parts: Record<string, string> = {};\n for (const p of header.split(',')) {\n const i = p.indexOf('=');\n if (i > 0) parts[p.slice(0, i).trim()] = p.slice(i + 1).trim();\n }\n const t = Number(parts.t);\n if (!Number.isFinite(t) || t <= 0 || !parts.v1) return null;\n return { t, v1: parts.v1 };\n}\n\nexport const signingString = (t: number, payload: string): string => `${t}.${payload}`;\n\nexport const hmacHex = (secret: string, message: string): string =>\n createHmac('sha256', secret).update(message).digest('hex');\n\n/** Is `t` within `toleranceSec` of `nowMs`? `toleranceSec <= 0` disables. */\nexport function timestampFresh(t: number, toleranceSec: number, nowMs: number): boolean {\n return toleranceSec <= 0 || Math.abs(nowMs / 1000 - t) <= toleranceSec;\n}\n\n/** Constant-time string equality (length-aware). */\nexport function timingSafeEqualStr(a: string, b: string): boolean {\n const ba = Buffer.from(a);\n const bb = Buffer.from(b);\n return ba.length === bb.length && timingSafeEqual(ba, bb);\n}\n\n// ── URL building ─────────────────────────────────────────────────────────────\nexport type QueryValue = string | number | boolean | undefined | null;\n\nexport function buildUrl(base: string, path: string, query?: Record<string, QueryValue>): string {\n const url = new URL(base + path);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\n// ── Retry decisions (pure) ───────────────────────────────────────────────────\nexport const isIdempotent = (method: string): boolean =>\n method === 'GET' || method === 'DELETE' || method === 'PUT';\n\n/** Network/timeout error: retry only idempotent methods, within budget. */\nexport const shouldRetryNetwork = (method: string, attempt: number, maxRetries: number): boolean =>\n isIdempotent(method) && attempt < maxRetries;\n\n/** A 429 (didn't process → any method) or 5xx (idempotent only), within budget. */\nexport function shouldRetryStatus(method: string, status: number, attempt: number, maxRetries: number): boolean {\n const transient = status === 429 || status >= 500;\n const allowed = status === 429 || isIdempotent(method);\n return transient && allowed && attempt < maxRetries;\n}\n\n/** Exponential backoff with jitter: ~250ms · 2^attempt · [0.8,1.2). `rand` injected. */\nexport const backoffMs = (attempt: number, rand: () => number): number =>\n Math.round(250 * 2 ** attempt * (0.8 + rand() * 0.4));\n\n/** Honor a numeric `Retry-After` (seconds), else fall back to backoff. */\nexport function retryDelayMs(retryAfter: string | null, attempt: number, rand: () => number): number {\n const ra = Number(retryAfter);\n return Number.isFinite(ra) && ra > 0 ? ra * 1000 : backoffMs(attempt, rand);\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -52,6 +52,14 @@ interface Lead {
52
52
  status: LeadStatus;
53
53
  messages?: Message[];
54
54
  }
55
+ /** A lead pushed in from your own scraper — who to DM + the engagement context. */
56
+ interface IngestLead {
57
+ username: string;
58
+ engagedWith?: string;
59
+ engagementType?: 'like' | 'comment';
60
+ comment?: string | null;
61
+ fullName?: string | null;
62
+ }
55
63
  type WebhookType = 'generic' | 'slack';
56
64
  type WebhookEvent = 'reply.received' | 'dm.sent' | 'dm.failed' | 'lead.qualified' | 'lead.booked' | 'account.active' | 'account.challenged' | 'account.banned';
57
65
  interface Webhook {
@@ -132,6 +140,10 @@ interface ClawProOptions {
132
140
  * Default 2. 429s honor the `Retry-After` header; only idempotent methods
133
141
  * (GET/DELETE/PUT) are retried on network/5xx. */
134
142
  maxRetries?: number;
143
+ /** Injected for determinism/testing — defaults to a real timer. */
144
+ sleep?: (ms: number) => Promise<void>;
145
+ /** Injected for determinism/testing — defaults to Math.random. */
146
+ rand?: () => number;
135
147
  }
136
148
  type Query = Record<string, string | number | boolean | undefined | null>;
137
149
  declare class ClawPro {
@@ -146,6 +158,8 @@ declare class ClawPro {
146
158
  private readonly fetchImpl;
147
159
  private readonly timeoutMs;
148
160
  private readonly maxRetries;
161
+ private readonly sleep;
162
+ private readonly rand;
149
163
  constructor(opts: ClawProOptions);
150
164
  /** @internal */
151
165
  request<T>(method: string, path: string, opts?: {
@@ -201,6 +215,17 @@ declare class CampaignsResource {
201
215
  leads(id: string, opts?: {
202
216
  status?: LeadStatus;
203
217
  }): Promise<Lead[]>;
218
+ /** Push externally-sourced leads into a campaign (bring-your-own-scraper). Each
219
+ * carries the engagement context that feeds the DM. Deduped on username; up to
220
+ * 500/call. Pass `score: true` to route them through the ICP scorer first. */
221
+ addLeads(id: string, leads: IngestLead[], opts?: {
222
+ score?: boolean;
223
+ }): Promise<{
224
+ created: number;
225
+ skipped: number;
226
+ total: number;
227
+ status: string;
228
+ }>;
204
229
  /** Threads with at least one message (the unified inbox). */
205
230
  inbox(id: string): Promise<Lead[]>;
206
231
  }
@@ -211,6 +236,9 @@ declare class LeadsResource {
211
236
  update(id: string, patch: {
212
237
  status: 'qualified' | 'rejected' | 'booked';
213
238
  }): Promise<Lead>;
239
+ /** Send a reply / follow-up to a lead in its existing thread (inbox-write —
240
+ * manage the conversation via API). Goes out promptly through the account. */
241
+ reply(id: string, text: string): Promise<unknown>;
214
242
  }
215
243
  declare class WebhooksResource {
216
244
  private c;
@@ -291,4 +319,4 @@ interface VerifyWebhookOptions {
291
319
  */
292
320
  declare function verifyWebhook(opts: VerifyWebhookOptions): boolean;
293
321
 
294
- export { type Account, type AccountStatus, type ApiKey, type ApiKeyAccess, type ApiKeyEnv, type Campaign, type CampaignStatus, type CampaignTarget, ClawPro, ClawProError, type ClawProOptions, type Lead, type LeadStatus, type LogFilter, type Message, type ProxyType, type RequestLog, type UsageSummary, type VerifyWebhookOptions, type Webhook, type WebhookDelivery, type WebhookEvent, type WebhookType, verifyWebhook };
322
+ export { type Account, type AccountStatus, type ApiKey, type ApiKeyAccess, type ApiKeyEnv, type Campaign, type CampaignStatus, type CampaignTarget, ClawPro, ClawProError, type ClawProOptions, type IngestLead, type Lead, type LeadStatus, type LogFilter, type Message, type ProxyType, type RequestLog, type UsageSummary, type VerifyWebhookOptions, type Webhook, type WebhookDelivery, type WebhookEvent, type WebhookType, verifyWebhook };
package/dist/index.d.ts CHANGED
@@ -52,6 +52,14 @@ interface Lead {
52
52
  status: LeadStatus;
53
53
  messages?: Message[];
54
54
  }
55
+ /** A lead pushed in from your own scraper — who to DM + the engagement context. */
56
+ interface IngestLead {
57
+ username: string;
58
+ engagedWith?: string;
59
+ engagementType?: 'like' | 'comment';
60
+ comment?: string | null;
61
+ fullName?: string | null;
62
+ }
55
63
  type WebhookType = 'generic' | 'slack';
56
64
  type WebhookEvent = 'reply.received' | 'dm.sent' | 'dm.failed' | 'lead.qualified' | 'lead.booked' | 'account.active' | 'account.challenged' | 'account.banned';
57
65
  interface Webhook {
@@ -132,6 +140,10 @@ interface ClawProOptions {
132
140
  * Default 2. 429s honor the `Retry-After` header; only idempotent methods
133
141
  * (GET/DELETE/PUT) are retried on network/5xx. */
134
142
  maxRetries?: number;
143
+ /** Injected for determinism/testing — defaults to a real timer. */
144
+ sleep?: (ms: number) => Promise<void>;
145
+ /** Injected for determinism/testing — defaults to Math.random. */
146
+ rand?: () => number;
135
147
  }
136
148
  type Query = Record<string, string | number | boolean | undefined | null>;
137
149
  declare class ClawPro {
@@ -146,6 +158,8 @@ declare class ClawPro {
146
158
  private readonly fetchImpl;
147
159
  private readonly timeoutMs;
148
160
  private readonly maxRetries;
161
+ private readonly sleep;
162
+ private readonly rand;
149
163
  constructor(opts: ClawProOptions);
150
164
  /** @internal */
151
165
  request<T>(method: string, path: string, opts?: {
@@ -201,6 +215,17 @@ declare class CampaignsResource {
201
215
  leads(id: string, opts?: {
202
216
  status?: LeadStatus;
203
217
  }): Promise<Lead[]>;
218
+ /** Push externally-sourced leads into a campaign (bring-your-own-scraper). Each
219
+ * carries the engagement context that feeds the DM. Deduped on username; up to
220
+ * 500/call. Pass `score: true` to route them through the ICP scorer first. */
221
+ addLeads(id: string, leads: IngestLead[], opts?: {
222
+ score?: boolean;
223
+ }): Promise<{
224
+ created: number;
225
+ skipped: number;
226
+ total: number;
227
+ status: string;
228
+ }>;
204
229
  /** Threads with at least one message (the unified inbox). */
205
230
  inbox(id: string): Promise<Lead[]>;
206
231
  }
@@ -211,6 +236,9 @@ declare class LeadsResource {
211
236
  update(id: string, patch: {
212
237
  status: 'qualified' | 'rejected' | 'booked';
213
238
  }): Promise<Lead>;
239
+ /** Send a reply / follow-up to a lead in its existing thread (inbox-write —
240
+ * manage the conversation via API). Goes out promptly through the account. */
241
+ reply(id: string, text: string): Promise<unknown>;
214
242
  }
215
243
  declare class WebhooksResource {
216
244
  private c;
@@ -291,4 +319,4 @@ interface VerifyWebhookOptions {
291
319
  */
292
320
  declare function verifyWebhook(opts: VerifyWebhookOptions): boolean;
293
321
 
294
- export { type Account, type AccountStatus, type ApiKey, type ApiKeyAccess, type ApiKeyEnv, type Campaign, type CampaignStatus, type CampaignTarget, ClawPro, ClawProError, type ClawProOptions, type Lead, type LeadStatus, type LogFilter, type Message, type ProxyType, type RequestLog, type UsageSummary, type VerifyWebhookOptions, type Webhook, type WebhookDelivery, type WebhookEvent, type WebhookType, verifyWebhook };
322
+ export { type Account, type AccountStatus, type ApiKey, type ApiKeyAccess, type ApiKeyEnv, type Campaign, type CampaignStatus, type CampaignTarget, ClawPro, ClawProError, type ClawProOptions, type IngestLead, type Lead, type LeadStatus, type LogFilter, type Message, type ProxyType, type RequestLog, type UsageSummary, type VerifyWebhookOptions, type Webhook, type WebhookDelivery, type WebhookEvent, type WebhookType, verifyWebhook };
package/dist/index.js CHANGED
@@ -1,5 +1,48 @@
1
- // src/index.ts
1
+ // src/internal.ts
2
2
  import { createHmac, timingSafeEqual } from "crypto";
3
+ function parseSignature(header) {
4
+ const parts = {};
5
+ for (const p of header.split(",")) {
6
+ const i = p.indexOf("=");
7
+ if (i > 0) parts[p.slice(0, i).trim()] = p.slice(i + 1).trim();
8
+ }
9
+ const t = Number(parts.t);
10
+ if (!Number.isFinite(t) || t <= 0 || !parts.v1) return null;
11
+ return { t, v1: parts.v1 };
12
+ }
13
+ var signingString = (t, payload) => `${t}.${payload}`;
14
+ var hmacHex = (secret, message) => createHmac("sha256", secret).update(message).digest("hex");
15
+ function timestampFresh(t, toleranceSec, nowMs) {
16
+ return toleranceSec <= 0 || Math.abs(nowMs / 1e3 - t) <= toleranceSec;
17
+ }
18
+ function timingSafeEqualStr(a, b) {
19
+ const ba = Buffer.from(a);
20
+ const bb = Buffer.from(b);
21
+ return ba.length === bb.length && timingSafeEqual(ba, bb);
22
+ }
23
+ function buildUrl(base, path, query) {
24
+ const url = new URL(base + path);
25
+ if (query) {
26
+ for (const [k, v] of Object.entries(query)) {
27
+ if (v !== void 0 && v !== null && v !== "") url.searchParams.set(k, String(v));
28
+ }
29
+ }
30
+ return url.toString();
31
+ }
32
+ var isIdempotent = (method) => method === "GET" || method === "DELETE" || method === "PUT";
33
+ var shouldRetryNetwork = (method, attempt, maxRetries) => isIdempotent(method) && attempt < maxRetries;
34
+ function shouldRetryStatus(method, status, attempt, maxRetries) {
35
+ const transient = status === 429 || status >= 500;
36
+ const allowed = status === 429 || isIdempotent(method);
37
+ return transient && allowed && attempt < maxRetries;
38
+ }
39
+ var backoffMs = (attempt, rand) => Math.round(250 * 2 ** attempt * (0.8 + rand() * 0.4));
40
+ function retryDelayMs(retryAfter, attempt, rand) {
41
+ const ra = Number(retryAfter);
42
+ return Number.isFinite(ra) && ra > 0 ? ra * 1e3 : backoffMs(attempt, rand);
43
+ }
44
+
45
+ // src/index.ts
3
46
  var ClawProError = class extends Error {
4
47
  status;
5
48
  code;
@@ -24,6 +67,8 @@ var ClawPro = class {
24
67
  fetchImpl;
25
68
  timeoutMs;
26
69
  maxRetries;
70
+ sleep;
71
+ rand;
27
72
  constructor(opts) {
28
73
  if (!opts?.apiKey) throw new Error("ClawPro: `apiKey` is required");
29
74
  this.apiKey = opts.apiKey;
@@ -33,6 +78,8 @@ var ClawPro = class {
33
78
  if (!this.fetchImpl) throw new Error("ClawPro: no fetch available \u2014 pass `fetch` (Node <18)");
34
79
  this.timeoutMs = opts.timeoutMs ?? 3e4;
35
80
  this.maxRetries = opts.maxRetries ?? 2;
81
+ this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
82
+ this.rand = opts.rand ?? Math.random;
36
83
  this.accounts = new AccountsResource(this);
37
84
  this.campaigns = new CampaignsResource(this);
38
85
  this.leads = new LeadsResource(this);
@@ -42,19 +89,13 @@ var ClawPro = class {
42
89
  }
43
90
  /** @internal */
44
91
  async request(method, path, opts = {}) {
45
- const url = new URL(this.base + path);
46
- if (opts.query) {
47
- for (const [k, v] of Object.entries(opts.query)) {
48
- if (v !== void 0 && v !== null && v !== "") url.searchParams.set(k, String(v));
49
- }
50
- }
51
- const idempotent = method === "GET" || method === "DELETE" || method === "PUT";
92
+ const url = buildUrl(this.base, path, opts.query);
52
93
  for (let attempt = 0; ; attempt++) {
53
94
  const ctrl = new AbortController();
54
95
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
55
96
  let res;
56
97
  try {
57
- res = await this.fetchImpl(url.toString(), {
98
+ res = await this.fetchImpl(url, {
58
99
  method,
59
100
  headers: {
60
101
  "X-API-Key": this.apiKey,
@@ -66,21 +107,18 @@ var ClawPro = class {
66
107
  });
67
108
  } catch (err) {
68
109
  clearTimeout(timer);
69
- if (idempotent && attempt < this.maxRetries) {
70
- await sleep(backoff(attempt));
110
+ if (shouldRetryNetwork(method, attempt, this.maxRetries)) {
111
+ await this.sleep(backoffMs(attempt, this.rand));
71
112
  continue;
72
113
  }
73
114
  throw new ClawProError(`Network error: ${err.message}`, { status: 0, code: "network_error" });
74
115
  }
75
116
  clearTimeout(timer);
76
- const requestId = res.headers.get("x-request-id") ?? void 0;
77
- const transient = res.status === 429 || res.status >= 500;
78
- const retryable = res.status === 429 || idempotent;
79
- if (transient && retryable && attempt < this.maxRetries) {
80
- const ra = Number(res.headers.get("retry-after"));
81
- await sleep(Number.isFinite(ra) && ra > 0 ? ra * 1e3 : backoff(attempt));
117
+ if (shouldRetryStatus(method, res.status, attempt, this.maxRetries)) {
118
+ await this.sleep(retryDelayMs(res.headers.get("retry-after"), attempt, this.rand));
82
119
  continue;
83
120
  }
121
+ const requestId = res.headers.get("x-request-id") ?? void 0;
84
122
  const text = await res.text();
85
123
  const data = text ? safeJson(text) : void 0;
86
124
  if (!res.ok) {
@@ -91,8 +129,6 @@ var ClawPro = class {
91
129
  }
92
130
  }
93
131
  };
94
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
95
- var backoff = (attempt) => Math.round(250 * 2 ** attempt * (0.8 + Math.random() * 0.4));
96
132
  function safeJson(text) {
97
133
  try {
98
134
  return JSON.parse(text);
@@ -140,6 +176,16 @@ var CampaignsResource = class {
140
176
  leads(id, opts = {}) {
141
177
  return this.c.request("GET", `/campaigns/${id}/leads`, { query: { status: opts.status } }).then((r) => r.leads);
142
178
  }
179
+ /** Push externally-sourced leads into a campaign (bring-your-own-scraper). Each
180
+ * carries the engagement context that feeds the DM. Deduped on username; up to
181
+ * 500/call. Pass `score: true` to route them through the ICP scorer first. */
182
+ addLeads(id, leads, opts = {}) {
183
+ return this.c.request(
184
+ "POST",
185
+ `/campaigns/${id}/leads`,
186
+ { body: leads, query: { score: opts.score ? "1" : void 0 } }
187
+ );
188
+ }
143
189
  /** Threads with at least one message (the unified inbox). */
144
190
  inbox(id) {
145
191
  return this.c.request("GET", `/campaigns/${id}/inbox`).then((r) => r.threads);
@@ -154,6 +200,11 @@ var LeadsResource = class {
154
200
  update(id, patch) {
155
201
  return this.c.request("PATCH", `/leads/${id}`, { body: patch }).then((r) => r.lead);
156
202
  }
203
+ /** Send a reply / follow-up to a lead in its existing thread (inbox-write —
204
+ * manage the conversation via API). Goes out promptly through the account. */
205
+ reply(id, text) {
206
+ return this.c.request("POST", `/leads/${id}/reply`, { body: { text } }).then((r) => r.message);
207
+ }
157
208
  };
158
209
  var WebhooksResource = class {
159
210
  constructor(c) {
@@ -224,19 +275,11 @@ var UsageResource = class {
224
275
  };
225
276
  function verifyWebhook(opts) {
226
277
  const { payload, signature, secret, toleranceSec = 300 } = opts;
227
- const parts = {};
228
- for (const p of signature.split(",")) {
229
- const i = p.indexOf("=");
230
- if (i > 0) parts[p.slice(0, i).trim()] = p.slice(i + 1).trim();
231
- }
232
- const t = Number(parts.t);
233
- const v1 = parts.v1;
234
- if (!t || !v1) return false;
235
- if (toleranceSec > 0 && Math.abs(Date.now() / 1e3 - t) > toleranceSec) return false;
236
- const expected = createHmac("sha256", secret).update(`${t}.${payload}`).digest("hex");
237
- const a = Buffer.from(expected);
238
- const b = Buffer.from(v1);
239
- return a.length === b.length && timingSafeEqual(a, b);
278
+ const parsed = parseSignature(signature);
279
+ if (!parsed) return false;
280
+ if (!timestampFresh(parsed.t, toleranceSec, Date.now())) return false;
281
+ const expected = hmacHex(secret, signingString(parsed.t, payload));
282
+ return timingSafeEqualStr(expected, parsed.v1);
240
283
  }
241
284
  export {
242
285
  ClawPro,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @clawpro/node — official SDK for the ClawPro Instagram outbound API.\n *\n * ```ts\n * import { ClawPro } from '@clawpro/node'\n * const clawpro = new ClawPro({ apiKey: process.env.CLAWPRO_API_KEY! })\n * const { accounts } = await clawpro.accounts.list()\n * ```\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n// ─────────────────────────────── Types ───────────────────────────────────────\n\nexport type AccountStatus = 'new' | 'warming' | 'active' | 'challenged' | 'banned' | 'disconnected';\nexport type ProxyType = 'mobile' | 'isp' | 'residential' | 'none';\n\nexport interface Account {\n id: string;\n username: string;\n status: AccountStatus;\n warmupTier?: number;\n dailyDmCap?: number;\n}\n\nexport type CampaignStatus = 'draft' | 'active' | 'paused';\n\nexport interface CampaignTarget {\n username: string;\n lastCheckedPostId: string | null;\n}\n\nexport interface Campaign {\n id: string;\n accountId: string;\n name: string;\n status: CampaignStatus;\n targets: CampaignTarget[];\n offer: string;\n icpCriteria: string;\n dailyDmTarget: number;\n}\n\nexport type LeadStatus =\n | 'discovered' | 'enriched' | 'scored' | 'qualified' | 'rejected'\n | 'queued' | 'dmed' | 'replied' | 'booked';\n\nexport interface Message {\n id: string;\n direction: 'out' | 'in';\n body: string;\n status: string;\n sentAt: string | null;\n createdAt: string;\n}\n\nexport interface Lead {\n id: string;\n igUsername: string;\n fullName: string | null;\n sourceTargetUsername: string;\n engagementType: 'like' | 'comment';\n commentText: string | null;\n score: number | null;\n scoreReason: string | null;\n status: LeadStatus;\n messages?: Message[];\n}\n\nexport type WebhookType = 'generic' | 'slack';\nexport type WebhookEvent =\n | 'reply.received' | 'dm.sent' | 'dm.failed'\n | 'lead.qualified' | 'lead.booked'\n | 'account.active' | 'account.challenged' | 'account.banned';\n\nexport interface Webhook {\n id: string;\n label: string;\n url: string;\n type: WebhookType;\n events: (WebhookEvent | '*')[];\n active: boolean;\n lastStatus: number | null;\n lastDeliveryAt: string | null;\n lastError: string | null;\n /** Present only on the create response (generic HMAC secret). */\n secret?: string;\n}\n\nexport interface WebhookDelivery {\n id: string;\n event: string;\n status: 'pending' | 'delivered' | 'dead';\n attempts: number;\n lastStatusCode: number | null;\n lastError: string | null;\n nextRetryAt: string | null;\n deliveredAt: string | null;\n createdAt: string;\n}\n\nexport type ApiKeyEnv = 'production' | 'development';\nexport type ApiKeyAccess = 'full' | 'read' | 'write';\n\nexport interface ApiKey {\n id: string;\n name: string;\n environment: ApiKeyEnv;\n access: ApiKeyAccess;\n keyPrefix: string;\n createdAt: string;\n expiresAt: string | null;\n lastUsedAt: string | null;\n}\n\nexport interface UsageSummary {\n callsThisMonth: number;\n calls24h: number;\n errors: number;\n avgLatencyMs: number;\n}\n\nexport interface RequestLog {\n id: string;\n requestId: string;\n method: string;\n path: string;\n status: number;\n latencyMs: number;\n error: string | null;\n createdAt: string;\n keyName: string | null;\n keyPrefix: string | null;\n}\n\n// ─────────────────────────────── Errors ──────────────────────────────────────\n\n/** Thrown for any non-2xx API response. Inspect `status` / `requestId`. */\nexport class ClawProError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId?: string;\n constructor(message: string, opts: { status: number; code?: string; requestId?: string }) {\n super(message);\n this.name = 'ClawProError';\n this.status = opts.status;\n this.code = opts.code ?? 'error';\n this.requestId = opts.requestId;\n }\n}\n\n// ─────────────────────────────── Client ──────────────────────────────────────\n\nexport interface ClawProOptions {\n /** A `sk_live_…` / `sk_test_…` key from the developer portal. Sent as `X-API-Key`. */\n apiKey: string;\n /** API origin (no trailing slash). Defaults to https://api.tryclawpro.com */\n baseUrl?: string;\n /** Override the fetch implementation (defaults to global fetch). */\n fetch?: typeof fetch;\n /** Per-request timeout in ms (default 30000). */\n timeoutMs?: number;\n /** Auto-retry transient failures (network, 429, 5xx) with exponential backoff.\n * Default 2. 429s honor the `Retry-After` header; only idempotent methods\n * (GET/DELETE/PUT) are retried on network/5xx. */\n maxRetries?: number;\n}\n\ntype Query = Record<string, string | number | boolean | undefined | null>;\n\nexport class ClawPro {\n readonly accounts: AccountsResource;\n readonly campaigns: CampaignsResource;\n readonly leads: LeadsResource;\n readonly webhooks: WebhooksResource;\n readonly keys: ApiKeysResource;\n readonly usage: UsageResource;\n\n private readonly apiKey: string;\n private readonly base: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly maxRetries: number;\n\n constructor(opts: ClawProOptions) {\n if (!opts?.apiKey) throw new Error('ClawPro: `apiKey` is required');\n this.apiKey = opts.apiKey;\n const origin = (opts.baseUrl ?? 'https://api.tryclawpro.com').replace(/\\/$/, '');\n this.base = `${origin}/api/instagram`;\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!this.fetchImpl) throw new Error('ClawPro: no fetch available — pass `fetch` (Node <18)');\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n this.maxRetries = opts.maxRetries ?? 2;\n\n this.accounts = new AccountsResource(this);\n this.campaigns = new CampaignsResource(this);\n this.leads = new LeadsResource(this);\n this.webhooks = new WebhooksResource(this);\n this.keys = new ApiKeysResource(this);\n this.usage = new UsageResource(this);\n }\n\n /** @internal */\n async request<T>(method: string, path: string, opts: { body?: unknown; query?: Query } = {}): Promise<T> {\n const url = new URL(this.base + path);\n if (opts.query) {\n for (const [k, v] of Object.entries(opts.query)) {\n if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, String(v));\n }\n }\n const idempotent = method === 'GET' || method === 'DELETE' || method === 'PUT';\n\n for (let attempt = 0; ; attempt++) {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url.toString(), {\n method,\n headers: {\n 'X-API-Key': this.apiKey,\n Accept: 'application/json',\n ...(opts.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n },\n body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,\n signal: ctrl.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n // Network/timeout: safe to retry only idempotent methods.\n if (idempotent && attempt < this.maxRetries) { await sleep(backoff(attempt)); continue; }\n throw new ClawProError(`Network error: ${(err as Error).message}`, { status: 0, code: 'network_error' });\n }\n clearTimeout(timer);\n\n const requestId = res.headers.get('x-request-id') ?? undefined;\n // Transient server-side: 429 (didn't process → retry any method) or 5xx (idempotent only).\n const transient = res.status === 429 || res.status >= 500;\n const retryable = res.status === 429 || idempotent;\n if (transient && retryable && attempt < this.maxRetries) {\n const ra = Number(res.headers.get('retry-after'));\n await sleep(Number.isFinite(ra) && ra > 0 ? ra * 1000 : backoff(attempt));\n continue;\n }\n\n const text = await res.text();\n const data = text ? safeJson(text) : undefined;\n if (!res.ok) {\n const message = (data && (data.message || data.error)) || `HTTP ${res.status}`;\n throw new ClawProError(message, { status: res.status, code: (data && data.error) || 'error', requestId });\n }\n return data as T;\n }\n }\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n/** Exponential backoff with jitter: ~250ms, ~500ms, ~1s … */\nconst backoff = (attempt: number) => Math.round((250 * 2 ** attempt) * (0.8 + Math.random() * 0.4));\n\nfunction safeJson(text: string): any {\n try { return JSON.parse(text); } catch { return { raw: text }; }\n}\n\n// ───────────────────────────── Resources ─────────────────────────────────────\n\nclass AccountsResource {\n constructor(private c: ClawPro) {}\n /** List connected sending accounts. */\n list() { return this.c.request<{ accounts: Account[] }>('GET', '/accounts').then((r) => r.accounts); }\n /** Connect a new sending account (a geo-matched mobile proxy is assigned). */\n create(input: { username: string; country?: string; proxyType?: ProxyType; proxyUrl?: string; timezone?: string }) {\n return this.c.request<{ account: Account }>('POST', '/accounts', { body: input }).then((r) => r.account);\n }\n /** Remove an account (cascades to its campaigns/leads/messages). */\n delete(id: string) { return this.c.request<{ ok: true }>('DELETE', `/accounts/${id}`); }\n}\n\nclass CampaignsResource {\n constructor(private c: ClawPro) {}\n list() { return this.c.request<{ campaigns: Campaign[] }>('GET', '/campaigns').then((r) => r.campaigns); }\n create(input: { accountId: string; name: string; targets: string[]; offer?: string; icpCriteria?: string; dailyDmTarget?: number }) {\n return this.c.request<{ campaign: Campaign }>('POST', '/campaigns', { body: input }).then((r) => r.campaign);\n }\n update(id: string, patch: Partial<{ status: CampaignStatus; targets: string[]; offer: string; icpCriteria: string; dailyDmTarget: number; name: string }>) {\n return this.c.request<{ campaign: Campaign }>('PATCH', `/campaigns/${id}`, { body: patch }).then((r) => r.campaign);\n }\n /** Kick a discovery/scoring/queue run for a campaign. */\n run(id: string) { return this.c.request<{ discovered: number; scored: number; queued: number }>('POST', `/campaigns/${id}/run`); }\n /** Leads for a campaign, optionally filtered by status. */\n leads(id: string, opts: { status?: LeadStatus } = {}) {\n return this.c.request<{ leads: Lead[] }>('GET', `/campaigns/${id}/leads`, { query: { status: opts.status } }).then((r) => r.leads);\n }\n /** Threads with at least one message (the unified inbox). */\n inbox(id: string) { return this.c.request<{ threads: Lead[] }>('GET', `/campaigns/${id}/inbox`).then((r) => r.threads); }\n}\n\nclass LeadsResource {\n constructor(private c: ClawPro) {}\n /** Advance a lead — e.g. `{ status: 'booked' }` once a call is set (fires `lead.booked`). */\n update(id: string, patch: { status: 'qualified' | 'rejected' | 'booked' }) {\n return this.c.request<{ lead: Lead }>('PATCH', `/leads/${id}`, { body: patch }).then((r) => r.lead);\n }\n}\n\nclass WebhooksResource {\n constructor(private c: ClawPro) {}\n /** The catalog of subscribable event types. */\n events() { return this.c.request<{ events: WebhookEvent[] }>('GET', '/webhooks/events').then((r) => r.events); }\n list() { return this.c.request<{ webhooks: Webhook[] }>('GET', '/webhooks').then((r) => r.webhooks); }\n /** Create an endpoint. The generic HMAC `secret` is returned once, here. */\n create(input: { url: string; type?: WebhookType; events?: (WebhookEvent | '*')[]; label?: string }) {\n return this.c.request<{ webhook: Webhook }>('POST', '/webhooks', { body: { type: 'generic', events: ['*'], ...input } }).then((r) => r.webhook);\n }\n delete(id: string) { return this.c.request<{ ok: true }>('DELETE', `/webhooks/${id}`); }\n /** Send a `webhook.test` delivery. */\n test(id: string) { return this.c.request<unknown>('POST', `/webhooks/${id}/test`); }\n deliveries(id: string) { return this.c.request<{ deliveries: WebhookDelivery[] }>('GET', `/webhooks/${id}/deliveries`).then((r) => r.deliveries); }\n}\n\nclass ApiKeysResource {\n constructor(private c: ClawPro) {}\n list() { return this.c.request<{ keys: ApiKey[] }>('GET', '/keys').then((r) => r.keys); }\n /** Returns `{ secret, key }` — the plaintext `secret` is shown only here. */\n create(input: { name: string; environment?: ApiKeyEnv; access?: ApiKeyAccess; expiresAt?: string | null }) {\n return this.c.request<{ secret: string; key: ApiKey }>('POST', '/keys', { body: input });\n }\n revoke(id: string) { return this.c.request<{ ok: true }>('DELETE', `/keys/${id}`); }\n}\n\nexport interface LogFilter {\n key?: string; method?: string; status?: '2xx' | '4xx' | '5xx';\n from?: string; to?: string; endpoint?: string; requestId?: string; search?: string;\n limit?: number; offset?: number;\n}\n\nclass UsageResource {\n constructor(private c: ClawPro) {}\n /** Calls this month / 24h / errors / avg latency. */\n summary() { return this.c.request<UsageSummary>('GET', '/usage'); }\n /** A single page of request logs (`limit` + `offset`). */\n logs(filter: LogFilter = {}) {\n return this.c.request<{ logs: RequestLog[] }>('GET', '/logs', { query: filter as Query }).then((r) => r.logs);\n }\n /** Auto-paginate every matching log:\n * `for await (const log of clawpro.usage.iterateLogs()) { … }` */\n async *iterateLogs(filter: Omit<LogFilter, 'offset'> = {}): AsyncGenerator<RequestLog, void, unknown> {\n const pageSize = filter.limit ?? 100;\n for (let offset = 0; ; offset += pageSize) {\n const page = await this.logs({ ...filter, limit: pageSize, offset });\n for (const row of page) yield row;\n if (page.length < pageSize) return;\n }\n }\n}\n\n// ─────────────────────────── Webhook verification ────────────────────────────\n\nexport interface VerifyWebhookOptions {\n /** The raw request body, exactly as received (string). */\n payload: string;\n /** The `X-Souk-Signature` header value (`t=…,v1=…`). */\n signature: string;\n /** The endpoint's signing secret (shown once on webhook creation). */\n secret: string;\n /** Reject signatures older than this many seconds (default 300; 0 disables). */\n toleranceSec?: number;\n}\n\n/** Verify a ClawPro webhook's HMAC signature (timing-safe). Returns true if valid.\n *\n * ```ts\n * if (!verifyWebhook({ payload: rawBody, signature: req.headers['x-souk-signature'], secret })) return res.sendStatus(400)\n * ```\n */\nexport function verifyWebhook(opts: VerifyWebhookOptions): boolean {\n const { payload, signature, secret, toleranceSec = 300 } = opts;\n const parts: Record<string, string> = {};\n for (const p of signature.split(',')) {\n const i = p.indexOf('=');\n if (i > 0) parts[p.slice(0, i).trim()] = p.slice(i + 1).trim();\n }\n const t = Number(parts.t);\n const v1 = parts.v1;\n if (!t || !v1) return false;\n if (toleranceSec > 0 && Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;\n const expected = createHmac('sha256', secret).update(`${t}.${payload}`).digest('hex');\n const a = Buffer.from(expected);\n const b = Buffer.from(v1);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/internal.ts","../src/index.ts"],"sourcesContent":["/**\n * Pure, total helpers — the testable core of the SDK. No I/O, no throwing\n * (except the deliberately-effectful `hmacHex`, which is referentially\n * transparent: same inputs → same digest). The client (index.ts) is the thin\n * effectful shell that threads `fetch`, sleep, and randomness around these.\n */\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n// ── Webhook signatures ───────────────────────────────────────────────────────\nexport interface ParsedSignature { t: number; v1: string }\n\n/** Parse `t=<unix>,v1=<hex>`. Returns null for anything malformed (total). */\nexport function parseSignature(header: string): ParsedSignature | null {\n const parts: Record<string, string> = {};\n for (const p of header.split(',')) {\n const i = p.indexOf('=');\n if (i > 0) parts[p.slice(0, i).trim()] = p.slice(i + 1).trim();\n }\n const t = Number(parts.t);\n if (!Number.isFinite(t) || t <= 0 || !parts.v1) return null;\n return { t, v1: parts.v1 };\n}\n\nexport const signingString = (t: number, payload: string): string => `${t}.${payload}`;\n\nexport const hmacHex = (secret: string, message: string): string =>\n createHmac('sha256', secret).update(message).digest('hex');\n\n/** Is `t` within `toleranceSec` of `nowMs`? `toleranceSec <= 0` disables. */\nexport function timestampFresh(t: number, toleranceSec: number, nowMs: number): boolean {\n return toleranceSec <= 0 || Math.abs(nowMs / 1000 - t) <= toleranceSec;\n}\n\n/** Constant-time string equality (length-aware). */\nexport function timingSafeEqualStr(a: string, b: string): boolean {\n const ba = Buffer.from(a);\n const bb = Buffer.from(b);\n return ba.length === bb.length && timingSafeEqual(ba, bb);\n}\n\n// ── URL building ─────────────────────────────────────────────────────────────\nexport type QueryValue = string | number | boolean | undefined | null;\n\nexport function buildUrl(base: string, path: string, query?: Record<string, QueryValue>): string {\n const url = new URL(base + path);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\n// ── Retry decisions (pure) ───────────────────────────────────────────────────\nexport const isIdempotent = (method: string): boolean =>\n method === 'GET' || method === 'DELETE' || method === 'PUT';\n\n/** Network/timeout error: retry only idempotent methods, within budget. */\nexport const shouldRetryNetwork = (method: string, attempt: number, maxRetries: number): boolean =>\n isIdempotent(method) && attempt < maxRetries;\n\n/** A 429 (didn't process → any method) or 5xx (idempotent only), within budget. */\nexport function shouldRetryStatus(method: string, status: number, attempt: number, maxRetries: number): boolean {\n const transient = status === 429 || status >= 500;\n const allowed = status === 429 || isIdempotent(method);\n return transient && allowed && attempt < maxRetries;\n}\n\n/** Exponential backoff with jitter: ~250ms · 2^attempt · [0.8,1.2). `rand` injected. */\nexport const backoffMs = (attempt: number, rand: () => number): number =>\n Math.round(250 * 2 ** attempt * (0.8 + rand() * 0.4));\n\n/** Honor a numeric `Retry-After` (seconds), else fall back to backoff. */\nexport function retryDelayMs(retryAfter: string | null, attempt: number, rand: () => number): number {\n const ra = Number(retryAfter);\n return Number.isFinite(ra) && ra > 0 ? ra * 1000 : backoffMs(attempt, rand);\n}\n","/**\n * @clawpro/node — official SDK for the ClawPro Instagram outbound API.\n *\n * ```ts\n * import { ClawPro } from '@clawpro/node'\n * const clawpro = new ClawPro({ apiKey: process.env.CLAWPRO_API_KEY! })\n * const { accounts } = await clawpro.accounts.list()\n * ```\n */\n\nimport {\n parseSignature, timestampFresh, hmacHex, signingString, timingSafeEqualStr,\n buildUrl, shouldRetryNetwork, shouldRetryStatus, backoffMs, retryDelayMs,\n} from './internal';\n\n// ─────────────────────────────── Types ───────────────────────────────────────\n\nexport type AccountStatus = 'new' | 'warming' | 'active' | 'challenged' | 'banned' | 'disconnected';\nexport type ProxyType = 'mobile' | 'isp' | 'residential' | 'none';\n\nexport interface Account {\n id: string;\n username: string;\n status: AccountStatus;\n warmupTier?: number;\n dailyDmCap?: number;\n}\n\nexport type CampaignStatus = 'draft' | 'active' | 'paused';\n\nexport interface CampaignTarget {\n username: string;\n lastCheckedPostId: string | null;\n}\n\nexport interface Campaign {\n id: string;\n accountId: string;\n name: string;\n status: CampaignStatus;\n targets: CampaignTarget[];\n offer: string;\n icpCriteria: string;\n dailyDmTarget: number;\n}\n\nexport type LeadStatus =\n | 'discovered' | 'enriched' | 'scored' | 'qualified' | 'rejected'\n | 'queued' | 'dmed' | 'replied' | 'booked';\n\nexport interface Message {\n id: string;\n direction: 'out' | 'in';\n body: string;\n status: string;\n sentAt: string | null;\n createdAt: string;\n}\n\nexport interface Lead {\n id: string;\n igUsername: string;\n fullName: string | null;\n sourceTargetUsername: string;\n engagementType: 'like' | 'comment';\n commentText: string | null;\n score: number | null;\n scoreReason: string | null;\n status: LeadStatus;\n messages?: Message[];\n}\n\n/** A lead pushed in from your own scraper — who to DM + the engagement context. */\nexport interface IngestLead {\n username: string;\n engagedWith?: string;\n engagementType?: 'like' | 'comment';\n comment?: string | null;\n fullName?: string | null;\n}\n\nexport type WebhookType = 'generic' | 'slack';\nexport type WebhookEvent =\n | 'reply.received' | 'dm.sent' | 'dm.failed'\n | 'lead.qualified' | 'lead.booked'\n | 'account.active' | 'account.challenged' | 'account.banned';\n\nexport interface Webhook {\n id: string;\n label: string;\n url: string;\n type: WebhookType;\n events: (WebhookEvent | '*')[];\n active: boolean;\n lastStatus: number | null;\n lastDeliveryAt: string | null;\n lastError: string | null;\n /** Present only on the create response (generic HMAC secret). */\n secret?: string;\n}\n\nexport interface WebhookDelivery {\n id: string;\n event: string;\n status: 'pending' | 'delivered' | 'dead';\n attempts: number;\n lastStatusCode: number | null;\n lastError: string | null;\n nextRetryAt: string | null;\n deliveredAt: string | null;\n createdAt: string;\n}\n\nexport type ApiKeyEnv = 'production' | 'development';\nexport type ApiKeyAccess = 'full' | 'read' | 'write';\n\nexport interface ApiKey {\n id: string;\n name: string;\n environment: ApiKeyEnv;\n access: ApiKeyAccess;\n keyPrefix: string;\n createdAt: string;\n expiresAt: string | null;\n lastUsedAt: string | null;\n}\n\nexport interface UsageSummary {\n callsThisMonth: number;\n calls24h: number;\n errors: number;\n avgLatencyMs: number;\n}\n\nexport interface RequestLog {\n id: string;\n requestId: string;\n method: string;\n path: string;\n status: number;\n latencyMs: number;\n error: string | null;\n createdAt: string;\n keyName: string | null;\n keyPrefix: string | null;\n}\n\n// ─────────────────────────────── Errors ──────────────────────────────────────\n\n/** Thrown for any non-2xx API response. Inspect `status` / `requestId`. */\nexport class ClawProError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId?: string;\n constructor(message: string, opts: { status: number; code?: string; requestId?: string }) {\n super(message);\n this.name = 'ClawProError';\n this.status = opts.status;\n this.code = opts.code ?? 'error';\n this.requestId = opts.requestId;\n }\n}\n\n// ─────────────────────────────── Client ──────────────────────────────────────\n\nexport interface ClawProOptions {\n /** A `sk_live_…` / `sk_test_…` key from the developer portal. Sent as `X-API-Key`. */\n apiKey: string;\n /** API origin (no trailing slash). Defaults to https://api.tryclawpro.com */\n baseUrl?: string;\n /** Override the fetch implementation (defaults to global fetch). */\n fetch?: typeof fetch;\n /** Per-request timeout in ms (default 30000). */\n timeoutMs?: number;\n /** Auto-retry transient failures (network, 429, 5xx) with exponential backoff.\n * Default 2. 429s honor the `Retry-After` header; only idempotent methods\n * (GET/DELETE/PUT) are retried on network/5xx. */\n maxRetries?: number;\n /** Injected for determinism/testing — defaults to a real timer. */\n sleep?: (ms: number) => Promise<void>;\n /** Injected for determinism/testing — defaults to Math.random. */\n rand?: () => number;\n}\n\ntype Query = Record<string, string | number | boolean | undefined | null>;\n\nexport class ClawPro {\n readonly accounts: AccountsResource;\n readonly campaigns: CampaignsResource;\n readonly leads: LeadsResource;\n readonly webhooks: WebhooksResource;\n readonly keys: ApiKeysResource;\n readonly usage: UsageResource;\n\n private readonly apiKey: string;\n private readonly base: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly maxRetries: number;\n private readonly sleep: (ms: number) => Promise<void>;\n private readonly rand: () => number;\n\n constructor(opts: ClawProOptions) {\n if (!opts?.apiKey) throw new Error('ClawPro: `apiKey` is required');\n this.apiKey = opts.apiKey;\n const origin = (opts.baseUrl ?? 'https://api.tryclawpro.com').replace(/\\/$/, '');\n this.base = `${origin}/api/instagram`;\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!this.fetchImpl) throw new Error('ClawPro: no fetch available — pass `fetch` (Node <18)');\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n this.maxRetries = opts.maxRetries ?? 2;\n this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));\n this.rand = opts.rand ?? Math.random;\n\n this.accounts = new AccountsResource(this);\n this.campaigns = new CampaignsResource(this);\n this.leads = new LeadsResource(this);\n this.webhooks = new WebhooksResource(this);\n this.keys = new ApiKeysResource(this);\n this.usage = new UsageResource(this);\n }\n\n /** @internal */\n async request<T>(method: string, path: string, opts: { body?: unknown; query?: Query } = {}): Promise<T> {\n const url = buildUrl(this.base, path, opts.query);\n\n for (let attempt = 0; ; attempt++) {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method,\n headers: {\n 'X-API-Key': this.apiKey,\n Accept: 'application/json',\n ...(opts.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n },\n body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,\n signal: ctrl.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n if (shouldRetryNetwork(method, attempt, this.maxRetries)) {\n await this.sleep(backoffMs(attempt, this.rand));\n continue;\n }\n throw new ClawProError(`Network error: ${(err as Error).message}`, { status: 0, code: 'network_error' });\n }\n clearTimeout(timer);\n\n if (shouldRetryStatus(method, res.status, attempt, this.maxRetries)) {\n await this.sleep(retryDelayMs(res.headers.get('retry-after'), attempt, this.rand));\n continue;\n }\n\n const requestId = res.headers.get('x-request-id') ?? undefined;\n const text = await res.text();\n const data = text ? safeJson(text) : undefined;\n if (!res.ok) {\n const message = (data && (data.message || data.error)) || `HTTP ${res.status}`;\n throw new ClawProError(message, { status: res.status, code: (data && data.error) || 'error', requestId });\n }\n return data as T;\n }\n }\n}\n\nfunction safeJson(text: string): any {\n try { return JSON.parse(text); } catch { return { raw: text }; }\n}\n\n// ───────────────────────────── Resources ─────────────────────────────────────\n\nclass AccountsResource {\n constructor(private c: ClawPro) {}\n /** List connected sending accounts. */\n list() { return this.c.request<{ accounts: Account[] }>('GET', '/accounts').then((r) => r.accounts); }\n /** Connect a new sending account (a geo-matched mobile proxy is assigned). */\n create(input: { username: string; country?: string; proxyType?: ProxyType; proxyUrl?: string; timezone?: string }) {\n return this.c.request<{ account: Account }>('POST', '/accounts', { body: input }).then((r) => r.account);\n }\n /** Remove an account (cascades to its campaigns/leads/messages). */\n delete(id: string) { return this.c.request<{ ok: true }>('DELETE', `/accounts/${id}`); }\n}\n\nclass CampaignsResource {\n constructor(private c: ClawPro) {}\n list() { return this.c.request<{ campaigns: Campaign[] }>('GET', '/campaigns').then((r) => r.campaigns); }\n create(input: { accountId: string; name: string; targets: string[]; offer?: string; icpCriteria?: string; dailyDmTarget?: number }) {\n return this.c.request<{ campaign: Campaign }>('POST', '/campaigns', { body: input }).then((r) => r.campaign);\n }\n update(id: string, patch: Partial<{ status: CampaignStatus; targets: string[]; offer: string; icpCriteria: string; dailyDmTarget: number; name: string }>) {\n return this.c.request<{ campaign: Campaign }>('PATCH', `/campaigns/${id}`, { body: patch }).then((r) => r.campaign);\n }\n /** Kick a discovery/scoring/queue run for a campaign. */\n run(id: string) { return this.c.request<{ discovered: number; scored: number; queued: number }>('POST', `/campaigns/${id}/run`); }\n /** Leads for a campaign, optionally filtered by status. */\n leads(id: string, opts: { status?: LeadStatus } = {}) {\n return this.c.request<{ leads: Lead[] }>('GET', `/campaigns/${id}/leads`, { query: { status: opts.status } }).then((r) => r.leads);\n }\n /** Push externally-sourced leads into a campaign (bring-your-own-scraper). Each\n * carries the engagement context that feeds the DM. Deduped on username; up to\n * 500/call. Pass `score: true` to route them through the ICP scorer first. */\n addLeads(id: string, leads: IngestLead[], opts: { score?: boolean } = {}) {\n return this.c.request<{ created: number; skipped: number; total: number; status: string }>(\n 'POST', `/campaigns/${id}/leads`, { body: leads, query: { score: opts.score ? '1' : undefined } },\n );\n }\n /** Threads with at least one message (the unified inbox). */\n inbox(id: string) { return this.c.request<{ threads: Lead[] }>('GET', `/campaigns/${id}/inbox`).then((r) => r.threads); }\n}\n\nclass LeadsResource {\n constructor(private c: ClawPro) {}\n /** Advance a lead — e.g. `{ status: 'booked' }` once a call is set (fires `lead.booked`). */\n update(id: string, patch: { status: 'qualified' | 'rejected' | 'booked' }) {\n return this.c.request<{ lead: Lead }>('PATCH', `/leads/${id}`, { body: patch }).then((r) => r.lead);\n }\n /** Send a reply / follow-up to a lead in its existing thread (inbox-write —\n * manage the conversation via API). Goes out promptly through the account. */\n reply(id: string, text: string) {\n return this.c.request<{ message: unknown }>('POST', `/leads/${id}/reply`, { body: { text } }).then((r) => r.message);\n }\n}\n\nclass WebhooksResource {\n constructor(private c: ClawPro) {}\n /** The catalog of subscribable event types. */\n events() { return this.c.request<{ events: WebhookEvent[] }>('GET', '/webhooks/events').then((r) => r.events); }\n list() { return this.c.request<{ webhooks: Webhook[] }>('GET', '/webhooks').then((r) => r.webhooks); }\n /** Create an endpoint. The generic HMAC `secret` is returned once, here. */\n create(input: { url: string; type?: WebhookType; events?: (WebhookEvent | '*')[]; label?: string }) {\n return this.c.request<{ webhook: Webhook }>('POST', '/webhooks', { body: { type: 'generic', events: ['*'], ...input } }).then((r) => r.webhook);\n }\n delete(id: string) { return this.c.request<{ ok: true }>('DELETE', `/webhooks/${id}`); }\n /** Send a `webhook.test` delivery. */\n test(id: string) { return this.c.request<unknown>('POST', `/webhooks/${id}/test`); }\n deliveries(id: string) { return this.c.request<{ deliveries: WebhookDelivery[] }>('GET', `/webhooks/${id}/deliveries`).then((r) => r.deliveries); }\n}\n\nclass ApiKeysResource {\n constructor(private c: ClawPro) {}\n list() { return this.c.request<{ keys: ApiKey[] }>('GET', '/keys').then((r) => r.keys); }\n /** Returns `{ secret, key }` — the plaintext `secret` is shown only here. */\n create(input: { name: string; environment?: ApiKeyEnv; access?: ApiKeyAccess; expiresAt?: string | null }) {\n return this.c.request<{ secret: string; key: ApiKey }>('POST', '/keys', { body: input });\n }\n revoke(id: string) { return this.c.request<{ ok: true }>('DELETE', `/keys/${id}`); }\n}\n\nexport interface LogFilter {\n key?: string; method?: string; status?: '2xx' | '4xx' | '5xx';\n from?: string; to?: string; endpoint?: string; requestId?: string; search?: string;\n limit?: number; offset?: number;\n}\n\nclass UsageResource {\n constructor(private c: ClawPro) {}\n /** Calls this month / 24h / errors / avg latency. */\n summary() { return this.c.request<UsageSummary>('GET', '/usage'); }\n /** A single page of request logs (`limit` + `offset`). */\n logs(filter: LogFilter = {}) {\n return this.c.request<{ logs: RequestLog[] }>('GET', '/logs', { query: filter as Query }).then((r) => r.logs);\n }\n /** Auto-paginate every matching log:\n * `for await (const log of clawpro.usage.iterateLogs()) { … }` */\n async *iterateLogs(filter: Omit<LogFilter, 'offset'> = {}): AsyncGenerator<RequestLog, void, unknown> {\n const pageSize = filter.limit ?? 100;\n for (let offset = 0; ; offset += pageSize) {\n const page = await this.logs({ ...filter, limit: pageSize, offset });\n for (const row of page) yield row;\n if (page.length < pageSize) return;\n }\n }\n}\n\n// ─────────────────────────── Webhook verification ────────────────────────────\n\nexport interface VerifyWebhookOptions {\n /** The raw request body, exactly as received (string). */\n payload: string;\n /** The `X-Souk-Signature` header value (`t=…,v1=…`). */\n signature: string;\n /** The endpoint's signing secret (shown once on webhook creation). */\n secret: string;\n /** Reject signatures older than this many seconds (default 300; 0 disables). */\n toleranceSec?: number;\n}\n\n/** Verify a ClawPro webhook's HMAC signature (timing-safe). Returns true if valid.\n *\n * ```ts\n * if (!verifyWebhook({ payload: rawBody, signature: req.headers['x-souk-signature'], secret })) return res.sendStatus(400)\n * ```\n */\nexport function verifyWebhook(opts: VerifyWebhookOptions): boolean {\n const { payload, signature, secret, toleranceSec = 300 } = opts;\n const parsed = parseSignature(signature);\n if (!parsed) return false;\n if (!timestampFresh(parsed.t, toleranceSec, Date.now())) return false;\n const expected = hmacHex(secret, signingString(parsed.t, payload));\n return timingSafeEqualStr(expected, parsed.v1);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawpro/node",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Official Node.js / TypeScript SDK for the ClawPro Instagram outbound API.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -13,14 +13,26 @@
13
13
  "require": "./dist/index.cjs"
14
14
  }
15
15
  },
16
- "files": ["dist", "README.md"],
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
17
20
  "sideEffects": false,
18
21
  "scripts": {
19
22
  "build": "tsup",
20
23
  "typecheck": "tsc --noEmit",
21
- "prepublishOnly": "npm run build"
24
+ "prepublishOnly": "npm run build",
25
+ "test": "vitest run"
22
26
  },
23
- "keywords": ["clawpro", "instagram", "outbound", "dm", "sdk", "api", "tryclawpro"],
27
+ "keywords": [
28
+ "clawpro",
29
+ "instagram",
30
+ "outbound",
31
+ "dm",
32
+ "sdk",
33
+ "api",
34
+ "tryclawpro"
35
+ ],
24
36
  "author": "ClawPro",
25
37
  "license": "MIT",
26
38
  "homepage": "https://tryclawpro.com",
@@ -37,6 +49,7 @@
37
49
  "devDependencies": {
38
50
  "@types/node": "^26.0.1",
39
51
  "tsup": "^8.3.0",
40
- "typescript": "^5.6.0"
52
+ "typescript": "^5.6.0",
53
+ "vitest": "^4.1.9"
41
54
  }
42
55
  }