@gravity-rail/sdk 0.8.0 → 0.11.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.
Files changed (46) hide show
  1. package/README.md +45 -8
  2. package/dist/chunk-FSSXKQR5.js +471 -0
  3. package/dist/chunk-FSSXKQR5.js.map +1 -0
  4. package/dist/{chunk-QS6ZRQUZ.js → chunk-V7ZKEH4V.js} +8 -1
  5. package/dist/chunk-V7ZKEH4V.js.map +1 -0
  6. package/dist/{chunk-ML55PVP7.js → chunk-YESHLUL7.js} +1435 -703
  7. package/dist/chunk-YESHLUL7.js.map +1 -0
  8. package/dist/{client-C2aWAalu.d.cts → client-BCgXtr8p.d.cts} +3684 -1291
  9. package/dist/{client-CLGovEox.d.ts → client-Dh1l20Ue.d.ts} +3684 -1291
  10. package/dist/helpers.cjs +177 -13
  11. package/dist/helpers.cjs.map +1 -1
  12. package/dist/helpers.d.cts +82 -3
  13. package/dist/helpers.d.ts +82 -3
  14. package/dist/helpers.js +155 -12
  15. package/dist/helpers.js.map +1 -1
  16. package/dist/index.cjs +1679 -420
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +32 -24
  19. package/dist/index.d.ts +32 -24
  20. package/dist/index.js +193 -113
  21. package/dist/index.js.map +1 -1
  22. package/dist/internal.cjs +1742 -413
  23. package/dist/internal.cjs.map +1 -1
  24. package/dist/internal.d.cts +634 -29
  25. package/dist/internal.d.ts +634 -29
  26. package/dist/internal.js +140 -7
  27. package/dist/internal.js.map +1 -1
  28. package/dist/realtime.cjs +43 -0
  29. package/dist/realtime.cjs.map +1 -1
  30. package/dist/realtime.d.cts +26 -0
  31. package/dist/realtime.d.ts +26 -0
  32. package/dist/realtime.js +43 -0
  33. package/dist/realtime.js.map +1 -1
  34. package/dist/{scopes-D6a_Mvqr.d.ts → revision-state-BTF-MnbW.d.cts} +270 -23
  35. package/dist/{scopes-CDaJBQVT.d.cts → revision-state-DOH1NMGb.d.ts} +270 -23
  36. package/dist/schemas.cjs +56 -117
  37. package/dist/schemas.cjs.map +1 -1
  38. package/dist/schemas.d.cts +121 -484
  39. package/dist/schemas.d.ts +121 -484
  40. package/dist/schemas.js +55 -114
  41. package/dist/schemas.js.map +1 -1
  42. package/dist/{inquiries-CmECZnkl.d.cts → tasks-C4aYbWVv.d.cts} +944 -148
  43. package/dist/{inquiries-CmECZnkl.d.ts → tasks-C4aYbWVv.d.ts} +944 -148
  44. package/package.json +5 -5
  45. package/dist/chunk-ML55PVP7.js.map +0 -1
  46. package/dist/chunk-QS6ZRQUZ.js.map +0 -1
package/dist/helpers.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ ApiError
3
+ } from "./chunk-FSSXKQR5.js";
4
+
1
5
  // src/helpers/outbound.ts
