@trycourier/courier-js 1.4.2 → 2.0.1-beta

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 (38) hide show
  1. package/dist/__tests__/brand-client.test.d.ts +1 -0
  2. package/dist/__tests__/courier-client.test.d.ts +1 -0
  3. package/dist/__tests__/inbox-client.test.d.ts +1 -0
  4. package/dist/__tests__/lists-client.test.d.ts +1 -0
  5. package/dist/__tests__/preferences-client.test.d.ts +1 -0
  6. package/dist/__tests__/shared-instance.test.d.ts +1 -0
  7. package/dist/__tests__/token-client.test.d.ts +1 -0
  8. package/dist/__tests__/tracking-client.test.d.ts +1 -0
  9. package/dist/__tests__/utils.d.ts +2 -0
  10. package/dist/client/brand-client.d.ts +12 -0
  11. package/dist/client/client.d.ts +5 -0
  12. package/dist/client/courier-client.d.ts +38 -0
  13. package/dist/client/inbox-client.d.ts +80 -0
  14. package/dist/client/list-client.d.ts +21 -0
  15. package/dist/client/preference-client.d.ts +46 -0
  16. package/dist/client/token-client.d.ts +24 -0
  17. package/dist/client/tracking-client.d.ts +32 -0
  18. package/dist/index.d.ts +19 -40
  19. package/dist/index.js +1 -189
  20. package/dist/index.mjs +1028 -133
  21. package/dist/jest.setup.d.ts +0 -0
  22. package/dist/shared/authentication-listener.d.ts +9 -0
  23. package/dist/shared/courier.d.ts +68 -0
  24. package/dist/socket/courier-socket.d.ts +24 -0
  25. package/dist/socket/inbox-socket.d.ts +19 -0
  26. package/dist/types/brands.d.ts +36 -0
  27. package/dist/types/courier-api-urls.d.ts +11 -0
  28. package/dist/types/inbox.d.ts +43 -0
  29. package/dist/types/pagination.d.ts +4 -0
  30. package/dist/types/preference.d.ts +37 -0
  31. package/dist/types/token.d.ts +12 -0
  32. package/dist/types/tracking-event.d.ts +1 -0
  33. package/dist/utils/coding.d.ts +2 -0
  34. package/dist/utils/logger.d.ts +10 -0
  35. package/dist/utils/request.d.ts +21 -0
  36. package/dist/utils/uuid.d.ts +3 -0
  37. package/package.json +32 -21
  38. package/README.md +0 -123
