@pushary/server 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -3
- package/dist/index.cjs +91 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +86 -10
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -121,10 +121,34 @@ await pushary.notifications.send({
|
|
|
121
121
|
})
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
### Human-in-the-loop
|
|
124
|
+
### Human-in-the-loop for agents (the two-call contract)
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
The whole integration is two calls. Connect an end-user's phone once, then ask them
|
|
127
|
+
whenever your agent needs a human. Requires the [Partner plan](https://pushary.com/agent-notifications-integration).
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// 1. Connect an end-user's phone (keyless, no account for them). Show the link.
|
|
131
|
+
const { universalLink } = await pushary.enroll('user-123')
|
|
132
|
+
// Render universalLink as a button or QR. One tap turns on approvals.
|
|
133
|
+
|
|
134
|
+
// 2. Ask that person and block until they answer. Fail-closed `approved` flag.
|
|
135
|
+
const { approved, value, status } = await pushary.decisions.ask({
|
|
136
|
+
externalId: 'user-123',
|
|
137
|
+
question: 'Issue a $50 refund?',
|
|
138
|
+
type: 'confirm', // confirm | select | input
|
|
139
|
+
})
|
|
140
|
+
if (approved) await issueRefund()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`ask()` creates the decision, derives a collision-safe idempotency key, and polls
|
|
144
|
+
durably until the human answers or the deadline passes (default 55s, serverless-safe).
|
|
145
|
+
`approved` is true only when the person actually said yes, so a declined, expired, or
|
|
146
|
+
unanswered decision safely blocks the action.
|
|
147
|
+
|
|
148
|
+
### Decisions (lower-level)
|
|
149
|
+
|
|
150
|
+
For long waits or your own resume logic, use `create` + a webhook or `get`, and
|
|
151
|
+
`verifyWebhookSignature`. Full guide: [Embed human approval](https://pushary.com/docs/agents/embed).
|
|
128
152
|
|
|
129
153
|
```typescript
|
|
130
154
|
import { verifyWebhookSignature } from '@pushary/server'
|
package/dist/index.cjs
CHANGED
|
@@ -21,13 +21,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
createPusharyServer: () => createPusharyServer,
|
|
24
|
+
deterministicKey: () => deterministicKey,
|
|
25
|
+
isApproved: () => isApproved,
|
|
24
26
|
verifyWebhookSignature: () => verifyWebhookSignature
|
|
25
27
|
});
|
|
26
28
|
module.exports = __toCommonJS(src_exports);
|
|
27
29
|
|
|
28
30
|
// src/request.ts
|
|
29
31
|
var buildUrl = (baseUrl, path, params) => {
|
|
30
|
-
const url = new URL(path
|
|
32
|
+
const url = new URL(`${baseUrl}${path}`);
|
|
31
33
|
if (params) {
|
|
32
34
|
Object.entries(params).forEach(([key, value]) => {
|
|
33
35
|
if (value !== void 0 && value !== null) {
|
|
@@ -102,15 +104,88 @@ var createFlowsResource = (request) => Object.freeze({
|
|
|
102
104
|
pause: (id) => request("POST", `/flows/${id}/pause`)
|
|
103
105
|
});
|
|
104
106
|
|
|
107
|
+
// src/util.ts
|
|
108
|
+
var import_crypto = require("crypto");
|
|
109
|
+
var deterministicKey = (parts) => (0, import_crypto.createHash)("sha256").update(parts.join("\0")).digest("hex").slice(0, 40);
|
|
110
|
+
var AFFIRMATIVE = /* @__PURE__ */ new Set([
|
|
111
|
+
"yes",
|
|
112
|
+
"y",
|
|
113
|
+
"approve",
|
|
114
|
+
"approved",
|
|
115
|
+
"ok",
|
|
116
|
+
"okay",
|
|
117
|
+
"confirm",
|
|
118
|
+
"accept",
|
|
119
|
+
"true"
|
|
120
|
+
]);
|
|
121
|
+
var isApproved = (decision) => {
|
|
122
|
+
if (decision.status !== "answered") return false;
|
|
123
|
+
if (decision.type && decision.type !== "confirm") return true;
|
|
124
|
+
return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase());
|
|
125
|
+
};
|
|
126
|
+
|
|
105
127
|
// src/resources/decisions.ts
|
|
106
|
-
var
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
128
|
+
var POLL_WINDOW_SECONDS = 50;
|
|
129
|
+
var DEFAULT_ASK_TIMEOUT_MS = 55e3;
|
|
130
|
+
var createDecisionsResource = (request) => {
|
|
131
|
+
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
|
+
]);
|
|
138
|
+
const created = await request("POST", "/decisions", {
|
|
139
|
+
question: data.question,
|
|
140
|
+
type: data.type,
|
|
141
|
+
options: data.options,
|
|
142
|
+
externalId: data.externalId,
|
|
143
|
+
email: data.email,
|
|
144
|
+
agentName: data.agentName,
|
|
145
|
+
context: data.context,
|
|
146
|
+
placeholder: data.placeholder,
|
|
147
|
+
expiresInSeconds: data.expiresInSeconds,
|
|
148
|
+
poweredBy: data.poweredBy,
|
|
149
|
+
callbackUrl: data.callbackUrl,
|
|
150
|
+
idempotencyKey,
|
|
151
|
+
wait: false
|
|
152
|
+
});
|
|
153
|
+
const type = created.type ?? data.type ?? "confirm";
|
|
154
|
+
let status = created.status;
|
|
155
|
+
let answered = created.answered;
|
|
156
|
+
let value = created.value ?? null;
|
|
157
|
+
const deadline = Date.now() + Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS);
|
|
158
|
+
while (status === "pending" && Date.now() < deadline) {
|
|
159
|
+
const waitSeconds = Math.max(
|
|
160
|
+
1,
|
|
161
|
+
Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1e3))
|
|
162
|
+
);
|
|
163
|
+
const polled = await request("GET", `/decisions/${created.decisionId}`, {
|
|
164
|
+
wait: waitSeconds
|
|
165
|
+
});
|
|
166
|
+
status = polled.status;
|
|
167
|
+
answered = polled.answered;
|
|
168
|
+
value = polled.value;
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
decisionId: created.decisionId,
|
|
172
|
+
status,
|
|
173
|
+
answered,
|
|
174
|
+
value,
|
|
175
|
+
type,
|
|
176
|
+
approved: isApproved({ status, type, value })
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
return Object.freeze({
|
|
180
|
+
create: (data) => request("POST", "/decisions", data),
|
|
181
|
+
get: (id, opts) => request("GET", `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : void 0),
|
|
182
|
+
ask,
|
|
183
|
+
answer: (id, answer) => request("POST", `/decisions/${id}`, { answer }),
|
|
184
|
+
cancel: (id) => request("DELETE", `/decisions/${id}`),
|
|
185
|
+
getWebhookSecret: () => request("GET", "/webhook-secret"),
|
|
186
|
+
rotateWebhookSecret: () => request("POST", "/webhook-secret")
|
|
187
|
+
});
|
|
188
|
+
};
|
|
114
189
|
|
|
115
190
|
// src/client.ts
|
|
116
191
|
var DEFAULT_BASE_URL = "https://pushary.com/api/v1/server";
|
|
@@ -141,23 +216,26 @@ var createPusharyServer = (config) => {
|
|
|
141
216
|
templates: createTemplatesResource(request),
|
|
142
217
|
notifications: createNotificationsResource(request),
|
|
143
218
|
flows: createFlowsResource(request),
|
|
144
|
-
decisions: createDecisionsResource(request)
|
|
219
|
+
decisions: createDecisionsResource(request),
|
|
220
|
+
enroll: (externalId) => request("POST", "/enroll", { externalId })
|
|
145
221
|
});
|
|
146
222
|
};
|
|
147
223
|
|
|
148
224
|
// src/webhook.ts
|
|
149
|
-
var
|
|
225
|
+
var import_crypto2 = require("crypto");
|
|
150
226
|
var verifyWebhookSignature = (rawBody, signature, secret) => {
|
|
151
227
|
if (!signature || !secret) return false;
|
|
152
|
-
const expected = (0,
|
|
228
|
+
const expected = (0, import_crypto2.createHmac)("sha256", secret).update(rawBody).digest("hex");
|
|
153
229
|
const a = Buffer.from(expected);
|
|
154
230
|
const b = Buffer.from(signature);
|
|
155
231
|
if (a.length !== b.length) return false;
|
|
156
|
-
return (0,
|
|
232
|
+
return (0, import_crypto2.timingSafeEqual)(a, b);
|
|
157
233
|
};
|
|
158
234
|
// Annotate the CommonJS export names for ESM import in node:
|
|
159
235
|
0 && (module.exports = {
|
|
160
236
|
createPusharyServer,
|
|
237
|
+
deterministicKey,
|
|
238
|
+
isApproved,
|
|
161
239
|
verifyWebhookSignature
|
|
162
240
|
});
|
|
163
241
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -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/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} 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\n","import type { RequestContext, RequestFn } from './types'\n\nconst buildUrl = (baseUrl: string, path: string, params?: Record<string, unknown>): string => {\n const url = new URL(path, baseUrl)\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} from '../types'\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 // 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 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 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","import type { PusharyConfig, RequestContext } 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}\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 })\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/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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -256,6 +256,36 @@ interface CancelDecisionResult {
|
|
|
256
256
|
interface WebhookSecret {
|
|
257
257
|
readonly webhookSecret: string;
|
|
258
258
|
}
|
|
259
|
+
interface EnrollResult {
|
|
260
|
+
readonly externalId: string;
|
|
261
|
+
readonly token: string;
|
|
262
|
+
readonly deepLink: string;
|
|
263
|
+
readonly universalLink: string;
|
|
264
|
+
readonly expiresInSeconds: number;
|
|
265
|
+
}
|
|
266
|
+
interface AskDecision {
|
|
267
|
+
readonly question: string;
|
|
268
|
+
readonly type?: DecisionType;
|
|
269
|
+
readonly options?: readonly string[];
|
|
270
|
+
readonly externalId?: string;
|
|
271
|
+
readonly email?: string;
|
|
272
|
+
readonly agentName?: string;
|
|
273
|
+
readonly context?: string;
|
|
274
|
+
readonly placeholder?: string;
|
|
275
|
+
readonly expiresInSeconds?: number;
|
|
276
|
+
readonly idempotencyKey?: string;
|
|
277
|
+
readonly poweredBy?: boolean;
|
|
278
|
+
readonly callbackUrl?: string;
|
|
279
|
+
readonly timeoutMs?: number;
|
|
280
|
+
}
|
|
281
|
+
interface AskResult {
|
|
282
|
+
readonly decisionId: string;
|
|
283
|
+
readonly status: DecisionStatus;
|
|
284
|
+
readonly answered: boolean;
|
|
285
|
+
readonly value: string | null;
|
|
286
|
+
readonly type: DecisionType;
|
|
287
|
+
readonly approved: boolean;
|
|
288
|
+
}
|
|
259
289
|
|
|
260
290
|
interface SubscribersResource {
|
|
261
291
|
readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>;
|
|
@@ -304,6 +334,7 @@ interface DecisionsResource {
|
|
|
304
334
|
readonly get: (id: string, opts?: {
|
|
305
335
|
readonly wait?: number;
|
|
306
336
|
}) => Promise<Decision>;
|
|
337
|
+
readonly ask: (data: AskDecision) => Promise<AskResult>;
|
|
307
338
|
readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>;
|
|
308
339
|
readonly cancel: (id: string) => Promise<CancelDecisionResult>;
|
|
309
340
|
readonly getWebhookSecret: () => Promise<WebhookSecret>;
|
|
@@ -317,9 +348,17 @@ interface PusharyServer {
|
|
|
317
348
|
readonly notifications: NotificationsResource;
|
|
318
349
|
readonly flows: FlowsResource;
|
|
319
350
|
readonly decisions: DecisionsResource;
|
|
351
|
+
readonly enroll: (externalId: string) => Promise<EnrollResult>;
|
|
320
352
|
}
|
|
321
353
|
declare const createPusharyServer: (config: PusharyConfig) => PusharyServer;
|
|
322
354
|
|
|
323
355
|
declare const verifyWebhookSignature: (rawBody: string, signature: string | null | undefined, secret: string) => boolean;
|
|
324
356
|
|
|
325
|
-
|
|
357
|
+
declare const deterministicKey: (parts: readonly string[]) => string;
|
|
358
|
+
declare const isApproved: (decision: {
|
|
359
|
+
readonly status: DecisionStatus;
|
|
360
|
+
readonly type?: DecisionType;
|
|
361
|
+
readonly value: string | null;
|
|
362
|
+
}) => boolean;
|
|
363
|
+
|
|
364
|
+
export { type AskDecision, type AskResult, type Campaign, type CampaignStats, type CampaignsResource, type CancelDecisionResult, type CreateCampaign, type CreateDecision, type CreateFlow, type CreateTemplate, type Decision, type DecisionAnswerResult, type DecisionResult, type DecisionStatus, type DecisionType, type DecisionsResource, type EnrollResult, type Flow, type FlowListParams, type FlowStatus, type FlowStep, type FlowStepConfig, type FlowStepInput, type FlowStepType, type FlowTriggerType, type FlowsResource, type ListParams, type NotificationsResource, type PaginatedResponse, type PusharyConfig, type PusharyServer, type SendNotification, type SendResult, type Subscriber, type SubscriberCount, type SubscriberListParams, type SubscribersResource, type Template, type TemplatesResource, type UpdateCampaign, type UpdateFlow, type UpdateSubscriber, type UpdateTemplate, type WebhookSecret, createPusharyServer, deterministicKey, isApproved, verifyWebhookSignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -256,6 +256,36 @@ interface CancelDecisionResult {
|
|
|
256
256
|
interface WebhookSecret {
|
|
257
257
|
readonly webhookSecret: string;
|
|
258
258
|
}
|
|
259
|
+
interface EnrollResult {
|
|
260
|
+
readonly externalId: string;
|
|
261
|
+
readonly token: string;
|
|
262
|
+
readonly deepLink: string;
|
|
263
|
+
readonly universalLink: string;
|
|
264
|
+
readonly expiresInSeconds: number;
|
|
265
|
+
}
|
|
266
|
+
interface AskDecision {
|
|
267
|
+
readonly question: string;
|
|
268
|
+
readonly type?: DecisionType;
|
|
269
|
+
readonly options?: readonly string[];
|
|
270
|
+
readonly externalId?: string;
|
|
271
|
+
readonly email?: string;
|
|
272
|
+
readonly agentName?: string;
|
|
273
|
+
readonly context?: string;
|
|
274
|
+
readonly placeholder?: string;
|
|
275
|
+
readonly expiresInSeconds?: number;
|
|
276
|
+
readonly idempotencyKey?: string;
|
|
277
|
+
readonly poweredBy?: boolean;
|
|
278
|
+
readonly callbackUrl?: string;
|
|
279
|
+
readonly timeoutMs?: number;
|
|
280
|
+
}
|
|
281
|
+
interface AskResult {
|
|
282
|
+
readonly decisionId: string;
|
|
283
|
+
readonly status: DecisionStatus;
|
|
284
|
+
readonly answered: boolean;
|
|
285
|
+
readonly value: string | null;
|
|
286
|
+
readonly type: DecisionType;
|
|
287
|
+
readonly approved: boolean;
|
|
288
|
+
}
|
|
259
289
|
|
|
260
290
|
interface SubscribersResource {
|
|
261
291
|
readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>;
|
|
@@ -304,6 +334,7 @@ interface DecisionsResource {
|
|
|
304
334
|
readonly get: (id: string, opts?: {
|
|
305
335
|
readonly wait?: number;
|
|
306
336
|
}) => Promise<Decision>;
|
|
337
|
+
readonly ask: (data: AskDecision) => Promise<AskResult>;
|
|
307
338
|
readonly answer: (id: string, answer: string) => Promise<DecisionAnswerResult>;
|
|
308
339
|
readonly cancel: (id: string) => Promise<CancelDecisionResult>;
|
|
309
340
|
readonly getWebhookSecret: () => Promise<WebhookSecret>;
|
|
@@ -317,9 +348,17 @@ interface PusharyServer {
|
|
|
317
348
|
readonly notifications: NotificationsResource;
|
|
318
349
|
readonly flows: FlowsResource;
|
|
319
350
|
readonly decisions: DecisionsResource;
|
|
351
|
+
readonly enroll: (externalId: string) => Promise<EnrollResult>;
|
|
320
352
|
}
|
|
321
353
|
declare const createPusharyServer: (config: PusharyConfig) => PusharyServer;
|
|
322
354
|
|
|
323
355
|
declare const verifyWebhookSignature: (rawBody: string, signature: string | null | undefined, secret: string) => boolean;
|
|
324
356
|
|
|
325
|
-
|
|
357
|
+
declare const deterministicKey: (parts: readonly string[]) => string;
|
|
358
|
+
declare const isApproved: (decision: {
|
|
359
|
+
readonly status: DecisionStatus;
|
|
360
|
+
readonly type?: DecisionType;
|
|
361
|
+
readonly value: string | null;
|
|
362
|
+
}) => boolean;
|
|
363
|
+
|
|
364
|
+
export { type AskDecision, type AskResult, type Campaign, type CampaignStats, type CampaignsResource, type CancelDecisionResult, type CreateCampaign, type CreateDecision, type CreateFlow, type CreateTemplate, type Decision, type DecisionAnswerResult, type DecisionResult, type DecisionStatus, type DecisionType, type DecisionsResource, type EnrollResult, type Flow, type FlowListParams, type FlowStatus, type FlowStep, type FlowStepConfig, type FlowStepInput, type FlowStepType, type FlowTriggerType, type FlowsResource, type ListParams, type NotificationsResource, type PaginatedResponse, type PusharyConfig, type PusharyServer, type SendNotification, type SendResult, type Subscriber, type SubscriberCount, type SubscriberListParams, type SubscribersResource, type Template, type TemplatesResource, type UpdateCampaign, type UpdateFlow, type UpdateSubscriber, type UpdateTemplate, type WebhookSecret, createPusharyServer, deterministicKey, isApproved, verifyWebhookSignature };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/request.ts
|
|
2
2
|
var buildUrl = (baseUrl, path, params) => {
|
|
3
|
-
const url = new URL(path
|
|
3
|
+
const url = new URL(`${baseUrl}${path}`);
|
|
4
4
|
if (params) {
|
|
5
5
|
Object.entries(params).forEach(([key, value]) => {
|
|
6
6
|
if (value !== void 0 && value !== null) {
|
|
@@ -75,15 +75,88 @@ var createFlowsResource = (request) => Object.freeze({
|
|
|
75
75
|
pause: (id) => request("POST", `/flows/${id}/pause`)
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
// src/util.ts
|
|
79
|
+
import { createHash } from "crypto";
|
|
80
|
+
var deterministicKey = (parts) => createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 40);
|
|
81
|
+
var AFFIRMATIVE = /* @__PURE__ */ new Set([
|
|
82
|
+
"yes",
|
|
83
|
+
"y",
|
|
84
|
+
"approve",
|
|
85
|
+
"approved",
|
|
86
|
+
"ok",
|
|
87
|
+
"okay",
|
|
88
|
+
"confirm",
|
|
89
|
+
"accept",
|
|
90
|
+
"true"
|
|
91
|
+
]);
|
|
92
|
+
var isApproved = (decision) => {
|
|
93
|
+
if (decision.status !== "answered") return false;
|
|
94
|
+
if (decision.type && decision.type !== "confirm") return true;
|
|
95
|
+
return decision.value != null && AFFIRMATIVE.has(decision.value.trim().toLowerCase());
|
|
96
|
+
};
|
|
97
|
+
|
|
78
98
|
// src/resources/decisions.ts
|
|
79
|
-
var
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
var POLL_WINDOW_SECONDS = 50;
|
|
100
|
+
var DEFAULT_ASK_TIMEOUT_MS = 55e3;
|
|
101
|
+
var createDecisionsResource = (request) => {
|
|
102
|
+
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
|
+
]);
|
|
109
|
+
const created = await request("POST", "/decisions", {
|
|
110
|
+
question: data.question,
|
|
111
|
+
type: data.type,
|
|
112
|
+
options: data.options,
|
|
113
|
+
externalId: data.externalId,
|
|
114
|
+
email: data.email,
|
|
115
|
+
agentName: data.agentName,
|
|
116
|
+
context: data.context,
|
|
117
|
+
placeholder: data.placeholder,
|
|
118
|
+
expiresInSeconds: data.expiresInSeconds,
|
|
119
|
+
poweredBy: data.poweredBy,
|
|
120
|
+
callbackUrl: data.callbackUrl,
|
|
121
|
+
idempotencyKey,
|
|
122
|
+
wait: false
|
|
123
|
+
});
|
|
124
|
+
const type = created.type ?? data.type ?? "confirm";
|
|
125
|
+
let status = created.status;
|
|
126
|
+
let answered = created.answered;
|
|
127
|
+
let value = created.value ?? null;
|
|
128
|
+
const deadline = Date.now() + Math.max(0, data.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS);
|
|
129
|
+
while (status === "pending" && Date.now() < deadline) {
|
|
130
|
+
const waitSeconds = Math.max(
|
|
131
|
+
1,
|
|
132
|
+
Math.min(POLL_WINDOW_SECONDS, Math.ceil((deadline - Date.now()) / 1e3))
|
|
133
|
+
);
|
|
134
|
+
const polled = await request("GET", `/decisions/${created.decisionId}`, {
|
|
135
|
+
wait: waitSeconds
|
|
136
|
+
});
|
|
137
|
+
status = polled.status;
|
|
138
|
+
answered = polled.answered;
|
|
139
|
+
value = polled.value;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
decisionId: created.decisionId,
|
|
143
|
+
status,
|
|
144
|
+
answered,
|
|
145
|
+
value,
|
|
146
|
+
type,
|
|
147
|
+
approved: isApproved({ status, type, value })
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
return Object.freeze({
|
|
151
|
+
create: (data) => request("POST", "/decisions", data),
|
|
152
|
+
get: (id, opts) => request("GET", `/decisions/${id}`, opts?.wait ? { wait: opts.wait } : void 0),
|
|
153
|
+
ask,
|
|
154
|
+
answer: (id, answer) => request("POST", `/decisions/${id}`, { answer }),
|
|
155
|
+
cancel: (id) => request("DELETE", `/decisions/${id}`),
|
|
156
|
+
getWebhookSecret: () => request("GET", "/webhook-secret"),
|
|
157
|
+
rotateWebhookSecret: () => request("POST", "/webhook-secret")
|
|
158
|
+
});
|
|
159
|
+
};
|
|
87
160
|
|
|
88
161
|
// src/client.ts
|
|
89
162
|
var DEFAULT_BASE_URL = "https://pushary.com/api/v1/server";
|
|
@@ -114,7 +187,8 @@ var createPusharyServer = (config) => {
|
|
|
114
187
|
templates: createTemplatesResource(request),
|
|
115
188
|
notifications: createNotificationsResource(request),
|
|
116
189
|
flows: createFlowsResource(request),
|
|
117
|
-
decisions: createDecisionsResource(request)
|
|
190
|
+
decisions: createDecisionsResource(request),
|
|
191
|
+
enroll: (externalId) => request("POST", "/enroll", { externalId })
|
|
118
192
|
});
|
|
119
193
|
};
|
|
120
194
|
|
|
@@ -130,6 +204,8 @@ var verifyWebhookSignature = (rawBody, signature, secret) => {
|
|
|
130
204
|
};
|
|
131
205
|
export {
|
|
132
206
|
createPusharyServer,
|
|
207
|
+
deterministicKey,
|
|
208
|
+
isApproved,
|
|
133
209
|
verifyWebhookSignature
|
|
134
210
|
};
|
|
135
211
|
//# sourceMappingURL=index.js.map
|
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/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 const url = new URL(path, baseUrl)\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} from '../types'\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 // 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 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 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","import type { PusharyConfig, RequestContext } 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}\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 })\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/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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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",
|
|
@@ -34,12 +34,15 @@
|
|
|
34
34
|
"build": "tsup",
|
|
35
35
|
"dev": "tsup --watch",
|
|
36
36
|
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "vitest run",
|
|
37
38
|
"clean": "rm -rf dist",
|
|
38
39
|
"prepublishOnly": "npm run build && npm run typecheck"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
41
43
|
"tsup": "^8.0.0",
|
|
42
|
-
"typescript": "^5.0.0"
|
|
44
|
+
"typescript": "^5.0.0",
|
|
45
|
+
"vitest": "^2.1.0"
|
|
43
46
|
},
|
|
44
47
|
"publishConfig": {
|
|
45
48
|
"access": "public"
|