@clawpro/node 0.1.2 → 0.1.4

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
@@ -21,9 +21,56 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ClawPro: () => ClawPro,
24
- ClawProError: () => ClawProError
24
+ ClawProError: () => ClawProError,
25
+ verifyWebhook: () => verifyWebhook
25
26
  });
26
27
  module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/internal.ts
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
27
74
  var ClawProError = class extends Error {
28
75
  status;
29
76
  code;
@@ -48,6 +95,8 @@ var ClawPro = class {
48
95
  fetchImpl;
49
96
  timeoutMs;
50
97
  maxRetries;
98
+ sleep;
99
+ rand;
51
100
  constructor(opts) {
52
101
  if (!opts?.apiKey) throw new Error("ClawPro: `apiKey` is required");
53
102
  this.apiKey = opts.apiKey;
@@ -57,6 +106,8 @@ var ClawPro = class {
57
106
  if (!this.fetchImpl) throw new Error("ClawPro: no fetch available \u2014 pass `fetch` (Node <18)");
58
107
  this.timeoutMs = opts.timeoutMs ?? 3e4;
59
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;
60
111
  this.accounts = new AccountsResource(this);
61
112
  this.campaigns = new CampaignsResource(this);
62
113
  this.leads = new LeadsResource(this);
@@ -66,19 +117,13 @@ var ClawPro = class {
66
117
  }
67
118
  /** @internal */
68
119
  async request(method, path, opts = {}) {
69
- const url = new URL(this.base + path);
70
- if (opts.query) {
71
- for (const [k, v] of Object.entries(opts.query)) {
72
- if (v !== void 0 && v !== null && v !== "") url.searchParams.set(k, String(v));
73
- }
74
- }
75
- const idempotent = method === "GET" || method === "DELETE" || method === "PUT";
120
+ const url = buildUrl(this.base, path, opts.query);
76
121
  for (let attempt = 0; ; attempt++) {
77
122
  const ctrl = new AbortController();
78
123
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
79
124
  let res;
80
125
  try {
81
- res = await this.fetchImpl(url.toString(), {
126
+ res = await this.fetchImpl(url, {
82
127
  method,
83
128
  headers: {
84
129
  "X-API-Key": this.apiKey,
@@ -90,21 +135,18 @@ var ClawPro = class {
90
135
  });
91
136
  } catch (err) {
92
137
  clearTimeout(timer);
93
- if (idempotent && attempt < this.maxRetries) {
94
- await sleep(backoff(attempt));
138
+ if (shouldRetryNetwork(method, attempt, this.maxRetries)) {
139
+ await this.sleep(backoffMs(attempt, this.rand));
95
140
  continue;
96
141
  }
97
142
  throw new ClawProError(`Network error: ${err.message}`, { status: 0, code: "network_error" });
98
143
  }
99
144
  clearTimeout(timer);
100
- const requestId = res.headers.get("x-request-id") ?? void 0;
101
- const transient = res.status === 429 || res.status >= 500;
102
- const retryable = res.status === 429 || idempotent;
103
- if (transient && retryable && attempt < this.maxRetries) {
104
- const ra = Number(res.headers.get("retry-after"));
105
- 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));
106
147
  continue;
107
148
  }
149
+ const requestId = res.headers.get("x-request-id") ?? void 0;
108
150
  const text = await res.text();
109
151
  const data = text ? safeJson(text) : void 0;
110
152
  if (!res.ok) {
@@ -115,8 +157,6 @@ var ClawPro = class {
115
157
  }
116
158
  }
117
159
  };
118
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
119
- var backoff = (attempt) => Math.round(250 * 2 ** attempt * (0.8 + Math.random() * 0.4));
120
160
  function safeJson(text) {
121
161
  try {
122
162
  return JSON.parse(text);
@@ -246,9 +286,18 @@ var UsageResource = class {
246
286
  }
247
287
  }
248
288
  };
289
+ function verifyWebhook(opts) {
290
+ const { payload, signature, secret, toleranceSec = 300 } = opts;
291
+ const parsed = parseSignature(signature);
292
+ if (!parsed) return false;
293
+ if (!timestampFresh(parsed.t, toleranceSec, Date.now())) return false;
294
+ const expected = hmacHex(secret, signingString(parsed.t, payload));
295
+ return timingSafeEqualStr(expected, parsed.v1);
296
+ }
249
297
  // Annotate the CommonJS export names for ESM import in node:
250
298
  0 && (module.exports = {
251
299
  ClawPro,
252
- ClawProError
300
+ ClawProError,
301
+ verifyWebhook
253
302
  });
254
303
  //# sourceMappingURL=index.cjs.map
@@ -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\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"],"mappings":"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","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\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 /** 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 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
@@ -132,6 +132,10 @@ interface ClawProOptions {
132
132
  * Default 2. 429s honor the `Retry-After` header; only idempotent methods
133
133
  * (GET/DELETE/PUT) are retried on network/5xx. */
134
134
  maxRetries?: number;
135
+ /** Injected for determinism/testing — defaults to a real timer. */
136
+ sleep?: (ms: number) => Promise<void>;
137
+ /** Injected for determinism/testing — defaults to Math.random. */
138
+ rand?: () => number;
135
139
  }
136
140
  type Query = Record<string, string | number | boolean | undefined | null>;
137
141
  declare class ClawPro {
@@ -146,6 +150,8 @@ declare class ClawPro {
146
150
  private readonly fetchImpl;
147
151
  private readonly timeoutMs;
148
152
  private readonly maxRetries;
153
+ private readonly sleep;
154
+ private readonly rand;
149
155
  constructor(opts: ClawProOptions);
150
156
  /** @internal */
151
157
  request<T>(method: string, path: string, opts?: {
@@ -273,5 +279,22 @@ declare class UsageResource {
273
279
  * `for await (const log of clawpro.usage.iterateLogs()) { … }` */
274
280
  iterateLogs(filter?: Omit<LogFilter, 'offset'>): AsyncGenerator<RequestLog, void, unknown>;
275
281
  }
282
+ interface VerifyWebhookOptions {
283
+ /** The raw request body, exactly as received (string). */
284
+ payload: string;
285
+ /** The `X-Souk-Signature` header value (`t=…,v1=…`). */
286
+ signature: string;
287
+ /** The endpoint's signing secret (shown once on webhook creation). */
288
+ secret: string;
289
+ /** Reject signatures older than this many seconds (default 300; 0 disables). */
290
+ toleranceSec?: number;
291
+ }
292
+ /** Verify a ClawPro webhook's HMAC signature (timing-safe). Returns true if valid.
293
+ *
294
+ * ```ts
295
+ * if (!verifyWebhook({ payload: rawBody, signature: req.headers['x-souk-signature'], secret })) return res.sendStatus(400)
296
+ * ```
297
+ */
298
+ declare function verifyWebhook(opts: VerifyWebhookOptions): boolean;
276
299
 
277
- 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 Webhook, type WebhookDelivery, type WebhookEvent, type WebhookType };
300
+ 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 };
package/dist/index.d.ts CHANGED
@@ -132,6 +132,10 @@ interface ClawProOptions {
132
132
  * Default 2. 429s honor the `Retry-After` header; only idempotent methods
133
133
  * (GET/DELETE/PUT) are retried on network/5xx. */
134
134
  maxRetries?: number;
135
+ /** Injected for determinism/testing — defaults to a real timer. */
136
+ sleep?: (ms: number) => Promise<void>;
137
+ /** Injected for determinism/testing — defaults to Math.random. */
138
+ rand?: () => number;
135
139
  }
136
140
  type Query = Record<string, string | number | boolean | undefined | null>;
137
141
  declare class ClawPro {
@@ -146,6 +150,8 @@ declare class ClawPro {
146
150
  private readonly fetchImpl;
147
151
  private readonly timeoutMs;
148
152
  private readonly maxRetries;
153
+ private readonly sleep;
154
+ private readonly rand;
149
155
  constructor(opts: ClawProOptions);
150
156
  /** @internal */
151
157
  request<T>(method: string, path: string, opts?: {
@@ -273,5 +279,22 @@ declare class UsageResource {
273
279
  * `for await (const log of clawpro.usage.iterateLogs()) { … }` */
274
280
  iterateLogs(filter?: Omit<LogFilter, 'offset'>): AsyncGenerator<RequestLog, void, unknown>;
275
281
  }
282
+ interface VerifyWebhookOptions {
283
+ /** The raw request body, exactly as received (string). */
284
+ payload: string;
285
+ /** The `X-Souk-Signature` header value (`t=…,v1=…`). */
286
+ signature: string;
287
+ /** The endpoint's signing secret (shown once on webhook creation). */
288
+ secret: string;
289
+ /** Reject signatures older than this many seconds (default 300; 0 disables). */
290
+ toleranceSec?: number;
291
+ }
292
+ /** Verify a ClawPro webhook's HMAC signature (timing-safe). Returns true if valid.
293
+ *
294
+ * ```ts
295
+ * if (!verifyWebhook({ payload: rawBody, signature: req.headers['x-souk-signature'], secret })) return res.sendStatus(400)
296
+ * ```
297
+ */
298
+ declare function verifyWebhook(opts: VerifyWebhookOptions): boolean;
276
299
 
277
- 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 Webhook, type WebhookDelivery, type WebhookEvent, type WebhookType };
300
+ 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 };
package/dist/index.js CHANGED
@@ -1,3 +1,47 @@
1
+ // src/internal.ts
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
+
1
45
  // src/index.ts
2
46
  var ClawProError = class extends Error {
3
47
  status;
@@ -23,6 +67,8 @@ var ClawPro = class {
23
67
  fetchImpl;
24
68
  timeoutMs;
25
69
  maxRetries;
70
+ sleep;
71
+ rand;
26
72
  constructor(opts) {
27
73
  if (!opts?.apiKey) throw new Error("ClawPro: `apiKey` is required");
28
74
  this.apiKey = opts.apiKey;
@@ -32,6 +78,8 @@ var ClawPro = class {
32
78
  if (!this.fetchImpl) throw new Error("ClawPro: no fetch available \u2014 pass `fetch` (Node <18)");
33
79
  this.timeoutMs = opts.timeoutMs ?? 3e4;
34
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;
35
83
  this.accounts = new AccountsResource(this);
36
84
  this.campaigns = new CampaignsResource(this);
37
85
  this.leads = new LeadsResource(this);
@@ -41,19 +89,13 @@ var ClawPro = class {
41
89
  }
42
90
  /** @internal */
43
91
  async request(method, path, opts = {}) {
44
- const url = new URL(this.base + path);
45
- if (opts.query) {
46
- for (const [k, v] of Object.entries(opts.query)) {
47
- if (v !== void 0 && v !== null && v !== "") url.searchParams.set(k, String(v));
48
- }
49
- }
50
- const idempotent = method === "GET" || method === "DELETE" || method === "PUT";
92
+ const url = buildUrl(this.base, path, opts.query);
51
93
  for (let attempt = 0; ; attempt++) {
52
94
  const ctrl = new AbortController();
53
95
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
54
96
  let res;
55
97
  try {
56
- res = await this.fetchImpl(url.toString(), {
98
+ res = await this.fetchImpl(url, {
57
99
  method,
58
100
  headers: {
59
101
  "X-API-Key": this.apiKey,
@@ -65,21 +107,18 @@ var ClawPro = class {
65
107
  });
66
108
  } catch (err) {
67
109
  clearTimeout(timer);
68
- if (idempotent && attempt < this.maxRetries) {
69
- await sleep(backoff(attempt));
110
+ if (shouldRetryNetwork(method, attempt, this.maxRetries)) {
111
+ await this.sleep(backoffMs(attempt, this.rand));
70
112
  continue;
71
113
  }
72
114
  throw new ClawProError(`Network error: ${err.message}`, { status: 0, code: "network_error" });
73
115
  }
74
116
  clearTimeout(timer);
75
- const requestId = res.headers.get("x-request-id") ?? void 0;
76
- const transient = res.status === 429 || res.status >= 500;
77
- const retryable = res.status === 429 || idempotent;
78
- if (transient && retryable && attempt < this.maxRetries) {
79
- const ra = Number(res.headers.get("retry-after"));
80
- 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));
81
119
  continue;
82
120
  }
121
+ const requestId = res.headers.get("x-request-id") ?? void 0;
83
122
  const text = await res.text();
84
123
  const data = text ? safeJson(text) : void 0;
85
124
  if (!res.ok) {
@@ -90,8 +129,6 @@ var ClawPro = class {
90
129
  }
91
130
  }
92
131
  };
93
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
94
- var backoff = (attempt) => Math.round(250 * 2 ** attempt * (0.8 + Math.random() * 0.4));
95
132
  function safeJson(text) {
96
133
  try {
97
134
  return JSON.parse(text);
@@ -221,8 +258,17 @@ var UsageResource = class {
221
258
  }
222
259
  }
223
260
  };
261
+ function verifyWebhook(opts) {
262
+ const { payload, signature, secret, toleranceSec = 300 } = opts;
263
+ const parsed = parseSignature(signature);
264
+ if (!parsed) return false;
265
+ if (!timestampFresh(parsed.t, toleranceSec, Date.now())) return false;
266
+ const expected = hmacHex(secret, signingString(parsed.t, payload));
267
+ return timingSafeEqualStr(expected, parsed.v1);
268
+ }
224
269
  export {
225
270
  ClawPro,
226
- ClawProError
271
+ ClawProError,
272
+ verifyWebhook
227
273
  };
228
274
  //# sourceMappingURL=index.js.map
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\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"],"mappings":";AAwIO,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;","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\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 /** 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 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.2",
3
+ "version": "0.1.4",
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,22 +13,43 @@
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",
27
- "repository": { "type": "git", "url": "git+https://github.com/Bojale-Labs/clawpro-node.git" },
28
- "bugs": { "url": "https://github.com/Bojale-Labs/clawpro-node/issues" },
29
- "engines": { "node": ">=18" },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/Bojale-Labs/clawpro-node.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/Bojale-Labs/clawpro-node/issues"
45
+ },
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
30
49
  "devDependencies": {
50
+ "@types/node": "^26.0.1",
31
51
  "tsup": "^8.3.0",
32
- "typescript": "^5.6.0"
52
+ "typescript": "^5.6.0",
53
+ "vitest": "^4.1.9"
33
54
  }
34
55
  }