package/dist/index.mjs CHANGED
@@ -1,7 +1,681 @@
1
- // package.json
2
- var version = "1.4.2";
3
-
4
- // src/helpers/decode.ts
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ const _CourierSocket = class _CourierSocket {
5
+ constructor(url, options) {
6
+ // Properties
7
+ __publicField(this, "webSocket", null);
8
+ __publicField(this, "pingInterval", null);
9
+ // Callbacks
10
+ __publicField(this, "onOpen");
11
+ __publicField(this, "onMessageReceived");
12
+ __publicField(this, "onClose");
13
+ __publicField(this, "onError");
14
+ // Properties
15
+ __publicField(this, "url");
16
+ __publicField(this, "options");
17
+ this.url = url;
18
+ this.options = options;
19
+ }
20
+ /**
21
+ * Dynamically checks if the WebSocket is connected
22
+ */
23
+ get isConnected() {
24
+ return this.webSocket !== null;
25
+ }
26
+ async connect() {
27
+ this.disconnect();
28
+ return new Promise((resolve, reject) => {
29
+ try {
30
+ this.webSocket = new WebSocket(this.url);
31
+ this.webSocket.onopen = () => {
32
+ var _a;
33
+ (_a = this.onOpen) == null ? void 0 : _a.call(this);
34
+ resolve();
35
+ };
36
+ this.webSocket.onmessage = (event) => {
37
+ var _a;
38
+ (_a = this.onMessageReceived) == null ? void 0 : _a.call(this, event.data);
39
+ };
40
+ this.webSocket.onclose = (event) => {
41
+ var _a;
42
+ this.webSocket = null;
43
+ (_a = this.onClose) == null ? void 0 : _a.call(this, event.code, event.reason);
44
+ };
45
+ this.webSocket.onerror = (event) => {
46
+ var _a;
47
+ this.webSocket = null;
48
+ const error = new Error("Courier Socket connection failed");
49
+ error.originalEvent = event;
50
+ (_a = this.onError) == null ? void 0 : _a.call(this, error);
51
+ reject(error);
52
+ };
53
+ } catch (error) {
54
+ this.webSocket = null;
55
+ reject(error);
56
+ }
57
+ });
58
+ }
59
+ disconnect() {
60
+ this.stopPing();
61
+ if (this.webSocket) {
62
+ this.webSocket.close(_CourierSocket.NORMAL_CLOSURE_STATUS);
63
+ this.webSocket = null;
64
+ }
65
+ }
66
+ async send(message) {
67
+ if (!this.webSocket) {
68
+ return false;
69
+ }
70
+ const json = JSON.stringify(message);
71
+ return this.webSocket.send(json) !== void 0;
72
+ }
73
+ keepAlive(props) {
74
+ this.stopPing();
75
+ this.pingInterval = setInterval(async () => {
76
+ var _a;
77
+ try {
78
+ await this.send({ action: "keepAlive" });
79
+ } catch (error) {
80
+ (_a = this.options.logger) == null ? void 0 : _a.error("Error occurred on Keep Alive:", error);
81
+ }
82
+ }, (props == null ? void 0 : props.intervalInMillis) ?? 3e5);
83
+ }
84
+ stopPing() {
85
+ if (this.pingInterval) {
86
+ clearInterval(this.pingInterval);
87
+ this.pingInterval = null;
88
+ }
89
+ }
90
+ };
91
+ // Constants
92
+ __publicField(_CourierSocket, "NORMAL_CLOSURE_STATUS", 1e3);
93
+ let CourierSocket = _CourierSocket;
94
+ const getCourierApiUrls = (urls) => ({
95
+ courier: {
96
+ rest: (urls == null ? void 0 : urls.courier.rest) || "https://api.courier.com",
97
+ graphql: (urls == null ? void 0 : urls.courier.graphql) || "https://api.courier.com/client/q"
98
+ },
99
+ inbox: {
100
+ graphql: (urls == null ? void 0 : urls.inbox.graphql) || "https://inbox.courier.com/q",
101
+ webSocket: (urls == null ? void 0 : urls.inbox.webSocket) || "wss://realtime.courier.com"
102
+ }
103
+ });
104
+ class Logger {
105
+ constructor(showLogs) {
106
+ __publicField(this, "PREFIX", "[COURIER]");
107
+ this.showLogs = showLogs;
108
+ }
109
+ warn(message, ...args) {
110
+ if (this.showLogs) {
111
+ console.warn(`${this.PREFIX} ${message}`, ...args);
112
+ }
113
+ }
114
+ log(message, ...args) {
115
+ if (this.showLogs) {
116
+ console.log(`${this.PREFIX} ${message}`, ...args);
117
+ }
118
+ }
119
+ error(message, ...args) {
120
+ if (this.showLogs) {
121
+ console.error(`${this.PREFIX} ${message}`, ...args);
122
+ }
123
+ }
124
+ debug(message, ...args) {
125
+ if (this.showLogs) {
126
+ console.debug(`${this.PREFIX} ${message}`, ...args);
127
+ }
128
+ }
129
+ info(message, ...args) {
130
+ if (this.showLogs) {
131
+ console.info(`${this.PREFIX} ${message}`, ...args);
132
+ }
133
+ }
134
+ }
135
+ class UUID {
136
+ static generate(prefix) {
137
+ const id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
138
+ return prefix ? prefix + id : id;
139
+ }
140
+ }
141
+ class CourierRequestError extends Error {
142
+ constructor(code, message, type) {
143
+ super(message);
144
+ this.code = code;
145
+ this.type = type;
146
+ this.name = "CourierRequestError";
147
+ }
148
+ }
149
+ function logRequest(logger, uid, type, data) {
150
+ logger.log(`
151
+ 📡 New Courier ${type} Request: ${uid}
152
+ URL: ${data.url}
153
+ ${data.method ? `Method: ${data.method}` : ""}
154
+ ${data.query ? `Query: ${data.query}` : ""}
155
+ ${data.variables ? `Variables: ${JSON.stringify(data.variables, null, 2)}` : ""}
156
+ Headers: ${JSON.stringify(data.headers, null, 2)}
157
+ Body: ${data.body ? JSON.stringify(data.body, null, 2) : "Empty"}
158
+ `);
159
+ }
160
+ function logResponse(logger, uid, type, data) {
161
+ logger.log(`
162
+ 📡 New Courier ${type} Response: ${uid}
163
+ Status Code: ${data.status}
164
+ Response JSON: ${JSON.stringify(data.response, null, 2)}
165
+ `);
166
+ }
167
+ async function http(props) {
168
+ const validCodes = props.validCodes ?? [200];
169
+ const uid = props.options.showLogs ? UUID.generate() : void 0;
170
+ const request = new Request(props.url, {
171
+ method: props.method,
172
+ headers: {
173
+ "Content-Type": "application/json",
174
+ ...props.headers
175
+ },
176
+ body: props.body ? JSON.stringify(props.body) : void 0
177
+ });
178
+ if (uid) {
179
+ logRequest(props.options.logger, uid, "HTTP", {
180
+ url: request.url,
181
+ method: request.method,
182
+ headers: Object.fromEntries(request.headers.entries()),
183
+ body: props.body
184
+ });
185
+ }
186
+ const response = await fetch(request);
187
+ if (response.status === 204) {
188
+ return;
189
+ }
190
+ let data;
191
+ try {
192
+ data = await response.json();
193
+ } catch (error) {
194
+ if (response.status === 200) {
195
+ return;
196
+ }
197
+ throw new CourierRequestError(
198
+ response.status,
199
+ "Failed to parse response as JSON",
200
+ "PARSE_ERROR"
201
+ );
202
+ }
203
+ if (uid) {
204
+ logResponse(props.options.logger, uid, "HTTP", {
205
+ status: response.status,
206
+ response: data
207
+ });
208
+ }
209
+ if (!validCodes.includes(response.status)) {
210
+ throw new CourierRequestError(
211
+ response.status,
212
+ (data == null ? void 0 : data.message) || "Unknown Error",
213
+ data == null ? void 0 : data.type
214
+ );
215
+ }
216
+ return data;
217
+ }
218
+ async function graphql(props) {
219
+ const uid = props.options.showLogs ? UUID.generate() : void 0;
220
+ if (uid) {
221
+ logRequest(props.options.logger, uid, "GraphQL", {
222
+ url: props.url,
223
+ headers: props.headers,
224
+ query: props.query,
225
+ variables: props.variables
226
+ });
227
+ }
228
+ const response = await fetch(props.url, {
229
+ method: "POST",
230
+ headers: {
231
+ "Content-Type": "application/json",
232
+ ...props.headers
233
+ },
234
+ body: JSON.stringify({
235
+ query: props.query,
236
+ variables: props.variables
237
+ })
238
+ });
239
+ let data;
240
+ try {
241
+ data = await response.json();
242
+ } catch (error) {
243
+ throw new CourierRequestError(
244
+ response.status,
245
+ "Failed to parse response as JSON",
246
+ "PARSE_ERROR"
247
+ );
248
+ }
249
+ if (uid) {
250
+ logResponse(props.options.logger, uid, "GraphQL", {
251
+ status: response.status,
252
+ response: data
253
+ });
254
+ }
255
+ if (!response.ok) {
256
+ throw new CourierRequestError(
257
+ response.status,
258
+ (data == null ? void 0 : data.message) || "Unknown Error",
259
+ data == null ? void 0 : data.type
260
+ );
261
+ }
262
+ return data;
263
+ }
264
+ class Client {
265
+ constructor(options) {
266
+ this.options = options;
267
+ }
268
+ }
269
+ class BrandClient extends Client {
270
+ /**
271
+ * Get a brand by ID using GraphQL
272
+ * @param brandId - The ID of the brand to retrieve
273
+ * @returns Promise resolving to the requested brand
274
+ */
275
+ async getBrand(props) {
276
+ const query = `
277
+ query GetBrand {
278
+ brand(brandId: "${props.brandId}") {
279
+ settings {
280
+ colors {
281
+ primary
282
+ secondary
283
+ tertiary
284
+ }
285
+ inapp {
286
+ borderRadius
287
+ disableCourierFooter
288
+ }
289
+ }
290
+ }
291
+ }
292
+ `;
293
+ const json = await graphql({
294
+ options: this.options,
295
+ url: this.options.apiUrls.courier.graphql,
296
+ headers: {
297
+ "x-courier-user-id": this.options.userId,
298
+ "x-courier-client-key": "empty",
299
+ // Empty for now. Will be removed in future.
300
+ "Authorization": `Bearer ${this.options.accessToken}`
301
+ },
302
+ query,
303
+ variables: { brandId: props.brandId }
304
+ });
305
+ return json.data.brand;
306
+ }
307
+ }
308
+ class InboxSocket extends CourierSocket {
309
+ constructor(options) {
310
+ const url = InboxSocket.buildUrl(options);
311
+ super(url, options);
312
+ __publicField(this, "receivedMessage");
313
+ __publicField(this, "receivedMessageEvent");
314
+ this.onMessageReceived = (data) => this.convertToType(data);
315
+ }
316
+ convertToType(data) {
317
+ var _a, _b, _c, _d;
318
+ try {
319
+ const payload = JSON.parse(data);
320
+ switch (payload.type) {
321
+ case "event":
322
+ const messageEvent = JSON.parse(data);
323
+ (_a = this.receivedMessageEvent) == null ? void 0 : _a.call(this, messageEvent);
324
+ break;
325
+ case "message":
326
+ const message = JSON.parse(data);
327
+ (_b = this.receivedMessage) == null ? void 0 : _b.call(this, message);
328
+ break;
329
+ }
330
+ } catch (error) {
331
+ (_c = this.options.logger) == null ? void 0 : _c.error("Error parsing socket message", error);
332
+ if (error instanceof Error) {
333
+ (_d = this.onError) == null ? void 0 : _d.call(this, error);
334
+ }
335
+ }
336
+ }
337
+ async sendSubscribe(props) {
338
+ var _a;
339
+ const subscription = {
340
+ action: "subscribe",
341
+ data: {
342
+ userAgent: "courier-js",
343
+ // TODO: Equivalent to Courier.agent.value()
344
+ channel: this.options.userId,
345
+ event: "*",
346
+ version: (props == null ? void 0 : props.version) ?? 5
347
+ }
348
+ };
349
+ if (this.options.connectionId) {
350
+ subscription.data.clientSourceId = this.options.connectionId;
351
+ }
352
+ if (this.options.tenantId) {
353
+ subscription.data.accountId = this.options.tenantId;
354
+ }
355
+ (_a = this.options.logger) == null ? void 0 : _a.debug("Sending subscribe request", subscription);
356
+ await this.send(subscription);
357
+ }
358
+ static buildUrl(options) {
359
+ var _a;
360
+ let url = ((_a = options.apiUrls) == null ? void 0 : _a.inbox.webSocket) ?? "";
361
+ if (options.accessToken) {
362
+ url += `/?auth=${options.accessToken}`;
363
+ }
364
+ return url;
365
+ }
366
+ }
367
+ class InboxClient extends Client {
368
+ constructor(options) {
369
+ super(options);
370
+ __publicField(this, "socket");
371
+ this.socket = new InboxSocket(options);
372
+ }
373
+ /**
374
+ * Get paginated messages
375
+ * @param paginationLimit - Number of messages to return per page (default: 24)
376
+ * @param startCursor - Cursor for pagination
377
+ * @returns Promise resolving to paginated messages response
378
+ */
379
+ async getMessages(props) {
380
+ const query = `
381
+ query GetInboxMessages(
382
+ $params: FilterParamsInput = { ${this.options.tenantId ? `accountId: "${this.options.tenantId}"` : ""} }
383
+ $limit: Int = ${(props == null ? void 0 : props.paginationLimit) ?? 24}
384
+ $after: String ${(props == null ? void 0 : props.startCursor) ? `= "${props.startCursor}"` : ""}
385
+ ) {
386
+ count(params: $params)
387
+ messages(params: $params, limit: $limit, after: $after) {
388
+ totalCount
389
+ pageInfo {
390
+ startCursor
391
+ hasNextPage
392
+ }
393
+ nodes {
394
+ messageId
395
+ read
396
+ archived
397
+ created
398
+ opened
399
+ title
400
+ preview
401
+ data
402
+ tags
403
+ trackingIds {
404
+ clickTrackingId
405
+ }
406
+ actions {
407
+ content
408
+ data
409
+ href
410
+ }
411
+ }
412
+ }
413
+ }
414
+ `;
415
+ return await graphql({
416
+ options: this.options,
417
+ query,
418
+ headers: {
419
+ "x-courier-user-id": this.options.userId,
420
+ "Authorization": `Bearer ${this.options.accessToken}`
421
+ },
422
+ url: this.options.apiUrls.inbox.graphql
423
+ });
424
+ }
425
+ /**
426
+ * Get paginated archived messages
427
+ * @param paginationLimit - Number of messages to return per page (default: 24)
428
+ * @param startCursor - Cursor for pagination
429
+ * @returns Promise resolving to paginated archived messages response
430
+ */
431
+ async getArchivedMessages(props) {
432
+ const query = `
433
+ query GetInboxMessages(
434
+ $params: FilterParamsInput = { ${this.options.tenantId ? `accountId: "${this.options.tenantId}"` : ""}, archived: true }
435
+ $limit: Int = ${(props == null ? void 0 : props.paginationLimit) ?? 24}
436
+ $after: String ${(props == null ? void 0 : props.startCursor) ? `= "${props.startCursor}"` : ""}
437
+ ) {
438
+ count(params: $params)
439
+ messages(params: $params, limit: $limit, after: $after) {
440
+ totalCount
441
+ pageInfo {
442
+ startCursor
443
+ hasNextPage
444
+ }
445
+ nodes {
446
+ messageId
447
+ read
448
+ archived
449
+ created
450
+ opened
451
+ title
452
+ preview
453
+ data
454
+ tags
455
+ trackingIds {
456
+ clickTrackingId
457
+ }
458
+ actions {
459
+ content
460
+ data
461
+ href
462
+ }
463
+ }
464
+ }
465
+ }
466
+ `;
467
+ return graphql({
468
+ options: this.options,
469
+ query,
470
+ headers: {
471
+ "x-courier-user-id": this.options.userId,
472
+ "Authorization": `Bearer ${this.options.accessToken}`
473
+ },
474
+ url: this.options.apiUrls.inbox.graphql
475
+ });
476
+ }
477
+ /**
478
+ * Get unread message count
479
+ * @returns Promise resolving to number of unread messages
480
+ */
481
+ async getUnreadMessageCount() {
482
+ var _a;
483
+ const query = `
484
+ query GetMessages {
485
+ count(params: { status: "unread" ${this.options.tenantId ? `, accountId: "${this.options.tenantId}"` : ""} })
486
+ }
487
+ `;
488
+ const response = await graphql({
489
+ options: this.options,
490
+ query,
491
+ headers: {
492
+ "x-courier-user-id": this.options.userId,
493
+ "Authorization": `Bearer ${this.options.accessToken}`
494
+ },
495
+ url: this.options.apiUrls.inbox.graphql
496
+ });
497
+ return ((_a = response.data) == null ? void 0 : _a.count) ?? 0;
498
+ }
499
+ /**
500
+ * Track a click event
501
+ * @param messageId - ID of the message
502
+ * @param trackingId - ID for tracking the click
503
+ * @returns Promise resolving when click is tracked
504
+ */
505
+ async click(props) {
506
+ const query = `
507
+ mutation TrackEvent {
508
+ clicked(messageId: "${props.messageId}", trackingId: "${props.trackingId}")
509
+ }
510
+ `;
511
+ const headers = {
512
+ "x-courier-user-id": this.options.userId,
513
+ "Authorization": `Bearer ${this.options.accessToken}`
514
+ };
515
+ if (this.options.connectionId) {
516
+ headers["x-courier-client-source-id"] = this.options.connectionId;
517
+ }
518
+ await graphql({
519
+ options: this.options,
520
+ query,
521
+ headers,
522
+ url: this.options.apiUrls.inbox.graphql
523
+ });
524
+ }
525
+ /**
526
+ * Mark a message as read
527
+ * @param messageId - ID of the message to mark as read
528
+ * @returns Promise resolving when message is marked as read
529
+ */
530
+ async read(props) {
531
+ const query = `
532
+ mutation TrackEvent {
533
+ read(messageId: "${props.messageId}")
534
+ }
535
+ `;
536
+ const headers = {
537
+ "x-courier-user-id": this.options.userId,
538
+ "Authorization": `Bearer ${this.options.accessToken}`
539
+ };
540
+ if (this.options.connectionId) {
541
+ headers["x-courier-client-source-id"] = this.options.connectionId;
542
+ }
543
+ await graphql({
544
+ options: this.options,
545
+ query,
546
+ headers,
547
+ url: this.options.apiUrls.inbox.graphql
548
+ });
549
+ }
550
+ /**
551
+ * Mark a message as unread
552
+ * @param messageId - ID of the message to mark as unread
553
+ * @returns Promise resolving when message is marked as unread
554
+ */
555
+ async unread(props) {
556
+ const query = `
557
+ mutation TrackEvent {
558
+ unread(messageId: "${props.messageId}")
559
+ }
560
+ `;
561
+ const headers = {
562
+ "x-courier-user-id": this.options.userId,
563
+ "Authorization": `Bearer ${this.options.accessToken}`
564
+ };
565
+ if (this.options.connectionId) {
566
+ headers["x-courier-client-source-id"] = this.options.connectionId;
567
+ }
568
+ await graphql({
569
+ options: this.options,
570
+ query,
571
+ headers,
572
+ url: this.options.apiUrls.inbox.graphql
573
+ });
574
+ }
575
+ /**
576
+ * Mark all messages as read
577
+ * @returns Promise resolving when all messages are marked as read
578
+ */
579
+ async readAll() {
580
+ const query = `
581
+ mutation TrackEvent {
582
+ markAllRead
583
+ }
584
+ `;
585
+ const headers = {
586
+ "x-courier-user-id": this.options.userId,
587
+ "Authorization": `Bearer ${this.options.accessToken}`
588
+ };
589
+ if (this.options.connectionId) {
590
+ headers["x-courier-client-source-id"] = this.options.connectionId;
591
+ }
592
+ await graphql({
593
+ options: this.options,
594
+ query,
595
+ headers,
596
+ url: this.options.apiUrls.inbox.graphql
597
+ });
598
+ }
599
+ /**
600
+ * Mark a message as opened
601
+ * @param messageId - ID of the message to mark as opened
602
+ * @returns Promise resolving when message is marked as opened
603
+ */
604
+ async open(props) {
605
+ const query = `
606
+ mutation TrackEvent {
607
+ opened(messageId: "${props.messageId}")
608
+ }
609
+ `;
610
+ const headers = {
611
+ "x-courier-user-id": this.options.userId,
612
+ "Authorization": `Bearer ${this.options.accessToken}`
613
+ };
614
+ if (this.options.connectionId) {
615
+ headers["x-courier-client-source-id"] = this.options.connectionId;
616
+ }
617
+ await graphql({
618
+ options: this.options,
619
+ query,
620
+ headers,
621
+ url: this.options.apiUrls.inbox.graphql
622
+ });
623
+ }
624
+ /**
625
+ * Archive a message
626
+ * @param messageId - ID of the message to archive
627
+ * @returns Promise resolving when message is archived
628
+ */
629
+ async archive(props) {
630
+ const query = `
631
+ mutation TrackEvent {
632
+ archive(messageId: "${props.messageId}")
633
+ }
634
+ `;
635
+ const headers = {
636
+ "x-courier-user-id": this.options.userId,
637
+ "Authorization": `Bearer ${this.options.accessToken}`
638
+ };
639
+ if (this.options.connectionId) {
640
+ headers["x-courier-client-source-id"] = this.options.connectionId;
641
+ }
642
+ await graphql({
643
+ options: this.options,
644
+ query,
645
+ headers,
646
+ url: this.options.apiUrls.inbox.graphql
647
+ });
648
+ }
649
+ }
650
+ class PreferenceTransformer {
651
+ /**
652
+ * Transforms a single API response item to the CourierUserPreferencesTopic type
653
+ * @param item - The API response item
654
+ * @returns A CourierUserPreferencesTopic object
655
+ */
656
+ transformItem(item) {
657
+ return {
658
+ topicId: item.topic_id,
659
+ topicName: item.topic_name,
660
+ sectionId: item.section_id,
661
+ sectionName: item.section_name,
662
+ status: item.status,
663
+ defaultStatus: item.default_status,
664
+ hasCustomRouting: item.has_custom_routing,
665
+ customRouting: item.custom_routing || []
666
+ };
667
+ }
668
+ /**
669
+ * Transforms an array of API response items to CourierUserPreferencesTopic objects
670
+ * @param items - The API response items
671
+ * @returns A generator of CourierUserPreferencesTopic objects
672
+ */
673
+ *transform(items) {
674
+ for (const item of items) {
675
+ yield this.transformItem(item);
676
+ }
677
+ }
678
+ }
5
679
  function decode(clientKey) {
6
680
  const binaryString = atob(clientKey);
7
681
  const bytes = new Uint8Array(binaryString.length);
@@ -17,148 +691,369 @@ function encode(key) {
17
691
  }
18
692
  return btoa(String.fromCharCode(...bytes));
19
693
  }
