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
@@ -142,6 +142,7 @@ blank contact.
142
142
  | Field | Type | Default | Notes |
143
143
  | --- | --- | --- | --- |
144
144
  | `name` | `Partial<ContactName>` | — | `salutation` ≤ 20 chars, `first_name` and `last_name` ≤ 120. |
145
+ | `whatsapp` | string | — | The contact's WhatsApp number, in any shape. Creates the channel identity. Create-only. |
145
146
  | `order` | string | — | Free-form external reference. |
146
147
  | `tags` | ObjectId[] | `[]` | Contact tag ids. |
147
148
  | `agent` | ObjectId \| null | — | Assigned agent. |
@@ -152,17 +153,41 @@ blank contact.
152
153
  | `custom_field_hash` | object | — | Values keyed by custom field **slug**. |
153
154
  | `chat_metadata` | partial | — | Including `blocked`, itself partial. |
154
155
 
155
- Channel identities cannot be set here; they are written when a conversation
156
- starts on a channel.
157
-
158
156
  ```ts
159
157
  const contact = await crm.contacts.create({
160
158
  name: { first_name: 'Ada', last_name: 'Lovelace' },
159
+ whatsapp: '+91 98765 43210',
161
160
  tags: [vipTagId],
162
161
  custom_field_hash: { city: 'Pune', lead_score: 80 },
163
162
  });
163
+
164
+ contact.profile.whatsapp.identifier; // '919876543210'
164
165
  ```
165
166
 
167
+ ### The `whatsapp` field
168
+
169
+ Pass it unless the contact is genuinely a placeholder. A contact with no channel
170
+ identity cannot be messaged, will not be matched to an inbound message, is
171
+ invisible to a number search, and drops out of every segment whose filter reaches
172
+ into channel identities.
173
+
174
+ **Any shape is accepted and stored as digits only.** `+91 98765 43210`,
175
+ `+919876543210` and `919876543210` all store `919876543210`, which is the form an
176
+ inbound message carries — the comparison that matches a reply to this contact is
177
+ made against that string, so the punctuation has to go.
178
+
179
+ | Response | When |
180
+ | --- | --- |
181
+ | `409 conflict` | The account already holds that number, on another contact or on this one. Nothing is created; the existing contact is untouched. |
182
+ | `422` | The value contains no digits at all. An empty identifier would be a channel identity that can never be messaged or matched. |
183
+
184
+ A number identifies **one contact per account**, and `profile` holds **one
185
+ identity per channel** — so a contact cannot carry two WhatsApp numbers, and two
186
+ contacts cannot share one.
187
+
188
+ Identities for the other channels are still written by the conversation that
189
+ starts on them.
190
+
166
191
  ---
167
192
 
168
193
  ## update
@@ -173,11 +198,16 @@ crm.contacts.update(id: string, input: UpdateContactInput): Promise<Contact>
173
198
 
174
199
  `PATCH /v1/audience/contacts/:id` · `contacts:write` · **200**
175
200
 
176
- Takes the same body as `create`, all optional. Anything left out is untouched.
201
+ Takes the same body as `create` minus `whatsapp`, all optional. Anything left out
202
+ is untouched.
177
203
 
178
204
  `tags` and `custom_field_hash` **replace** their stored value rather than merging
179
205
  into it — read the contact first if you mean to add one tag to the existing set.
180
206
 
207
+ `whatsapp` is **not** accepted. A channel identity lives in its own collection, so
208
+ changing a number is a write there plus a delete of the identity it replaces —
209
+ not something a patch of the contact can express. It is create-only for now.
210
+
181
211
  ```ts
182
212
  await crm.contacts.update(id, { agent: agentId, chat_metadata: { closed: true } });
183
213
  ```
