@pushary/server 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -104,6 +104,9 @@ var createFlowsResource = (request) => Object.freeze({
104
104
  pause: (id) => request("POST", `/flows/${id}/pause`)
105
105
  });
106
106
 
107
+ // src/resources/decisions.ts
108
+ var import_crypto2 = require("crypto");
109
+
107
110
  // src/util.ts
108
111
  var import_crypto = require("crypto");
109
112
  var deterministicKey = (parts) => (0, import_crypto.createHash)("sha256").update(parts.join("\0")).digest("hex").slice(0, 40);
@@ -129,12 +132,7 @@ var POLL_WINDOW_SECONDS = 50;
129
132
  var DEFAULT_ASK_TIMEOUT_MS = 55e3;
130
133
  var createDecisionsResource = (request) => {
131
134
  const ask = async (data) => {
132
- const idempotencyKey = data.idempotencyKey ?? deterministicKey([
133
- data.externalId ?? "",
134
- data.type ?? "confirm",
135
- data.question,
136
- (data.options ?? []).join("|")
137
- ]);
135
+ const idempotencyKey = data.idempotencyKey ?? (0, import_crypto2.randomUUID)();
138
136
  const created = await request("POST", "/decisions", {
139
137
  question: data.question,
140
138
  type: data.type,
@@ -222,14 +220,14 @@ var createPusharyServer = (config) => {
222
220
  };
223
221
 
224
222
  // src/webhook.ts
225
- var import_crypto2 = require("crypto");
223
+ var import_crypto3 = require("crypto");
226
224
  var verifyWebhookSignature = (rawBody, signature, secret) => {
227
225
  if (!signature || !secret) return false;
228
- const expected = (0, import_crypto2.createHmac)("sha256", secret).update(rawBody).digest("hex");
226
+ const expected = (0, import_crypto3.createHmac)("sha256", secret).update(rawBody).digest("hex");
229
227
  const a = Buffer.from(expected);
230
228
  const b = Buffer.from(signature);
231
229
  if (a.length !== b.length) return false;
232
- return (0, import_crypto2.timingSafeEqual)(a, b);
230
+ return (0, import_crypto3.timingSafeEqual)(a, b);
233
231
  };
234
232
  // Annotate the CommonJS export names for ESM import in node:
235
233
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/util.ts","../src/resources/decisions.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["export { createPusharyServer, type PusharyServer } from './client'\n\nexport type {\n PusharyConfig,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n SubscriberCount,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n CampaignStats,\n Template,\n CreateTemplate,\n UpdateTemplate,\n SendNotification,\n SendResult,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n FlowTriggerType,\n FlowStepType,\n FlowStatus,\n FlowStep,\n FlowStepConfig,\n FlowStepInput,\n ListParams,\n PaginatedResponse,\n DecisionType,\n DecisionStatus,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n EnrollResult,\n AskDecision,\n AskResult,\n} from './types'\n\nexport type { SubscribersResource } from './resources/subscribers'\nexport type { CampaignsResource } from './resources/campaigns'\nexport type { TemplatesResource } from './resources/templates'\nexport type { NotificationsResource } from './resources/notifications'\nexport type { FlowsResource } from './resources/flows'\nexport type { DecisionsResource } from './resources/decisions'\n\nexport { verifyWebhookSignature } from './webhook'\n\nexport { deterministicKey, isApproved } from './util'\n\n","import type { RequestContext, RequestFn } from './types'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n }\n throw new Error(body.message || body.error || `HTTP ${response.status}`)\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { deterministicKey, isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Derives\n // a collision-safe idempotencyKey when you omit one and returns a fail-closed\n // `approved` flag. Default deadline 55s (serverless-safe); the decision stays\n // answerable for its full TTL, so an {answered:false} return is resolvable\n // later via get() or a callbackUrl. This is the one call most agents need.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n const idempotencyKey =\n data.idempotencyKey ??\n deterministicKey([\n data.externalId ?? '',\n data.type ?? 'confirm',\n data.question,\n (data.options ?? []).join('|'),\n ])\n\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n idempotencyKey,\n wait: false,\n })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n const deadline = Date.now() + Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n })\n status = polled.status\n answered = polled.answered\n value = polled.value\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import type { PusharyConfig, RequestContext, EnrollResult } from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions: createDecisionsResource(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n"],"mappings":"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","names":["import_crypto"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/resources/decisions.ts","../src/util.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["export { createPusharyServer, type PusharyServer } from './client'\n\nexport type {\n PusharyConfig,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n SubscriberCount,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n CampaignStats,\n Template,\n CreateTemplate,\n UpdateTemplate,\n SendNotification,\n SendResult,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n FlowTriggerType,\n FlowStepType,\n FlowStatus,\n FlowStep,\n FlowStepConfig,\n FlowStepInput,\n ListParams,\n PaginatedResponse,\n DecisionType,\n DecisionStatus,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n EnrollResult,\n AskDecision,\n AskResult,\n} from './types'\n\nexport type { SubscribersResource } from './resources/subscribers'\nexport type { CampaignsResource } from './resources/campaigns'\nexport type { TemplatesResource } from './resources/templates'\nexport type { NotificationsResource } from './resources/notifications'\nexport type { FlowsResource } from './resources/flows'\nexport type { DecisionsResource } from './resources/decisions'\n\nexport { verifyWebhookSignature } from './webhook'\n\nexport { deterministicKey, isApproved } from './util'\n\n","import type { RequestContext, RequestFn } from './types'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n }\n throw new Error(body.message || body.error || `HTTP ${response.status}`)\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { randomUUID } from 'crypto'\nimport { isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Returns\n // a fail-closed `approved` flag. Default deadline 55s (serverless-safe); the\n // decision stays answerable for its full TTL, so an {answered:false} return is\n // resolvable later via get() or a callbackUrl. This is the one call most agents\n // need. Each call reaches the human: without an idempotencyKey a fresh unique\n // key is generated per call, so two asks with the same text never collapse into\n // one silent auto-approval. Pass your own stable idempotencyKey (tied to your\n // operation id) only when you WANT a retried call to dedupe.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n // Idempotency is caller intent, never inferred from content: two asks with the\n // same text are usually two distinct decisions, so a content hash would make\n // the second silently replay the first answer (auto-approving a second refund,\n // or blocking a re-ask for the server's whole idempotency window). Default to a\n // unique key per call so every ask reaches the human; callers wanting retry\n // dedup pass their own stable key tied to their operation.\n const idempotencyKey = data.idempotencyKey ?? randomUUID()\n\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n idempotencyKey,\n wait: false,\n })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n const deadline = Date.now() + Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n })\n status = polled.status\n answered = polled.answered\n value = polled.value\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type { PusharyConfig, RequestContext, EnrollResult } from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions: createDecisionsResource(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n"],"mappings":"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","names":["import_crypto","import_crypto"]}
package/dist/index.js CHANGED
@@ -75,6 +75,9 @@ var createFlowsResource = (request) => Object.freeze({
75
75
  pause: (id) => request("POST", `/flows/${id}/pause`)
76
76
  });
77
77
 
78
+ // src/resources/decisions.ts
79
+ import { randomUUID } from "crypto";
80
+
78
81
  // src/util.ts
79
82
  import { createHash } from "crypto";
80
83
  var deterministicKey = (parts) => createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 40);
@@ -100,12 +103,7 @@ var POLL_WINDOW_SECONDS = 50;
100
103
  var DEFAULT_ASK_TIMEOUT_MS = 55e3;
101
104
  var createDecisionsResource = (request) => {
102
105
  const ask = async (data) => {
103
- const idempotencyKey = data.idempotencyKey ?? deterministicKey([
104
- data.externalId ?? "",
105
- data.type ?? "confirm",
106
- data.question,
107
- (data.options ?? []).join("|")
108
- ]);
106
+ const idempotencyKey = data.idempotencyKey ?? randomUUID();
109
107
  const created = await request("POST", "/decisions", {
110
108
  question: data.question,
111
109
  type: data.type,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/util.ts","../src/resources/decisions.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["import type { RequestContext, RequestFn } from './types'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n }\n throw new Error(body.message || body.error || `HTTP ${response.status}`)\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { deterministicKey, isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Derives\n // a collision-safe idempotencyKey when you omit one and returns a fail-closed\n // `approved` flag. Default deadline 55s (serverless-safe); the decision stays\n // answerable for its full TTL, so an {answered:false} return is resolvable\n // later via get() or a callbackUrl. This is the one call most agents need.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n const idempotencyKey =\n data.idempotencyKey ??\n deterministicKey([\n data.externalId ?? '',\n data.type ?? 'confirm',\n data.question,\n (data.options ?? []).join('|'),\n ])\n\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n idempotencyKey,\n wait: false,\n })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n const deadline = Date.now() + Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n })\n status = polled.status\n answered = polled.answered\n value = polled.value\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import type { PusharyConfig, RequestContext, EnrollResult } from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions: createDecisionsResource(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/request.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/templates.ts","../src/resources/notifications.ts","../src/resources/flows.ts","../src/resources/decisions.ts","../src/util.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["import type { RequestContext, RequestFn } from './types'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n // Build from the full absolute URL so the base path (e.g. /api/v1/server) is\n // preserved. `new URL(path, baseUrl)` treats a leading-slash path as absolute\n // and would drop that prefix, sending GET-with-query calls to the wrong URL.\n const url = new URL(`${baseUrl}${path}`)\n\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n })\n }\n \n return url.toString()\n}\n\nconst handleResponse = async <T>(response: Response): Promise<T> => {\n if (!response.ok) {\n // The API returns its reason under `error`; older paths use `message`. Read\n // both so callers get the real reason (e.g. \"No subscribers found\") instead\n // of a bare \"HTTP 400\" they cannot classify.\n const body = (await response.json().catch(() => ({}))) as {\n message?: string\n error?: string\n }\n throw new Error(body.message || body.error || `HTTP ${response.status}`)\n }\n return response.json() as Promise<T>\n}\n\nexport const createRequest = (ctx: RequestContext): RequestFn =>\n async <T>(method: string, path: string, body?: unknown): Promise<T> => {\n const isGet = method === 'GET'\n const url = isGet && body \n ? buildUrl(ctx.baseUrl, path, body as Record<string, unknown>)\n : `${ctx.baseUrl}${path}`\n \n const response = await fetch(url, {\n method,\n headers: ctx.headers,\n body: !isGet && body ? JSON.stringify(body) : undefined,\n })\n \n return handleResponse<T>(response)\n }\n\n","import type {\n RequestFn,\n Subscriber,\n SubscriberListParams,\n UpdateSubscriber,\n PaginatedResponse,\n SubscriberCount,\n} from '../types'\n\nexport interface SubscribersResource {\n readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>\n readonly get: (id: string) => Promise<Subscriber>\n readonly update: (id: string, data: UpdateSubscriber) => Promise<Subscriber>\n readonly delete: (id: string) => Promise<void>\n readonly count: () => Promise<SubscriberCount>\n}\n\nexport const createSubscribersResource = (request: RequestFn): SubscribersResource =>\n Object.freeze({\n list: (params?: SubscriberListParams) =>\n request<PaginatedResponse<Subscriber>>('GET', '/subscribers', params),\n \n get: (id: string) =>\n request<Subscriber>('GET', `/subscribers/${id}`),\n \n update: (id: string, data: UpdateSubscriber) =>\n request<Subscriber>('PATCH', `/subscribers/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/subscribers/${id}`),\n \n count: () =>\n request<SubscriberCount>('GET', '/subscribers/count'),\n })\n\n","import type {\n RequestFn,\n Campaign,\n CreateCampaign,\n UpdateCampaign,\n ListParams,\n PaginatedResponse,\n CampaignStats,\n} from '../types'\n\nexport interface CampaignsResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Campaign>>\n readonly create: (data: CreateCampaign) => Promise<Campaign>\n readonly get: (id: string) => Promise<Campaign>\n readonly update: (id: string, data: UpdateCampaign) => Promise<Campaign>\n readonly delete: (id: string) => Promise<void>\n readonly send: (id: string) => Promise<Campaign>\n readonly pause: (id: string) => Promise<Campaign>\n readonly resume: (id: string) => Promise<Campaign>\n readonly stats: (id: string) => Promise<CampaignStats>\n}\n\nexport const createCampaignsResource = (request: RequestFn): CampaignsResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Campaign>>('GET', '/campaigns', params),\n \n create: (data: CreateCampaign) =>\n request<Campaign>('POST', '/campaigns', data),\n \n get: (id: string) =>\n request<Campaign>('GET', `/campaigns/${id}`),\n \n update: (id: string, data: UpdateCampaign) =>\n request<Campaign>('PATCH', `/campaigns/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/campaigns/${id}`),\n \n send: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/send`),\n \n pause: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/pause`),\n \n resume: (id: string) =>\n request<Campaign>('POST', `/campaigns/${id}/resume`),\n \n stats: (id: string) =>\n request<CampaignStats>('GET', `/campaigns/${id}/stats`),\n })\n\n","import type {\n RequestFn,\n Template,\n CreateTemplate,\n UpdateTemplate,\n ListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface TemplatesResource {\n readonly list: (params?: ListParams) => Promise<PaginatedResponse<Template>>\n readonly create: (data: CreateTemplate) => Promise<Template>\n readonly get: (id: string) => Promise<Template>\n readonly update: (id: string, data: UpdateTemplate) => Promise<Template>\n readonly delete: (id: string) => Promise<void>\n}\n\nexport const createTemplatesResource = (request: RequestFn): TemplatesResource =>\n Object.freeze({\n list: (params?: ListParams) =>\n request<PaginatedResponse<Template>>('GET', '/templates', params),\n \n create: (data: CreateTemplate) =>\n request<Template>('POST', '/templates', data),\n \n get: (id: string) =>\n request<Template>('GET', `/templates/${id}`),\n \n update: (id: string, data: UpdateTemplate) =>\n request<Template>('PATCH', `/templates/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/templates/${id}`),\n })\n\n","import type { RequestFn, SendNotification, SendResult } from '../types'\n\nexport interface NotificationsResource {\n readonly send: (data: SendNotification) => Promise<SendResult>\n}\n\nexport const createNotificationsResource = (request: RequestFn): NotificationsResource =>\n Object.freeze({\n send: (data: SendNotification) =>\n request<SendResult>('POST', '/send', data),\n })\n\n","import type {\n RequestFn,\n Flow,\n CreateFlow,\n UpdateFlow,\n FlowListParams,\n PaginatedResponse,\n} from '../types'\n\nexport interface FlowsResource {\n readonly list: (params?: FlowListParams) => Promise<PaginatedResponse<Flow>>\n readonly create: (data: CreateFlow) => Promise<Flow>\n readonly get: (id: string) => Promise<Flow>\n readonly update: (id: string, data: UpdateFlow) => Promise<Flow>\n readonly delete: (id: string) => Promise<void>\n readonly activate: (id: string) => Promise<Flow>\n readonly pause: (id: string) => Promise<Flow>\n}\n\nexport const createFlowsResource = (request: RequestFn): FlowsResource =>\n Object.freeze({\n list: (params?: FlowListParams) =>\n request<PaginatedResponse<Flow>>('GET', '/flows', params),\n \n create: (data: CreateFlow) =>\n request<Flow>('POST', '/flows', data),\n \n get: (id: string) =>\n request<Flow>('GET', `/flows/${id}`),\n \n update: (id: string, data: UpdateFlow) =>\n request<Flow>('PATCH', `/flows/${id}`, data),\n \n delete: (id: string) =>\n request<void>('DELETE', `/flows/${id}`),\n \n activate: (id: string) =>\n request<Flow>('POST', `/flows/${id}/activate`),\n \n pause: (id: string) =>\n request<Flow>('POST', `/flows/${id}/pause`),\n })\n","import type {\n RequestFn,\n CreateDecision,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n WebhookSecret,\n AskDecision,\n AskResult,\n DecisionType,\n DecisionStatus,\n} from '../types'\nimport { randomUUID } from 'crypto'\nimport { isApproved } from '../util'\n\n// A single long-poll window. The server clamps GET ?wait to 55s, so ask() loops\n// windows of this size until the decision resolves or the caller deadline passes.\nconst POLL_WINDOW_SECONDS = 50\nconst DEFAULT_ASK_TIMEOUT_MS = 55_000\n\n// Human-in-the-loop decisions: ask a specific end-user to approve, then resume\n// on their answer via webhook or poll. See the Partner integration guide.\nexport interface DecisionsResource {\n // Create a decision. Defaults to async (returns immediately with a decisionId);\n // pass wait:true to block up to ~55s for a fast answer. Always pass\n // idempotencyKey so a retried call does not ask the same human twice.\n readonly create: (data: CreateDecision) => Promise<DecisionResult>\n // Poll for the outcome. opts.wait long-polls up to N seconds. Reads durably, so\n // it still resolves after the live window closes.\n readonly get: (id: string, opts?: { readonly wait?: number }) => Promise<Decision>\n // Create a decision and block until the human answers or the deadline passes,\n // polling durably (so a crashed/resumed process still gets the answer). Returns\n // a fail-closed `approved` flag. Default deadline 55s (serverless-safe); the\n // decision stays answerable for its full TTL, so an {answered:false} return is\n // resolvable later via get() or a callbackUrl. This is the one call most agents\n // need. Each call reaches the human: without an idempotencyKey a fresh unique\n // key is generated per call, so two asks with the same text never collapse into\n // one silent auto-approval. Pass your own stable idempotencyKey (tied to your\n // operation id) only when you WANT a retried call to dedupe.\n readonly ask: (data: AskDecision) => Promise<AskResult>\n // Relay the end-user's answer from your own authenticated app (delegated auth).\n readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>\n readonly cancel: (id: string) => Promise<CancelDecisionResult>\n // Your webhook signing secret (created on first call). Verify callbacks with\n // verifyWebhookSignature(rawBody, header, secret).\n readonly getWebhookSecret: () => Promise<WebhookSecret>\n readonly rotateWebhookSecret: () => Promise<WebhookSecret>\n}\n\nexport const createDecisionsResource = (request: RequestFn): DecisionsResource => {\n const ask = async (data: AskDecision): Promise<AskResult> => {\n // Idempotency is caller intent, never inferred from content: two asks with the\n // same text are usually two distinct decisions, so a content hash would make\n // the second silently replay the first answer (auto-approving a second refund,\n // or blocking a re-ask for the server's whole idempotency window). Default to a\n // unique key per call so every ask reaches the human; callers wanting retry\n // dedup pass their own stable key tied to their operation.\n const idempotencyKey = data.idempotencyKey ?? randomUUID()\n\n const created = await request<DecisionResult>('POST', '/decisions', {\n question: data.question,\n type: data.type,\n options: data.options,\n externalId: data.externalId,\n email: data.email,\n agentName: data.agentName,\n context: data.context,\n placeholder: data.placeholder,\n expiresInSeconds: data.expiresInSeconds,\n poweredBy: data.poweredBy,\n callbackUrl: data.callbackUrl,\n idempotencyKey,\n wait: false,\n })\n\n const type: DecisionType = created.type ?? data.type ?? 'confirm'\n let status: DecisionStatus = created.status\n let answered = created.answered\n let value: string | null = created.value ?? null\n\n const deadline = Date.now() + Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS)\n while (status === 'pending' && Date.now() < deadline) {\n const waitSeconds = Math.max(\n 1,\n Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1000)),\n )\n const polled = await request<Decision>('GET', `/decisions/${created.decisionId}`, {\n wait: waitSeconds,\n })\n status = polled.status\n answered = polled.answered\n value = polled.value\n }\n\n return {\n decisionId: created.decisionId,\n status,\n answered,\n value,\n type,\n approved: isApproved({ status, type, value }),\n }\n }\n\n return Object.freeze({\n create: (data: CreateDecision) => request<DecisionResult>('POST', '/decisions', data),\n get: (id: string, opts?: { readonly wait?: number }) =>\n request<Decision>('GET', `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : undefined),\n ask,\n answer: (id: string, answer: string) =>\n request<DecisionAnswerResult>('POST', `/decisions/${id}`, { answer }),\n cancel: (id: string) => request<CancelDecisionResult>('DELETE', `/decisions/${id}`),\n getWebhookSecret: () => request<WebhookSecret>('GET', '/webhook-secret'),\n rotateWebhookSecret: () => request<WebhookSecret>('POST', '/webhook-secret'),\n })\n}\n","import { createHash } from 'crypto'\nimport type { DecisionStatus, DecisionType } from './types'\n\n// A collision-safe idempotency key that is STABLE across process restarts\n// (unlike the builtin salted hash()), so a retried create reuses the same key\n// and never asks the same human twice. Pass the parts that make the ask unique\n// (e.g. [externalId, stepId, question]). Parts are joined with a NUL separator\n// so ['a', 'bc'] and ['ab', 'c'] never collide.\nexport const deterministicKey = (parts: readonly string[]): string =>\n createHash('sha256').update(parts.join('\\x00')).digest('hex').slice(0, 40)\n\nconst AFFIRMATIVE: ReadonlySet<string> = new Set([\n 'yes',\n 'y',\n 'approve',\n 'approved',\n 'ok',\n 'okay',\n 'confirm',\n 'accept',\n 'true',\n])\n\n// Fail-closed approval check. A `confirm` decision is approved ONLY on an\n// affirmative answer; `select`/`input` decisions are \"approved\" once answered.\n// Anything not answered — pending, expired, or cancelled — is NOT approved.\nexport const isApproved = (decision: {\n readonly status: DecisionStatus\n readonly type?: DecisionType\n readonly value: string | null\n}): boolean => {\n if (decision.status !== 'answered') return false\n if (decision.type && decision.type !== 'confirm') return true\n return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase())\n}\n","import type { PusharyConfig, RequestContext, EnrollResult } from './types'\nimport { createRequest } from './request'\nimport { createSubscribersResource, type SubscribersResource } from './resources/subscribers'\nimport { createCampaignsResource, type CampaignsResource } from './resources/campaigns'\nimport { createTemplatesResource, type TemplatesResource } from './resources/templates'\nimport { createNotificationsResource, type NotificationsResource } from './resources/notifications'\nimport { createFlowsResource, type FlowsResource } from './resources/flows'\nimport { createDecisionsResource, type DecisionsResource } from './resources/decisions'\n\nconst DEFAULT_BASE_URL = 'https://pushary.com/api/v1/server'\n\nexport interface PusharyServer {\n readonly subscribers: SubscribersResource\n readonly campaigns: CampaignsResource\n readonly templates: TemplatesResource\n readonly notifications: NotificationsResource\n readonly flows: FlowsResource\n readonly decisions: DecisionsResource\n // Connect one of your end-users' phones (keyless, no Pushary account for them).\n // Returns a single-use universalLink to show them; one tap turns on approvals.\n // Requires the Partner plan.\n readonly enroll: (externalId: string) => Promise<EnrollResult>\n}\n\nconst validateApiKey = (apiKey: string): void => {\n if (!apiKey) {\n throw new Error('API key is required. Get your API key from https://pushary.com/dashboard/settings')\n }\n \n if (!apiKey.includes('.')) {\n throw new Error('Invalid API key format. Use the full API key (pk_xxx.sk_xxx)')\n }\n}\n\nconst createContext = (config: PusharyConfig): RequestContext => {\n validateApiKey(config.apiKey)\n \n return Object.freeze({\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n headers: Object.freeze({\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }),\n })\n}\n\nexport const createPusharyServer = (config: PusharyConfig): PusharyServer => {\n const ctx = createContext(config)\n const request = createRequest(ctx)\n \n return Object.freeze({\n subscribers: createSubscribersResource(request),\n campaigns: createCampaignsResource(request),\n templates: createTemplatesResource(request),\n notifications: createNotificationsResource(request),\n flows: createFlowsResource(request),\n decisions: createDecisionsResource(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\n\n// Verify a Pushary webhook. Pass the RAW request body (not re-serialized JSON),\n// the X-Pushary-Signature header, and your webhook secret (decisions.getWebhookSecret()).\n// Constant-time comparison; returns false on any mismatch.\nexport const verifyWebhookSignature = (\n rawBody: string,\n signature: string | null | undefined,\n secret: string,\n): boolean => {\n if (!signature || !secret) return false\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(expected)\n const b = Buffer.from(signature)\n if (a.length !== b.length) return false\n return timingSafeEqual(a, b)\n}\n"],"mappings":";AAEA,IAAM,WAAW,CAAC,SAAiB,MAAc,WAA6C;AAI5F,QAAM,MAAM,IAAI,IAAI,GAAG,OAAO,GAAG,IAAI,EAAE;AAEvC,MAAI,QAAQ;AACV,WAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC/C,UAAI,UAAU,UAAa,UAAU,MAAM;AACzC,YAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,IAAI,SAAS;AACtB;AAEA,IAAM,iBAAiB,OAAU,aAAmC;AAClE,MAAI,CAAC,SAAS,IAAI;AAIhB,UAAM,OAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAIpD,UAAM,IAAI,MAAM,KAAK,WAAW,KAAK,SAAS,QAAQ,SAAS,MAAM,EAAE;AAAA,EACzE;AACA,SAAO,SAAS,KAAK;AACvB;AAEO,IAAM,gBAAgB,CAAC,QAC5B,OAAU,QAAgB,MAAc,SAA+B;AACrE,QAAM,QAAQ,WAAW;AACzB,QAAM,MAAM,SAAS,OACjB,SAAS,IAAI,SAAS,MAAM,IAA+B,IAC3D,GAAG,IAAI,OAAO,GAAG,IAAI;AAEzB,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC;AAAA,IACA,SAAS,IAAI;AAAA,IACb,MAAM,CAAC,SAAS,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,EAChD,CAAC;AAED,SAAO,eAAkB,QAAQ;AACnC;;;AC9BK,IAAM,4BAA4B,CAAC,YACxC,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,WACL,QAAuC,OAAO,gBAAgB,MAAM;AAAA,EAEtE,KAAK,CAAC,OACJ,QAAoB,OAAO,gBAAgB,EAAE,EAAE;AAAA,EAEjD,QAAQ,CAAC,IAAY,SACnB,QAAoB,SAAS,gBAAgB,EAAE,IAAI,IAAI;AAAA,EAEzD,QAAQ,CAAC,OACP,QAAc,UAAU,gBAAgB,EAAE,EAAE;AAAA,EAE9C,OAAO,MACL,QAAyB,OAAO,oBAAoB;AACxD,CAAC;;;ACXI,IAAM,0BAA0B,CAAC,YACtC,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,WACL,QAAqC,OAAO,cAAc,MAAM;AAAA,EAElE,QAAQ,CAAC,SACP,QAAkB,QAAQ,cAAc,IAAI;AAAA,EAE9C,KAAK,CAAC,OACJ,QAAkB,OAAO,cAAc,EAAE,EAAE;AAAA,EAE7C,QAAQ,CAAC,IAAY,SACnB,QAAkB,SAAS,cAAc,EAAE,IAAI,IAAI;AAAA,EAErD,QAAQ,CAAC,OACP,QAAc,UAAU,cAAc,EAAE,EAAE;AAAA,EAE5C,MAAM,CAAC,OACL,QAAkB,QAAQ,cAAc,EAAE,OAAO;AAAA,EAEnD,OAAO,CAAC,OACN,QAAkB,QAAQ,cAAc,EAAE,QAAQ;AAAA,EAEpD,QAAQ,CAAC,OACP,QAAkB,QAAQ,cAAc,EAAE,SAAS;AAAA,EAErD,OAAO,CAAC,OACN,QAAuB,OAAO,cAAc,EAAE,QAAQ;AAC1D,CAAC;;;ACjCI,IAAM,0BAA0B,CAAC,YACtC,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,WACL,QAAqC,OAAO,cAAc,MAAM;AAAA,EAElE,QAAQ,CAAC,SACP,QAAkB,QAAQ,cAAc,IAAI;AAAA,EAE9C,KAAK,CAAC,OACJ,QAAkB,OAAO,cAAc,EAAE,EAAE;AAAA,EAE7C,QAAQ,CAAC,IAAY,SACnB,QAAkB,SAAS,cAAc,EAAE,IAAI,IAAI;AAAA,EAErD,QAAQ,CAAC,OACP,QAAc,UAAU,cAAc,EAAE,EAAE;AAC9C,CAAC;;;AC3BI,IAAM,8BAA8B,CAAC,YAC1C,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,SACL,QAAoB,QAAQ,SAAS,IAAI;AAC7C,CAAC;;;ACSI,IAAM,sBAAsB,CAAC,YAClC,OAAO,OAAO;AAAA,EACZ,MAAM,CAAC,WACL,QAAiC,OAAO,UAAU,MAAM;AAAA,EAE1D,QAAQ,CAAC,SACP,QAAc,QAAQ,UAAU,IAAI;AAAA,EAEtC,KAAK,CAAC,OACJ,QAAc,OAAO,UAAU,EAAE,EAAE;AAAA,EAErC,QAAQ,CAAC,IAAY,SACnB,QAAc,SAAS,UAAU,EAAE,IAAI,IAAI;AAAA,EAE7C,QAAQ,CAAC,OACP,QAAc,UAAU,UAAU,EAAE,EAAE;AAAA,EAExC,UAAU,CAAC,OACT,QAAc,QAAQ,UAAU,EAAE,WAAW;AAAA,EAE/C,OAAO,CAAC,OACN,QAAc,QAAQ,UAAU,EAAE,QAAQ;AAC9C,CAAC;;;AC5BH,SAAS,kBAAkB;;;ACb3B,SAAS,kBAAkB;AAQpB,IAAM,mBAAmB,CAAC,UAC/B,WAAW,QAAQ,EAAE,OAAO,MAAM,KAAK,IAAM,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAE3E,IAAM,cAAmC,oBAAI,IAAI;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKM,IAAM,aAAa,CAAC,aAIZ;AACb,MAAI,SAAS,WAAW,WAAY,QAAO;AAC3C,MAAI,SAAS,QAAQ,SAAS,SAAS,UAAW,QAAO;AACzD,SAAO,SAAS,SAAS,QAAQ,YAAY,IAAI,SAAS,MAAM,KAAK,EAAE,YAAY,CAAC;AACtF;;;ADhBA,IAAM,sBAAsB;AAC5B,IAAM,yBAAyB;AA+BxB,IAAM,0BAA0B,CAAC,YAA0C;AAChF,QAAM,MAAM,OAAO,SAA0C;AAO3D,UAAM,iBAAiB,KAAK,kBAAkB,WAAW;AAEzD,UAAM,UAAU,MAAM,QAAwB,QAAQ,cAAc;AAAA,MAClE,UAAU,KAAK;AAAA,MACf,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,MACjB,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,SAAS,KAAK;AAAA,MACd,aAAa,KAAK;AAAA,MAClB,kBAAkB,KAAK;AAAA,MACvB,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB;AAAA,MACA,MAAM;AAAA,IACR,CAAC;AAED,UAAM,OAAqB,QAAQ,QAAQ,KAAK,QAAQ;AACxD,QAAI,SAAyB,QAAQ;AACrC,QAAI,WAAW,QAAQ;AACvB,QAAI,QAAuB,QAAQ,SAAS;AAE5C,UAAM,WAAW,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,aAAa,sBAAsB;AAClF,WAAO,WAAW,aAAa,KAAK,IAAI,IAAI,UAAU;AACpD,YAAM,cAAc,KAAK;AAAA,QACvB;AAAA,QACA,KAAK,IAAI,qBAAqB,KAAK,MAAM,WAAW,KAAK,IAAI,KAAK,GAAI,CAAC;AAAA,MACzE;AACA,YAAM,SAAS,MAAM,QAAkB,OAAO,cAAc,QAAQ,UAAU,IAAI;AAAA,QAChF,MAAM;AAAA,MACR,CAAC;AACD,eAAS,OAAO;AAChB,iBAAW,OAAO;AAClB,cAAQ,OAAO;AAAA,IACjB;AAEA,WAAO;AAAA,MACL,YAAY,QAAQ;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,WAAW,EAAE,QAAQ,MAAM,MAAM,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO,OAAO,OAAO;AAAA,IACnB,QAAQ,CAAC,SAAyB,QAAwB,QAAQ,cAAc,IAAI;AAAA,IACpF,KAAK,CAAC,IAAY,SAChB,QAAkB,OAAO,cAAc,EAAE,IAAI,MAAM,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,MAAS;AAAA,IAC3F;AAAA,IACA,QAAQ,CAAC,IAAY,WACnB,QAA8B,QAAQ,cAAc,EAAE,IAAI,EAAE,OAAO,CAAC;AAAA,IACtE,QAAQ,CAAC,OAAe,QAA8B,UAAU,cAAc,EAAE,EAAE;AAAA,IAClF,kBAAkB,MAAM,QAAuB,OAAO,iBAAiB;AAAA,IACvE,qBAAqB,MAAM,QAAuB,QAAQ,iBAAiB;AAAA,EAC7E,CAAC;AACH;;;AE3GA,IAAM,mBAAmB;AAezB,IAAM,iBAAiB,CAAC,WAAyB;AAC/C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,mFAAmF;AAAA,EACrG;AAEA,MAAI,CAAC,OAAO,SAAS,GAAG,GAAG;AACzB,UAAM,IAAI,MAAM,8DAA8D;AAAA,EAChF;AACF;AAEA,IAAM,gBAAgB,CAAC,WAA0C;AAC/D,iBAAe,OAAO,MAAM;AAE5B,SAAO,OAAO,OAAO;AAAA,IACnB,SAAS,OAAO,WAAW;AAAA,IAC3B,SAAS,OAAO,OAAO;AAAA,MACrB,gBAAgB;AAAA,MAChB,iBAAiB,UAAU,OAAO,MAAM;AAAA,IAC1C,CAAC;AAAA,EACH,CAAC;AACH;AAEO,IAAM,sBAAsB,CAAC,WAAyC;AAC3E,QAAM,MAAM,cAAc,MAAM;AAChC,QAAM,UAAU,cAAc,GAAG;AAEjC,SAAO,OAAO,OAAO;AAAA,IACnB,aAAa,0BAA0B,OAAO;AAAA,IAC9C,WAAW,wBAAwB,OAAO;AAAA,IAC1C,WAAW,wBAAwB,OAAO;AAAA,IAC1C,eAAe,4BAA4B,OAAO;AAAA,IAClD,OAAO,oBAAoB,OAAO;AAAA,IAClC,WAAW,wBAAwB,OAAO;AAAA,IAC1C,QAAQ,CAAC,eAAuB,QAAsB,QAAQ,WAAW,EAAE,WAAW,CAAC;AAAA,EACzF,CAAC;AACH;;;AC3DA,SAAS,YAAY,uBAAuB;AAKrC,IAAM,yBAAyB,CACpC,SACA,WACA,WACY;AACZ,MAAI,CAAC,aAAa,CAAC,OAAQ,QAAO;AAClC,QAAM,WAAW,WAAW,UAAU,MAAM,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC1E,QAAM,IAAI,OAAO,KAAK,QAAQ;AAC9B,QAAM,IAAI,OAAO,KAAK,SAAS;AAC/B,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,SAAO,gBAAgB,GAAG,CAAC;AAC7B;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/server",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Pushary Server SDK: send push notifications and add human-in-the-loop approvals to your AI agent (pause on a decision until a specific human answers).",
5
5
  "author": "Pushary <business@pushary.com>",
6
6
  "homepage": "https://pushary.com",