@gravity-rail/sdk 0.18.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2YLKG4TT.js → chunk-3HAV5XUJ.js} +1 -1
- package/dist/chunk-3HAV5XUJ.js.map +1 -0
- package/dist/{chunk-PJFEGUHM.js → chunk-7OLBLLV7.js} +6 -2
- package/dist/{chunk-PJFEGUHM.js.map → chunk-7OLBLLV7.js.map} +1 -1
- package/dist/{chunk-PUIYXJSZ.js → chunk-WMA6YCAS.js} +496 -41
- package/dist/chunk-WMA6YCAS.js.map +1 -0
- package/dist/{client-B-jmQ4UA.d.cts → client-C10qSd6b.d.ts} +1678 -285
- package/dist/{client-CatNn6FD.d.ts → client-Ct0soeip.d.cts} +1678 -285
- package/dist/helpers.cjs +141 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +17 -3
- package/dist/helpers.d.ts +17 -3
- package/dist/helpers.js +127 -1
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +595 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -5
- package/dist/index.d.ts +28 -5
- package/dist/index.js +123 -5
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +502 -41
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +630 -551
- package/dist/internal.d.ts +630 -551
- package/dist/internal.js +9 -5
- package/dist/internal.js.map +1 -1
- package/dist/{revision-state-Fw1jsQOa.d.ts → revision-state-C6aM-PHu.d.ts} +53 -172
- package/dist/{revision-state-CuESPYpi.d.cts → revision-state-CCPTiW-i.d.cts} +53 -172
- package/dist/schemas.cjs +31 -5
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +37 -14
- package/dist/schemas.d.ts +37 -14
- package/dist/schemas.js +31 -6
- package/dist/schemas.js.map +1 -1
- package/dist/{tasks-GVbVa_Lj.d.cts → tasks-WH_VfzMv.d.cts} +351 -16
- package/dist/{tasks-GVbVa_Lj.d.ts → tasks-WH_VfzMv.d.ts} +351 -16
- package/package.json +6 -6
- package/dist/chunk-2YLKG4TT.js.map +0 -1
- package/dist/chunk-PUIYXJSZ.js.map +0 -1
package/dist/helpers.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ApiError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7OLBLLV7.js";
|
|
4
4
|
|
|
5
5
|
// src/helpers/outbound.ts
|
|
6
6
|
function normalizePhoneLast10(phone) {
|
|
@@ -218,11 +218,137 @@ async function verifyWebhookSignature(rawBody, signatureHeader, secret, options
|
|
|
218
218
|
}
|
|
219
219
|
return { ok: true };
|
|
220
220
|
}
|
|
221
|
+
|
|
222
|
+
// src/helpers/workspace-export-archive.ts
|
|
223
|
+
var WORKSPACE_EXPORT_JSON_MEMBER = "export.json";
|
|
224
|
+
var MAX_EXPORT_JSON_BYTES = 8 * 1024 * 1024;
|
|
225
|
+
var EOCD_SIGNATURE = 101010256;
|
|
226
|
+
var CDFH_SIGNATURE = 33639248;
|
|
227
|
+
var LFH_SIGNATURE = 67324752;
|
|
228
|
+
var ZIP_STORED = 0;
|
|
229
|
+
var ZIP_DEFLATED = 8;
|
|
230
|
+
function requireRange(bytes, start, length) {
|
|
231
|
+
if (start < 0 || length < 0 || start + length > bytes.length) {
|
|
232
|
+
throw new Error("ZIP offsets are out of range.");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function u16(bytes, offset) {
|
|
236
|
+
requireRange(bytes, offset, 2);
|
|
237
|
+
return bytes[offset] | bytes[offset + 1] << 8;
|
|
238
|
+
}
|
|
239
|
+
function u32(bytes, offset) {
|
|
240
|
+
requireRange(bytes, offset, 4);
|
|
241
|
+
return (bytes[offset] | bytes[offset + 1] << 8 | bytes[offset + 2] << 16 | bytes[offset + 3] << 24) >>> 0;
|
|
242
|
+
}
|
|
243
|
+
function isZipBuffer(bytes) {
|
|
244
|
+
return bytes.length >= 4 && bytes[0] === 80 && bytes[1] === 75;
|
|
245
|
+
}
|
|
246
|
+
async function inflateRaw(data, maxBytes) {
|
|
247
|
+
if (typeof DecompressionStream === "function") {
|
|
248
|
+
const stream = new Blob([data]).stream().pipeThrough(new DecompressionStream("deflate-raw"));
|
|
249
|
+
const reader = stream.getReader();
|
|
250
|
+
const chunks = [];
|
|
251
|
+
let total = 0;
|
|
252
|
+
while (true) {
|
|
253
|
+
const { done, value } = await reader.read();
|
|
254
|
+
if (done) break;
|
|
255
|
+
total += value.byteLength;
|
|
256
|
+
if (total > maxBytes) {
|
|
257
|
+
await reader.cancel();
|
|
258
|
+
throw new Error("ZIP member exceeds the export.json size limit.");
|
|
259
|
+
}
|
|
260
|
+
chunks.push(value);
|
|
261
|
+
}
|
|
262
|
+
const out = new Uint8Array(total);
|
|
263
|
+
let offset = 0;
|
|
264
|
+
for (const chunk of chunks) {
|
|
265
|
+
out.set(chunk, offset);
|
|
266
|
+
offset += chunk.byteLength;
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
const zlib = await import("zlib");
|
|
271
|
+
return Uint8Array.from(zlib.inflateRawSync(data, { maxOutputLength: maxBytes }));
|
|
272
|
+
}
|
|
273
|
+
function findEocdOffset(bytes) {
|
|
274
|
+
const min = 22;
|
|
275
|
+
const maxComment = 65535;
|
|
276
|
+
const start = Math.max(0, bytes.length - (min + maxComment));
|
|
277
|
+
for (let i = bytes.length - min; i >= start; i--) {
|
|
278
|
+
if (u32(bytes, i) !== EOCD_SIGNATURE) continue;
|
|
279
|
+
const commentLength = u16(bytes, i + 20);
|
|
280
|
+
if (i + min + commentLength === bytes.length) {
|
|
281
|
+
return i;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
throw new Error("Not a ZIP archive (missing end-of-central-directory).");
|
|
285
|
+
}
|
|
286
|
+
async function extractZipMember(bytes, memberName) {
|
|
287
|
+
const eocd = findEocdOffset(bytes);
|
|
288
|
+
const entries = u16(bytes, eocd + 10);
|
|
289
|
+
let offset = u32(bytes, eocd + 16);
|
|
290
|
+
for (let i = 0; i < entries; i++) {
|
|
291
|
+
if (u32(bytes, offset) !== CDFH_SIGNATURE) {
|
|
292
|
+
throw new Error("ZIP central directory is corrupt.");
|
|
293
|
+
}
|
|
294
|
+
requireRange(bytes, offset, 46);
|
|
295
|
+
const method = u16(bytes, offset + 10);
|
|
296
|
+
const compressedSize = u32(bytes, offset + 20);
|
|
297
|
+
const uncompressedSize = u32(bytes, offset + 24);
|
|
298
|
+
const nameLen = u16(bytes, offset + 28);
|
|
299
|
+
const extraLen = u16(bytes, offset + 30);
|
|
300
|
+
const commentLen = u16(bytes, offset + 32);
|
|
301
|
+
const localOffset = u32(bytes, offset + 42);
|
|
302
|
+
requireRange(bytes, offset + 46, nameLen);
|
|
303
|
+
const name = new TextDecoder().decode(bytes.subarray(offset + 46, offset + 46 + nameLen));
|
|
304
|
+
offset += 46 + nameLen + extraLen + commentLen;
|
|
305
|
+
requireRange(bytes, offset, 0);
|
|
306
|
+
if (name !== memberName) continue;
|
|
307
|
+
if (uncompressedSize === 4294967295 || compressedSize === 4294967295) {
|
|
308
|
+
throw new Error("ZIP64 members are not supported for export.json.");
|
|
309
|
+
}
|
|
310
|
+
if (uncompressedSize > MAX_EXPORT_JSON_BYTES || compressedSize > MAX_EXPORT_JSON_BYTES) {
|
|
311
|
+
throw new Error("ZIP member exceeds the export.json size limit.");
|
|
312
|
+
}
|
|
313
|
+
if (u32(bytes, localOffset) !== LFH_SIGNATURE) {
|
|
314
|
+
throw new Error(`ZIP local header for ${memberName} is corrupt.`);
|
|
315
|
+
}
|
|
316
|
+
const localNameLen = u16(bytes, localOffset + 26);
|
|
317
|
+
const localExtraLen = u16(bytes, localOffset + 28);
|
|
318
|
+
const dataStart = localOffset + 30 + localNameLen + localExtraLen;
|
|
319
|
+
requireRange(bytes, dataStart, compressedSize);
|
|
320
|
+
const compressed = bytes.subarray(dataStart, dataStart + compressedSize);
|
|
321
|
+
if (method === ZIP_STORED) {
|
|
322
|
+
if (compressed.length !== uncompressedSize) {
|
|
323
|
+
throw new Error("ZIP stored member size does not match the directory.");
|
|
324
|
+
}
|
|
325
|
+
return compressed.slice();
|
|
326
|
+
}
|
|
327
|
+
if (method === ZIP_DEFLATED) {
|
|
328
|
+
const inflated = await inflateRaw(compressed, MAX_EXPORT_JSON_BYTES);
|
|
329
|
+
if (inflated.length !== uncompressedSize) {
|
|
330
|
+
throw new Error("ZIP deflated member size does not match the directory.");
|
|
331
|
+
}
|
|
332
|
+
return inflated;
|
|
333
|
+
}
|
|
334
|
+
throw new Error(`Unsupported ZIP compression method ${method} for ${memberName}.`);
|
|
335
|
+
}
|
|
336
|
+
throw new Error(`ZIP is missing ${memberName}.`);
|
|
337
|
+
}
|
|
338
|
+
async function extractWorkspaceExportJson(bytes) {
|
|
339
|
+
const raw = await extractZipMember(bytes, WORKSPACE_EXPORT_JSON_MEMBER);
|
|
340
|
+
return new TextDecoder().decode(raw);
|
|
341
|
+
}
|
|
221
342
|
export {
|
|
343
|
+
MAX_EXPORT_JSON_BYTES,
|
|
222
344
|
WEBHOOK_SIGNATURE_HEADER,
|
|
345
|
+
WORKSPACE_EXPORT_JSON_MEMBER,
|
|
223
346
|
enrollMemberInJourney,
|
|
347
|
+
extractWorkspaceExportJson,
|
|
348
|
+
extractZipMember,
|
|
224
349
|
findMemberByPhone,
|
|
225
350
|
findOrCreateMemberLabels,
|
|
351
|
+
isZipBuffer,
|
|
226
352
|
listOutboundPhoneNumbers,
|
|
227
353
|
normalizePhoneLast10,
|
|
228
354
|
resolveMemberId,
|
package/dist/helpers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helpers/outbound.ts","../src/helpers/enrollment.ts","../src/helpers/webhooks.ts"],"sourcesContent":["import type { GravityRailClient } from '../client/client';\nimport type { Member } from '../types/member-types';\n\nexport interface OutboundPhoneNumberOption {\n id: number;\n number: string;\n name: string | null;\n}\n\nexport interface WorkspacePhoneNumbersPayload {\n phones: OutboundPhoneNumberOption[];\n defaultPhoneNumberId: number | null;\n}\n\n/** App/env fallbacks when resolving outbound phone numbers. */\nexport interface OutboundPhoneNumberConfigDefaults {\n /** e.g. GRAVITY_RAIL_PHONE_NUMBER_ID */\n phoneNumberId?: number;\n /** Explicit selection overriding workspace default */\n requestedPhoneNumberId?: number;\n}\n\n/** Strip to digits and return the last 10 (US-friendly match). */\nexport function normalizePhoneLast10(phone: string): string {\n const digits = phone.replace(/\\D/g, '');\n return digits.length >= 10 ? digits.slice(-10) : digits;\n}\n\nfunction phonesMatchLast10(a: string, b: string): boolean {\n const da = normalizePhoneLast10(a);\n const db = normalizePhoneLast10(b);\n if (!da || !db) return false;\n if (da.length >= 10 && db.length >= 10) {\n return da === db;\n }\n return da === db;\n}\n\nexport async function listOutboundPhoneNumbers(\n client: GravityRailClient,\n workspaceUuid: string\n): Promise<WorkspacePhoneNumbersPayload> {\n const [phones, defaults] = await Promise.all([\n client.getPhoneNumbers(workspaceUuid, { isArchived: false }),\n client.getWorkspaceDefaults(workspaceUuid),\n ]);\n\n const voicePhones = phones\n .filter((phone) => phone.enableVoice && !phone.isArchived)\n .map((phone) => ({\n id: phone.id,\n number: phone.number,\n name: phone.name,\n }));\n\n let defaultPhoneNumberId = defaults.defaultPhoneNumberId ?? null;\n if (\n defaultPhoneNumberId !== null &&\n !voicePhones.some((phone) => phone.id === defaultPhoneNumberId)\n ) {\n defaultPhoneNumberId = voicePhones[0]?.id ?? null;\n }\n if (defaultPhoneNumberId === null) {\n defaultPhoneNumberId = voicePhones[0]?.id ?? null;\n }\n\n return { phones: voicePhones, defaultPhoneNumberId };\n}\n\nexport async function resolveOutboundPhoneNumberId(\n client: GravityRailClient,\n workspaceUuid: string,\n configDefaults: OutboundPhoneNumberConfigDefaults\n): Promise<number> {\n const { phones, defaultPhoneNumberId } = await listOutboundPhoneNumbers(client, workspaceUuid);\n\n if (phones.length === 0) {\n throw new Error(\n 'No voice-enabled phone numbers in this workspace. Add a line in Gravity Rail first.'\n );\n }\n\n const requestedId = configDefaults.requestedPhoneNumberId;\n const candidate =\n requestedId ?? defaultPhoneNumberId ?? configDefaults.phoneNumberId ?? phones[0]?.id;\n\n if (candidate === undefined || !phones.some((phone) => phone.id === candidate)) {\n throw new Error('Selected outbound phone number is not available.');\n }\n\n return candidate;\n}\n\nexport async function resolveOutboundWorkflowId(\n client: GravityRailClient,\n workspaceUuid: string,\n defaultWorkflowId: number,\n override?: number\n): Promise<number> {\n const workflowsPage = await client.getWorkflows(workspaceUuid, { archived: false });\n const options = workflowsPage.items\n .filter((item) => !item.workflow.archived)\n .map((item) => ({\n id: item.workflow.id,\n name: item.workflow.name,\n }))\n .sort((a, b) => a.name.localeCompare(b.name));\n\n if (options.length === 0) {\n throw new Error('No workflows in this workspace. Create a workflow in Gravity Rail first.');\n }\n\n let resolvedDefault = defaultWorkflowId;\n if (!options.some((workflow) => workflow.id === resolvedDefault)) {\n resolvedDefault = options[0]?.id ?? defaultWorkflowId;\n }\n\n const candidate = override ?? resolvedDefault;\n\n if (!options.some((workflow) => workflow.id === candidate)) {\n throw new Error('Selected workflow is not available.');\n }\n\n return candidate;\n}\n\nexport async function findMemberByPhone(\n client: GravityRailClient,\n workspaceUuid: string,\n phone: string\n): Promise<Member | null> {\n const searchDigits = phone.replace(/\\D/g, '');\n\n for (let page = 1; page <= 5; page += 1) {\n const response = await client.getWorkspaceMembers(workspaceUuid, page, 50, searchDigits);\n const match = response.items.find(\n (member) => member.phone && phonesMatchLast10(member.phone, phone)\n );\n if (match) return match;\n\n if (page >= response.totalPages) break;\n }\n\n return null;\n}\n","/**\n * Enrollment-flow helpers: the small compound operations every integration\n * re-implements when wiring \"register member → populate a form → enroll into a\n * journey\". They build on the plain client methods and are safe to call from a\n * server-side BFF.\n */\nimport { ApiError } from '../api/base';\nimport type { GravityRailClient } from '../client/client';\nimport type { DataRecord } from '../types/data-types';\nimport { findMemberByPhone } from './outbound';\n\n/** Identify a member by any one of these; `memberId` wins when present. */\nexport interface MemberRef {\n memberId?: number;\n phone?: string;\n externalId?: string;\n}\n\n/** Resolve a `MemberRef` to a concrete member id (throws if none matches). */\nexport async function resolveMemberId(\n client: GravityRailClient,\n workspaceUuid: string,\n ref: MemberRef\n): Promise<number> {\n if (typeof ref.memberId === 'number') {\n return ref.memberId;\n }\n if (ref.phone) {\n const member = await findMemberByPhone(client, workspaceUuid, ref.phone);\n if (member?.id) return member.id;\n // Do not put the phone (PHI) in the message — it propagates to callers.\n throw new Error('No member found for the provided phone number');\n }\n if (ref.externalId) {\n const page = await client.getWorkspaceMembers(workspaceUuid, 1, 25, ref.externalId);\n const match = page.items.find((m) => m.externalId === ref.externalId);\n if (match?.id) return match.id;\n // Do not put the externalId (PHI) in the message — it propagates to callers.\n throw new Error('No member found for the provided externalId');\n }\n throw new Error('Provide memberId, phone, or externalId');\n}\n\n/**\n * Find member labels by name, creating any that don't exist, and return their\n * ids. Case-insensitive match on name. (The API derives the slug from the name.)\n */\nexport async function findOrCreateMemberLabels(\n client: GravityRailClient,\n workspaceUuid: string,\n names: string[],\n options: { color?: string } = {}\n): Promise<number[]> {\n const clean = names.map((n) => n.trim()).filter(Boolean);\n if (clean.length === 0) return [];\n\n const existing = await client.getMemberLabels(workspaceUuid);\n const byName = new Map(existing.map((l) => [l.name.toLowerCase(), l]));\n const ids: number[] = [];\n for (const name of clean) {\n const found = byName.get(name.toLowerCase());\n if (found) {\n ids.push(found.id);\n continue;\n }\n const created = await client.createMemberLabel(workspaceUuid, {\n name,\n color: options.color ?? '#6366f1',\n });\n byName.set(name.toLowerCase(), created);\n ids.push(created.id);\n }\n return ids;\n}\n\n/**\n * Idempotently write a member-scoped data record: update the member's existing\n * record for the DataType if one exists, otherwise create it.\n */\nexport async function upsertDataRecordForMember(\n client: GravityRailClient,\n workspaceUuid: string,\n dataTypeId: number,\n memberId: number,\n fieldValues: Record<string, unknown>\n): Promise<DataRecord> {\n const existing = await client.getDataRecords(workspaceUuid, dataTypeId, {\n memberId,\n pageSize: 1,\n });\n const record = existing.items?.[0];\n const body = { dataTypeId, memberId, fieldValues };\n if (record?.id) {\n return client.updateDataRecord(workspaceUuid, dataTypeId, record.id, body);\n }\n return client.createDataRecord(workspaceUuid, dataTypeId, body);\n}\n\nexport interface EnrollMemberInJourneyOptions extends MemberRef {\n /** Journey to enroll into. Defaults to the workspace's first journey. */\n journeyId?: number;\n /** Labels to apply to the member before enrolling (journey/cohort tags). */\n labels?: string[];\n labelColor?: string;\n}\n\nexport interface EnrollMemberInJourneyResult {\n journeyId: number;\n journeyName?: string;\n memberId: number;\n appliedLabels: string[];\n}\n\n/**\n * Resolve a member, optionally tag them, and enroll them into a journey. Treats\n * an \"already enrolled\" (409) response as success.\n */\nexport async function enrollMemberInJourney(\n client: GravityRailClient,\n workspaceUuid: string,\n options: EnrollMemberInJourneyOptions\n): Promise<EnrollMemberInJourneyResult> {\n const memberId = await resolveMemberId(client, workspaceUuid, options);\n\n const page = await client.getJourneys(workspaceUuid);\n const journeys = page.items ?? [];\n let journey: { id: number; name?: string } | undefined;\n if (options.journeyId != null) {\n journey = journeys.find((j) => j.id === options.journeyId) ?? { id: options.journeyId };\n } else {\n journey = journeys[0];\n }\n if (!journey) {\n throw new Error('No journey to enroll into: pass journeyId or create a journey');\n }\n\n const labelNames = (options.labels ?? []).map((l) => l.trim()).filter(Boolean);\n if (labelNames.length > 0) {\n const labelIds = await findOrCreateMemberLabels(client, workspaceUuid, labelNames, {\n color: options.labelColor,\n });\n // updateMember replaces the label set, so merge with the member's existing\n // labels rather than wiping them.\n const member = await client.getWorkspaceMember(workspaceUuid, memberId);\n const existingIds = (member.labels ?? [])\n .map((l) => l.id)\n .filter((id): id is number => typeof id === 'number');\n const mergedIds = Array.from(new Set([...existingIds, ...labelIds]));\n await client.updateMember(workspaceUuid, memberId, { labelIds: mergedIds });\n }\n\n try {\n await client.enrollMembers(workspaceUuid, journey.id, { memberId });\n } catch (error) {\n if (!(error instanceof ApiError && error.statusCode === 409)) {\n throw error;\n }\n }\n\n return { journeyId: journey.id, journeyName: journey.name, memberId, appliedLabels: labelNames };\n}\n","/**\n * Webhook signature helpers.\n *\n * Gravity Rail signs every outbound webhook with HMAC-SHA256 and sends the\n * result in the `X-Webhook-Signature` header as `t={unixSeconds},v1={hexDigest}`.\n * The signed message is the timestamp, a literal dot, then the raw request body:\n *\n * HMAC_SHA256(secret, `${t}.` + rawBody)\n *\n * The timestamp defeats replay — reject signatures older than the tolerance\n * window. Verify the signature against the RAW request body bytes, before any\n * JSON parsing/re-serialization.\n *\n * These use the Web Crypto API (`crypto.subtle`), so they run unchanged in Node\n * 18+ and the browser/edge runtimes.\n */\n\nexport const WEBHOOK_SIGNATURE_HEADER = 'X-Webhook-Signature';\n\nconst encoder = new TextEncoder();\n\nasync function hmacSha256Hex(secret: string, message: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(message));\n return Array.from(new Uint8Array(signature))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/** Constant-time comparison of two equal-length hex strings. */\nfunction timingSafeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let mismatch = 0;\n for (let i = 0; i < a.length; i += 1) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return mismatch === 0;\n}\n\n/**\n * Produce the `X-Webhook-Signature` header value for a payload. Primarily useful\n * for tests and for re-signing when relaying webhooks.\n */\nexport async function signWebhookPayload(\n rawBody: string,\n secret: string,\n timestampSeconds: number\n): Promise<string> {\n const digest = await hmacSha256Hex(secret, `${timestampSeconds}.${rawBody}`);\n return `t=${timestampSeconds},v1=${digest}`;\n}\n\nexport interface VerifyWebhookOptions {\n /** Max age of the signature in seconds (default 300). */\n toleranceSeconds?: number;\n /** Override \"now\" (unix seconds) — for deterministic tests. */\n nowSeconds?: number;\n}\n\nexport type VerifyWebhookResult = { ok: true } | { ok: false; reason: string };\n\n/**\n * Verify a webhook signature against the raw body. Returns a discriminated\n * result so callers can log the reason; it never throws on a bad signature.\n */\nexport async function verifyWebhookSignature(\n rawBody: string,\n signatureHeader: string | null | undefined,\n secret: string,\n options: VerifyWebhookOptions = {}\n): Promise<VerifyWebhookResult> {\n if (!signatureHeader) {\n return { ok: false, reason: 'missing signature header' };\n }\n\n const parts: Record<string, string> = {};\n for (const segment of signatureHeader.split(',')) {\n const idx = segment.indexOf('=');\n if (idx > 0) {\n parts[segment.slice(0, idx).trim()] = segment.slice(idx + 1).trim();\n }\n }\n\n const timestamp = Number(parts.t);\n const provided = parts.v1;\n if (!Number.isFinite(timestamp) || !provided) {\n return { ok: false, reason: 'malformed signature header' };\n }\n\n const tolerance = options.toleranceSeconds ?? 300;\n const now = options.nowSeconds ?? Math.floor(Date.now() / 1000);\n if (Math.abs(now - timestamp) > tolerance) {\n return { ok: false, reason: 'signature timestamp outside tolerance' };\n }\n\n const expected = await hmacSha256Hex(secret, `${timestamp}.${rawBody}`);\n if (!timingSafeEqualHex(expected, provided)) {\n return { ok: false, reason: 'signature mismatch' };\n }\n\n return { ok: true };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/helpers/outbound.ts","../src/helpers/enrollment.ts","../src/helpers/webhooks.ts","../src/helpers/workspace-export-archive.ts"],"sourcesContent":["import type { GravityRailClient } from '../client/client';\nimport type { Member } from '../types/member-types';\n\nexport interface OutboundPhoneNumberOption {\n id: number;\n number: string;\n name: string | null;\n}\n\nexport interface WorkspacePhoneNumbersPayload {\n phones: OutboundPhoneNumberOption[];\n defaultPhoneNumberId: number | null;\n}\n\n/** App/env fallbacks when resolving outbound phone numbers. */\nexport interface OutboundPhoneNumberConfigDefaults {\n /** e.g. GRAVITY_RAIL_PHONE_NUMBER_ID */\n phoneNumberId?: number;\n /** Explicit selection overriding workspace default */\n requestedPhoneNumberId?: number;\n}\n\n/** Strip to digits and return the last 10 (US-friendly match). */\nexport function normalizePhoneLast10(phone: string): string {\n const digits = phone.replace(/\\D/g, '');\n return digits.length >= 10 ? digits.slice(-10) : digits;\n}\n\nfunction phonesMatchLast10(a: string, b: string): boolean {\n const da = normalizePhoneLast10(a);\n const db = normalizePhoneLast10(b);\n if (!da || !db) return false;\n if (da.length >= 10 && db.length >= 10) {\n return da === db;\n }\n return da === db;\n}\n\nexport async function listOutboundPhoneNumbers(\n client: GravityRailClient,\n workspaceUuid: string\n): Promise<WorkspacePhoneNumbersPayload> {\n const [phones, defaults] = await Promise.all([\n client.getPhoneNumbers(workspaceUuid, { isArchived: false }),\n client.getWorkspaceDefaults(workspaceUuid),\n ]);\n\n const voicePhones = phones\n .filter((phone) => phone.enableVoice && !phone.isArchived)\n .map((phone) => ({\n id: phone.id,\n number: phone.number,\n name: phone.name,\n }));\n\n let defaultPhoneNumberId = defaults.defaultPhoneNumberId ?? null;\n if (\n defaultPhoneNumberId !== null &&\n !voicePhones.some((phone) => phone.id === defaultPhoneNumberId)\n ) {\n defaultPhoneNumberId = voicePhones[0]?.id ?? null;\n }\n if (defaultPhoneNumberId === null) {\n defaultPhoneNumberId = voicePhones[0]?.id ?? null;\n }\n\n return { phones: voicePhones, defaultPhoneNumberId };\n}\n\nexport async function resolveOutboundPhoneNumberId(\n client: GravityRailClient,\n workspaceUuid: string,\n configDefaults: OutboundPhoneNumberConfigDefaults\n): Promise<number> {\n const { phones, defaultPhoneNumberId } = await listOutboundPhoneNumbers(client, workspaceUuid);\n\n if (phones.length === 0) {\n throw new Error(\n 'No voice-enabled phone numbers in this workspace. Add a line in Gravity Rail first.'\n );\n }\n\n const requestedId = configDefaults.requestedPhoneNumberId;\n const candidate =\n requestedId ?? defaultPhoneNumberId ?? configDefaults.phoneNumberId ?? phones[0]?.id;\n\n if (candidate === undefined || !phones.some((phone) => phone.id === candidate)) {\n throw new Error('Selected outbound phone number is not available.');\n }\n\n return candidate;\n}\n\nexport async function resolveOutboundWorkflowId(\n client: GravityRailClient,\n workspaceUuid: string,\n defaultWorkflowId: number,\n override?: number\n): Promise<number> {\n const workflowsPage = await client.getWorkflows(workspaceUuid, { archived: false });\n const options = workflowsPage.items\n .filter((item) => !item.workflow.archived)\n .map((item) => ({\n id: item.workflow.id,\n name: item.workflow.name,\n }))\n .sort((a, b) => a.name.localeCompare(b.name));\n\n if (options.length === 0) {\n throw new Error('No workflows in this workspace. Create a workflow in Gravity Rail first.');\n }\n\n let resolvedDefault = defaultWorkflowId;\n if (!options.some((workflow) => workflow.id === resolvedDefault)) {\n resolvedDefault = options[0]?.id ?? defaultWorkflowId;\n }\n\n const candidate = override ?? resolvedDefault;\n\n if (!options.some((workflow) => workflow.id === candidate)) {\n throw new Error('Selected workflow is not available.');\n }\n\n return candidate;\n}\n\nexport async function findMemberByPhone(\n client: GravityRailClient,\n workspaceUuid: string,\n phone: string\n): Promise<Member | null> {\n const searchDigits = phone.replace(/\\D/g, '');\n\n for (let page = 1; page <= 5; page += 1) {\n const response = await client.getWorkspaceMembers(workspaceUuid, page, 50, searchDigits);\n const match = response.items.find(\n (member) => member.phone && phonesMatchLast10(member.phone, phone)\n );\n if (match) return match;\n\n if (page >= response.totalPages) break;\n }\n\n return null;\n}\n","/**\n * Enrollment-flow helpers: the small compound operations every integration\n * re-implements when wiring \"register member → populate a form → enroll into a\n * journey\". They build on the plain client methods and are safe to call from a\n * server-side BFF.\n */\nimport { ApiError } from '../api/base';\nimport type { GravityRailClient } from '../client/client';\nimport type { DataRecord } from '../types/data-types';\nimport { findMemberByPhone } from './outbound';\n\n/** Identify a member by any one of these; `memberId` wins when present. */\nexport interface MemberRef {\n memberId?: number;\n phone?: string;\n externalId?: string;\n}\n\n/** Resolve a `MemberRef` to a concrete member id (throws if none matches). */\nexport async function resolveMemberId(\n client: GravityRailClient,\n workspaceUuid: string,\n ref: MemberRef\n): Promise<number> {\n if (typeof ref.memberId === 'number') {\n return ref.memberId;\n }\n if (ref.phone) {\n const member = await findMemberByPhone(client, workspaceUuid, ref.phone);\n if (member?.id) return member.id;\n // Do not put the phone (PHI) in the message — it propagates to callers.\n throw new Error('No member found for the provided phone number');\n }\n if (ref.externalId) {\n const page = await client.getWorkspaceMembers(workspaceUuid, 1, 25, ref.externalId);\n const match = page.items.find((m) => m.externalId === ref.externalId);\n if (match?.id) return match.id;\n // Do not put the externalId (PHI) in the message — it propagates to callers.\n throw new Error('No member found for the provided externalId');\n }\n throw new Error('Provide memberId, phone, or externalId');\n}\n\n/**\n * Find member labels by name, creating any that don't exist, and return their\n * ids. Case-insensitive match on name. (The API derives the slug from the name.)\n */\nexport async function findOrCreateMemberLabels(\n client: GravityRailClient,\n workspaceUuid: string,\n names: string[],\n options: { color?: string } = {}\n): Promise<number[]> {\n const clean = names.map((n) => n.trim()).filter(Boolean);\n if (clean.length === 0) return [];\n\n const existing = await client.getMemberLabels(workspaceUuid);\n const byName = new Map(existing.map((l) => [l.name.toLowerCase(), l]));\n const ids: number[] = [];\n for (const name of clean) {\n const found = byName.get(name.toLowerCase());\n if (found) {\n ids.push(found.id);\n continue;\n }\n const created = await client.createMemberLabel(workspaceUuid, {\n name,\n color: options.color ?? '#6366f1',\n });\n byName.set(name.toLowerCase(), created);\n ids.push(created.id);\n }\n return ids;\n}\n\n/**\n * Idempotently write a member-scoped data record: update the member's existing\n * record for the DataType if one exists, otherwise create it.\n */\nexport async function upsertDataRecordForMember(\n client: GravityRailClient,\n workspaceUuid: string,\n dataTypeId: number,\n memberId: number,\n fieldValues: Record<string, unknown>\n): Promise<DataRecord> {\n const existing = await client.getDataRecords(workspaceUuid, dataTypeId, {\n memberId,\n pageSize: 1,\n });\n const record = existing.items?.[0];\n const body = { dataTypeId, memberId, fieldValues };\n if (record?.id) {\n return client.updateDataRecord(workspaceUuid, dataTypeId, record.id, body);\n }\n return client.createDataRecord(workspaceUuid, dataTypeId, body);\n}\n\nexport interface EnrollMemberInJourneyOptions extends MemberRef {\n /** Journey to enroll into. Defaults to the workspace's first journey. */\n journeyId?: number;\n /** Labels to apply to the member before enrolling (journey/cohort tags). */\n labels?: string[];\n labelColor?: string;\n}\n\nexport interface EnrollMemberInJourneyResult {\n journeyId: number;\n journeyName?: string;\n memberId: number;\n appliedLabels: string[];\n}\n\n/**\n * Resolve a member, optionally tag them, and enroll them into a journey. Treats\n * an \"already enrolled\" (409) response as success.\n */\nexport async function enrollMemberInJourney(\n client: GravityRailClient,\n workspaceUuid: string,\n options: EnrollMemberInJourneyOptions\n): Promise<EnrollMemberInJourneyResult> {\n const memberId = await resolveMemberId(client, workspaceUuid, options);\n\n const page = await client.getJourneys(workspaceUuid);\n const journeys = page.items ?? [];\n let journey: { id: number; name?: string } | undefined;\n if (options.journeyId != null) {\n journey = journeys.find((j) => j.id === options.journeyId) ?? { id: options.journeyId };\n } else {\n journey = journeys[0];\n }\n if (!journey) {\n throw new Error('No journey to enroll into: pass journeyId or create a journey');\n }\n\n const labelNames = (options.labels ?? []).map((l) => l.trim()).filter(Boolean);\n if (labelNames.length > 0) {\n const labelIds = await findOrCreateMemberLabels(client, workspaceUuid, labelNames, {\n color: options.labelColor,\n });\n // updateMember replaces the label set, so merge with the member's existing\n // labels rather than wiping them.\n const member = await client.getWorkspaceMember(workspaceUuid, memberId);\n const existingIds = (member.labels ?? [])\n .map((l) => l.id)\n .filter((id): id is number => typeof id === 'number');\n const mergedIds = Array.from(new Set([...existingIds, ...labelIds]));\n await client.updateMember(workspaceUuid, memberId, { labelIds: mergedIds });\n }\n\n try {\n await client.enrollMembers(workspaceUuid, journey.id, { memberId });\n } catch (error) {\n if (!(error instanceof ApiError && error.statusCode === 409)) {\n throw error;\n }\n }\n\n return { journeyId: journey.id, journeyName: journey.name, memberId, appliedLabels: labelNames };\n}\n","/**\n * Webhook signature helpers.\n *\n * Gravity Rail signs every outbound webhook with HMAC-SHA256 and sends the\n * result in the `X-Webhook-Signature` header as `t={unixSeconds},v1={hexDigest}`.\n * The signed message is the timestamp, a literal dot, then the raw request body:\n *\n * HMAC_SHA256(secret, `${t}.` + rawBody)\n *\n * The timestamp defeats replay — reject signatures older than the tolerance\n * window. Verify the signature against the RAW request body bytes, before any\n * JSON parsing/re-serialization.\n *\n * These use the Web Crypto API (`crypto.subtle`), so they run unchanged in Node\n * 18+ and the browser/edge runtimes.\n */\n\nexport const WEBHOOK_SIGNATURE_HEADER = 'X-Webhook-Signature';\n\nconst encoder = new TextEncoder();\n\nasync function hmacSha256Hex(secret: string, message: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(message));\n return Array.from(new Uint8Array(signature))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/** Constant-time comparison of two equal-length hex strings. */\nfunction timingSafeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let mismatch = 0;\n for (let i = 0; i < a.length; i += 1) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return mismatch === 0;\n}\n\n/**\n * Produce the `X-Webhook-Signature` header value for a payload. Primarily useful\n * for tests and for re-signing when relaying webhooks.\n */\nexport async function signWebhookPayload(\n rawBody: string,\n secret: string,\n timestampSeconds: number\n): Promise<string> {\n const digest = await hmacSha256Hex(secret, `${timestampSeconds}.${rawBody}`);\n return `t=${timestampSeconds},v1=${digest}`;\n}\n\nexport interface VerifyWebhookOptions {\n /** Max age of the signature in seconds (default 300). */\n toleranceSeconds?: number;\n /** Override \"now\" (unix seconds) — for deterministic tests. */\n nowSeconds?: number;\n}\n\nexport type VerifyWebhookResult = { ok: true } | { ok: false; reason: string };\n\n/**\n * Verify a webhook signature against the raw body. Returns a discriminated\n * result so callers can log the reason; it never throws on a bad signature.\n */\nexport async function verifyWebhookSignature(\n rawBody: string,\n signatureHeader: string | null | undefined,\n secret: string,\n options: VerifyWebhookOptions = {}\n): Promise<VerifyWebhookResult> {\n if (!signatureHeader) {\n return { ok: false, reason: 'missing signature header' };\n }\n\n const parts: Record<string, string> = {};\n for (const segment of signatureHeader.split(',')) {\n const idx = segment.indexOf('=');\n if (idx > 0) {\n parts[segment.slice(0, idx).trim()] = segment.slice(idx + 1).trim();\n }\n }\n\n const timestamp = Number(parts.t);\n const provided = parts.v1;\n if (!Number.isFinite(timestamp) || !provided) {\n return { ok: false, reason: 'malformed signature header' };\n }\n\n const tolerance = options.toleranceSeconds ?? 300;\n const now = options.nowSeconds ?? Math.floor(Date.now() / 1000);\n if (Math.abs(now - timestamp) > tolerance) {\n return { ok: false, reason: 'signature timestamp outside tolerance' };\n }\n\n const expected = await hmacSha256Hex(secret, `${timestamp}.${rawBody}`);\n if (!timingSafeEqualHex(expected, provided)) {\n return { ok: false, reason: 'signature mismatch' };\n }\n\n return { ok: true };\n}\n","/**\n * Helpers for the portable workspace export ZIP.\n *\n * Layout: ``export.json`` plus ``blobs/ab/cd/<sha256>``.\n */\n\nexport const WORKSPACE_EXPORT_JSON_MEMBER = 'export.json';\nexport const MAX_EXPORT_JSON_BYTES = 8 * 1024 * 1024;\n\nconst EOCD_SIGNATURE = 0x06054b50;\nconst CDFH_SIGNATURE = 0x02014b50;\nconst LFH_SIGNATURE = 0x04034b50;\nconst ZIP_STORED = 0;\nconst ZIP_DEFLATED = 8;\n\nfunction requireRange(bytes: Uint8Array, start: number, length: number): void {\n if (start < 0 || length < 0 || start + length > bytes.length) {\n throw new Error('ZIP offsets are out of range.');\n }\n}\n\nfunction u16(bytes: Uint8Array, offset: number): number {\n requireRange(bytes, offset, 2);\n return bytes[offset]! | (bytes[offset + 1]! << 8);\n}\n\nfunction u32(bytes: Uint8Array, offset: number): number {\n requireRange(bytes, offset, 4);\n return (\n (bytes[offset]! |\n (bytes[offset + 1]! << 8) |\n (bytes[offset + 2]! << 16) |\n (bytes[offset + 3]! << 24)) >>>\n 0\n );\n}\n\nexport function isZipBuffer(bytes: Uint8Array): boolean {\n return bytes.length >= 4 && bytes[0] === 0x50 && bytes[1] === 0x4b;\n}\n\nasync function inflateRaw(data: Uint8Array, maxBytes: number): Promise<Uint8Array> {\n if (typeof DecompressionStream === 'function') {\n const stream = new Blob([data as BlobPart])\n .stream()\n .pipeThrough(new DecompressionStream('deflate-raw'));\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > maxBytes) {\n await reader.cancel();\n throw new Error('ZIP member exceeds the export.json size limit.');\n }\n chunks.push(value);\n }\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n }\n const zlib = await import('node:zlib');\n return Uint8Array.from(zlib.inflateRawSync(data, { maxOutputLength: maxBytes }));\n}\n\nfunction findEocdOffset(bytes: Uint8Array): number {\n const min = 22;\n const maxComment = 65535;\n const start = Math.max(0, bytes.length - (min + maxComment));\n for (let i = bytes.length - min; i >= start; i--) {\n if (u32(bytes, i) !== EOCD_SIGNATURE) continue;\n const commentLength = u16(bytes, i + 20);\n if (i + min + commentLength === bytes.length) {\n return i;\n }\n }\n throw new Error('Not a ZIP archive (missing end-of-central-directory).');\n}\n\n/**\n * Extract a single ZIP member. Supports STORE and DEFLATE (method 8).\n */\nexport async function extractZipMember(bytes: Uint8Array, memberName: string): Promise<Uint8Array> {\n const eocd = findEocdOffset(bytes);\n const entries = u16(bytes, eocd + 10);\n let offset = u32(bytes, eocd + 16);\n for (let i = 0; i < entries; i++) {\n if (u32(bytes, offset) !== CDFH_SIGNATURE) {\n throw new Error('ZIP central directory is corrupt.');\n }\n requireRange(bytes, offset, 46);\n const method = u16(bytes, offset + 10);\n const compressedSize = u32(bytes, offset + 20);\n const uncompressedSize = u32(bytes, offset + 24);\n const nameLen = u16(bytes, offset + 28);\n const extraLen = u16(bytes, offset + 30);\n const commentLen = u16(bytes, offset + 32);\n const localOffset = u32(bytes, offset + 42);\n requireRange(bytes, offset + 46, nameLen);\n const name = new TextDecoder().decode(bytes.subarray(offset + 46, offset + 46 + nameLen));\n offset += 46 + nameLen + extraLen + commentLen;\n requireRange(bytes, offset, 0);\n if (name !== memberName) continue;\n if (uncompressedSize === 0xffffffff || compressedSize === 0xffffffff) {\n throw new Error('ZIP64 members are not supported for export.json.');\n }\n if (uncompressedSize > MAX_EXPORT_JSON_BYTES || compressedSize > MAX_EXPORT_JSON_BYTES) {\n throw new Error('ZIP member exceeds the export.json size limit.');\n }\n\n if (u32(bytes, localOffset) !== LFH_SIGNATURE) {\n throw new Error(`ZIP local header for ${memberName} is corrupt.`);\n }\n const localNameLen = u16(bytes, localOffset + 26);\n const localExtraLen = u16(bytes, localOffset + 28);\n const dataStart = localOffset + 30 + localNameLen + localExtraLen;\n requireRange(bytes, dataStart, compressedSize);\n const compressed = bytes.subarray(dataStart, dataStart + compressedSize);\n if (method === ZIP_STORED) {\n if (compressed.length !== uncompressedSize) {\n throw new Error('ZIP stored member size does not match the directory.');\n }\n return compressed.slice();\n }\n if (method === ZIP_DEFLATED) {\n const inflated = await inflateRaw(compressed, MAX_EXPORT_JSON_BYTES);\n if (inflated.length !== uncompressedSize) {\n throw new Error('ZIP deflated member size does not match the directory.');\n }\n return inflated;\n }\n throw new Error(`Unsupported ZIP compression method ${method} for ${memberName}.`);\n }\n throw new Error(`ZIP is missing ${memberName}.`);\n}\n\nexport async function extractWorkspaceExportJson(bytes: Uint8Array): Promise<string> {\n const raw = await extractZipMember(bytes, WORKSPACE_EXPORT_JSON_MEMBER);\n return new TextDecoder().decode(raw);\n}\n"],"mappings":"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","names":[]}
|