@@ -0,0 +1,263 @@
1
+ # Push notifications
2
+
3
+ `crm.notifications` — `/v1/audience/devices`
4
+
5
+ Which browsers and apps a user wants notifications delivered to. A subscription
6
+ is one device — one browser profile, one app installation — held against the
7
+ acting user, and the only party who can see or revoke it is that user.
8
+
9
+ | Action | Route | Scope |
10
+ | --- | --- | --- |
11
+ | [`subscribe`](#subscribe) | `POST /v1/audience/devices` | `devices:write` |
12
+ | [`unsubscribe`](#unsubscribe) | `POST /v1/audience/devices/unregister` | `devices:write` |
13
+ | [`devices`](#devices) | `GET /v1/audience/devices` | `devices:read` |
14
+
15
+ ## What the SDK does, and what you do
16
+
17
+ A push subscription has two halves and only one of them is an API call.
18
+
19
+ **In the browser**, which this SDK deliberately does not wrap:
20
+
21
+ ```js
22
+ const permission = await Notification.requestPermission(); // 'granted' | 'denied' | 'default'
23
+ const token = await getToken(messaging, { vapidKey }); // firebase/messaging
24
+ ```
25
+
26
+ Wrapping those two would mean a hard dependency on `firebase/*` — in a package
27
+ that also runs on Node — to pass arguments that come from your own Firebase
28
+ project.
29
+
30
+ **Here**, which is the part that is an API call:
31
+
32
+ ```ts
33
+ if (permission === 'granted') {
34
+ await crm.notifications.subscribe({ token });
35
+ }
36
+ ```
37
+
38
+ ## Where the subscription lives
39
+
40
+ On the platform, against the acting user, **in this product** — not in the core
41
+ platform. Core owns identity and the agency's Firebase credentials; messages
42
+ arrive here, so the tokens and the sending live here too, and core is never on
43
+ the delivery path.
44
+
45
+ Nothing you send names a Firebase project. The platform resolves it from the
46
+ client the call arrives on and stamps it onto the row, because a registration
47
+ token is only sendable through the project that minted it and nothing in the
48
+ token itself says which. That resolution is a cached read, so it costs nothing in
49
+ steady state — but it is why `subscribe` is the one method here that can answer
50
+ `503`.
51
+
52
+ ## The device object
53
+
54
+ ```ts
55
+ interface Device {
56
+ id: string;
57
+ platform: 'web' | 'android' | 'ios';
58
+ label: string; // what you sent as device_label; '' when you sent none
59
+ clientId: string | null; // the reseller frontend it registered from
60
+ lastSeenAt: string; // ISO; last successful send, or last subscribe
61
+ createdAt: string;
62
+ }
63
+ ```
64
+
65
+ The **registration token is never returned**. It is a send credential: anything
66
+ holding one can push to that browser through the agency's Firebase project. The
67
+ list exists so a user can recognise and revoke their own devices, which needs the
68
+ label and the last-seen time and nothing more.
69
+
70
+ ---
71
+
72
+ ## subscribe
73
+
74
+ ```ts
75
+ crm.notifications.subscribe(input: SubscribeNotificationsInput): Promise<Device>
76
+ ```
77
+
78
+ `POST /v1/audience/devices` · `devices:write` · **201**
79
+
80
+ ### Payload
81
+
82
+ | Field | Type | Required | Notes |
83
+ | --- | --- | --- | --- |
84
+ | `token` | string | yes | The FCM registration token for this instance, from `getToken()`. 20–4096 characters — a sanity check, not a format; Google publishes no shape for these. Trimmed. |
85
+ | `platform` | `'web' \| 'android' \| 'ios'` | defaulted | `'web'` when the SDK can see a DOM. Required otherwise — see below. |
86
+ | `device_label` | string | no | Up to 200 characters; longer is trimmed, not refused. Defaults to something like `Chrome on Windows`, derived from the user agent. Cosmetic, never matched on. |
87
+ | `tpc_id` | string | no | The reseller client (`tpc_…`) this frontend belongs to. Rarely needed. |
88
+
89
+ What goes on the wire, with everything defaulted:
90
+
91
+ ```http
92
+ POST /v1/audience/devices
93
+ authorization: Bearer <ecosystem token>
94
+ client-id: tpc_8Kd3mXp7qR2wL9zB
95
+ content-type: application/json
96
+
97
+ {
98
+ "token": "fNx8…:APA91bH…",
99
+ "platform": "web",
100
+ "device_label": "Chrome on Windows"
101
+ }
102
+ ```
103
+
104
+ ```ts
105
+ const device = await crm.notifications.subscribe({ token });
106
+ // → { id: '…', platform: 'web', label: 'Chrome on Windows', clientId: 'tpc_…', … }
107
+ ```
108
+
109
+ There is **no user field**, and that is deliberate: the acting user comes from
110
+ the verified token the client was constructed with. A body that could name its
111
+ own user would let any caller point someone else's notifications at their own
112
+ browser.
113
+
114
+ ### Call it on every app load, not once
115
+
116
+ It is an idempotent upsert keyed on the token, so re-sending one the platform
117
+ already holds costs a single indexed update. That is the intended usage, because
118
+ two things change underneath you:
119
+
120
+ - FCM rotates registration tokens on its own schedule, and the old one stops
121
+ being delivered to without anyone being told.
122
+ - A device unseen for **60 days** is pruned, and "seen" means a successful send
123
+ or a call to this method. Someone who receives no notifications for two months
124
+ would otherwise quietly fall off the list.
125
+
126
+ Re-subscribing a token already on file against **another** user moves it rather
127
+ than duplicating it. One browser has one person signed in, and the previous
128
+ owner's notifications have to stop reaching it the moment someone else logs in
129
+ there.
130
+
131
+ ### `platform` off-browser
132
+
133
+ The SDK defaults it from `document`, not from `navigator` — which is the
134
+ distinction that matters. React Native defines a `navigator` and no `document`,
135
+ so a navigator-based guess would register every phone as a browser, and the send
136
+ path would fill in the `webpush` block instead of `android`/`apns`: a
137
+ notification FCM accepts and the device never shows. Outside a browser the SDK
138
+ asks instead of guessing:
139
+
140
+ ```ts
141
+ await crm.notifications.subscribe({ token, platform: 'android' });
142
+ ```
143
+
144
+ ### `tpc_id`
145
+
146
+ Only ever a confirmation. The `clientId` the `CrmClient` already sends is
147
+ verified by the gateway and wins; this field says which reseller frontend is
148
+ registering, which decides whose Firebase project and which deep-link host apply.
149
+ A value that contradicts the verified client is refused with `403` rather than
150
+ ignored — the two disagreeing means either a frontend bug or an attempt to file a
151
+ device under another tenant, and silently preferring one would hide the first and
152
+ partially succeed at the second. The white-label frontend has no client and sends
153
+ nothing.
154
+
155
+ ### Errors
156
+
157
+ | Status | Code | Meaning |
158
+ | --- | --- | --- |
159
+ | 400 | `validation_failed` | Token missing or implausible, or `platform` could not be defaulted. Thrown by the SDK before a request is made. |
160
+ | 403 | `forbidden` | `tpc_id` names a different client than the token was issued for. |
161
+ | 503 | `push_not_configured` | The agency has no Firebase notification resource. Nothing the caller can fix; push is simply off for this account. |
162
+ | 503 | `push_unavailable` | The platform could not read the configuration from core. Transient. |
163
+
164
+ Both `503`s want the same handling as success: try again on the next app load,
165
+ which the advice above means you are doing anyway.
166
+
167
+ ---
168
+
169
+ ## unsubscribe
170
+
171
+ ```ts
172
+ crm.notifications.unsubscribe(token: string): Promise<void>
173
+ ```
174
+
175
+ `POST /v1/audience/devices/unregister` · `devices:write` · **204**
176
+
177
+ ```ts
178
+ await crm.notifications.unsubscribe(token);
179
+ ```
180
+
181
+ ### Payload
182
+
183
+ | Field | Type | Required | Notes |
184
+ | --- | --- | --- | --- |
185
+ | `token` | string | yes | The same registration token. Trimmed. |
186
+
187
+ `204` with no body **whether or not a row went** — you are asking for a
188
+ guarantee about the future, and a token that is already gone provides it. So this
189
+ is safe to call unconditionally on logout without first checking whether the
190
+ device was ever subscribed.
191
+
192
+ Scoped to the caller's own devices. A token identifies a browser rather than a
193
+ person, so by the time one user logs out the row may already belong to whoever
194
+ logged in after them; an unscoped delete would cut off the person now holding it.
195
+
196
+ A `POST` with the token in the body rather than `DELETE /devices/:token`: a
197
+ registration token is hundreds of characters and may contain `:` and `/`, and
198
+ putting one in a path invites proxy length limits and writes a live send
199
+ credential into every access log along the way.
200
+
201
+ ### Also delete it on the device
202
+
203
+ This withdraws the platform's permission to send to that token. If the user is
204
+ turning notifications **off** — rather than logging out — also drop the token in
205
+ the browser, or the next `getToken()` hands back the same one and the
206
+ subscription can come back by accident:
207
+
208
+ ```ts
209
+ await crm.notifications.unsubscribe(token);
210
+ await deleteToken(messaging); // firebase/messaging
211
+ ```
212
+
213
+ ---
214
+
215
+ ## devices
216
+
217
+ ```ts
218
+ crm.notifications.devices(query?: Partial<ListDevicesQuery>): Promise<Paginated<Device>>
219
+ ```
220
+
221
+ `GET /v1/audience/devices` · `devices:read` · **200**
222
+
223
+ ### Query
224
+
225
+ | Field | Type | Default | Notes |
226
+ | --- | --- | --- | --- |
227
+ | `page` | number | `1` | 1-based. |
228
+ | `rows` | number | `25` | 1–100. |
229
+
230
+ Sorted by `lastSeenAt` descending. This is what a "notification settings" screen
231
+ lists.
232
+
233
+ ```ts
234
+ const { data } = await crm.notifications.devices();
235
+ data.map((d) => `${d.label || 'This device'} · last seen ${d.lastSeenAt}`);
236
+ ```
237
+
238
+ No `search`: the endpoint shares the common list query so the server would accept
239
+ one, but the service does not apply it — a device list is one person's handful of
240
+ rows. It is left out of the SDK type rather than accepted and silently dropped.
241
+
242
+ Only ever the acting user's own devices. There is no account-wide view and no
243
+ device id in the path: a device is one person's browser.
244
+
245
+ Worth knowing: a notification is sent to the **20 most recently seen** devices
246
+ of a user. A longer list is itself the answer to "why did my old laptop stop
247
+ buzzing".
248
+
249
+ ### Telling whether *this* browser is subscribed
250
+
251
+ You can't, from here — tokens are not returned, by design. Decide it locally
252
+ instead. The browser knows its own `Notification.permission` and its own token,
253
+ and because `subscribe` is an idempotent upsert, the cheap and correct answer to
254
+ "am I still subscribed?" is to subscribe again:
255
+
256
+ ```ts
257
+ if (Notification.permission === 'granted') {
258
+ await crm.notifications.subscribe({ token: await getToken(messaging, { vapidKey }) });
259
+ }
260
+ ```
261
+
262
+ That also repairs the two cases a stored flag gets wrong: a rotated token, and a
263
+ row pruned for staleness.