ab-ecosystem-cpass 1.0.0 → 1.0.6

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 (86) hide show
  1. package/README.md +121 -21
  2. package/dist/client.d.ts +38 -0
  3. package/dist/client.js +49 -5
  4. package/dist/client.js.map +1 -1
  5. package/dist/http.d.ts +15 -0
  6. package/dist/http.js +21 -1
  7. package/dist/http.js.map +1 -1
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.js +24 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal/bytes.d.ts +38 -0
  12. package/dist/internal/bytes.js +84 -0
  13. package/dist/internal/bytes.js.map +1 -0
  14. package/dist/internal/device.d.ts +24 -0
  15. package/dist/internal/device.js +91 -0
  16. package/dist/internal/device.js.map +1 -0
  17. package/dist/internal/md5.d.ts +4 -0
  18. package/dist/internal/md5.js +109 -0
  19. package/dist/internal/md5.js.map +1 -0
  20. package/dist/internal/receiver-file.d.ts +106 -0
  21. package/dist/internal/receiver-file.js +128 -0
  22. package/dist/internal/receiver-file.js.map +1 -0
  23. package/dist/realtime/client.d.ts +85 -0
  24. package/dist/realtime/client.js +126 -0
  25. package/dist/realtime/client.js.map +1 -0
  26. package/dist/realtime/events.d.ts +147 -0
  27. package/dist/realtime/events.js +3 -0
  28. package/dist/realtime/events.js.map +1 -0
  29. package/dist/realtime/inbox.d.ts +173 -0
  30. package/dist/realtime/inbox.js +263 -0
  31. package/dist/realtime/inbox.js.map +1 -0
  32. package/dist/realtime/index.d.ts +16 -0
  33. package/dist/realtime/index.js +37 -0
  34. package/dist/realtime/index.js.map +1 -0
  35. package/dist/resources/audience/contacts.d.ts +19 -3
  36. package/dist/resources/audience/contacts.js +19 -3
  37. package/dist/resources/audience/contacts.js.map +1 -1
  38. package/dist/resources/audience/inbox-media.d.ts +92 -0
  39. package/dist/resources/audience/inbox-media.js +127 -0
  40. package/dist/resources/audience/inbox-media.js.map +1 -0
  41. package/dist/resources/audience/notifications.d.ts +171 -0
  42. package/dist/resources/audience/notifications.js +215 -0
  43. package/dist/resources/audience/notifications.js.map +1 -0
  44. package/dist/resources/audience/scheduled-messages.d.ts +234 -0
  45. package/dist/resources/audience/scheduled-messages.js +259 -0
  46. package/dist/resources/audience/scheduled-messages.js.map +1 -0
  47. package/dist/resources/campaign-audience.d.ts +132 -0
  48. package/dist/resources/campaign-audience.js +224 -0
  49. package/dist/resources/campaign-audience.js.map +1 -0
  50. package/dist/resources/campaign-report-exports.d.ts +14 -0
  51. package/dist/resources/campaign-report-exports.js +28 -0
  52. package/dist/resources/campaign-report-exports.js.map +1 -0
  53. package/dist/resources/campaign-reports.d.ts +15 -0
  54. package/dist/resources/campaign-reports.js +25 -0
  55. package/dist/resources/campaign-reports.js.map +1 -0
  56. package/dist/resources/campaigns.d.ts +239 -0
  57. package/dist/resources/campaigns.js +300 -0
  58. package/dist/resources/campaigns.js.map +1 -0
  59. package/dist/types/audience.d.ts +93 -1
  60. package/dist/types/campaign.d.ts +229 -0
  61. package/dist/types/campaign.js +3 -0
  62. package/dist/types/campaign.js.map +1 -0
  63. package/dist/types/contact-filter.d.ts +3 -2
  64. package/dist/types/contact-filter.js +3 -2
  65. package/dist/types/contact-filter.js.map +1 -1
  66. package/dist/types/inbox.d.ts +391 -0
  67. package/dist/types/inbox.js +16 -0
  68. package/dist/types/inbox.js.map +1 -0
  69. package/dist/types/index.d.ts +3 -0
  70. package/dist/types/index.js +5 -0
  71. package/dist/types/index.js.map +1 -1
  72. package/dist/types/scheduled-message.d.ts +221 -0
  73. package/dist/types/scheduled-message.js +16 -0
  74. package/dist/types/scheduled-message.js.map +1 -0
  75. package/docs/audience/contacts.md +34 -4
  76. package/docs/audience/notifications.md +263 -0
  77. package/docs/audience/scheduled-messages.md +557 -0
  78. package/docs/campaign/campaigns.md +489 -0
  79. package/docs/contact-filter.md +2 -1
  80. package/docs/inbox/chats.md +356 -0
  81. package/docs/inbox/connection.md +171 -0
  82. package/docs/inbox/media.md +202 -0
  83. package/docs/inbox/messages.md +338 -0
  84. package/docs/inbox/referrals.md +72 -0
  85. package/docs/inbox/sessions.md +74 -0
  86. package/package.json +28 -2
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScheduledMessages = void 0;
4
+ const paths_1 = require("../paths");
5
+ const BASE = `${paths_1.AUDIENCE}/scheduled-messages`;
6
+ /**
7
+ * Scheduled messages: a send with a time, and optionally a condition checked
8
+ * immediately before it goes out.
9
+ *
10
+ * ### What the condition is for
11
+ *
12
+ * A drip is easy to start and awkward to stop. Schedule five messages over two
13
+ * weeks, your lead converts after the third, and the last two still go out --
14
+ * now addressed to a customer, as a pitch. Cancelling them means somebody
15
+ * noticing in time.
16
+ *
17
+ * So each occurrence re-reads the contact a moment before sending and stands
18
+ * down if they no longer match. Keep a `stage` custom field on your contacts,
19
+ * condition the series on `stage equal to Lead`, and set `stage` to `Customer`
20
+ * when they convert: the rest of the sequence cancels itself. No cancellation
21
+ * call, no scheduled-message ids to track.
22
+ *
23
+ * ```ts
24
+ * const nurture = await crm.scheduledMessages.createSeries({
25
+ * title: 'Lead nurture',
26
+ * channel: channelId,
27
+ * contact: { id: contactId },
28
+ * timezone: 'Asia/Kolkata',
29
+ * starts_at: '2026-10-13T10:00:00',
30
+ * conditions: {
31
+ * conditions: [
32
+ * { field_id: 'stage', field_type: 'dropdown', condition: 'equal to', value: 'Lead' },
33
+ * ],
34
+ * },
35
+ * on_condition_fail: 'cancel_series',
36
+ * steps: [
37
+ * { after: { value: 0, unit: 'days' }, content: intro },
38
+ * { after: { value: 3, unit: 'days' }, content: caseStudy },
39
+ * { after: { value: 6, unit: 'days' }, content: pricing },
40
+ * ],
41
+ * });
42
+ *
43
+ * // Later, when the lead converts -- this is the whole of "stop the drip":
44
+ * await crm.contacts.update(contactId, { custom_field_hash: { stage: 'Customer' } });
45
+ * ```
46
+ *
47
+ * ### Two things that will catch you out
48
+ *
49
+ * **Templates, not free text.** WhatsApp only allows free-form messages within
50
+ * 24 hours of the contact's last inbound message, and a drip is by construction
51
+ * outside that window from the second message onward. Every step but the first
52
+ * has to be an approved template or the provider refuses it; `create` rejects
53
+ * free text past the window rather than letting you discover it on day four.
54
+ *
55
+ * **A series is expanded, not recurring.** `createSeries` writes one row per
56
+ * message straight away, so you can list them, move one, or cancel one without
57
+ * touching the others. There is no rule being evaluated later and nothing to
58
+ * recompute -- which is also why `until` has to be a real end rather than
59
+ * "forever".
60
+ *
61
+ * Scoping: scheduled messages belong to an account the same way contacts do --
62
+ * reads match the caller or the account they act for, writes land on the
63
+ * caller's own.
64
+ */
65
+ class ScheduledMessages {
66
+ http;
67
+ constructor(http) {
68
+ this.http = http;
69
+ }
70
+ /**
71
+ * `GET /v1/audience/scheduled-messages` -- offset paginated, soonest first
72
+ * among `pending`, newest first among the rest. `search` matches the title.
73
+ *
74
+ * Narrow it with `contact` for one person's queue, `series` for one drip's
75
+ * occurrences, or `status` to answer "what is still waiting" (`pending`)
76
+ * versus "what stood down and why" (`skipped`).
77
+ *
78
+ * Scope: `messages:read`.
79
+ */
80
+ list(query = {}) {
81
+ return this.http.request('GET', BASE, { query });
82
+ }
83
+ /**
84
+ * `GET /v1/audience/scheduled-messages/:id` -- one occurrence, with its
85
+ * `result` if it has reached a terminal status.
86
+ *
87
+ * Scope: `messages:read`.
88
+ */
89
+ get(id) {
90
+ return this.http.request('GET', `${BASE}/${(0, paths_1.seg)(id)}`);
91
+ }
92
+ /**
93
+ * `POST /v1/audience/scheduled-messages` -- 201 with the created occurrence.
94
+ *
95
+ * One message, one time. `series` on the response is `null` and `occurrence`
96
+ * is `1`.
97
+ *
98
+ * ```ts
99
+ * await crm.scheduledMessages.create({
100
+ * title: 'Pricing follow-up',
101
+ * channel: channelId,
102
+ * contact: { id: contactId },
103
+ * timezone: 'Asia/Kolkata',
104
+ * scheduled_at: '2026-10-15T10:00:00', // 10:00 IST
105
+ * content: { type: 'text', text: { body: 'Following up on the pricing.' } },
106
+ * conditions: {
107
+ * conditions: [
108
+ * { field_id: 'stage', field_type: 'dropdown', condition: 'equal to', value: 'Lead' },
109
+ * ],
110
+ * },
111
+ * });
112
+ * ```
113
+ *
114
+ * `on_condition_fail` defaults to `skip` here, which for a single message is
115
+ * the only reading that means anything -- there is no series to cancel.
116
+ *
117
+ * 422 if `scheduled_at` is in the past, if `timezone` is not an IANA zone, if
118
+ * the condition names a field type the comparison cannot apply to, or if the
119
+ * message is free text and the send falls outside the channel's reply window.
120
+ *
121
+ * Scope: `messages:write`.
122
+ */
123
+ create(input) {
124
+ return this.http.request('POST', BASE, { body: input });
125
+ }
126
+ /**
127
+ * `POST /v1/audience/scheduled-messages/series` -- 201 with the series and
128
+ * every occurrence it expanded into.
129
+ *
130
+ * For a sequence whose steps carry different copy. `after` is measured from
131
+ * `starts_at`, not from the previous step, so changing one delay leaves the
132
+ * others where they were.
133
+ *
134
+ * ```ts
135
+ * const series = await crm.scheduledMessages.createSeries({
136
+ * title: 'Lead nurture -- 2 weeks',
137
+ * channel: channelId,
138
+ * contact: { id: contactId },
139
+ * timezone: 'Asia/Kolkata',
140
+ * starts_at: '2026-10-13T10:00:00',
141
+ * conditions: { conditions: [stillALead] },
142
+ * steps: [
143
+ * { after: { value: 0, unit: 'days' }, content: intro },
144
+ * { after: { value: 3, unit: 'days' }, content: caseStudy },
145
+ * { after: { value: 6, unit: 'days' }, content: pricing },
146
+ * { after: { value: 9, unit: 'days' }, content: demo },
147
+ * { after: { value: 12, unit: 'days' }, content: lastCall },
148
+ * ],
149
+ * });
150
+ *
151
+ * series.occurrences.length; // 5
152
+ * ```
153
+ *
154
+ * A step may carry its own `conditions`, which are checked **in addition to**
155
+ * the series' -- both have to pass. Leave them out to inherit.
156
+ *
157
+ * `on_condition_fail` defaults to `cancel_series`.
158
+ *
159
+ * Scope: `messages:write`.
160
+ */
161
+ createSeries(input) {
162
+ return this.http.request('POST', `${BASE}/series`, { body: input });
163
+ }
164
+ /**
165
+ * `POST /v1/audience/scheduled-messages/series` -- the same route, for the
166
+ * case where every occurrence sends the same message.
167
+ *
168
+ * ```ts
169
+ * await crm.scheduledMessages.createRepeating({
170
+ * title: 'Weekly check-in',
171
+ * channel: channelId,
172
+ * contact: { id: contactId },
173
+ * timezone: 'Asia/Kolkata',
174
+ * starts_at: '2026-10-13T10:00:00',
175
+ * repeat: { every: 3, unit: 'days', until: '2026-10-27T10:00:00' },
176
+ * content: nudgeTemplate,
177
+ * conditions: { conditions: [stillALead] },
178
+ * });
179
+ * ```
180
+ *
181
+ * `until` is inclusive and has to be a real end: the expansion is written as
182
+ * rows, so an open-ended cadence has nothing to expand into. The result is the
183
+ * same `ScheduledMessageSeries` as `createSeries` -- once created, a repeating
184
+ * schedule and a stepped one are indistinguishable.
185
+ *
186
+ * 422 if the expansion would exceed 365 occurrences.
187
+ *
188
+ * Scope: `messages:write`.
189
+ */
190
+ createRepeating(input) {
191
+ return this.http.request('POST', `${BASE}/series`, { body: input });
192
+ }
193
+ /**
194
+ * `PATCH /v1/audience/scheduled-messages/:id` -- moves one occurrence.
195
+ *
196
+ * Only a `pending` one. Rescheduling something already `sent`, `skipped` or
197
+ * `cancelled` is a 409 rather than a silent no-op -- legacy answered
198
+ * `{ success: true }` either way, which made a failed reschedule look like a
199
+ * successful one.
200
+ *
201
+ * Moving one occurrence does not move the rest of its series.
202
+ *
203
+ * ```ts
204
+ * await crm.scheduledMessages.reschedule(id, { scheduled_at: '2026-10-16T14:00:00' });
205
+ * ```
206
+ *
207
+ * Scope: `messages:write`.
208
+ */
209
+ reschedule(id, input) {
210
+ return this.http.request('PATCH', `${BASE}/${(0, paths_1.seg)(id)}`, { body: input });
211
+ }
212
+ /**
213
+ * `DELETE /v1/audience/scheduled-messages/:id` -- 200 with the cancelled row.
214
+ *
215
+ * The row is kept rather than deleted, at `status: 'cancelled'`, so a schedule
216
+ * that did not send still shows why. Use `cancelSeries` to stop a whole drip;
217
+ * this stops one message and leaves the others queued.
218
+ *
219
+ * Scope: `messages:write`.
220
+ */
221
+ cancel(id) {
222
+ return this.http.request('DELETE', `${BASE}/${(0, paths_1.seg)(id)}`);
223
+ }
224
+ /**
225
+ * `DELETE /v1/audience/scheduled-messages/series/:series` -- 200 with the
226
+ * number of occurrences stood down.
227
+ *
228
+ * Cancels every `pending` occurrence; anything already sent is untouched and
229
+ * not counted. Idempotent -- a second call cancels nothing and answers `0`.
230
+ *
231
+ * Usually you do not need this. If the series carries a condition, changing
232
+ * the contact so it no longer matches is the better way to stop a drip: it
233
+ * leaves the reason on the record, and it works for every contact in the same
234
+ * sequence rather than one series at a time.
235
+ *
236
+ * Scope: `messages:write`.
237
+ */
238
+ cancelSeries(series) {
239
+ return this.http.request('DELETE', `${BASE}/series/${(0, paths_1.seg)(series)}`);
240
+ }
241
+ /**
242
+ * `POST /v1/audience/scheduled-messages/:id/send-now` -- 200 with the row.
243
+ *
244
+ * Brings a `pending` occurrence forward to immediately. The condition is still
245
+ * checked, because the condition is the point -- a send-now on a contact who
246
+ * no longer matches comes back `skipped`, not `sent`.
247
+ *
248
+ * It does not duplicate: the occurrence is moved, not copied, so the original
249
+ * time no longer fires. Legacy's equivalent scheduled a second job without
250
+ * cancelling the first and delivered the message twice.
251
+ *
252
+ * Scope: `messages:write`.
253
+ */
254
+ sendNow(id) {
255
+ return this.http.request('POST', `${BASE}/${(0, paths_1.seg)(id)}/send-now`);
256
+ }
257
+ }
258
+ exports.ScheduledMessages = ScheduledMessages;
259
+ //# sourceMappingURL=scheduled-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-messages.js","sourceRoot":"","sources":["../../../src/resources/audience/scheduled-messages.ts"],"names":[],"mappings":";;;AAYA,oCAAyC;AAEzC,MAAM,IAAI,GAAG,GAAG,gBAAQ,qBAAqB,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,MAAa,iBAAiB;IACC;IAA7B,YAA6B,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD;;;;;;;;;OASG;IACH,IAAI,CAAC,QAA6C,EAAE;QAClD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAA8B,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC;IAED;;;;;OAKG;IACH,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAmB,KAAK,EAAE,GAAG,IAAI,IAAI,IAAA,WAAG,EAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,MAAM,CAAC,KAAkC;QACvC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAmB,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,YAAY,CAAC,KAAiC;QAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAyB,MAAM,EAAE,GAAG,IAAI,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,eAAe,CAAC,KAAmC;QACjD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAyB,MAAM,EAAE,GAAG,IAAI,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,EAAU,EAAE,KAA6B;QAClD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAmB,OAAO,EAAE,GAAG,IAAI,IAAI,IAAA,WAAG,EAAC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAmB,QAAQ,EAAE,GAAG,IAAI,IAAI,IAAA,WAAG,EAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,MAAc;QACzB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAqB,QAAQ,EAAE,GAAG,IAAI,WAAW,IAAA,WAAG,EAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAU;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAmB,MAAM,EAAE,GAAG,IAAI,IAAI,IAAA,WAAG,EAAC,EAAE,CAAC,WAAW,CAAC,CAAC;IACpF,CAAC;CACF;AAtMD,8CAsMC"}
@@ -0,0 +1,132 @@
1
+ import type { CampaignReceiver } from '../internal/receiver-file';
2
+ /**
3
+ * Most recipients one bulk campaign may carry.
4
+ *
5
+ * The same ceiling the panel has always enforced. It is not arbitrary: the list
6
+ * is encrypted into a single file and read back into memory by the campaign
7
+ * manager, so this is the point at which "one campaign" stops being a sensible
8
+ * unit of work.
9
+ */
10
+ export declare const MAX_BULK_RECEIVERS = 1000000;
11
+ /**
12
+ * How a bulk audience is described.
13
+ *
14
+ * Four shapes, because there are four ways a person actually has this data to
15
+ * hand. All of them end as the same encrypted file; the difference is only how
16
+ * much parsing the SDK does on the way.
17
+ */
18
+ export type BulkAudienceInput =
19
+ /**
20
+ * Just numbers, with no per-recipient variables.
21
+ *
22
+ * Accepts an array, or one string holding many -- a textarea's contents,
23
+ * separated by commas, newlines, semicolons or spaces, in any combination.
24
+ */
25
+ {
26
+ numbers: string | readonly string[];
27
+ dedupe?: boolean;
28
+ }
29
+ /**
30
+ * Raw CSV or TSV text whose first row is the header.
31
+ *
32
+ * `recipientColumn` names the header holding the number; every column,
33
+ * including that one, also becomes a template variable. The delimiter is
34
+ * detected from the header row unless you pass one.
35
+ */
36
+ | {
37
+ csv: string;
38
+ recipientColumn: string;
39
+ delimiter?: string;
40
+ dedupe?: boolean;
41
+ }
42
+ /**
43
+ * Rows you have already parsed -- from your own CSV reader, a spreadsheet API,
44
+ * or a database query.
45
+ */
46
+ | {
47
+ rows: readonly Record<string, unknown>[];
48
+ recipientColumn: string;
49
+ dedupe?: boolean;
50
+ }
51
+ /** Receivers you have already built. The escape hatch; nothing is inferred. */
52
+ | {
53
+ receivers: readonly CampaignReceiver[];
54
+ };
55
+ /** What resolving an audience produced, including what it discarded. */
56
+ export interface ResolvedAudience {
57
+ receivers: CampaignReceiver[];
58
+ /** Entries that carried no usable digits and were skipped. */
59
+ skipped: number;
60
+ /** Entries dropped because an earlier entry had the same number. */
61
+ duplicates: number;
62
+ }
63
+ /**
64
+ * Normalises a column name into a template variable id.
65
+ *
66
+ * Lower-cased with every non-alphanumeric character replaced by `_`, which is
67
+ * what the panel's CSV reader and its paste-a-table path both did -- so a header
68
+ * of `First Name` is the variable `first_name` here exactly as it was there, and
69
+ * templates built against the old behaviour keep working.
70
+ *
71
+ * A blank header becomes `empty_<index>` rather than `_`, so two unnamed columns
72
+ * do not collapse into one variable.
73
+ */
74
+ export declare const normaliseColumn: (header: unknown, index: number) => string;
75
+ /**
76
+ * Pulls the numbers out of one string, or tidies an array of them.
77
+ *
78
+ * Splits on commas, newlines, carriage returns, tabs, semicolons and spaces --
79
+ * all of them, in any mixture. The panel chose a single separator per paste
80
+ * (commas if the text contained any, otherwise newlines), which quietly mangled
81
+ * the common case of pasting a column from a spreadsheet into a field that
82
+ * already had comma-separated numbers: the newlines were never split on, so two
83
+ * numbers merged into one 24-digit number and the send went nowhere. Splitting on
84
+ * every separator cannot produce that, and for input with one separator it
85
+ * behaves identically.
86
+ */
87
+ export declare const normaliseNumbers: (input: string | readonly string[]) => string[];
88
+ /**
89
+ * Splits delimited text into a header and rows, RFC 4180 style.
90
+ *
91
+ * A proper parser rather than `split(',')`, because a quoted field may contain
92
+ * the delimiter, a newline, or an escaped quote -- and a currency-formatted
93
+ * variable (`"1,299"`) is exactly the sort of value that appears in these files.
94
+ * Splitting naively turns one such row into two bad ones.
95
+ *
96
+ * Values stay strings. The panel parsed with `dynamicTyping`, which coerced
97
+ * numeric-looking cells to JavaScript numbers -- and that silently corrupted the
98
+ * one column that matters: a 16-digit number exceeds `Number.MAX_SAFE_INTEGER`
99
+ * and comes back with its last digits changed, while a number written with a
100
+ * leading zero loses it. Keeping text as text avoids both.
101
+ */
102
+ export declare const parseDelimited: (text: string, delimiter?: string) => {
103
+ headers: string[];
104
+ rows: Record<string, string>[];
105
+ };
106
+ /**
107
+ * Turns parsed rows into receivers.
108
+ *
109
+ * Every column becomes a variable, including the recipient column itself -- which
110
+ * is intentional and matches the panel: a template that greets someone by the
111
+ * number they were messaged on still works.
112
+ *
113
+ * A row whose recipient column holds no digits is skipped rather than failing the
114
+ * whole batch. In a file of ten thousand rows, a handful of blanks and a trailing
115
+ * footer line are normal, and refusing the campaign over them would mean the
116
+ * caller has to clean a file they may not control. The count comes back in
117
+ * {@link ResolvedAudience.skipped} so the omission is visible rather than silent.
118
+ */
119
+ export declare const buildReceivers: (rows: readonly Record<string, unknown>[], recipientColumn: string, options?: {
120
+ dedupe?: boolean;
121
+ }) => ResolvedAudience;
122
+ /**
123
+ * Resolves any of the four audience shapes into one receiver list.
124
+ *
125
+ * De-duplicates by number unless `dedupe: false`. On by default because the cost
126
+ * of a duplicate here is not a wasted row in a table -- it is the same person
127
+ * receiving the same marketing message twice, billed twice, and more likely to
128
+ * report the sender. A caller who genuinely wants two sends to one number can
129
+ * say so.
130
+ */
131
+ export declare const resolveAudience: (input: BulkAudienceInput) => ResolvedAudience;
132
+ export type { CampaignReceiver };
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAudience = exports.buildReceivers = exports.parseDelimited = exports.normaliseNumbers = exports.normaliseColumn = exports.MAX_BULK_RECEIVERS = void 0;
4
+ /**
5
+ * Most recipients one bulk campaign may carry.
6
+ *
7
+ * The same ceiling the panel has always enforced. It is not arbitrary: the list
8
+ * is encrypted into a single file and read back into memory by the campaign
9
+ * manager, so this is the point at which "one campaign" stops being a sensible
10
+ * unit of work.
11
+ */
12
+ exports.MAX_BULK_RECEIVERS = 1_000_000;
13
+ /** Digits only: no `+`, no spaces, no dashes, no parentheses. */
14
+ const digitsOf = (value) => (value === null || value === undefined ? '' : String(value).replace(/\D/g, ''));
15
+ /**
16
+ * Normalises a column name into a template variable id.
17
+ *
18
+ * Lower-cased with every non-alphanumeric character replaced by `_`, which is
19
+ * what the panel's CSV reader and its paste-a-table path both did -- so a header
20
+ * of `First Name` is the variable `first_name` here exactly as it was there, and
21
+ * templates built against the old behaviour keep working.
22
+ *
23
+ * A blank header becomes `empty_<index>` rather than `_`, so two unnamed columns
24
+ * do not collapse into one variable.
25
+ */
26
+ const normaliseColumn = (header, index) => {
27
+ const name = String(header ?? '').trim();
28
+ if (!name)
29
+ return `empty_${index}`;
30
+ return name.toLowerCase().replace(/[^a-z0-9]/g, '_');
31
+ };
32
+ exports.normaliseColumn = normaliseColumn;
33
+ /**
34
+ * Pulls the numbers out of one string, or tidies an array of them.
35
+ *
36
+ * Splits on commas, newlines, carriage returns, tabs, semicolons and spaces --
37
+ * all of them, in any mixture. The panel chose a single separator per paste
38
+ * (commas if the text contained any, otherwise newlines), which quietly mangled
39
+ * the common case of pasting a column from a spreadsheet into a field that
40
+ * already had comma-separated numbers: the newlines were never split on, so two
41
+ * numbers merged into one 24-digit number and the send went nowhere. Splitting on
42
+ * every separator cannot produce that, and for input with one separator it
43
+ * behaves identically.
44
+ */
45
+ const normaliseNumbers = (input) => {
46
+ const parts = typeof input === 'string' ? input.split(/[\s,;]+/) : input;
47
+ return parts.map(digitsOf).filter((value) => value.length > 0);
48
+ };
49
+ exports.normaliseNumbers = normaliseNumbers;
50
+ /**
51
+ * Splits delimited text into a header and rows, RFC 4180 style.
52
+ *
53
+ * A proper parser rather than `split(',')`, because a quoted field may contain
54
+ * the delimiter, a newline, or an escaped quote -- and a currency-formatted
55
+ * variable (`"1,299"`) is exactly the sort of value that appears in these files.
56
+ * Splitting naively turns one such row into two bad ones.
57
+ *
58
+ * Values stay strings. The panel parsed with `dynamicTyping`, which coerced
59
+ * numeric-looking cells to JavaScript numbers -- and that silently corrupted the
60
+ * one column that matters: a 16-digit number exceeds `Number.MAX_SAFE_INTEGER`
61
+ * and comes back with its last digits changed, while a number written with a
62
+ * leading zero loses it. Keeping text as text avoids both.
63
+ */
64
+ const parseDelimited = (text, delimiter) => {
65
+ const rows = splitRows(text, delimiter ?? detectDelimiter(text));
66
+ const headerRow = rows.shift();
67
+ if (!headerRow)
68
+ return { headers: [], rows: [] };
69
+ const headers = headerRow.map(exports.normaliseColumn);
70
+ const parsed = rows
71
+ // A trailing newline, or a blank line in the middle, is not a record.
72
+ .filter((cells) => cells.some((cell) => cell.trim() !== ''))
73
+ .map((cells) => {
74
+ const row = {};
75
+ headers.forEach((header, index) => {
76
+ row[header] = cells[index] ?? '';
77
+ });
78
+ return row;
79
+ });
80
+ return { headers, rows: parsed };
81
+ };
82
+ exports.parseDelimited = parseDelimited;
83
+ /** Tab if the header row has one, else semicolon, else comma. */
84
+ const detectDelimiter = (text) => {
85
+ const firstLine = text.slice(0, text.search(/\r?\n/) === -1 ? undefined : text.search(/\r?\n/));
86
+ if (firstLine.includes('\t'))
87
+ return '\t';
88
+ if (firstLine.includes(';') && !firstLine.includes(','))
89
+ return ';';
90
+ return ',';
91
+ };
92
+ /** The parser proper: quotes, doubled quotes inside them, and newlines within a field. */
93
+ const splitRows = (text, delimiter) => {
94
+ const rows = [];
95
+ let row = [];
96
+ let field = '';
97
+ let quoted = false;
98
+ for (let i = 0; i < text.length; i += 1) {
99
+ const char = text[i];
100
+ if (quoted) {
101
+ if (char !== '"') {
102
+ field += char;
103
+ }
104
+ else if (text[i + 1] === '"') {
105
+ // A doubled quote inside a quoted field is one literal quote.
106
+ field += '"';
107
+ i += 1;
108
+ }
109
+ else {
110
+ quoted = false;
111
+ }
112
+ continue;
113
+ }
114
+ if (char === '"' && field === '') {
115
+ quoted = true;
116
+ }
117
+ else if (char === delimiter) {
118
+ row.push(field);
119
+ field = '';
120
+ }
121
+ else if (char === '\n' || char === '\r') {
122
+ // Either line ending, and CRLF counts once.
123
+ if (char === '\r' && text[i + 1] === '\n')
124
+ i += 1;
125
+ row.push(field);
126
+ rows.push(row);
127
+ row = [];
128
+ field = '';
129
+ }
130
+ else {
131
+ field += char;
132
+ }
133
+ }
134
+ if (field !== '' || row.length) {
135
+ row.push(field);
136
+ rows.push(row);
137
+ }
138
+ return rows;
139
+ };
140
+ /**
141
+ * Turns parsed rows into receivers.
142
+ *
143
+ * Every column becomes a variable, including the recipient column itself -- which
144
+ * is intentional and matches the panel: a template that greets someone by the
145
+ * number they were messaged on still works.
146
+ *
147
+ * A row whose recipient column holds no digits is skipped rather than failing the
148
+ * whole batch. In a file of ten thousand rows, a handful of blanks and a trailing
149
+ * footer line are normal, and refusing the campaign over them would mean the
150
+ * caller has to clean a file they may not control. The count comes back in
151
+ * {@link ResolvedAudience.skipped} so the omission is visible rather than silent.
152
+ */
153
+ const buildReceivers = (rows, recipientColumn, options = {}) => {
154
+ const column = (0, exports.normaliseColumn)(recipientColumn, 0);
155
+ const receivers = [];
156
+ const seen = new Set();
157
+ let skipped = 0;
158
+ let duplicates = 0;
159
+ for (const row of rows) {
160
+ // Accept the column as written or as normalised, so a caller passing
161
+ // `'First Name'` against already-normalised rows still resolves.
162
+ const raw = row[recipientColumn] ?? row[column];
163
+ const waba = digitsOf(raw);
164
+ if (!waba) {
165
+ skipped += 1;
166
+ continue;
167
+ }
168
+ if (options.dedupe !== false) {
169
+ if (seen.has(waba)) {
170
+ duplicates += 1;
171
+ continue;
172
+ }
173
+ seen.add(waba);
174
+ }
175
+ receivers.push({
176
+ waba,
177
+ custom_fields: Object.keys(row).map((key, index) => ({
178
+ id: (0, exports.normaliseColumn)(key, index),
179
+ value: row[key] === null || row[key] === undefined ? '' : String(row[key]),
180
+ })),
181
+ });
182
+ }
183
+ return { receivers, skipped, duplicates };
184
+ };
185
+ exports.buildReceivers = buildReceivers;
186
+ /**
187
+ * Resolves any of the four audience shapes into one receiver list.
188
+ *
189
+ * De-duplicates by number unless `dedupe: false`. On by default because the cost
190
+ * of a duplicate here is not a wasted row in a table -- it is the same person
191
+ * receiving the same marketing message twice, billed twice, and more likely to
192
+ * report the sender. A caller who genuinely wants two sends to one number can
193
+ * say so.
194
+ */
195
+ const resolveAudience = (input) => {
196
+ if ('receivers' in input) {
197
+ // Taken as given: a caller who built these chose the shape deliberately.
198
+ return { receivers: [...input.receivers], skipped: 0, duplicates: 0 };
199
+ }
200
+ if ('numbers' in input) {
201
+ const numbers = (0, exports.normaliseNumbers)(input.numbers);
202
+ const receivers = [];
203
+ const seen = new Set();
204
+ let duplicates = 0;
205
+ for (const waba of numbers) {
206
+ if (input.dedupe !== false) {
207
+ if (seen.has(waba)) {
208
+ duplicates += 1;
209
+ continue;
210
+ }
211
+ seen.add(waba);
212
+ }
213
+ receivers.push({ waba, custom_fields: [] });
214
+ }
215
+ const supplied = typeof input.numbers === 'string' ? numbers.length + countBlank(input.numbers) : input.numbers.length;
216
+ return { receivers, skipped: Math.max(0, supplied - numbers.length - duplicates), duplicates };
217
+ }
218
+ const rows = 'csv' in input ? (0, exports.parseDelimited)(input.csv, input.delimiter).rows : input.rows;
219
+ return (0, exports.buildReceivers)(rows, input.recipientColumn, { dedupe: input.dedupe });
220
+ };
221
+ exports.resolveAudience = resolveAudience;
222
+ /** Entries in a pasted string that held no digits at all, for the skipped count. */
223
+ const countBlank = (text) => text.split(/[\s,;]+/).filter((part) => part !== '' && digitsOf(part) === '').length;
224
+ //# sourceMappingURL=campaign-audience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"campaign-audience.js","sourceRoot":"","sources":["../../src/resources/campaign-audience.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { CampaignReportExport, CreateCampaignReportExportInput } from '../types/campaign';
2
+ import type { Page, PageQuery } from '../types/common';
3
+ import type { HttpClient } from '../http';
4
+ export declare class CampaignReportExports {
5
+ private readonly http;
6
+ constructor(http: HttpClient);
7
+ list(query?: Partial<PageQuery>): Promise<Page<CampaignReportExport>>;
8
+ /**
9
+ * One export. Poll this until `status` is `completed`, then read `fileUrl` --
10
+ * which expires, so download rather than storing the link.
11
+ */
12
+ get(id: string): Promise<CampaignReportExport>;
13
+ create(input: CreateCampaignReportExportInput): Promise<CampaignReportExport>;
14
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CampaignReportExports = void 0;
4
+ class CampaignReportExports {
5
+ http;
6
+ constructor(http) {
7
+ this.http = http;
8
+ }
9
+ list(query = {}) {
10
+ return this.http.request('GET', '/v1/campaign-report-exports', {
11
+ query,
12
+ });
13
+ }
14
+ /**
15
+ * One export. Poll this until `status` is `completed`, then read `fileUrl` --
16
+ * which expires, so download rather than storing the link.
17
+ */
18
+ get(id) {
19
+ return this.http.request('GET', `/v1/campaign-report-exports/${encodeURIComponent(id)}`);
20
+ }
21
+ create(input) {
22
+ return this.http.request('POST', '/v1/campaign-report-exports', {
23
+ body: input,
24
+ });
25
+ }
26
+ }
27
+ exports.CampaignReportExports = CampaignReportExports;
28
+ //# sourceMappingURL=campaign-report-exports.js.map