2
6
  function normalizePhoneLast10(phone) {
3
7
  const digits = phone.replace(/\D/g, "");
@@ -46,10 +50,10 @@ async function resolveOutboundPhoneNumberId(client, workspaceUuid, configDefault
46
50
  return candidate;
47
51
  }
48
52
  async function resolveOutboundWorkflowId(client, workspaceUuid, defaultWorkflowId, override) {
49
- const workflows = await client.getWorkflows(workspaceUuid, false);
50
- const options = workflows.filter((workflow) => !workflow.archived).map((workflow) => ({
51
- id: workflow.id,
52
- name: workflow.name
53
+ const workflowsPage = await client.getWorkflows(workspaceUuid, { archived: false });
54
+ const options = workflowsPage.items.filter((item) => !item.workflow.archived).map((item) => ({
55
+ id: item.workflow.id,
56
+ name: item.workflow.name
53
57
  })).sort((a, b) => a.name.localeCompare(b.name));
54
58
  if (options.length === 0) {
55
59
  throw new Error("No workflows in this workspace. Create a workflow in Gravity Rail first.");
@@ -67,13 +71,7 @@ async function resolveOutboundWorkflowId(client, workspaceUuid, defaultWorkflowI
67
71
  async function findMemberByPhone(client, workspaceUuid, phone) {
68
72
  const searchDigits = phone.replace(/\D/g, "");
69
73
  for (let page = 1; page <= 5; page += 1) {
70
- const response = await client.getWorkspaceMembers(
71
- workspaceUuid,
72
- page,
73
- 50,
74
- void 0,
75
- searchDigits
76
- );
74
+ const response = await client.getWorkspaceMembers(workspaceUuid, page, 50, searchDigits);
77
75
  const match = response.items.find(
78
76
  (member) => member.phone && phonesMatchLast10(member.phone, phone)
79
77
  );
@@ -82,11 +80,156 @@ async function findMemberByPhone(client, workspaceUuid, phone) {
82
80
  }
83
81
  return null;
84
82
  }
83
+
84
+ // src/helpers/enrollment.ts
85
+ async function resolveMemberId(client, workspaceUuid, ref) {
86
+ if (typeof ref.memberId === "number") {
87
+ return ref.memberId;
88
+ }
89
+ if (ref.phone) {
90
+ const member = await findMemberByPhone(client, workspaceUuid, ref.phone);
91
+ if (member?.id) return member.id;
92
+ throw new Error("No member found for the provided phone number");
93
+ }
94
+ if (ref.externalId) {
95
+ const page = await client.getWorkspaceMembers(workspaceUuid, 1, 25, ref.externalId);
96
+ const match = page.items.find((m) => m.externalId === ref.externalId);
97
+ if (match?.id) return match.id;
98
+ throw new Error("No member found for the provided externalId");
99
+ }
100
+ throw new Error("Provide memberId, phone, or externalId");
101
+ }
102
+ async function findOrCreateMemberLabels(client, workspaceUuid, names, options = {}) {
103
+ const clean = names.map((n) => n.trim()).filter(Boolean);
104
+ if (clean.length === 0) return [];
105
+ const existing = await client.getMemberLabels(workspaceUuid);
106
+ const byName = new Map(existing.map((l) => [l.name.toLowerCase(), l]));
107
+ const ids = [];
108
+ for (const name of clean) {
109
+ const found = byName.get(name.toLowerCase());
110
+ if (found) {
111
+ ids.push(found.id);
112
+ continue;
113
+ }
114
+ const created = await client.createMemberLabel(workspaceUuid, {
115
+ name,
116
+ color: options.color ?? "#6366f1"
117
+ });
118
+ byName.set(name.toLowerCase(), created);
119
+ ids.push(created.id);
120
+ }
121
+ return ids;
122
+ }
123
+ async function upsertDataRecordForMember(client, workspaceUuid, dataTypeId, memberId, fieldValues) {
124
+ const existing = await client.getDataRecords(workspaceUuid, dataTypeId, {
125
+ memberId,
126
+ pageSize: 1
127
+ });
128
+ const record = existing.items?.[0];
129
+ const body = { dataTypeId, memberId, fieldValues };
130
+ if (record?.id) {
131
+ return client.updateDataRecord(workspaceUuid, dataTypeId, record.id, body);
132
+ }
133
+ return client.createDataRecord(workspaceUuid, dataTypeId, body);
134
+ }
135
+ async function enrollMemberInJourney(client, workspaceUuid, options) {
136
+ const memberId = await resolveMemberId(client, workspaceUuid, options);
137
+ const page = await client.getJourneys(workspaceUuid);
138
+ const journeys = page.items ?? [];
139
+ let journey;
140
+ if (options.journeyId != null) {
141
+ journey = journeys.find((j) => j.id === options.journeyId) ?? { id: options.journeyId };
142
+ } else {
143
+ journey = journeys[0];
144
+ }
145
+ if (!journey) {
146
+ throw new Error("No journey to enroll into: pass journeyId or create a journey");
147
+ }
148
+ const labelNames = (options.labels ?? []).map((l) => l.trim()).filter(Boolean);
149
+ if (labelNames.length > 0) {
150
+ const labelIds = await findOrCreateMemberLabels(client, workspaceUuid, labelNames, {
151
+ color: options.labelColor
152
+ });
153
+ const member = await client.getWorkspaceMember(workspaceUuid, memberId);
154
+ const existingIds = (member.labels ?? []).map((l) => l.id).filter((id) => typeof id === "number");
155
+ const mergedIds = Array.from(/* @__PURE__ */ new Set([...existingIds, ...labelIds]));
156
+ await client.updateMember(workspaceUuid, memberId, { labelIds: mergedIds });
157
+ }
158
+ try {
159
+ await client.enrollMembers(workspaceUuid, journey.id, { memberId });
160
+ } catch (error) {
161
+ if (!(error instanceof ApiError && error.statusCode === 409)) {
162
+ throw error;
163
+ }
164
+ }
165
+ return { journeyId: journey.id, journeyName: journey.name, memberId, appliedLabels: labelNames };
166
+ }
167
+
168
+ // src/helpers/webhooks.ts
169
+ var WEBHOOK_SIGNATURE_HEADER = "X-Webhook-Signature";
170
+ var encoder = new TextEncoder();
171
+ async function hmacSha256Hex(secret, message) {
172
+ const key = await crypto.subtle.importKey(
173
+ "raw",
174
+ encoder.encode(secret),
175
+ { name: "HMAC", hash: "SHA-256" },
176
+ false,
177
+ ["sign"]
178
+ );
179
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message));
180
+ return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
181
+ }
182
+ function timingSafeEqualHex(a, b) {
183
+ if (a.length !== b.length) return false;
184
+ let mismatch = 0;
185
+ for (let i = 0; i < a.length; i += 1) {
186
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
187
+ }
188
+ return mismatch === 0;
189
+ }
190
+ async function signWebhookPayload(rawBody, secret, timestampSeconds) {
191
+ const digest = await hmacSha256Hex(secret, `${timestampSeconds}.${rawBody}`);
192
+ return `t=${timestampSeconds},v1=${digest}`;
193
+ }
194
+ async function verifyWebhookSignature(rawBody, signatureHeader, secret, options = {}) {
195
+ if (!signatureHeader) {
196
+ return { ok: false, reason: "missing signature header" };
197
+ }
198
+ const parts = {};
199
+ for (const segment of signatureHeader.split(",")) {
200
+ const idx = segment.indexOf("=");
201
+ if (idx > 0) {
202
+ parts[segment.slice(0, idx).trim()] = segment.slice(idx + 1).trim();
203
+ }
204
+ }
205
+ const timestamp = Number(parts.t);
206
+ const provided = parts.v1;
207
+ if (!Number.isFinite(timestamp) || !provided) {
208
+ return { ok: false, reason: "malformed signature header" };
209
+ }
210
+ const tolerance = options.toleranceSeconds ?? 300;
211
+ const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
212
+ if (Math.abs(now - timestamp) > tolerance) {
213
+ return { ok: false, reason: "signature timestamp outside tolerance" };
214
+ }
215
+ const expected = await hmacSha256Hex(secret, `${timestamp}.${rawBody}`);
216
+ if (!timingSafeEqualHex(expected, provided)) {
217
+ return { ok: false, reason: "signature mismatch" };
218
+ }
219
+ return { ok: true };
220
+ }
85
221
  export {
222
+ WEBHOOK_SIGNATURE_HEADER,
223
+ enrollMemberInJourney,
86
224
  findMemberByPhone,
225
+ findOrCreateMemberLabels,
87
226
  listOutboundPhoneNumbers,
88
227
  normalizePhoneLast10,
228
+ resolveMemberId,
89
229
  resolveOutboundPhoneNumberId,
90
- resolveOutboundWorkflowId
230
+ resolveOutboundWorkflowId,
231
+ signWebhookPayload,
232
+ upsertDataRecordForMember,
233
+ verifyWebhookSignature
91
234
  };
92
235
  //# sourceMappingURL=helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/helpers/outbound.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 workflows = await client.getWorkflows(workspaceUuid, false);\n const options = workflows\n .filter((workflow) => !workflow.archived)\n .map((workflow) => ({\n id: workflow.id,\n name: 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(\n workspaceUuid,\n page,\n 50,\n undefined,\n searchDigits\n );\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"],"mappings":";AAuBO,SAAS,qBAAqB,OAAuB;AACzD,QAAM,SAAS,MAAM,QAAQ,OAAO,EAAE;AACtC,SAAO,OAAO,UAAU,KAAK,OAAO,MAAM,GAAG,IAAI;AACpD;AAEA,SAAS,kBAAkB,GAAW,GAAoB;AACvD,QAAM,KAAK,qBAAqB,CAAC;AACjC,QAAM,KAAK,qBAAqB,CAAC;AACjC,MAAI,CAAC,MAAM,CAAC,GAAI,QAAO;AACvB,MAAI,GAAG,UAAU,MAAM,GAAG,UAAU,IAAI;AACrC,WAAO,OAAO;AAAA,EACjB;AACA,SAAO,OAAO;AACjB;AAEA,eAAsB,yBACnB,QACA,eACsC;AACtC,QAAM,CAAC,QAAQ,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC1C,OAAO,gBAAgB,eAAe,EAAE,YAAY,MAAM,CAAC;AAAA,IAC3D,OAAO,qBAAqB,aAAa;AAAA,EAC5C,CAAC;AAED,QAAM,cAAc,OAChB,OAAO,CAAC,UAAU,MAAM,eAAe,CAAC,MAAM,UAAU,EACxD,IAAI,CAAC,WAAW;AAAA,IACd,IAAI,MAAM;AAAA,IACV,QAAQ,MAAM;AAAA,IACd,MAAM,MAAM;AAAA,EACf,EAAE;AAEL,MAAI,uBAAuB,SAAS,wBAAwB;AAC5D,MACG,yBAAyB,QACzB,CAAC,YAAY,KAAK,CAAC,UAAU,MAAM,OAAO,oBAAoB,GAC/D;AACC,2BAAuB,YAAY,CAAC,GAAG,MAAM;AAAA,EAChD;AACA,MAAI,yBAAyB,MAAM;AAChC,2BAAuB,YAAY,CAAC,GAAG,MAAM;AAAA,EAChD;AAEA,SAAO,EAAE,QAAQ,aAAa,qBAAqB;AACtD;AAEA,eAAsB,6BACnB,QACA,eACA,gBACgB;AAChB,QAAM,EAAE,QAAQ,qBAAqB,IAAI,MAAM,yBAAyB,QAAQ,aAAa;AAE7F,MAAI,OAAO,WAAW,GAAG;AACtB,UAAM,IAAI;AAAA,MACP;AAAA,IACH;AAAA,EACH;AAEA,QAAM,cAAc,eAAe;AACnC,QAAM,YACH,eAAe,wBAAwB,eAAe,iBAAiB,OAAO,CAAC,GAAG;AAErF,MAAI,cAAc,UAAa,CAAC,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,SAAS,GAAG;AAC7E,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACrE;AAEA,SAAO;AACV;AAEA,eAAsB,0BACnB,QACA,eACA,mBACA,UACgB;AAChB,QAAM,YAAY,MAAM,OAAO,aAAa,eAAe,KAAK;AAChE,QAAM,UAAU,UACZ,OAAO,CAAC,aAAa,CAAC,SAAS,QAAQ,EACvC,IAAI,CAAC,cAAc;AAAA,IACjB,IAAI,SAAS;AAAA,IACb,MAAM,SAAS;AAAA,EAClB,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAE/C,MAAI,QAAQ,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,0EAA0E;AAAA,EAC7F;AAEA,MAAI,kBAAkB;AACtB,MAAI,CAAC,QAAQ,KAAK,CAAC,aAAa,SAAS,OAAO,eAAe,GAAG;AAC/D,sBAAkB,QAAQ,CAAC,GAAG,MAAM;AAAA,EACvC;AAEA,QAAM,YAAY,YAAY;AAE9B,MAAI,CAAC,QAAQ,KAAK,CAAC,aAAa,SAAS,OAAO,SAAS,GAAG;AACzD,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACxD;AAEA,SAAO;AACV;AAEA,eAAsB,kBACnB,QACA,eACA,OACuB;AACvB,QAAM,eAAe,MAAM,QAAQ,OAAO,EAAE;AAE5C,WAAS,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG;AACtC,UAAM,WAAW,MAAM,OAAO;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACH;AACA,UAAM,QAAQ,SAAS,MAAM;AAAA,MAC1B,CAAC,WAAW,OAAO,SAAS,kBAAkB,OAAO,OAAO,KAAK;AAAA,IACpE;AACA,QAAI,MAAO,QAAO;AAElB,QAAI,QAAQ,SAAS,WAAY;AAAA,EACpC;AAEA,SAAO;AACV;","names":[]}
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":[]}