@pushary/server 1.3.1 → 1.3.3
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 +77 -1
- package/dist/index.cjs +71 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -3
- package/dist/index.d.ts +85 -3
- package/dist/index.js +68 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ The server SDK requires your full API key (`pk_xxx.sk_xxx`) which includes the s
|
|
|
36
36
|
|
|
37
37
|
**Never expose this in client-side code.**
|
|
38
38
|
|
|
39
|
-
Get your API key
|
|
39
|
+
Get your API key by following [Get your API key](https://pushary.com/docs/agents/api-key).
|
|
40
40
|
|
|
41
41
|
## Resources
|
|
42
42
|
|
|
@@ -174,6 +174,82 @@ const { webhookSecret } = await pushary.decisions.getWebhookSecret()
|
|
|
174
174
|
const ok = verifyWebhookSignature(rawBody, signatureHeader, webhookSecret)
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
+
### Helpers
|
|
178
|
+
|
|
179
|
+
Four exports that the flows above rely on. Signatures are given because guessing
|
|
180
|
+
them is easy to get wrong: `deterministicKey` takes an **array**, and `isApproved`
|
|
181
|
+
reads a decision's `status`/`value`, not an `answer` field.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import {
|
|
185
|
+
deterministicKey,
|
|
186
|
+
isApproved,
|
|
187
|
+
parseDecisionCallback,
|
|
188
|
+
SIGNATURE_HEADER,
|
|
189
|
+
} from '@pushary/server'
|
|
190
|
+
|
|
191
|
+
// deterministicKey(parts: readonly string[]): string
|
|
192
|
+
// A stable idempotency key from the parts that identify one logical step. Same
|
|
193
|
+
// parts in, same key out, so a retried run reuses its decision instead of asking
|
|
194
|
+
// the human twice.
|
|
195
|
+
const idempotencyKey = deterministicKey(['run-abc', 'step-3', 'user-123'])
|
|
196
|
+
|
|
197
|
+
// isApproved(decision: { status, type?, value }): boolean
|
|
198
|
+
// Fail-closed: true ONLY for an answered confirm whose value is yes. Pending,
|
|
199
|
+
// expired, declined and free-text answers all return false.
|
|
200
|
+
isApproved({ status: 'answered', type: 'confirm', value: 'yes' }) // true
|
|
201
|
+
isApproved({ status: 'answered', type: 'confirm', value: 'no' }) // false
|
|
202
|
+
isApproved({ status: 'pending', type: 'confirm', value: null }) // false
|
|
203
|
+
|
|
204
|
+
// SIGNATURE_HEADER: 'x-pushary-signature', the header carrying the signature.
|
|
205
|
+
// parseDecisionCallback(rawBody: string): DecisionCallback | null
|
|
206
|
+
// Takes the RAW body string, not a parsed object, and returns null if the
|
|
207
|
+
// payload is not a well-formed callback. Verify the signature first.
|
|
208
|
+
app.post('/webhooks/pushary', async (req, res) => {
|
|
209
|
+
const rawBody = req.rawBody.toString('utf8')
|
|
210
|
+
if (!verifyWebhookSignature(rawBody, req.header(SIGNATURE_HEADER), webhookSecret)) {
|
|
211
|
+
return res.sendStatus(401)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const callback = parseDecisionCallback(rawBody)
|
|
215
|
+
if (!callback) return res.sendStatus(400)
|
|
216
|
+
|
|
217
|
+
// A callback carries { correlationId, answer, value, answeredAt }. It has no
|
|
218
|
+
// `status` field, so it is not an isApproved() argument. Reaching this point
|
|
219
|
+
// already means the decision was answered, so judge the value directly, or
|
|
220
|
+
// re-read the decision if you want isApproved to do it for you.
|
|
221
|
+
if (callback.value === 'yes') await issueRefund()
|
|
222
|
+
|
|
223
|
+
res.sendStatus(200)
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Handling errors
|
|
228
|
+
|
|
229
|
+
Every non-2xx response throws a `PusharyApiError`. It extends `Error`, so existing
|
|
230
|
+
`catch (e) { e.message }` code is unaffected, and it carries the status so you can
|
|
231
|
+
tell "stop and re-authenticate" from "back off and retry" without matching on
|
|
232
|
+
message text.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { PusharyApiError } from '@pushary/server'
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await pushary.notifications.send({ title: 'Deploy finished' })
|
|
239
|
+
} catch (err) {
|
|
240
|
+
if (err instanceof PusharyApiError) {
|
|
241
|
+
if (err.isAuthError) throw err // 401/403: key is bad or revoked, do not retry
|
|
242
|
+
if (err.isRateLimited) return backOff() // 429
|
|
243
|
+
if (err.isServerError) return retry() // 5xx
|
|
244
|
+
console.error(err.status, err.code, err.message) // 4xx: your request, fix the call
|
|
245
|
+
}
|
|
246
|
+
throw err
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`status`, `statusText`, `code` and `body` are all readable; `code` and `body` are
|
|
251
|
+
present only when the API sent them.
|
|
252
|
+
|
|
177
253
|
## TypeScript
|
|
178
254
|
|
|
179
255
|
Full TypeScript support with exported types:
|
package/dist/index.cjs
CHANGED
|
@@ -20,13 +20,41 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
PusharyApiError: () => PusharyApiError,
|
|
24
|
+
SIGNATURE_HEADER: () => SIGNATURE_HEADER,
|
|
23
25
|
createPusharyServer: () => createPusharyServer,
|
|
24
26
|
deterministicKey: () => deterministicKey,
|
|
25
27
|
isApproved: () => isApproved,
|
|
28
|
+
parseDecisionCallback: () => parseDecisionCallback,
|
|
26
29
|
verifyWebhookSignature: () => verifyWebhookSignature
|
|
27
30
|
});
|
|
28
31
|
module.exports = __toCommonJS(src_exports);
|
|
29
32
|
|
|
33
|
+
// src/error.ts
|
|
34
|
+
var PusharyApiError = class _PusharyApiError extends Error {
|
|
35
|
+
constructor(message, init) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "PusharyApiError";
|
|
38
|
+
this.status = init.status;
|
|
39
|
+
this.statusText = init.statusText ?? "";
|
|
40
|
+
this.code = init.code;
|
|
41
|
+
this.body = init.body;
|
|
42
|
+
Object.setPrototypeOf(this, _PusharyApiError.prototype);
|
|
43
|
+
}
|
|
44
|
+
/** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */
|
|
45
|
+
get isAuthError() {
|
|
46
|
+
return this.status === 401 || this.status === 403;
|
|
47
|
+
}
|
|
48
|
+
/** Rate limited. Back off and retry. */
|
|
49
|
+
get isRateLimited() {
|
|
50
|
+
return this.status === 429;
|
|
51
|
+
}
|
|
52
|
+
/** Server-side fault. Safe to retry an idempotent call. */
|
|
53
|
+
get isServerError() {
|
|
54
|
+
return this.status >= 500;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
30
58
|
// src/request.ts
|
|
31
59
|
var buildUrl = (baseUrl, path, params) => {
|
|
32
60
|
const url = new URL(`${baseUrl}${path}`);
|
|
@@ -42,7 +70,12 @@ var buildUrl = (baseUrl, path, params) => {
|
|
|
42
70
|
var handleResponse = async (response) => {
|
|
43
71
|
if (!response.ok) {
|
|
44
72
|
const body = await response.json().catch(() => ({}));
|
|
45
|
-
throw new
|
|
73
|
+
throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {
|
|
74
|
+
status: response.status,
|
|
75
|
+
statusText: response.statusText,
|
|
76
|
+
code: body.code,
|
|
77
|
+
body
|
|
78
|
+
});
|
|
46
79
|
}
|
|
47
80
|
return response.json();
|
|
48
81
|
};
|
|
@@ -145,6 +178,7 @@ var createDecisionsResource = (request) => {
|
|
|
145
178
|
expiresInSeconds: data.expiresInSeconds,
|
|
146
179
|
poweredBy: data.poweredBy,
|
|
147
180
|
callbackUrl: data.callbackUrl,
|
|
181
|
+
requireReachable: data.requireReachable,
|
|
148
182
|
idempotencyKey,
|
|
149
183
|
wait: false
|
|
150
184
|
});
|
|
@@ -171,7 +205,12 @@ var createDecisionsResource = (request) => {
|
|
|
171
205
|
answered,
|
|
172
206
|
value,
|
|
173
207
|
type,
|
|
174
|
-
approved: isApproved({ status, type, value })
|
|
208
|
+
approved: isApproved({ status, type, value }),
|
|
209
|
+
// Surfaced from the create response so a caller can tell an unanswered
|
|
210
|
+
// decision that reached nobody (reachable:false) apart from a real decline.
|
|
211
|
+
reachable: created.reachable,
|
|
212
|
+
reachableChannels: created.reachableChannels,
|
|
213
|
+
deviceCount: created.deviceCount
|
|
175
214
|
};
|
|
176
215
|
};
|
|
177
216
|
return Object.freeze({
|
|
@@ -185,6 +224,13 @@ var createDecisionsResource = (request) => {
|
|
|
185
224
|
});
|
|
186
225
|
};
|
|
187
226
|
|
|
227
|
+
// src/resources/keys.ts
|
|
228
|
+
var createKeysResource = (request) => Object.freeze({
|
|
229
|
+
issue: (data) => request("POST", "/keys", data),
|
|
230
|
+
list: () => request("GET", "/keys").then((r) => r.keys),
|
|
231
|
+
revoke: (keyPrefix) => request("DELETE", `/keys/${encodeURIComponent(keyPrefix)}`)
|
|
232
|
+
});
|
|
233
|
+
|
|
188
234
|
// src/client.ts
|
|
189
235
|
var DEFAULT_BASE_URL = "https://pushary.com/api/v1/server";
|
|
190
236
|
var validateApiKey = (apiKey) => {
|
|
@@ -215,12 +261,14 @@ var createPusharyServer = (config) => {
|
|
|
215
261
|
notifications: createNotificationsResource(request),
|
|
216
262
|
flows: createFlowsResource(request),
|
|
217
263
|
decisions: createDecisionsResource(request),
|
|
264
|
+
keys: createKeysResource(request),
|
|
218
265
|
enroll: (externalId) => request("POST", "/enroll", { externalId })
|
|
219
266
|
});
|
|
220
267
|
};
|
|
221
268
|
|
|
222
269
|
// src/webhook.ts
|
|
223
270
|
var import_crypto3 = require("crypto");
|
|
271
|
+
var SIGNATURE_HEADER = "x-pushary-signature";
|
|
224
272
|
var verifyWebhookSignature = (rawBody, signature, secret) => {
|
|
225
273
|
if (!signature || !secret) return false;
|
|
226
274
|
const expected = (0, import_crypto3.createHmac)("sha256", secret).update(rawBody).digest("hex");
|
|
@@ -229,11 +277,32 @@ var verifyWebhookSignature = (rawBody, signature, secret) => {
|
|
|
229
277
|
if (a.length !== b.length) return false;
|
|
230
278
|
return (0, import_crypto3.timingSafeEqual)(a, b);
|
|
231
279
|
};
|
|
280
|
+
var parseDecisionCallback = (rawBody) => {
|
|
281
|
+
let parsed;
|
|
282
|
+
try {
|
|
283
|
+
parsed = JSON.parse(rawBody);
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
288
|
+
const p = parsed;
|
|
289
|
+
if (typeof p.correlationId !== "string" || typeof p.answer !== "string") return null;
|
|
290
|
+
return {
|
|
291
|
+
correlationId: p.correlationId,
|
|
292
|
+
answer: p.answer,
|
|
293
|
+
value: typeof p.value === "string" ? p.value : p.answer,
|
|
294
|
+
answeredAt: typeof p.answeredAt === "string" ? p.answeredAt : "",
|
|
295
|
+
...typeof p.context === "string" ? { context: p.context } : {}
|
|
296
|
+
};
|
|
297
|
+
};
|
|
232
298
|
// Annotate the CommonJS export names for ESM import in node:
|
|
233
299
|
0 && (module.exports = {
|
|
300
|
+
PusharyApiError,
|
|
301
|
+
SIGNATURE_HEADER,
|
|
234
302
|
createPusharyServer,
|
|
235
303
|
deterministicKey,
|
|
236
304
|
isApproved,
|
|
305
|
+
parseDecisionCallback,
|
|
237
306
|
verifyWebhookSignature
|
|
238
307
|
});
|
|
239
308
|
//# 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/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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/error.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/resources/keys.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["export { createPusharyServer, type PusharyServer } from './client'\n\nexport { PusharyApiError } from './error'\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 DecisionReachability,\n DecisionResult,\n Decision,\n DecisionAnswerResult,\n CancelDecisionResult,\n DecisionCallback,\n WebhookSecret,\n EnrollResult,\n AskDecision,\n AskResult,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\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'\nexport type { KeysResource } from './resources/keys'\n\nexport { verifyWebhookSignature, parseDecisionCallback, SIGNATURE_HEADER } from './webhook'\n\nexport { deterministicKey, isApproved } from './util'\n\n","/**\n * Thrown for any non-2xx response from the API.\n *\n * Extends Error rather than replacing it, so every existing `catch (e)` that\n * reads `e.message` keeps working unchanged. What it adds is the thing callers\n * previously had to string-match the message for: an HTTP status they can branch\n * on. Telling a revoked key (401) apart from a plan limit (403), a bad argument\n * (400) and an outage (5xx) is the difference between \"stop and re-authenticate\"\n * and \"retry in a minute\", and message text is not a stable API to decide that on.\n */\nexport class PusharyApiError extends Error {\n /** HTTP status of the response that failed. */\n readonly status: number\n /** HTTP status text, when the runtime provides one. */\n readonly statusText: string\n /** The API's own machine-readable error code, when it sent one. */\n readonly code?: string\n /** The parsed response body, for anything the fields above do not cover. */\n readonly body?: unknown\n\n constructor(\n message: string,\n init: { status: number; statusText?: string; code?: string; body?: unknown }\n ) {\n super(message)\n this.name = 'PusharyApiError'\n this.status = init.status\n this.statusText = init.statusText ?? ''\n this.code = init.code\n this.body = init.body\n // Restores the prototype chain so `err instanceof PusharyApiError` holds\n // even when this package is consumed as ES5-targeted CommonJS.\n Object.setPrototypeOf(this, PusharyApiError.prototype)\n }\n\n /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */\n get isAuthError(): boolean {\n return this.status === 401 || this.status === 403\n }\n\n /** Rate limited. Back off and retry. */\n get isRateLimited(): boolean {\n return this.status === 429\n }\n\n /** Server-side fault. Safe to retry an idempotent call. */\n get isServerError(): boolean {\n return this.status >= 500\n }\n}\n","import type { RequestContext, RequestFn } from './types'\nimport { PusharyApiError } from './error'\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 code?: string\n }\n // PusharyApiError extends Error, so an existing `catch (e) { e.message }`\n // is unaffected. It carries the status too, because the reason to branch on\n // a failure (re-authenticate vs back off vs retry) was previously only\n // recoverable by string-matching this message, which is not a stable API.\n throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {\n status: response.status,\n statusText: response.statusText,\n code: body.code,\n body,\n })\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 requireReachable: data.requireReachable,\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 // Surfaced from the create response so a caller can tell an unanswered\n // decision that reached nobody (reachable:false) apart from a real decline.\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\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 {\n RequestFn,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from '../types'\n\n// Per-end-user (bound) key issuance for multi-tenant Partners. Call issue() from\n// your backend with your own (unbound, Partner-plan) key to mint a child key bound\n// to one end-user, hand that key to the agent runtime acting for that user, then\n// revoke() it when the session ends. A bound key can only create/resolve decisions\n// and enroll that exact end-user, so a prompt-injected agent can never reach\n// another of your users. Requires the Partner plan.\nexport interface KeysResource {\n // Mint a key bound to externalId. The full key is returned once in `apiKey` and\n // cannot be retrieved again — store it as the session credential.\n readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>\n // List this site's bound keys (never the secret) for audit/rotation.\n readonly list: () => Promise<readonly BoundKeySummary[]>\n // Revoke (deactivate) a bound key by its prefix. Returns revoked:false if the\n // prefix is unknown or already inactive.\n readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>\n}\n\nexport const createKeysResource = (request: RequestFn): KeysResource =>\n Object.freeze({\n issue: (data: IssueBoundKey) => request<BoundKeyResult>('POST', '/keys', data),\n list: () =>\n request<{ keys: readonly BoundKeySummary[] }>('GET', '/keys').then((r) => r.keys),\n revoke: (keyPrefix: string) =>\n request<RevokeKeyResult>('DELETE', `/keys/${encodeURIComponent(keyPrefix)}`),\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'\nimport { createKeysResource, type KeysResource } from './resources/keys'\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 // Mint/rotate per-end-user bound keys for multi-tenant Partner integrations.\n readonly keys: KeysResource\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 keys: createKeysResource(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\nimport type { DecisionCallback } from './types'\n\n// The header carrying the HMAC-SHA256 signature of the raw callback body.\nexport const SIGNATURE_HEADER = 'x-pushary-signature'\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\n// Parse a verified decision callback body into a typed object. Returns null if the\n// body is not a decision callback (missing correlationId/answer). Verify the\n// signature FIRST — this does no verification. Read `answer` (canonical) keyed by\n// `correlationId`; `context` echoes what you passed at create time.\nexport const parseDecisionCallback = (rawBody: string): DecisionCallback | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (typeof p.correlationId !== 'string' || typeof p.answer !== 'string') return null\n return {\n correlationId: p.correlationId,\n answer: p.answer,\n value: typeof p.value === 'string' ? p.value : p.answer,\n answeredAt: typeof p.answeredAt === 'string' ? p.answeredAt : '',\n ...(typeof p.context === 'string' ? { context: p.context } : {}),\n }\n}\n"],"mappings":"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","names":["import_crypto","import_crypto"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -215,8 +215,14 @@ interface CreateDecision {
|
|
|
215
215
|
readonly timeoutSeconds?: number;
|
|
216
216
|
readonly idempotencyKey?: string;
|
|
217
217
|
readonly poweredBy?: boolean;
|
|
218
|
+
readonly requireReachable?: boolean;
|
|
218
219
|
}
|
|
219
|
-
interface
|
|
220
|
+
interface DecisionReachability {
|
|
221
|
+
readonly reachable?: boolean;
|
|
222
|
+
readonly reachableChannels?: number;
|
|
223
|
+
readonly deviceCount?: number;
|
|
224
|
+
}
|
|
225
|
+
interface DecisionResult extends DecisionReachability {
|
|
220
226
|
readonly decisionId: string;
|
|
221
227
|
readonly status: DecisionStatus;
|
|
222
228
|
readonly answered: boolean;
|
|
@@ -238,6 +244,7 @@ interface Decision {
|
|
|
238
244
|
readonly question: string;
|
|
239
245
|
readonly options: readonly string[] | null;
|
|
240
246
|
readonly externalId: string | null;
|
|
247
|
+
readonly context: string | null;
|
|
241
248
|
readonly createdAt: string;
|
|
242
249
|
readonly answeredAt: string | null;
|
|
243
250
|
readonly expiresAt: string | null;
|
|
@@ -256,6 +263,13 @@ interface CancelDecisionResult {
|
|
|
256
263
|
interface WebhookSecret {
|
|
257
264
|
readonly webhookSecret: string;
|
|
258
265
|
}
|
|
266
|
+
interface DecisionCallback {
|
|
267
|
+
readonly correlationId: string;
|
|
268
|
+
readonly answer: string;
|
|
269
|
+
readonly value: string;
|
|
270
|
+
readonly answeredAt: string;
|
|
271
|
+
readonly context?: string;
|
|
272
|
+
}
|
|
259
273
|
interface EnrollResult {
|
|
260
274
|
readonly externalId: string;
|
|
261
275
|
readonly token: string;
|
|
@@ -276,9 +290,10 @@ interface AskDecision {
|
|
|
276
290
|
readonly idempotencyKey?: string;
|
|
277
291
|
readonly poweredBy?: boolean;
|
|
278
292
|
readonly callbackUrl?: string;
|
|
293
|
+
readonly requireReachable?: boolean;
|
|
279
294
|
readonly timeoutMs?: number;
|
|
280
295
|
}
|
|
281
|
-
interface AskResult {
|
|
296
|
+
interface AskResult extends DecisionReachability {
|
|
282
297
|
readonly decisionId: string;
|
|
283
298
|
readonly status: DecisionStatus;
|
|
284
299
|
readonly answered: boolean;
|
|
@@ -286,6 +301,31 @@ interface AskResult {
|
|
|
286
301
|
readonly type: DecisionType;
|
|
287
302
|
readonly approved: boolean;
|
|
288
303
|
}
|
|
304
|
+
interface IssueBoundKey {
|
|
305
|
+
readonly externalId: string;
|
|
306
|
+
readonly name?: string;
|
|
307
|
+
readonly expiresInSeconds?: number;
|
|
308
|
+
}
|
|
309
|
+
interface BoundKeyResult {
|
|
310
|
+
readonly apiKey: string;
|
|
311
|
+
readonly keyPrefix: string;
|
|
312
|
+
readonly scope: 'bound';
|
|
313
|
+
readonly boundExternalId: string;
|
|
314
|
+
readonly expiresAt: string | null;
|
|
315
|
+
}
|
|
316
|
+
interface BoundKeySummary {
|
|
317
|
+
readonly keyPrefix: string;
|
|
318
|
+
readonly name: string;
|
|
319
|
+
readonly boundExternalId: string | null;
|
|
320
|
+
readonly isActive: boolean;
|
|
321
|
+
readonly lastUsedAt: string | null;
|
|
322
|
+
readonly expiresAt: string | null;
|
|
323
|
+
readonly createdAt: string;
|
|
324
|
+
}
|
|
325
|
+
interface RevokeKeyResult {
|
|
326
|
+
readonly keyPrefix: string;
|
|
327
|
+
readonly revoked: boolean;
|
|
328
|
+
}
|
|
289
329
|
|
|
290
330
|
interface SubscribersResource {
|
|
291
331
|
readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>;
|
|
@@ -341,6 +381,12 @@ interface DecisionsResource {
|
|
|
341
381
|
readonly rotateWebhookSecret: () => Promise<WebhookSecret>;
|
|
342
382
|
}
|
|
343
383
|
|
|
384
|
+
interface KeysResource {
|
|
385
|
+
readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>;
|
|
386
|
+
readonly list: () => Promise<readonly BoundKeySummary[]>;
|
|
387
|
+
readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>;
|
|
388
|
+
}
|
|
389
|
+
|
|
344
390
|
interface PusharyServer {
|
|
345
391
|
readonly subscribers: SubscribersResource;
|
|
346
392
|
readonly campaigns: CampaignsResource;
|
|
@@ -348,11 +394,47 @@ interface PusharyServer {
|
|
|
348
394
|
readonly notifications: NotificationsResource;
|
|
349
395
|
readonly flows: FlowsResource;
|
|
350
396
|
readonly decisions: DecisionsResource;
|
|
397
|
+
readonly keys: KeysResource;
|
|
351
398
|
readonly enroll: (externalId: string) => Promise<EnrollResult>;
|
|
352
399
|
}
|
|
353
400
|
declare const createPusharyServer: (config: PusharyConfig) => PusharyServer;
|
|
354
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Thrown for any non-2xx response from the API.
|
|
404
|
+
*
|
|
405
|
+
* Extends Error rather than replacing it, so every existing `catch (e)` that
|
|
406
|
+
* reads `e.message` keeps working unchanged. What it adds is the thing callers
|
|
407
|
+
* previously had to string-match the message for: an HTTP status they can branch
|
|
408
|
+
* on. Telling a revoked key (401) apart from a plan limit (403), a bad argument
|
|
409
|
+
* (400) and an outage (5xx) is the difference between "stop and re-authenticate"
|
|
410
|
+
* and "retry in a minute", and message text is not a stable API to decide that on.
|
|
411
|
+
*/
|
|
412
|
+
declare class PusharyApiError extends Error {
|
|
413
|
+
/** HTTP status of the response that failed. */
|
|
414
|
+
readonly status: number;
|
|
415
|
+
/** HTTP status text, when the runtime provides one. */
|
|
416
|
+
readonly statusText: string;
|
|
417
|
+
/** The API's own machine-readable error code, when it sent one. */
|
|
418
|
+
readonly code?: string;
|
|
419
|
+
/** The parsed response body, for anything the fields above do not cover. */
|
|
420
|
+
readonly body?: unknown;
|
|
421
|
+
constructor(message: string, init: {
|
|
422
|
+
status: number;
|
|
423
|
+
statusText?: string;
|
|
424
|
+
code?: string;
|
|
425
|
+
body?: unknown;
|
|
426
|
+
});
|
|
427
|
+
/** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */
|
|
428
|
+
get isAuthError(): boolean;
|
|
429
|
+
/** Rate limited. Back off and retry. */
|
|
430
|
+
get isRateLimited(): boolean;
|
|
431
|
+
/** Server-side fault. Safe to retry an idempotent call. */
|
|
432
|
+
get isServerError(): boolean;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
declare const SIGNATURE_HEADER = "x-pushary-signature";
|
|
355
436
|
declare const verifyWebhookSignature: (rawBody: string, signature: string | null | undefined, secret: string) => boolean;
|
|
437
|
+
declare const parseDecisionCallback: (rawBody: string) => DecisionCallback | null;
|
|
356
438
|
|
|
357
439
|
declare const deterministicKey: (parts: readonly string[]) => string;
|
|
358
440
|
declare const isApproved: (decision: {
|
|
@@ -361,4 +443,4 @@ declare const isApproved: (decision: {
|
|
|
361
443
|
readonly value: string | null;
|
|
362
444
|
}) => boolean;
|
|
363
445
|
|
|
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 };
|
|
446
|
+
export { type AskDecision, type AskResult, type BoundKeyResult, type BoundKeySummary, type Campaign, type CampaignStats, type CampaignsResource, type CancelDecisionResult, type CreateCampaign, type CreateDecision, type CreateFlow, type CreateTemplate, type Decision, type DecisionAnswerResult, type DecisionCallback, type DecisionReachability, 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 IssueBoundKey, type KeysResource, type ListParams, type NotificationsResource, type PaginatedResponse, PusharyApiError, type PusharyConfig, type PusharyServer, type RevokeKeyResult, SIGNATURE_HEADER, 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, parseDecisionCallback, verifyWebhookSignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -215,8 +215,14 @@ interface CreateDecision {
|
|
|
215
215
|
readonly timeoutSeconds?: number;
|
|
216
216
|
readonly idempotencyKey?: string;
|
|
217
217
|
readonly poweredBy?: boolean;
|
|
218
|
+
readonly requireReachable?: boolean;
|
|
218
219
|
}
|
|
219
|
-
interface
|
|
220
|
+
interface DecisionReachability {
|
|
221
|
+
readonly reachable?: boolean;
|
|
222
|
+
readonly reachableChannels?: number;
|
|
223
|
+
readonly deviceCount?: number;
|
|
224
|
+
}
|
|
225
|
+
interface DecisionResult extends DecisionReachability {
|
|
220
226
|
readonly decisionId: string;
|
|
221
227
|
readonly status: DecisionStatus;
|
|
222
228
|
readonly answered: boolean;
|
|
@@ -238,6 +244,7 @@ interface Decision {
|
|
|
238
244
|
readonly question: string;
|
|
239
245
|
readonly options: readonly string[] | null;
|
|
240
246
|
readonly externalId: string | null;
|
|
247
|
+
readonly context: string | null;
|
|
241
248
|
readonly createdAt: string;
|
|
242
249
|
readonly answeredAt: string | null;
|
|
243
250
|
readonly expiresAt: string | null;
|
|
@@ -256,6 +263,13 @@ interface CancelDecisionResult {
|
|
|
256
263
|
interface WebhookSecret {
|
|
257
264
|
readonly webhookSecret: string;
|
|
258
265
|
}
|
|
266
|
+
interface DecisionCallback {
|
|
267
|
+
readonly correlationId: string;
|
|
268
|
+
readonly answer: string;
|
|
269
|
+
readonly value: string;
|
|
270
|
+
readonly answeredAt: string;
|
|
271
|
+
readonly context?: string;
|
|
272
|
+
}
|
|
259
273
|
interface EnrollResult {
|
|
260
274
|
readonly externalId: string;
|
|
261
275
|
readonly token: string;
|
|
@@ -276,9 +290,10 @@ interface AskDecision {
|
|
|
276
290
|
readonly idempotencyKey?: string;
|
|
277
291
|
readonly poweredBy?: boolean;
|
|
278
292
|
readonly callbackUrl?: string;
|
|
293
|
+
readonly requireReachable?: boolean;
|
|
279
294
|
readonly timeoutMs?: number;
|
|
280
295
|
}
|
|
281
|
-
interface AskResult {
|
|
296
|
+
interface AskResult extends DecisionReachability {
|
|
282
297
|
readonly decisionId: string;
|
|
283
298
|
readonly status: DecisionStatus;
|
|
284
299
|
readonly answered: boolean;
|
|
@@ -286,6 +301,31 @@ interface AskResult {
|
|
|
286
301
|
readonly type: DecisionType;
|
|
287
302
|
readonly approved: boolean;
|
|
288
303
|
}
|
|
304
|
+
interface IssueBoundKey {
|
|
305
|
+
readonly externalId: string;
|
|
306
|
+
readonly name?: string;
|
|
307
|
+
readonly expiresInSeconds?: number;
|
|
308
|
+
}
|
|
309
|
+
interface BoundKeyResult {
|
|
310
|
+
readonly apiKey: string;
|
|
311
|
+
readonly keyPrefix: string;
|
|
312
|
+
readonly scope: 'bound';
|
|
313
|
+
readonly boundExternalId: string;
|
|
314
|
+
readonly expiresAt: string | null;
|
|
315
|
+
}
|
|
316
|
+
interface BoundKeySummary {
|
|
317
|
+
readonly keyPrefix: string;
|
|
318
|
+
readonly name: string;
|
|
319
|
+
readonly boundExternalId: string | null;
|
|
320
|
+
readonly isActive: boolean;
|
|
321
|
+
readonly lastUsedAt: string | null;
|
|
322
|
+
readonly expiresAt: string | null;
|
|
323
|
+
readonly createdAt: string;
|
|
324
|
+
}
|
|
325
|
+
interface RevokeKeyResult {
|
|
326
|
+
readonly keyPrefix: string;
|
|
327
|
+
readonly revoked: boolean;
|
|
328
|
+
}
|
|
289
329
|
|
|
290
330
|
interface SubscribersResource {
|
|
291
331
|
readonly list: (params?: SubscriberListParams) => Promise<PaginatedResponse<Subscriber>>;
|
|
@@ -341,6 +381,12 @@ interface DecisionsResource {
|
|
|
341
381
|
readonly rotateWebhookSecret: () => Promise<WebhookSecret>;
|
|
342
382
|
}
|
|
343
383
|
|
|
384
|
+
interface KeysResource {
|
|
385
|
+
readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>;
|
|
386
|
+
readonly list: () => Promise<readonly BoundKeySummary[]>;
|
|
387
|
+
readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>;
|
|
388
|
+
}
|
|
389
|
+
|
|
344
390
|
interface PusharyServer {
|
|
345
391
|
readonly subscribers: SubscribersResource;
|
|
346
392
|
readonly campaigns: CampaignsResource;
|
|
@@ -348,11 +394,47 @@ interface PusharyServer {
|
|
|
348
394
|
readonly notifications: NotificationsResource;
|
|
349
395
|
readonly flows: FlowsResource;
|
|
350
396
|
readonly decisions: DecisionsResource;
|
|
397
|
+
readonly keys: KeysResource;
|
|
351
398
|
readonly enroll: (externalId: string) => Promise<EnrollResult>;
|
|
352
399
|
}
|
|
353
400
|
declare const createPusharyServer: (config: PusharyConfig) => PusharyServer;
|
|
354
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Thrown for any non-2xx response from the API.
|
|
404
|
+
*
|
|
405
|
+
* Extends Error rather than replacing it, so every existing `catch (e)` that
|
|
406
|
+
* reads `e.message` keeps working unchanged. What it adds is the thing callers
|
|
407
|
+
* previously had to string-match the message for: an HTTP status they can branch
|
|
408
|
+
* on. Telling a revoked key (401) apart from a plan limit (403), a bad argument
|
|
409
|
+
* (400) and an outage (5xx) is the difference between "stop and re-authenticate"
|
|
410
|
+
* and "retry in a minute", and message text is not a stable API to decide that on.
|
|
411
|
+
*/
|
|
412
|
+
declare class PusharyApiError extends Error {
|
|
413
|
+
/** HTTP status of the response that failed. */
|
|
414
|
+
readonly status: number;
|
|
415
|
+
/** HTTP status text, when the runtime provides one. */
|
|
416
|
+
readonly statusText: string;
|
|
417
|
+
/** The API's own machine-readable error code, when it sent one. */
|
|
418
|
+
readonly code?: string;
|
|
419
|
+
/** The parsed response body, for anything the fields above do not cover. */
|
|
420
|
+
readonly body?: unknown;
|
|
421
|
+
constructor(message: string, init: {
|
|
422
|
+
status: number;
|
|
423
|
+
statusText?: string;
|
|
424
|
+
code?: string;
|
|
425
|
+
body?: unknown;
|
|
426
|
+
});
|
|
427
|
+
/** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */
|
|
428
|
+
get isAuthError(): boolean;
|
|
429
|
+
/** Rate limited. Back off and retry. */
|
|
430
|
+
get isRateLimited(): boolean;
|
|
431
|
+
/** Server-side fault. Safe to retry an idempotent call. */
|
|
432
|
+
get isServerError(): boolean;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
declare const SIGNATURE_HEADER = "x-pushary-signature";
|
|
355
436
|
declare const verifyWebhookSignature: (rawBody: string, signature: string | null | undefined, secret: string) => boolean;
|
|
437
|
+
declare const parseDecisionCallback: (rawBody: string) => DecisionCallback | null;
|
|
356
438
|
|
|
357
439
|
declare const deterministicKey: (parts: readonly string[]) => string;
|
|
358
440
|
declare const isApproved: (decision: {
|
|
@@ -361,4 +443,4 @@ declare const isApproved: (decision: {
|
|
|
361
443
|
readonly value: string | null;
|
|
362
444
|
}) => boolean;
|
|
363
445
|
|
|
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 };
|
|
446
|
+
export { type AskDecision, type AskResult, type BoundKeyResult, type BoundKeySummary, type Campaign, type CampaignStats, type CampaignsResource, type CancelDecisionResult, type CreateCampaign, type CreateDecision, type CreateFlow, type CreateTemplate, type Decision, type DecisionAnswerResult, type DecisionCallback, type DecisionReachability, 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 IssueBoundKey, type KeysResource, type ListParams, type NotificationsResource, type PaginatedResponse, PusharyApiError, type PusharyConfig, type PusharyServer, type RevokeKeyResult, SIGNATURE_HEADER, 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, parseDecisionCallback, verifyWebhookSignature };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
// src/error.ts
|
|
2
|
+
var PusharyApiError = class _PusharyApiError extends Error {
|
|
3
|
+
constructor(message, init) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "PusharyApiError";
|
|
6
|
+
this.status = init.status;
|
|
7
|
+
this.statusText = init.statusText ?? "";
|
|
8
|
+
this.code = init.code;
|
|
9
|
+
this.body = init.body;
|
|
10
|
+
Object.setPrototypeOf(this, _PusharyApiError.prototype);
|
|
11
|
+
}
|
|
12
|
+
/** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */
|
|
13
|
+
get isAuthError() {
|
|
14
|
+
return this.status === 401 || this.status === 403;
|
|
15
|
+
}
|
|
16
|
+
/** Rate limited. Back off and retry. */
|
|
17
|
+
get isRateLimited() {
|
|
18
|
+
return this.status === 429;
|
|
19
|
+
}
|
|
20
|
+
/** Server-side fault. Safe to retry an idempotent call. */
|
|
21
|
+
get isServerError() {
|
|
22
|
+
return this.status >= 500;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
1
26
|
// src/request.ts
|
|
2
27
|
var buildUrl = (baseUrl, path, params) => {
|
|
3
28
|
const url = new URL(`${baseUrl}${path}`);
|
|
@@ -13,7 +38,12 @@ var buildUrl = (baseUrl, path, params) => {
|
|
|
13
38
|
var handleResponse = async (response) => {
|
|
14
39
|
if (!response.ok) {
|
|
15
40
|
const body = await response.json().catch(() => ({}));
|
|
16
|
-
throw new
|
|
41
|
+
throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {
|
|
42
|
+
status: response.status,
|
|
43
|
+
statusText: response.statusText,
|
|
44
|
+
code: body.code,
|
|
45
|
+
body
|
|
46
|
+
});
|
|
17
47
|
}
|
|
18
48
|
return response.json();
|
|
19
49
|
};
|
|
@@ -116,6 +146,7 @@ var createDecisionsResource = (request) => {
|
|
|
116
146
|
expiresInSeconds: data.expiresInSeconds,
|
|
117
147
|
poweredBy: data.poweredBy,
|
|
118
148
|
callbackUrl: data.callbackUrl,
|
|
149
|
+
requireReachable: data.requireReachable,
|
|
119
150
|
idempotencyKey,
|
|
120
151
|
wait: false
|
|
121
152
|
});
|
|
@@ -142,7 +173,12 @@ var createDecisionsResource = (request) => {
|
|
|
142
173
|
answered,
|
|
143
174
|
value,
|
|
144
175
|
type,
|
|
145
|
-
approved: isApproved({ status, type, value })
|
|
176
|
+
approved: isApproved({ status, type, value }),
|
|
177
|
+
// Surfaced from the create response so a caller can tell an unanswered
|
|
178
|
+
// decision that reached nobody (reachable:false) apart from a real decline.
|
|
179
|
+
reachable: created.reachable,
|
|
180
|
+
reachableChannels: created.reachableChannels,
|
|
181
|
+
deviceCount: created.deviceCount
|
|
146
182
|
};
|
|
147
183
|
};
|
|
148
184
|
return Object.freeze({
|
|
@@ -156,6 +192,13 @@ var createDecisionsResource = (request) => {
|
|
|
156
192
|
});
|
|
157
193
|
};
|
|
158
194
|
|
|
195
|
+
// src/resources/keys.ts
|
|
196
|
+
var createKeysResource = (request) => Object.freeze({
|
|
197
|
+
issue: (data) => request("POST", "/keys", data),
|
|
198
|
+
list: () => request("GET", "/keys").then((r) => r.keys),
|
|
199
|
+
revoke: (keyPrefix) => request("DELETE", `/keys/${encodeURIComponent(keyPrefix)}`)
|
|
200
|
+
});
|
|
201
|
+
|
|
159
202
|
// src/client.ts
|
|
160
203
|
var DEFAULT_BASE_URL = "https://pushary.com/api/v1/server";
|
|
161
204
|
var validateApiKey = (apiKey) => {
|
|
@@ -186,12 +229,14 @@ var createPusharyServer = (config) => {
|
|
|
186
229
|
notifications: createNotificationsResource(request),
|
|
187
230
|
flows: createFlowsResource(request),
|
|
188
231
|
decisions: createDecisionsResource(request),
|
|
232
|
+
keys: createKeysResource(request),
|
|
189
233
|
enroll: (externalId) => request("POST", "/enroll", { externalId })
|
|
190
234
|
});
|
|
191
235
|
};
|
|
192
236
|
|
|
193
237
|
// src/webhook.ts
|
|
194
238
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
239
|
+
var SIGNATURE_HEADER = "x-pushary-signature";
|
|
195
240
|
var verifyWebhookSignature = (rawBody, signature, secret) => {
|
|
196
241
|
if (!signature || !secret) return false;
|
|
197
242
|
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
|
|
@@ -200,10 +245,31 @@ var verifyWebhookSignature = (rawBody, signature, secret) => {
|
|
|
200
245
|
if (a.length !== b.length) return false;
|
|
201
246
|
return timingSafeEqual(a, b);
|
|
202
247
|
};
|
|
248
|
+
var parseDecisionCallback = (rawBody) => {
|
|
249
|
+
let parsed;
|
|
250
|
+
try {
|
|
251
|
+
parsed = JSON.parse(rawBody);
|
|
252
|
+
} catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
256
|
+
const p = parsed;
|
|
257
|
+
if (typeof p.correlationId !== "string" || typeof p.answer !== "string") return null;
|
|
258
|
+
return {
|
|
259
|
+
correlationId: p.correlationId,
|
|
260
|
+
answer: p.answer,
|
|
261
|
+
value: typeof p.value === "string" ? p.value : p.answer,
|
|
262
|
+
answeredAt: typeof p.answeredAt === "string" ? p.answeredAt : "",
|
|
263
|
+
...typeof p.context === "string" ? { context: p.context } : {}
|
|
264
|
+
};
|
|
265
|
+
};
|
|
203
266
|
export {
|
|
267
|
+
PusharyApiError,
|
|
268
|
+
SIGNATURE_HEADER,
|
|
204
269
|
createPusharyServer,
|
|
205
270
|
deterministicKey,
|
|
206
271
|
isApproved,
|
|
272
|
+
parseDecisionCallback,
|
|
207
273
|
verifyWebhookSignature
|
|
208
274
|
};
|
|
209
275
|
//# 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/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":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/error.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/resources/keys.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["/**\n * Thrown for any non-2xx response from the API.\n *\n * Extends Error rather than replacing it, so every existing `catch (e)` that\n * reads `e.message` keeps working unchanged. What it adds is the thing callers\n * previously had to string-match the message for: an HTTP status they can branch\n * on. Telling a revoked key (401) apart from a plan limit (403), a bad argument\n * (400) and an outage (5xx) is the difference between \"stop and re-authenticate\"\n * and \"retry in a minute\", and message text is not a stable API to decide that on.\n */\nexport class PusharyApiError extends Error {\n /** HTTP status of the response that failed. */\n readonly status: number\n /** HTTP status text, when the runtime provides one. */\n readonly statusText: string\n /** The API's own machine-readable error code, when it sent one. */\n readonly code?: string\n /** The parsed response body, for anything the fields above do not cover. */\n readonly body?: unknown\n\n constructor(\n message: string,\n init: { status: number; statusText?: string; code?: string; body?: unknown }\n ) {\n super(message)\n this.name = 'PusharyApiError'\n this.status = init.status\n this.statusText = init.statusText ?? ''\n this.code = init.code\n this.body = init.body\n // Restores the prototype chain so `err instanceof PusharyApiError` holds\n // even when this package is consumed as ES5-targeted CommonJS.\n Object.setPrototypeOf(this, PusharyApiError.prototype)\n }\n\n /** The key is missing, malformed, or revoked. Re-authenticate; do not retry. */\n get isAuthError(): boolean {\n return this.status === 401 || this.status === 403\n }\n\n /** Rate limited. Back off and retry. */\n get isRateLimited(): boolean {\n return this.status === 429\n }\n\n /** Server-side fault. Safe to retry an idempotent call. */\n get isServerError(): boolean {\n return this.status >= 500\n }\n}\n","import type { RequestContext, RequestFn } from './types'\nimport { PusharyApiError } from './error'\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 code?: string\n }\n // PusharyApiError extends Error, so an existing `catch (e) { e.message }`\n // is unaffected. It carries the status too, because the reason to branch on\n // a failure (re-authenticate vs back off vs retry) was previously only\n // recoverable by string-matching this message, which is not a stable API.\n throw new PusharyApiError(body.message || body.error || `HTTP ${response.status}`, {\n status: response.status,\n statusText: response.statusText,\n code: body.code,\n body,\n })\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 requireReachable: data.requireReachable,\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 // Surfaced from the create response so a caller can tell an unanswered\n // decision that reached nobody (reachable:false) apart from a real decline.\n reachable: created.reachable,\n reachableChannels: created.reachableChannels,\n deviceCount: created.deviceCount,\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 {\n RequestFn,\n IssueBoundKey,\n BoundKeyResult,\n BoundKeySummary,\n RevokeKeyResult,\n} from '../types'\n\n// Per-end-user (bound) key issuance for multi-tenant Partners. Call issue() from\n// your backend with your own (unbound, Partner-plan) key to mint a child key bound\n// to one end-user, hand that key to the agent runtime acting for that user, then\n// revoke() it when the session ends. A bound key can only create/resolve decisions\n// and enroll that exact end-user, so a prompt-injected agent can never reach\n// another of your users. Requires the Partner plan.\nexport interface KeysResource {\n // Mint a key bound to externalId. The full key is returned once in `apiKey` and\n // cannot be retrieved again — store it as the session credential.\n readonly issue: (data: IssueBoundKey) => Promise<BoundKeyResult>\n // List this site's bound keys (never the secret) for audit/rotation.\n readonly list: () => Promise<readonly BoundKeySummary[]>\n // Revoke (deactivate) a bound key by its prefix. Returns revoked:false if the\n // prefix is unknown or already inactive.\n readonly revoke: (keyPrefix: string) => Promise<RevokeKeyResult>\n}\n\nexport const createKeysResource = (request: RequestFn): KeysResource =>\n Object.freeze({\n issue: (data: IssueBoundKey) => request<BoundKeyResult>('POST', '/keys', data),\n list: () =>\n request<{ keys: readonly BoundKeySummary[] }>('GET', '/keys').then((r) => r.keys),\n revoke: (keyPrefix: string) =>\n request<RevokeKeyResult>('DELETE', `/keys/${encodeURIComponent(keyPrefix)}`),\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'\nimport { createKeysResource, type KeysResource } from './resources/keys'\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 // Mint/rotate per-end-user bound keys for multi-tenant Partner integrations.\n readonly keys: KeysResource\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 keys: createKeysResource(request),\n enroll: (externalId: string) => request<EnrollResult>('POST', '/enroll', { externalId }),\n })\n}\n\n","import { createHmac, timingSafeEqual } from 'crypto'\nimport type { DecisionCallback } from './types'\n\n// The header carrying the HMAC-SHA256 signature of the raw callback body.\nexport const SIGNATURE_HEADER = 'x-pushary-signature'\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\n// Parse a verified decision callback body into a typed object. Returns null if the\n// body is not a decision callback (missing correlationId/answer). Verify the\n// signature FIRST — this does no verification. Read `answer` (canonical) keyed by\n// `correlationId`; `context` echoes what you passed at create time.\nexport const parseDecisionCallback = (rawBody: string): DecisionCallback | null => {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawBody)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (typeof p.correlationId !== 'string' || typeof p.answer !== 'string') return null\n return {\n correlationId: p.correlationId,\n answer: p.answer,\n value: typeof p.value === 'string' ? p.value : p.answer,\n answeredAt: typeof p.answeredAt === 'string' ? p.answeredAt : '',\n ...(typeof p.context === 'string' ? { context: p.context } : {}),\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/server",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
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",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"url": "https://github.com/pushary/pushary.git",
|
|
10
|
-
"directory": "packages/server"
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://pushary.com/support"
|
|
11
9
|
},
|
|
12
10
|
"license": "MIT",
|
|
13
11
|
"type": "module",
|