20
-
21
- // src/helpers/client.ts
22
- async function tryCatch(fn, debug = true) {
23
- var _a, _b;
24
- const response = await fn();
25
- if (!response.ok && debug) {
26
- console.error(
27
- `Error invoking ${response.url}: ${(_a = response.status) != null ? _a : 404} because ${JSON.stringify((_b = await response.json()) == null ? void 0 : _b.message)}`
28
- );
694
+ class PreferenceClient extends Client {
695
+ constructor() {
696
+ super(...arguments);
697
+ __publicField(this, "transformer", new PreferenceTransformer());
29
698
  }
30
- }
31
- var Courier = class {
32
- constructor({
33
- authorization,
34
- baseUrl,
35
- clientKey,
36
- debug = false,
37
- userId,
38
- userSignature
39
- }) {
40
- if (!clientKey) {
41
- throw new Error("Courier client key is required");
42
- }
43
- this.authorization = authorization;
44
- this.baseUrl = `${baseUrl != null ? baseUrl : "https://api.courier.com"}`;
45
- this.clientKey = clientKey;
46
- this.debug = debug;
47
- this.userId = userId;
48
- this.userSignature = userSignature;
49
- }
50
- getHeaders() {
51
- return new Headers({
52
- "Content-Type": "application/json",
53
- "x-courier-client-version": `courier-js@${version}`,
54
- "Access-Control-Allow-Origin": "*",
55
- ...this.authorization && { Authorization: this.authorization },
56
- ...this.userId && { "x-courier-user-id": this.userId },
57
- ...this.userSignature && {
58
- "x-courier-user-signature": this.userSignature
59
- },
60
- ...this.clientKey && { "x-courier-client-key": this.clientKey }
699
+ /**
700
+ * Get all preferences for a user
701
+ * @param paginationCursor - Optional cursor for pagination
702
+ * @returns Promise resolving to user preferences
703
+ * @see https://www.courier.com/docs/reference/user-preferences/list-all-user-preferences
704
+ */
705
+ async getUserPreferences(props) {
706
+ let url = `${this.options.apiUrls.courier.rest}/users/${this.options.userId}/preferences`;
707
+ if (props == null ? void 0 : props.paginationCursor) {
708
+ url += `?cursor=${props.paginationCursor}`;
709
+ }
710
+ const json = await http({
711
+ options: this.options,
712
+ url,
713
+ method: "GET",
714
+ headers: {
715
+ "Authorization": `Bearer ${this.options.accessToken}`
716
+ }
61
717
  });
62
- }
63
- async post(path, body, useClientPath = true) {
64
- const postFn = () => {
65
- return fetch(this.getPathURL(path, useClientPath), {
66
- body: JSON.stringify(body),
67
- headers: this.getHeaders(),
68
- method: "POST"
69
- });
718
+ const data = json;
719
+ return {
720
+ items: [...this.transformer.transform(data.items)],
721
+ paging: data.paging
70
722
  };
71
- await tryCatch(postFn, this.debug);
72
- }
73
- async put(path, body, useClientPath = true) {
74
- const putFn = () => {
75
- return fetch(this.getPathURL(path, useClientPath), {
76
- ...body ? { body: JSON.stringify(body) } : {},
77
- headers: this.getHeaders(),
78
- method: "PUT"
79
- });
723
+ }
724
+ /**
725
+ * Get preferences for a specific topic
726
+ * @param topicId - The ID of the topic to get preferences for
727
+ * @returns Promise resolving to topic preferences
728
+ * @see https://www.courier.com/docs/reference/user-preferences/get-subscription-topic-preferences
729
+ */
730
+ async getUserPreferenceTopic(props) {
731
+ const json = await http({
732
+ options: this.options,
733
+ url: `${this.options.apiUrls.courier.rest}/users/${this.options.userId}/preferences/${props.topicId}`,
734
+ method: "GET",
735
+ headers: {
736
+ "Authorization": `Bearer ${this.options.accessToken}`
737
+ }
738
+ });
739
+ const res = json;
740
+ return this.transformer.transformItem(res.topic);
741
+ }
742
+ /**
743
+ * Update preferences for a specific topic
744
+ * @param topicId - The ID of the topic to update preferences for
745
+ * @param status - The new status for the topic
746
+ * @param hasCustomRouting - Whether the topic has custom routing
747
+ * @param customRouting - The custom routing channels for the topic
748
+ * @returns Promise resolving when update is complete
749
+ * @see https://www.courier.com/docs/reference/user-preferences/update-subscription-topic-preferences
750
+ */
751
+ async putUserPreferenceTopic(props) {
752
+ const payload = {
753
+ topic: {
754
+ status: props.status,
755
+ has_custom_routing: props.hasCustomRouting,
756
+ custom_routing: props.customRouting
757
+ }
80
758
  };
81
- await tryCatch(putFn, this.debug);
82
- }
83
- async delete(path, useClientPath = true) {
84
- const deleteFn = () => {
85
- return fetch(this.getPathURL(path, useClientPath), {
86
- headers: this.getHeaders(),
87
- method: "DELETE"
88
- });
759
+ await http({
760
+ options: this.options,
761
+ url: `${this.options.apiUrls.courier.rest}/users/${this.options.userId}/preferences/${props.topicId}`,
762
+ method: "PUT",
763
+ headers: {
764
+ "Authorization": `Bearer ${this.options.accessToken}`
765
+ },
766
+ body: payload
767
+ });
768
+ }
769
+ /**
770
+ * Get the notification center URL
771
+ * @param clientKey - The client key to use for the URL
772
+ * @returns The notification center URL
773
+ */
774
+ getNotificationCenterUrl(props) {
775
+ const rootTenantId = decode(props.clientKey);
776
+ const url = encode(`${rootTenantId}#${this.options.userId}${this.options.tenantId ? `#${this.options.tenantId}` : ""}#${false}`);
777
+ return `https://view.notificationcenter.app/p/${url}`;
778
+ }
779
+ }
780
+ class TokenClient extends Client {
781
+ /**
782
+ * Store a push notification token for a user
783
+ * @param token - The push notification token
784
+ * @param provider - The provider of the token
785
+ * @param device - The device information
786
+ * @see https://www.courier.com/docs/reference/token-management/put-token
787
+ */
788
+ async putUserToken(props) {
789
+ const payload = {
790
+ provider_key: props.provider,
791
+ ...props.device && {
792
+ device: {
793
+ app_id: props.device.appId,
794
+ ad_id: props.device.adId,
795
+ device_id: props.device.deviceId,
796
+ platform: props.device.platform,
797
+ manufacturer: props.device.manufacturer,
798
+ model: props.device.model
799
+ }
800
+ }
89
801
  };
90
- await tryCatch(deleteFn, this.debug);
802
+ await http({
803
+ options: this.options,
804
+ url: `${this.options.apiUrls.courier.rest}/users/${this.options.userId}/tokens/${props.token}`,
805
+ method: "PUT",
806
+ headers: {
807
+ "Authorization": `Bearer ${this.options.accessToken}`
808
+ },
809
+ body: payload,
810
+ validCodes: [200, 204]
811
+ });
91
812
  }
92
- generatePreferencesUrl(userId, options) {
93
- var _a;
94
- if (!userId) {
95
- throw new Error("User ID is required to generate preferences URL");
96
- }
97
- const id = decode(this.clientKey);
98
- return `https://view.notificationcenter.app/p/${encode(
99
- `${id}#${(_a = options == null ? void 0 : options.brandId) != null ? _a : ""}#${userId}${(options == null ? void 0 : options.tenantId) ? `#${options.tenantId}` : ""}#${false}`
100
- )}`;
813
+ /**
814
+ * Delete a push notification token for a user
815
+ * @param token - The push notification token
816
+ * @returns Promise resolving when token is deleted
817
+ */
818
+ async deleteUserToken(props) {
819
+ await http({
820
+ options: this.options,
821
+ url: `${this.options.apiUrls.courier.rest}/users/${this.options.userId}/tokens/${props.token}`,
822
+ method: "DELETE",
823
+ headers: {
824
+ "Authorization": `Bearer ${this.options.accessToken}`
825
+ },
826
+ validCodes: [200, 204]
827
+ });
101
828
  }
102
- getPathURL(path, useClientPath) {
103
- let pathUrl = this.baseUrl;
104
- if (useClientPath) {
105
- pathUrl = pathUrl.concat("/client");
106
- }
107
- return pathUrl.concat(path);
829
+ }
830
+ class ListClient extends Client {
831
+ /**
832
+ * Subscribe a user to a list
833
+ * @param listId - The ID of the list to subscribe to
834
+ * @returns Promise resolving when subscription is complete
835
+ * @see https://www.courier.com/docs/reference/lists/recipient-subscribe
836
+ */
837
+ async putSubscription(props) {
838
+ return await http({
839
+ url: `${this.options.apiUrls.courier.rest}/lists/${props.listId}/subscriptions/${this.options.userId}`,
840
+ options: this.options,
841
+ method: "PUT",
842
+ headers: {
843
+ Authorization: `Bearer ${this.options.accessToken}`
844
+ }
845
+ });
108
846
  }
109
- };
110
-
111
- // src/index.ts
112
- var client = {
113
- __instance: null,
114
- init(options) {
115
- this.__instance = new Courier(options);
116
- },
117
- get instance() {
118
- if (!this.__instance) {
119
- throw new Error("Courier instance not initialized");
120
- }
121
- return this.__instance;
122
- },
123
- async identify(userId, payload) {
124
- if (!userId) {
125
- throw new Error("userId is required");
126
- }
127
- await this.instance.post(`/identify/${userId}`, {
128
- profile: {
129
- ...payload
847
+ /**
848
+ * Unsubscribe a user from a list
849
+ * @param listId - The ID of the list to unsubscribe from
850
+ * @returns Promise resolving when unsubscription is complete
851
+ * @see https://www.courier.com/docs/reference/lists/delete-subscription
852
+ */
853
+ async deleteSubscription(props) {
854
+ return await http({
855
+ url: `${this.options.apiUrls.courier.rest}/lists/${props.listId}/subscriptions/${this.options.userId}`,
856
+ options: this.options,
857
+ method: "DELETE",
858
+ headers: {
859
+ Authorization: `Bearer ${this.options.accessToken}`
130
860
  }
131
861
  });
132
- },
133
- async subscribe(userId, listId) {
134
- if (!userId || !listId) {
135
- throw new Error("userId is required");
862
+ }
863
+ }
864
+ class TrackingClient extends Client {
865
+ /**
866
+ * Post an inbound courier event
867
+ * @param event - The event type: Example: "New Order Placed"
868
+ * @param messageId - The message ID
869
+ * @param type - The type of event: Available options: "track"
870
+ * @param properties - The properties of the event
871
+ * @returns Promise resolving to the message ID
872
+ * @see https://www.courier.com/docs/reference/inbound/courier-track-event
873
+ */
874
+ async postInboundCourier(props) {
875
+ return await http({
876
+ url: `${this.options.apiUrls.courier.rest}/inbound/courier`,
877
+ options: this.options,
878
+ method: "POST",
879
+ headers: {
880
+ Authorization: `Bearer ${this.options.accessToken}`
881
+ },
882
+ body: {
883
+ ...props,
884
+ userId: this.options.userId
885
+ },
886
+ validCodes: [200, 202]
887
+ });
888
+ }
889
+ /**
890
+ * Post a tracking URL event
891
+ * These urls are found in messages sent from Courier
892
+ * @param url - The URL to post the event to
893
+ * @param event - The event type: Available options: "click", "open", "unsubscribe"
894
+ * @returns Promise resolving when the event is posted
895
+ */
896
+ async postTrackingUrl(props) {
897
+ return await http({
898
+ url: props.url,
899
+ options: this.options,
900
+ method: "POST",
901
+ body: {
902
+ event: props.event
903
+ }
904
+ });
905
+ }
906
+ }
907
+ class CourierClient extends Client {
908
+ constructor(props) {
909
+ var _a, _b;
910
+ const showLogs = props.showLogs !== void 0 ? props.showLogs : process.env.NODE_ENV === "development";
911
+ const baseOptions = {
912
+ ...props,
913
+ showLogs,
914
+ apiUrls: props.apiUrls || getCourierApiUrls(),
915
+ accessToken: props.jwt ?? props.publicApiKey
916
+ };
917
+ super({
918
+ ...baseOptions,
919
+ logger: new Logger(baseOptions.showLogs),
920
+ apiUrls: getCourierApiUrls(baseOptions.apiUrls)
921
+ });
922
+ __publicField(this, "tokens");
923
+ __publicField(this, "brands");
924
+ __publicField(this, "preferences");
925
+ __publicField(this, "inbox");
926
+ __publicField(this, "lists");
927
+ __publicField(this, "tracking");
928
+ this.tokens = new TokenClient(this.options);
929
+ this.brands = new BrandClient(this.options);
930
+ this.preferences = new PreferenceClient(this.options);
931
+ this.inbox = new InboxClient(this.options);
932
+ this.lists = new ListClient(this.options);
933
+ this.tracking = new TrackingClient(this.options);
934
+ if (!this.options.jwt && !this.options.publicApiKey) {
935
+ this.options.logger.warn("Courier Client initialized with no authentication method. Please provide a JWT or public API key.");
136
936
  }
137
- await this.instance.put(`/lists/${listId}/subscribe/${userId}`);
138
- },
139
- async track(event, properties) {
140
- if (!event) {
141
- throw new Error("event is required");
142
- }
143
- let indempotentKey = self.crypto.randomUUID ? self.crypto.randomUUID() : Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
144
- await this.instance.post(`/inbound/courier`, {
145
- messageId: indempotentKey,
146
- type: "track",
147
- event,
148
- properties: { ...properties }
149
- }, false);
150
- },
151
- async unsubscribe(userId, listId) {
152
- if (!userId || !listId) {
153
- throw new Error("userId is required");
937
+ if (this.options.publicApiKey) {
938
+ (_a = this.options.logger) == null ? void 0 : _a.warn(
939
+ "Courier Warning: Public API Keys are for testing only. Please use JWTs for production.\nYou can generate a JWT with this endpoint: https://www.courier.com/docs/reference/auth/issue-token\nThis endpoint should be called from your backend server, not the SDK."
940
+ );
154
941
  }
155
- this.instance.delete(`/lists/${listId}/unsubscribe/${userId}`);
156
- },
157
- generatePreferencesUrl(userId, options) {
158
- return this.instance.generatePreferencesUrl(userId, options);
942
+ if (this.options.jwt && this.options.publicApiKey) {
943
+ (_b = this.options.logger) == null ? void 0 : _b.warn(
944
+ "Courier Warning: Both a JWT and a Public API Key were provided. The Public API Key will be ignored."
945
+ );
946
+ }
947
+ }
948
+ }
949
+ class AuthenticationListener {
950
+ constructor(callback) {
951
+ __publicField(this, "callback");
952
+ this.callback = callback;
953
+ }
954
+ remove() {
955
+ Courier.shared.removeAuthenticationListener(this);
956
+ }
957
+ }
958
+ const _Courier = class _Courier {
959
+ constructor() {
960
+ /**
961
+ * The unique identifier for the Courier instance
962
+ */
963
+ __publicField(this, "id", UUID.generate());
964
+ /**
965
+ * The Courier client instance
966
+ */
967
+ __publicField(this, "instanceClient");
968
+ /**
969
+ * The pagination limit (min: 1, max: 100)
970
+ */
971
+ __publicField(this, "_paginationLimit", 24);
972
+ /**
973
+ * The authentication listeners
974
+ */
975
+ __publicField(this, "authenticationListeners", []);
976
+ }
977
+ get paginationLimit() {
978
+ return this._paginationLimit;
979
+ }
980
+ set paginationLimit(value) {
981
+ this._paginationLimit = Math.min(Math.max(value, 1), 100);
982
+ }
983
+ /**
984
+ * Get the Courier client instance
985
+ * @returns The Courier client instance or undefined if not signed in
986
+ */
987
+ get client() {
988
+ return this.instanceClient;
989
+ }
990
+ /**
991
+ * Get the shared Courier instance
992
+ * @returns The shared Courier instance
993
+ */
994
+ static get shared() {
995
+ if (!_Courier.instance) {
996
+ _Courier.instance = new _Courier();
997
+ }
998
+ return _Courier.instance;
999
+ }
1000
+ /**
1001
+ * Sign in to Courier
1002
+ * @param options - The options for the Courier client
1003
+ */
1004
+ signIn(props) {
1005
+ const connectionId = props.connectionId ?? UUID.generate();
1006
+ this.instanceClient = new CourierClient({ ...props, connectionId });
1007
+ this.notifyAuthenticationListeners({ userId: props.userId });
1008
+ }
1009
+ /**
1010
+ * Sign out of Courier
1011
+ */
1012
+ signOut() {
1013
+ this.instanceClient = void 0;
1014
+ this.notifyAuthenticationListeners({ userId: void 0 });
1015
+ }
1016
+ /**
1017
+ * Register a callback to be notified of authentication state changes
1018
+ * @param callback - Function to be called when authentication state changes
1019
+ * @returns AuthenticationListener instance that can be used to remove the listener
1020
+ */
1021
+ addAuthenticationListener(callback) {
1022
+ var _a;
1023
+ (_a = this.instanceClient) == null ? void 0 : _a.options.logger.info("Adding authentication listener");
1024
+ const listener = new AuthenticationListener(callback);
1025
+ this.authenticationListeners.push(listener);
1026
+ return listener;
1027
+ }
1028
+ /**
1029
+ * Unregister an authentication state change listener
1030
+ * @param listener - The AuthenticationListener instance to remove
1031
+ */
1032
+ removeAuthenticationListener(listener) {
1033
+ var _a;
1034
+ (_a = this.instanceClient) == null ? void 0 : _a.options.logger.info("Removing authentication listener");
1035
+ this.authenticationListeners = this.authenticationListeners.filter((l) => l !== listener);
1036
+ }
1037
+ /**
1038
+ * Notify all authentication listeners
1039
+ * @param props - The props to notify the listeners with
1040
+ */
1041
+ notifyAuthenticationListeners(props) {
1042
+ this.authenticationListeners.forEach((listener) => listener.callback(props));
159
1043
  }
160
1044
  };
161
- var src_default = client;
1045
+ /**
1046
+ * The shared Courier instance
1047
+ */
1048
+ __publicField(_Courier, "instance");
1049
+ let Courier = _Courier;
162
1050
  export {
163
- src_default as default
1051
+ BrandClient,
1052
+ Courier,
1053
+ CourierClient,
1054
+ CourierSocket,
1055
+ InboxClient,
1056
+ ListClient,
1057
+ PreferenceClient,
1058
+ TokenClient
164
1059
  };