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.
- package/README.md +121 -21
- package/dist/client.d.ts +38 -0
- package/dist/client.js +49 -5
- package/dist/client.js.map +1 -1
- package/dist/http.d.ts +15 -0
- package/dist/http.js +21 -1
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/bytes.d.ts +38 -0
- package/dist/internal/bytes.js +84 -0
- package/dist/internal/bytes.js.map +1 -0
- package/dist/internal/device.d.ts +24 -0
- package/dist/internal/device.js +91 -0
- package/dist/internal/device.js.map +1 -0
- package/dist/internal/md5.d.ts +4 -0
- package/dist/internal/md5.js +109 -0
- package/dist/internal/md5.js.map +1 -0
- package/dist/internal/receiver-file.d.ts +106 -0
- package/dist/internal/receiver-file.js +128 -0
- package/dist/internal/receiver-file.js.map +1 -0
- package/dist/realtime/client.d.ts +85 -0
- package/dist/realtime/client.js +126 -0
- package/dist/realtime/client.js.map +1 -0
- package/dist/realtime/events.d.ts +147 -0
- package/dist/realtime/events.js +3 -0
- package/dist/realtime/events.js.map +1 -0
- package/dist/realtime/inbox.d.ts +173 -0
- package/dist/realtime/inbox.js +263 -0
- package/dist/realtime/inbox.js.map +1 -0
- package/dist/realtime/index.d.ts +16 -0
- package/dist/realtime/index.js +37 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/resources/audience/contacts.d.ts +19 -3
- package/dist/resources/audience/contacts.js +19 -3
- package/dist/resources/audience/contacts.js.map +1 -1
- package/dist/resources/audience/inbox-media.d.ts +92 -0
- package/dist/resources/audience/inbox-media.js +127 -0
- package/dist/resources/audience/inbox-media.js.map +1 -0
- package/dist/resources/audience/notifications.d.ts +171 -0
- package/dist/resources/audience/notifications.js +215 -0
- package/dist/resources/audience/notifications.js.map +1 -0
- package/dist/resources/audience/scheduled-messages.d.ts +234 -0
- package/dist/resources/audience/scheduled-messages.js +259 -0
- package/dist/resources/audience/scheduled-messages.js.map +1 -0
- package/dist/resources/campaign-audience.d.ts +132 -0
- package/dist/resources/campaign-audience.js +224 -0
- package/dist/resources/campaign-audience.js.map +1 -0
- package/dist/resources/campaign-report-exports.d.ts +14 -0
- package/dist/resources/campaign-report-exports.js +28 -0
- package/dist/resources/campaign-report-exports.js.map +1 -0
- package/dist/resources/campaign-reports.d.ts +15 -0
- package/dist/resources/campaign-reports.js +25 -0
- package/dist/resources/campaign-reports.js.map +1 -0
- package/dist/resources/campaigns.d.ts +239 -0
- package/dist/resources/campaigns.js +300 -0
- package/dist/resources/campaigns.js.map +1 -0
- package/dist/types/audience.d.ts +93 -1
- package/dist/types/campaign.d.ts +229 -0
- package/dist/types/campaign.js +3 -0
- package/dist/types/campaign.js.map +1 -0
- package/dist/types/contact-filter.d.ts +3 -2
- package/dist/types/contact-filter.js +3 -2
- package/dist/types/contact-filter.js.map +1 -1
- package/dist/types/inbox.d.ts +391 -0
- package/dist/types/inbox.js +16 -0
- package/dist/types/inbox.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/scheduled-message.d.ts +221 -0
- package/dist/types/scheduled-message.js +16 -0
- package/dist/types/scheduled-message.js.map +1 -0
- package/docs/audience/contacts.md +34 -4
- package/docs/audience/notifications.md +263 -0
- package/docs/audience/scheduled-messages.md +557 -0
- package/docs/campaign/campaigns.md +489 -0
- package/docs/contact-filter.md +2 -1
- package/docs/inbox/chats.md +356 -0
- package/docs/inbox/connection.md +171 -0
- package/docs/inbox/media.md +202 -0
- package/docs/inbox/messages.md +338 -0
- package/docs/inbox/referrals.md +72 -0
- package/docs/inbox/sessions.md +74 -0
- 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
|