@trycourier/courier-js 2.0.12 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # `@trycourier/courier-js`
2
2
 
3
- The base API client and shared instance singleton for Courier's JavaScript Browser SDK.
3
+ The HTTP and WebSocket API client and shared instance for Courier's JavaScript Browser SDK.
4
+
5
+ > @trycourier/courier-js exposes the Courier APIs for web browser-based applications.
6
+ >
7
+ >If you’re looking for full-featured UI components,
8
+ check out [Courier React](../courier-react/) or [Courier UI Web Components](../courier-ui-inbox/).
4
9
 
5
10
  ## Installation
6
11
 
@@ -10,244 +15,10 @@ npm install @trycourier/courier-js
10
15
 
11
16
  ## Usage
12
17
 
13
- Setup an API client.
14
-
15
- ### `CourierClient`
16
-
17
- A simple api client wrapper for all supported Courier endpoints and sockets.
18
-
19
- ```ts
20
- const courierClient = new CourierClient({
21
- userId: 'some_user_id', // The user id for your user. This is usually the user id you maintain in your system for a user.
22
- jwt: 'ey...n0', // The access token associated with the user.
23
- tenantId: 'asdf', // [OPTIONAL] Allows you to scope a client to a specific tenant. If you didn't configure multi-tenant routing, you probably don't need this.
24
- showLogs: true, // [OPTIONAL] Shows debugging logs from the client. Defaults to `false`.
25
- });
26
- ```
27
-
28
- ### `Courier.shared...`
29
-
30
- A singleton instance of the `CourierClient` and other logic used to authenticate a single user across multiple requests, websockets, and more.
31
-
32
- ```ts
33
- Courier.shared.signIn({
34
- userId: 'some_user_id', // The user id for your user. This is usually the user id you maintain in your system for a user.
35
- jwt: 'ey...n0', // The access token associated with the user.
36
- tenantId: 'asdf', // [OPTIONAL] Allows you to scope a client to a specific tenant. If you didn't configure multi-tenant routing, you probably don't need this.
37
- showLogs: true, // [OPTIONAL] Shows debugging logs from the client. Defaults to `false`.
38
- });
39
-
40
- Courier.shared.signOut();
41
-
42
- Courier.shared.addAuthenticationListener(({ userId }) => {
43
- console.log(userId);
44
- });
45
- ```
46
-
47
- ## Authentication
48
-
49
- To use the SDK, you need to generate a JWT (JSON Web Token) for your user. **This JWT should always be generated by your backend server, never in client-side code.**
50
-
51
- **How it works:**
52
-
53
- 1. **Your frontend calls your backend:**
54
- - When your app needs to authenticate a user, your frontend should make a request to your own backend (e.g., `/api/generate-courier-jwt`).
55
-
56
- 2. **Your backend calls Courier to issue a JWT:**
57
- - In your backend endpoint, use your [Courier API Key](https://app.courier.com/settings/api-keys) to call the [Courier JWT Token Endpoint](https://www.courier.com/docs/reference/auth/issue-token) and generate a JWT for the user.
58
- - Your backend then returns the JWT to your frontend.
59
-
60
- **Example: Quick Testing with cURL**
61
-
62
- To quickly test JWT generation (for development only), you can use the following cURL command to call Courier's API directly. **Do not use this in production or from client-side code.**
63
-
64
- ```sh
65
- curl --request POST \
66
- --url https://api.courier.com/auth/issue-token \
67
- --header 'Accept: application/json' \
68
- --header 'Authorization: Bearer $YOUR_API_KEY' \
69
- --header 'Content-Type: application/json' \
70
- --data \
71
- '{
72
- "scope": "user_id:$YOUR_USER_ID write:user-tokens inbox:read:messages inbox:write:events read:preferences write:preferences read:brands",
73
- "expires_in": "$YOUR_NUMBER days"
74
- }'
75
- ```
76
-
77
- ### Inbox APIs
78
-
79
- Read and update user inbox messages.
80
-
81
- ```ts
82
- // Fetch the inbox messages for the user
83
- const inboxMessages = await courierClient.inbox.getMessages();
84
-
85
- // Fetch the archived messages for the user
86
- const archivedMessages = await courierClient.inbox.getArchivedMessages();
87
-
88
- // Click a message
89
- await courierClient.inbox.click({
90
- messageId: '1-678...', // The message id. (Example: inboxMessages.data.messages.nodes[0].messageId)
91
- trackingId: 'u2602...', // The tracking id. (Example: inboxMessages.data.messages.nodes[0].trackingIds.clickTrackingId)
92
- });
93
-
94
- // Read a message
95
- await courierClient.inbox.read({
96
- messageId: '1-678...',
97
- });
98
-
99
- // Unread a message
100
- await courierClient.inbox.unread({
101
- messageId: '1-678...',
102
- });
103
-
104
- // Open a message
105
- await courierClient.inbox.open({
106
- messageId: '1-678...',
107
- });
108
-
109
- // Archive a message
110
- await courierClient.inbox.archive({
111
- messageId: '1-678...',
112
- });
113
-
114
- // Unarchive a message
115
- await courierClient.inbox.unarchive({
116
- messageId: '1-678...',
117
- });
118
-
119
- // Read all messages
120
- await courierClient.inbox.readAll();
121
-
122
- // Archive read messages
123
- await courierClient.inbox.archiveRead();
124
-
125
- // Archive all messages
126
- await courierClient.inbox.archiveAll();
127
- ```
128
-
129
- ### Preferences APIs
130
-
131
- Read and update user notification preferences.
132
-
133
- ```ts
134
- // Get list of preferences
135
- const preferences = await courierClient.preferences.getUserPreferences();
136
-
137
- // Get a preference topic
138
- const topic = await courierClient.preferences.getUserPreferenceTopic({
139
- topicId: 'HVS...'
140
- });
141
-
142
- // Update a preference topic
143
- const topic = await courierClient.preferences.putUserPreferenceTopic({
144
- topicId: 'HVS...',
145
- status: 'OPTED_IN', // 'OPTED_IN' | 'OPTED_OUT' | 'REQUIRED'
146
- hasCustomRouting: true, // true | false
147
- customRouting: ['inbox', 'push'], // Array of: 'direct_message' | 'inbox' | 'email' | 'push' | 'sms' | 'webhook'
148
- });
149
- ```
150
-
151
- ### Brands APIs
152
-
153
- Read data about a brand.
154
-
155
- ```ts
156
- // Gets a brand by id
157
- const brand = await courierClient.brands.getBrand({
158
- brandId: 'YF1...'
159
- });
160
- ```
161
-
162
- ### Lists APIs
163
-
164
- Read and update user list details.
165
-
166
- ```ts
167
- // Subscribes to a list
168
- await courierClient.lists.putSubscription({
169
- listId: 'your_list_id'
170
- });
171
-
172
- // Unsubscribes from a list
173
- await courierClient.lists.deleteSubscription({
174
- listId: 'your_list_id'
175
- });
176
- ```
177
-
178
- ### Models
179
-
180
- #### Inbox
181
-
182
- ```ts
183
- export interface InboxMessage {
184
- messageId: string;
185
- title?: string;
186
- body?: string;
187
- preview?: string;
188
- actions?: InboxAction[];
189
- data?: Record<string, any>;
190
- created?: string;
191
- archived?: string;
192
- read?: string;
193
- opened?: string;
194
- tags?: string[];
195
- trackingIds?: {
196
- archiveTrackingId?: string;
197
- openTrackingId?: string;
198
- clickTrackingId?: string;
199
- deliverTrackingId?: string;
200
- unreadTrackingId?: string;
201
- readTrackingId?: string;
202
- };
203
- }
204
-
205
- export interface InboxAction {
206
- content?: string;
207
- href?: string;
208
- data?: Record<string, any>;
209
- background_color?: string;
210
- style?: string;
211
- }
212
- ```
213
-
214
- #### Preferences
215
-
216
- ```ts
217
- export interface CourierUserPreferencesTopic {
218
- topicId: string;
219
- topicName: string;
220
- sectionId: string;
221
- sectionName: string;
222
- status: CourierUserPreferencesStatus;
223
- defaultStatus: CourierUserPreferencesStatus;
224
- hasCustomRouting: boolean;
225
- customRouting: CourierUserPreferencesChannel[];
226
- }
227
-
228
- export type CourierUserPreferencesStatus = 'OPTED_IN' | 'OPTED_OUT' | 'REQUIRED' | 'UNKNOWN';
229
-
230
- export type CourierUserPreferencesChannel = 'direct_message' | 'inbox' | 'email' | 'push' | 'sms' | 'webhook' | 'unknown';
231
- ```
232
-
233
- #### Brands
234
-
235
- ```ts
236
- export interface CourierBrand {
237
- id: string;
238
- name: string;
239
- created: number;
240
- updated: number;
241
- published: number;
242
- version: string;
243
- settings?: CourierBrandSettings;
244
- }
245
- ```
246
-
247
- See more endpoints that aren't in this SDK in the [Courier API Reference](https://www.courier.com/docs/reference/intro).
18
+ Check out the [Courier documentation](https://www.courier.com/docs/sdk-libraries/courier-js-web) for a full guide to the Courier JS SDK.
248
19
 
249
- # **Share feedback with Courier**
20
+ ## Share feedback with Courier
250
21
 
251
- We want to make this the best SDK for managing notifications! Have an idea or feedback about our SDKs? Let us know!
22
+ Have an idea or feedback about our SDKs? Let us know!
252
23
 
253
- [Courier Web Issues](https://github.com/trycourier/courier-web/issues)
24
+ Open an issue: [Courier Web Issues](https://github.com/trycourier/courier-web/issues)
@@ -7,6 +7,7 @@ import { TokenClient } from './token-client';
7
7
  import { Client } from './client';
8
8
  import { ListClient } from './list-client';
9
9
  import { TrackingClient } from './tracking-client';
10
+ import { CourierUserAgent } from '../utils/courier-user-agent';
10
11
  export interface CourierProps {
11
12
  /** User ID for the client. Normally matches the UID in your system */
12
13
  userId: string;
@@ -14,14 +15,28 @@ export interface CourierProps {
14
15
  jwt?: string;
15
16
  /** Public API key for testing (use JWTs in prod) */
16
17
  publicApiKey?: string;
17
- /** Inbox Websocket connection ID */
18
- connectionId?: string;
19
18
  /** Tenant ID. Used for multi-tenant apps */
20
19
  tenantId?: string;
21
20
  /** Flag to control logging. Logs are prefixed with [COURIER]. */
22
21
  showLogs?: boolean;
23
22
  /** Custom API URLs */
24
23
  apiUrls?: CourierApiUrls;
24
+ /**
25
+ * Optional: The name of the SDK calling courier-js methods.
26
+ *
27
+ * This is an internal prop, intended to be set by other Courier SDKs.
28
+ * If undefined, this defaults to "courier-js".
29
+ * @ignore
30
+ */
31
+ courierUserAgentName?: string;
32
+ /**
33
+ * Optional: The version of the SDK calling courier-js methods.
34
+ *
35
+ * This is an internal prop, intended to be set by other Courier SDKs.
36
+ * If undefined, this defaults to this package's version.
37
+ * @ignore
38
+ */
39
+ courierUserAgentVersion?: string;
25
40
  }
26
41
  export interface CourierClientOptions {
27
42
  /** JWT token for authentication: More info at https://www.courier.com/docs/reference/auth/issue-token */
@@ -31,7 +46,7 @@ export interface CourierClientOptions {
31
46
  /** User ID for the client. Normally matches the UID in your system */
32
47
  readonly userId: string;
33
48
  /** Inbox Websocket connection ID */
34
- readonly connectionId?: string;
49
+ readonly connectionId: string;
35
50
  /** Tenant ID. Used for multi-tenant apps */
36
51
  readonly tenantId?: string;
37
52
  /** Flag to control logging. Logs are prefixed with [COURIER]. */
@@ -42,8 +57,17 @@ export interface CourierClientOptions {
42
57
  readonly logger: Logger;
43
58
  /** Final API URLs configuration */
44
59
  readonly apiUrls: CourierApiUrls;
60
+ /** User agent describing Courier SDK and browser UA. */
61
+ readonly courierUserAgent: CourierUserAgent;
45
62
  }
46
63
  export declare class CourierClient extends Client {
64
+ /** User-agent reporting name of the courier-js package. */
65
+ private static readonly COURIER_JS_NAME;
66
+ /**
67
+ * User agent reporting version of the courier-js package.
68
+ * Inlined from package.json at build time.
69
+ */
70
+ private static readonly COURIER_JS_VERSION;
47
71
  readonly tokens: TokenClient;
48
72
  readonly brands: BrandClient;
49
73
  readonly preferences: PreferenceClient;
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).CourierJS={})}(this,(function(t){"use strict";var e=Object.defineProperty,n=(t,n,s)=>((t,n,s)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[n]=s)(t,"symbol"!=typeof n?n+"":n,s),s=(t=>(t.Subscribe="subscribe",t.Unsubscribe="unsubscribe",t.Pong="pong",t.Ping="ping",t.GetConfig="get-config",t))(s||{}),i=(t=>(t.Ping="ping",t))(i||{}),o=(t=>(t.NewMessage="message",t.Archive="archive",t.ArchiveAll="archive-all",t.ArchiveRead="archive-read",t.Clicked="clicked",t.MarkAllRead="mark-all-read",t.Opened="opened",t.Read="read",t.Unarchive="unarchive",t.Unopened="unopened",t.Unread="unread",t))(o||{});const r=t=>({courier:{rest:(null==t?void 0:t.courier.rest)||"https://api.courier.com",graphql:(null==t?void 0:t.courier.graphql)||"https://api.courier.com/client/q"},inbox:{graphql:(null==t?void 0:t.inbox.graphql)||"https://inbox.courier.com/q",webSocket:(null==t?void 0:t.inbox.webSocket)||"wss://realtime.courier.io"}});class a{constructor(t){n(this,"PREFIX","[COURIER]"),this.showLogs=t}warn(t,...e){this.showLogs&&console.warn(`${this.PREFIX} ${t}`,...e)}log(t,...e){this.showLogs&&console.log(`${this.PREFIX} ${t}`,...e)}error(t,...e){this.showLogs&&console.error(`${this.PREFIX} ${t}`,...e)}debug(t,...e){this.showLogs&&console.debug(`${this.PREFIX} ${t}`,...e)}info(t,...e){this.showLogs&&console.info(`${this.PREFIX} ${t}`,...e)}}const c=class t{static nanoid(e=21){let n="",s=crypto.getRandomValues(new Uint8Array(e|=0));for(;e--;)n+=t.ALPHABET[63&s[e]];return n}};n(c,"ALPHABET","useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict");let u=c;class h extends Error{constructor(t,e,n){super(e),this.code=t,this.type=n,this.name="CourierRequestError"}}function d(t,e,n,s){t.log(`\n📡 New Courier ${n} Request: ${e}\nURL: ${s.url}\n${s.method?`Method: ${s.method}`:""}\n${s.query?`Query: ${s.query}`:""}\n${s.variables?`Variables: ${JSON.stringify(s.variables,null,2)}`:""}\nHeaders: ${JSON.stringify(s.headers,null,2)}\nBody: ${s.body?JSON.stringify(s.body,null,2):"Empty"}\n `)}function l(t,e,n,s){t.log(`\n📡 New Courier ${n} Response: ${e}\nStatus Code: ${s.status}\nResponse JSON: ${JSON.stringify(s.response,null,2)}\n `)}async function p(t){const e=t.validCodes??[200],n=t.options.showLogs?u.nanoid():void 0,s=new Request(t.url,{method:t.method,headers:{"Content-Type":"application/json",...t.headers},body:t.body?JSON.stringify(t.body):void 0});n&&d(t.options.logger,n,"HTTP",{url:s.url,method:s.method,headers:Object.fromEntries(s.headers.entries()),body:t.body});const i=await fetch(s);if(204===i.status)return;let o;try{o=await i.json()}catch(r){if(200===i.status)return;throw new h(i.status,"Failed to parse response as JSON","PARSE_ERROR")}if(n&&l(t.options.logger,n,"HTTP",{status:i.status,response:o}),!e.includes(i.status))throw new h(i.status,(null==o?void 0:o.message)||"Unknown Error",null==o?void 0:o.type);return o}async function g(t){const e=t.options.showLogs?u.nanoid():void 0;e&&d(t.options.logger,e,"GraphQL",{url:t.url,headers:t.headers,query:t.query,variables:t.variables});const n=await fetch(t.url,{method:"POST",headers:{"Content-Type":"application/json",...t.headers},body:JSON.stringify({query:t.query,variables:t.variables})});let s;try{s=await n.json()}catch(i){throw new h(n.status,"Failed to parse response as JSON","PARSE_ERROR")}if(e&&l(t.options.logger,e,"GraphQL",{status:n.status,response:s}),!n.ok)throw new h(n.status,(null==s?void 0:s.message)||"Unknown Error",null==s?void 0:s.type);return s}class m{constructor(t){this.options=t}}class I extends m{async getBrand(t){const e=`\n query GetBrand {\n brand(brandId: "${t.brandId}") {\n settings {\n colors {\n primary\n secondary\n tertiary\n }\n inapp {\n borderRadius\n disableCourierFooter\n }\n }\n }\n }\n `;return(await g({options:this.options,url:this.options.apiUrls.courier.graphql,headers:{"x-courier-user-id":this.options.userId,"x-courier-client-key":"empty",Authorization:`Bearer ${this.options.accessToken}`},query:e,variables:{brandId:t.brandId}})).data.brand}}const y=1e3,v=class t{constructor(t){n(this,"webSocket",null),n(this,"retryAttempt",0),n(this,"retryTimeoutId",null),n(this,"closeRequested",!1),n(this,"url"),n(this,"options"),this.url=t.apiUrls.inbox.webSocket,this.options=t}async connect(){var e,n;return this.isConnecting||this.isOpen?(null==(n=this.options.logger)||n.info(`Attempted to open a WebSocket connection, but one already exists in state '${null==(e=this.webSocket)?void 0:e.readyState}'.`),Promise.resolve()):(this.clearRetryTimeout(),this.closeRequested=!1,new Promise(((e,n)=>{this.webSocket=new WebSocket(this.getWebSocketUrl()),this.webSocket.addEventListener("open",(t=>{this.retryAttempt=0,this.onOpen(t),e()})),this.webSocket.addEventListener("message",(async t=>{var e;try{const e=JSON.parse(t.data);if("event"in e&&"reconnect"===e.event)return this.close(y),void(await this.retryConnection(1e3*e.retryAfter));this.onMessageReceived(e)}catch(n){null==(e=this.options.logger)||e.error("Error parsing socket message",n)}})),this.webSocket.addEventListener("close",(e=>{if(e.code!==y&&!this.closeRequested){const n=t.parseCloseEvent(e);n.retryAfterSeconds?this.retryConnection(1e3*n.retryAfterSeconds):this.retryConnection()}this.onClose(e)})),this.webSocket.addEventListener("error",(t=>{this.closeRequested||this.retryConnection(),this.onError(t),n(t)}))})))}close(t=1e3,e){null!==this.webSocket&&(this.closeRequested=!0,this.clearRetryTimeout(),this.retryAttempt=0,this.webSocket.close(t,e))}send(t){var e;if(null===this.webSocket||this.isConnecting)return void(null==(e=this.options.logger)||e.info("Attempted to send a message, but the WebSocket is not yet open."));const n=JSON.stringify(t);this.webSocket.send(n)}get userId(){return this.options.userId}get subTenantId(){return this.options.tenantId}get logger(){return this.options.logger}get isConnecting(){return null!==this.webSocket&&this.webSocket.readyState===WebSocket.CONNECTING}get isOpen(){return null!==this.webSocket&&this.webSocket.readyState===WebSocket.OPEN}getWebSocketUrl(){const t=this.options.accessToken,e=this.options.connectionId,n=this.userId;return`${this.url}?auth=${t}&cid=${e}&iwpv=v1&userId=${n}`}static parseCloseEvent(e){if(null===e.reason||""===e.reason)return e;try{const n=JSON.parse(e.reason);if(!n[t.RETRY_AFTER_KEY])return e;const s=parseInt(n[t.RETRY_AFTER_KEY]);return Number.isNaN(s)||s<0?e:{...e,retryAfterSeconds:s}}catch(n){return e}}getBackoffTimeInMillis(){const e=t.BACKOFF_INTERVALS_IN_MILLIS[this.retryAttempt],n=e-e*t.BACKOFF_JITTER_FACTOR,s=e+e*t.BACKOFF_JITTER_FACTOR;return Math.floor(Math.random()*(s-n)+n)}async retryConnection(e){var n,s,i;if(null!==this.retryTimeoutId)return void(null==(n=this.logger)||n.debug("Skipping retry attempt because a previous retry is already scheduled."));if(this.retryAttempt>=t.MAX_RETRY_ATTEMPTS)return void(null==(s=this.logger)||s.error(`Max retry attempts (${t.MAX_RETRY_ATTEMPTS}) reached.`));const o=e??this.getBackoffTimeInMillis();this.retryTimeoutId=window.setTimeout((async()=>{try{await this.connect()}catch(t){}}),o),null==(i=this.logger)||i.debug(`Retrying connection in ${Math.floor(o/1e3)}s. Retry attempt ${this.retryAttempt+1} of ${t.MAX_RETRY_ATTEMPTS}.`),this.retryAttempt++}clearRetryTimeout(){null!==this.retryTimeoutId&&(window.clearTimeout(this.retryTimeoutId),this.retryTimeoutId=null)}};n(v,"BACKOFF_JITTER_FACTOR",.5),n(v,"MAX_RETRY_ATTEMPTS",5),n(v,"BACKOFF_INTERVALS_IN_MILLIS",[3e4,6e4,12e4,24e4,48e4]),n(v,"RETRY_AFTER_KEY","Retry-After");let T=v;class b{constructor(t=10){n(this,"outstandingRequestsMap",new Map),n(this,"completedTransactionsQueue",[]),n(this,"completedTransactionsToKeep"),this.completedTransactionsToKeep=t}addOutstandingRequest(t,e){if(this.outstandingRequestsMap.has(t))throw new Error(`Transaction [${t}] already has an outstanding request`);const n={transactionId:t,request:e,response:null,start:new Date,end:null};this.outstandingRequestsMap.set(t,n)}addResponse(t,e){const n=this.outstandingRequestsMap.get(t);if(void 0===n)throw new Error(`Transaction [${t}] does not have an outstanding request`);n.response=e,n.end=new Date,this.outstandingRequestsMap.delete(t),this.addCompletedTransaction(n)}get outstandingRequests(){return Array.from(this.outstandingRequestsMap.values())}get completedTransactions(){return this.completedTransactionsQueue}clearOutstandingRequests(){this.outstandingRequestsMap.clear()}addCompletedTransaction(t){this.completedTransactionsQueue.push(t),this.completedTransactionsQueue.length>this.completedTransactionsToKeep&&this.completedTransactionsQueue.shift()}}function f(t){return function(t){if(t.event===o.NewMessage){const e=t.data;return e.created||(e.created=(new Date).toISOString()),{...t,data:e}}return t}(t)}const w=class t extends T{constructor(t){super(t),n(this,"pingIntervalId",null),n(this,"messageEventListeners",[]),n(this,"config",null),n(this,"pingTransactionManager",new b)}onOpen(t){return this.pingTransactionManager.clearOutstandingRequests(),this.restartPingInterval(),this.sendGetConfig(),this.sendSubscribe(),Promise.resolve()}onMessageReceived(e){if("action"in e&&e.action===i.Ping){const t=e;this.sendPong(t)}if("response"in e&&"pong"===e.response){const t=e;this.pingTransactionManager.addResponse(t.tid,t),this.pingTransactionManager.clearOutstandingRequests()}if("response"in e&&"config"===e.response){const t=e;this.setConfig(t.data)}if("event"in e&&t.isInboxMessageEvent(e.event)){const t=f(e);for(const e of this.messageEventListeners)e(t)}return this.restartPingInterval(),Promise.resolve()}onClose(t){return this.clearPingInterval(),this.clearMessageEventListeners(),this.pingTransactionManager.clearOutstandingRequests(),Promise.resolve()}onError(t){return Promise.resolve()}sendSubscribe(){const t={channel:this.userId,event:"*"};this.subTenantId&&(t.accountId=this.subTenantId);const e={tid:u.nanoid(),action:s.Subscribe,data:t};this.send(e)}sendUnsubscribe(){const t={tid:u.nanoid(),action:s.Unsubscribe,data:{channel:this.userId}};this.send(t)}addMessageEventListener(t){this.messageEventListeners.push(t)}sendPing(){var t;if(this.pingTransactionManager.outstandingRequests.length>=this.maxOutstandingPings)return null==(t=this.logger)||t.debug("Max outstanding pings reached, retrying connection."),this.close(y,"Max outstanding pings reached, retrying connection."),void this.retryConnection();const e={tid:u.nanoid(),action:s.Ping};this.send(e),this.pingTransactionManager.addOutstandingRequest(e.tid,e)}sendPong(t){const e={tid:t.tid,action:s.Pong};this.send(e)}sendGetConfig(){const t={tid:u.nanoid(),action:s.GetConfig};this.send(t)}restartPingInterval(){this.clearPingInterval(),this.pingIntervalId=window.setInterval((()=>{this.sendPing()}),this.pingInterval)}clearPingInterval(){this.pingIntervalId&&window.clearInterval(this.pingIntervalId)}get pingInterval(){return this.config?1e3*this.config.pingInterval:t.DEFAULT_PING_INTERVAL_MILLIS}get maxOutstandingPings(){return this.config?this.config.maxOutstandingPings:t.DEFAULT_MAX_OUTSTANDING_PINGS}setConfig(t){this.config=t}clearMessageEventListeners(){this.messageEventListeners=[]}static isInboxMessageEvent(t){return Object.values(o).includes(t)}};n(w,"DEFAULT_PING_INTERVAL_MILLIS",6e4),n(w,"DEFAULT_MAX_OUTSTANDING_PINGS",3);let $=w;class A extends m{constructor(t){super(t),n(this,"socket"),this.socket=new $(t)}async getMessages(t){const e=`\n query GetInboxMessages(\n $params: FilterParamsInput = { ${this.options.tenantId?`accountId: "${this.options.tenantId}"`:""} }\n $limit: Int = ${(null==t?void 0:t.paginationLimit)??24}\n $after: String ${(null==t?void 0:t.startCursor)?`= "${t.startCursor}"`:""}\n ) {\n count(params: $params)\n messages(params: $params, limit: $limit, after: $after) {\n totalCount\n pageInfo {\n startCursor\n hasNextPage\n }\n nodes {\n messageId\n read\n archived\n created\n opened\n title\n preview\n data\n tags\n trackingIds {\n clickTrackingId\n }\n actions {\n content\n data\n href\n }\n }\n }\n }\n `;return await g({options:this.options,query:e,headers:{"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`},url:this.options.apiUrls.inbox.graphql})}async getArchivedMessages(t){const e=`\n query GetInboxMessages(\n $params: FilterParamsInput = { ${this.options.tenantId?`accountId: "${this.options.tenantId}"`:""}, archived: true }\n $limit: Int = ${(null==t?void 0:t.paginationLimit)??24}\n $after: String ${(null==t?void 0:t.startCursor)?`= "${t.startCursor}"`:""}\n ) {\n count(params: $params)\n messages(params: $params, limit: $limit, after: $after) {\n totalCount\n pageInfo {\n startCursor\n hasNextPage\n }\n nodes {\n messageId\n read\n archived\n created\n opened\n title\n preview\n data\n tags\n trackingIds {\n clickTrackingId\n }\n actions {\n content\n data\n href\n }\n }\n }\n }\n `;return g({options:this.options,query:e,headers:{"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`},url:this.options.apiUrls.inbox.graphql})}async getUnreadMessageCount(){var t;const e=`\n query GetMessages {\n count(params: { status: "unread" ${this.options.tenantId?`, accountId: "${this.options.tenantId}"`:""} })\n }\n `;return(null==(t=(await g({options:this.options,query:e,headers:{"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`},url:this.options.apiUrls.inbox.graphql})).data)?void 0:t.count)??0}async click(t){const e=`\n mutation TrackEvent {\n clicked(messageId: "${t.messageId}", trackingId: "${t.trackingId}")\n }\n `,n={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(n["x-courier-client-source-id"]=this.options.connectionId),await g({options:this.options,query:e,headers:n,url:this.options.apiUrls.inbox.graphql})}async read(t){const e=`\n mutation TrackEvent {\n read(messageId: "${t.messageId}")\n }\n `,n={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(n["x-courier-client-source-id"]=this.options.connectionId),await g({options:this.options,query:e,headers:n,url:this.options.apiUrls.inbox.graphql})}async unread(t){const e=`\n mutation TrackEvent {\n unread(messageId: "${t.messageId}")\n }\n `,n={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(n["x-courier-client-source-id"]=this.options.connectionId),await g({options:this.options,query:e,headers:n,url:this.options.apiUrls.inbox.graphql})}async open(t){const e=`\n mutation TrackEvent {\n opened(messageId: "${t.messageId}")\n }\n `,n={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(n["x-courier-client-source-id"]=this.options.connectionId),await g({options:this.options,query:e,headers:n,url:this.options.apiUrls.inbox.graphql})}async archive(t){const e=`\n mutation TrackEvent {\n archive(messageId: "${t.messageId}")\n }\n `,n={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(n["x-courier-client-source-id"]=this.options.connectionId),await g({options:this.options,query:e,headers:n,url:this.options.apiUrls.inbox.graphql})}async unarchive(t){const e=`\n mutation TrackEvent {\n unarchive(messageId: "${t.messageId}")\n }\n `,n={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(n["x-courier-client-source-id"]=this.options.connectionId),await g({options:this.options,query:e,headers:n,url:this.options.apiUrls.inbox.graphql})}async readAll(){const t={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(t["x-courier-client-source-id"]=this.options.connectionId),await g({options:this.options,query:"\n mutation TrackEvent {\n markAllRead\n }\n ",headers:t,url:this.options.apiUrls.inbox.graphql})}async archiveRead(){const t={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(t["x-courier-client-source-id"]=this.options.connectionId),await g({options:this.options,query:"\n mutation TrackEvent {\n archiveRead\n }\n ",headers:t,url:this.options.apiUrls.inbox.graphql})}async archiveAll(){const t={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(t["x-courier-client-source-id"]=this.options.connectionId),await g({options:this.options,query:"\n mutation TrackEvent {\n archiveAll\n }\n ",headers:t,url:this.options.apiUrls.inbox.graphql})}}class k{transformItem(t){return{topicId:t.topic_id,topicName:t.topic_name,sectionId:t.section_id,sectionName:t.section_name,status:t.status,defaultStatus:t.default_status,hasCustomRouting:t.has_custom_routing,customRouting:t.custom_routing||[]}}*transform(t){for(const e of t)yield this.transformItem(e)}}class R extends m{constructor(){super(...arguments),n(this,"transformer",new k)}async getUserPreferences(t){let e=`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/preferences`;(null==t?void 0:t.paginationCursor)&&(e+=`?cursor=${t.paginationCursor}`);const n=await p({options:this.options,url:e,method:"GET",headers:{Authorization:`Bearer ${this.options.accessToken}`}});return{items:[...this.transformer.transform(n.items)],paging:n.paging}}async getUserPreferenceTopic(t){const e=await p({options:this.options,url:`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/preferences/${t.topicId}`,method:"GET",headers:{Authorization:`Bearer ${this.options.accessToken}`}});return this.transformer.transformItem(e.topic)}async putUserPreferenceTopic(t){const e={topic:{status:t.status,has_custom_routing:t.hasCustomRouting,custom_routing:t.customRouting}};await p({options:this.options,url:`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/preferences/${t.topicId}`,method:"PUT",headers:{Authorization:`Bearer ${this.options.accessToken}`},body:e})}getNotificationCenterUrl(t){return`https://view.notificationcenter.app/p/${function(t){const e=new Uint8Array(t.length);for(let n=0;n<t.length;n++)e[n]=t.charCodeAt(n);return btoa(String.fromCharCode(...e))}(`${function(t){const e=atob(t),n=new Uint8Array(e.length);for(let s=0;s<e.length;s++)n[s]=e.charCodeAt(s);return String.fromCharCode(...n)}(t.clientKey)}#${this.options.userId}${this.options.tenantId?`#${this.options.tenantId}`:""}#false`)}`}}class E extends m{async putUserToken(t){const e={provider_key:t.provider,...t.device&&{device:{app_id:t.device.appId,ad_id:t.device.adId,device_id:t.device.deviceId,platform:t.device.platform,manufacturer:t.device.manufacturer,model:t.device.model}}};await p({options:this.options,url:`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/tokens/${t.token}`,method:"PUT",headers:{Authorization:`Bearer ${this.options.accessToken}`},body:e,validCodes:[200,204]})}async deleteUserToken(t){await p({options:this.options,url:`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/tokens/${t.token}`,method:"DELETE",headers:{Authorization:`Bearer ${this.options.accessToken}`},validCodes:[200,204]})}}class C extends m{async putSubscription(t){return await p({url:`${this.options.apiUrls.courier.rest}/lists/${t.listId}/subscriptions/${this.options.userId}`,options:this.options,method:"PUT",headers:{Authorization:`Bearer ${this.options.accessToken}`}})}async deleteSubscription(t){return await p({url:`${this.options.apiUrls.courier.rest}/lists/${t.listId}/subscriptions/${this.options.userId}`,options:this.options,method:"DELETE",headers:{Authorization:`Bearer ${this.options.accessToken}`}})}}class S extends m{async postInboundCourier(t){return await p({url:`${this.options.apiUrls.courier.rest}/inbound/courier`,options:this.options,method:"POST",headers:{Authorization:`Bearer ${this.options.accessToken}`},body:{...t,userId:this.options.userId},validCodes:[200,202]})}async postTrackingUrl(t){return await p({url:t.url,options:this.options,method:"POST",body:{event:t.event}})}}class x extends m{constructor(t){var e,s;const i=void 0!==t.showLogs&&t.showLogs,o={...t,showLogs:i,apiUrls:t.apiUrls||r(),accessToken:t.jwt??t.publicApiKey};super({...o,logger:new a(o.showLogs),apiUrls:r(o.apiUrls)}),n(this,"tokens"),n(this,"brands"),n(this,"preferences"),n(this,"inbox"),n(this,"lists"),n(this,"tracking"),this.tokens=new E(this.options),this.brands=new I(this.options),this.preferences=new R(this.options),this.inbox=new A(this.options),this.lists=new C(this.options),this.tracking=new S(this.options),this.options.jwt||this.options.publicApiKey||this.options.logger.warn("Courier Client initialized with no authentication method. Please provide a JWT or public API key."),this.options.publicApiKey&&(null==(e=this.options.logger)||e.warn("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.")),this.options.jwt&&this.options.publicApiKey&&(null==(s=this.options.logger)||s.warn("Courier Warning: Both a JWT and a Public API Key were provided. The Public API Key will be ignored."))}}class P{constructor(t){n(this,"callback"),this.callback=t}remove(){L.shared.removeAuthenticationListener(this)}}const q=class t{constructor(){n(this,"id",u.nanoid()),n(this,"instanceClient"),n(this,"_paginationLimit",24),n(this,"authenticationListeners",[])}get paginationLimit(){return this._paginationLimit}set paginationLimit(t){this._paginationLimit=Math.min(Math.max(t,1),100)}get client(){return this.instanceClient}static get shared(){return t.instance||(t.instance=new t),t.instance}signIn(t){this.instanceClient&&(this.instanceClient.options.logger.warn("Sign in called but there is already a user signed in. Signing out the current user."),this.signOut());const e=t.connectionId??u.nanoid();this.instanceClient=new x({...t,connectionId:e}),this.notifyAuthenticationListeners({userId:t.userId})}signOut(){var t,e;null==(e=null==(t=this.instanceClient)?void 0:t.inbox.socket)||e.close(),this.instanceClient=void 0,this.notifyAuthenticationListeners({userId:void 0})}addAuthenticationListener(t){var e;null==(e=this.instanceClient)||e.options.logger.info("Adding authentication listener");const n=new P(t);return this.authenticationListeners.push(n),n}removeAuthenticationListener(t){var e;null==(e=this.instanceClient)||e.options.logger.info("Removing authentication listener"),this.authenticationListeners=this.authenticationListeners.filter((e=>e!==t))}notifyAuthenticationListeners(t){this.authenticationListeners.forEach((e=>e.callback(t)))}};n(q,"instance");let L=q;t.BrandClient=I,t.Courier=L,t.CourierClient=x,t.InboxClient=A,t.InboxMessageEvent=o,t.ListClient=C,t.PreferenceClient=R,t.TokenClient=E,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})}));
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).CourierJS={})}(this,(function(t){"use strict";var e=Object.defineProperty,s=(t,s,n)=>((t,s,n)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[s]=n)(t,"symbol"!=typeof s?s+"":s,n),n=(t=>(t.Subscribe="subscribe",t.Unsubscribe="unsubscribe",t.Pong="pong",t.Ping="ping",t.GetConfig="get-config",t))(n||{}),i=(t=>(t.Ping="ping",t))(i||{}),o=(t=>(t.NewMessage="message",t.Archive="archive",t.ArchiveAll="archive-all",t.ArchiveRead="archive-read",t.Clicked="clicked",t.MarkAllRead="mark-all-read",t.Opened="opened",t.Read="read",t.Unarchive="unarchive",t.Unopened="unopened",t.Unread="unread",t))(o||{});const r=t=>({courier:{rest:(null==t?void 0:t.courier.rest)||"https://api.courier.com",graphql:(null==t?void 0:t.courier.graphql)||"https://api.courier.com/client/q"},inbox:{graphql:(null==t?void 0:t.inbox.graphql)||"https://inbox.courier.com/q",webSocket:(null==t?void 0:t.inbox.webSocket)||"wss://realtime.courier.io"}});class a{constructor(t){s(this,"PREFIX","[COURIER]"),this.showLogs=t}warn(t,...e){this.showLogs&&console.warn(`${this.PREFIX} ${t}`,...e)}log(t,...e){this.showLogs&&console.log(`${this.PREFIX} ${t}`,...e)}error(t,...e){this.showLogs&&console.error(`${this.PREFIX} ${t}`,...e)}debug(t,...e){this.showLogs&&console.debug(`${this.PREFIX} ${t}`,...e)}info(t,...e){this.showLogs&&console.info(`${this.PREFIX} ${t}`,...e)}}const c=class t{static nanoid(e=21){let s="",n=crypto.getRandomValues(new Uint8Array(e|=0));for(;e--;)s+=t.ALPHABET[63&n[e]];return s}};s(c,"ALPHABET","useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict");let u=c;const h="x-courier-ua",d="sdk",l="sdkv",p="cid";class g extends Error{constructor(t,e,s){super(e),this.code=t,this.type=s,this.name="CourierRequestError"}}function I(t,e,s,n){t.log(`\n📡 New Courier ${s} Request: ${e}\nURL: ${n.url}\n${n.method?`Method: ${n.method}`:""}\n${n.query?`Query: ${n.query}`:""}\n${n.variables?`Variables: ${JSON.stringify(n.variables,null,2)}`:""}\nHeaders: ${JSON.stringify(n.headers,null,2)}\nBody: ${n.body?JSON.stringify(n.body,null,2):"Empty"}\n `)}function m(t,e,s,n){t.log(`\n📡 New Courier ${s} Response: ${e}\nStatus Code: ${n.status}\nResponse JSON: ${JSON.stringify(n.response,null,2)}\n `)}async function y(t){const e=t.validCodes??[200],s=t.options.showLogs?u.nanoid():void 0,n=t.options.courierUserAgent.toHttpHeaderValue(),i=new Request(t.url,{method:t.method,headers:{"Content-Type":"application/json",[h]:n,...t.headers},body:t.body?JSON.stringify(t.body):void 0});s&&I(t.options.logger,s,"HTTP",{url:i.url,method:i.method,headers:Object.fromEntries(i.headers.entries()),body:t.body});const o=await fetch(i);if(204===o.status)return;let r;try{r=await o.json()}catch(a){if(200===o.status)return;throw new g(o.status,"Failed to parse response as JSON","PARSE_ERROR")}if(s&&m(t.options.logger,s,"HTTP",{status:o.status,response:r}),!e.includes(o.status))throw new g(o.status,(null==r?void 0:r.message)||"Unknown Error",null==r?void 0:r.type);return r}async function v(t){const e=t.options.showLogs?u.nanoid():void 0,s=t.options.courierUserAgent.toHttpHeaderValue();e&&I(t.options.logger,e,"GraphQL",{url:t.url,headers:t.headers,query:t.query,variables:t.variables});const n=await fetch(t.url,{method:"POST",headers:{"Content-Type":"application/json",[h]:s,...t.headers},body:JSON.stringify({query:t.query,variables:t.variables})});let i;try{i=await n.json()}catch(o){throw new g(n.status,"Failed to parse response as JSON","PARSE_ERROR")}if(e&&m(t.options.logger,e,"GraphQL",{status:n.status,response:i}),!n.ok)throw new g(n.status,(null==i?void 0:i.message)||"Unknown Error",null==i?void 0:i.type);return i}class T{constructor(t){this.options=t}}class b extends T{async getBrand(t){const e=`\n query GetBrand {\n brand(brandId: "${t.brandId}") {\n settings {\n colors {\n primary\n secondary\n tertiary\n }\n inapp {\n borderRadius\n disableCourierFooter\n }\n }\n }\n }\n `;return(await v({options:this.options,url:this.options.apiUrls.courier.graphql,headers:{"x-courier-user-id":this.options.userId,"x-courier-client-key":"empty",Authorization:`Bearer ${this.options.accessToken}`},query:e,variables:{brandId:t.brandId}})).data.brand}}const f=1e3,w=class t{constructor(t){s(this,"webSocket",null),s(this,"retryAttempt",0),s(this,"retryTimeoutId",null),s(this,"closeRequested",!1),s(this,"url"),s(this,"options"),this.url=t.apiUrls.inbox.webSocket,this.options=t}async connect(){var e,s;return this.isConnecting||this.isOpen?(null==(s=this.options.logger)||s.info(`Attempted to open a WebSocket connection, but one already exists in state '${null==(e=this.webSocket)?void 0:e.readyState}'.`),Promise.resolve()):(this.clearRetryTimeout(),this.closeRequested=!1,new Promise(((e,s)=>{this.webSocket=new WebSocket(this.getWebSocketUrl()),this.webSocket.addEventListener("open",(t=>{this.retryAttempt=0,this.onOpen(t),e()})),this.webSocket.addEventListener("message",(async t=>{var e;try{const e=JSON.parse(t.data);if("event"in e&&"reconnect"===e.event)return this.close(f),void(await this.retryConnection(1e3*e.retryAfter));this.onMessageReceived(e)}catch(s){null==(e=this.options.logger)||e.error("Error parsing socket message",s)}})),this.webSocket.addEventListener("close",(e=>{if(e.code!==f&&!this.closeRequested){const s=t.parseCloseEvent(e);s.retryAfterSeconds?this.retryConnection(1e3*s.retryAfterSeconds):this.retryConnection()}this.onClose(e)})),this.webSocket.addEventListener("error",(t=>{this.closeRequested||this.retryConnection(),this.onError(t),s(t)}))})))}close(t=1e3,e){null!==this.webSocket&&(this.closeRequested=!0,this.clearRetryTimeout(),this.retryAttempt=0,this.webSocket.close(t,e))}send(t){var e;if(null===this.webSocket||this.isConnecting)return void(null==(e=this.options.logger)||e.info("Attempted to send a message, but the WebSocket is not yet open."));const s=JSON.stringify(t);this.webSocket.send(s)}get userId(){return this.options.userId}get subTenantId(){return this.options.tenantId}get logger(){return this.options.logger}get courierUserAgent(){return this.options.courierUserAgent}get isConnecting(){return null!==this.webSocket&&this.webSocket.readyState===WebSocket.CONNECTING}get isOpen(){return null!==this.webSocket&&this.webSocket.readyState===WebSocket.OPEN}getWebSocketUrl(){const t=this.options.accessToken,e=this.options.connectionId,s=this.userId,n=this.courierUserAgent.getUserAgentInfo()[d],i=this.courierUserAgent.getUserAgentInfo()[l];return`${this.url}?auth=${t}&cid=${e}&iwpv=v1&userId=${s}&${d}=${n}&${l}=${i}`}static parseCloseEvent(e){if(null===e.reason||""===e.reason)return e;try{const s=JSON.parse(e.reason);if(!s[t.RETRY_AFTER_KEY])return e;const n=parseInt(s[t.RETRY_AFTER_KEY]);return Number.isNaN(n)||n<0?e:{...e,retryAfterSeconds:n}}catch(s){return e}}getBackoffTimeInMillis(){const e=t.BACKOFF_INTERVALS_IN_MILLIS[this.retryAttempt],s=e-e*t.BACKOFF_JITTER_FACTOR,n=e+e*t.BACKOFF_JITTER_FACTOR;return Math.floor(Math.random()*(n-s)+s)}async retryConnection(e){var s,n,i;if(null!==this.retryTimeoutId)return void(null==(s=this.logger)||s.debug("Skipping retry attempt because a previous retry is already scheduled."));if(this.retryAttempt>=t.MAX_RETRY_ATTEMPTS)return void(null==(n=this.logger)||n.error(`Max retry attempts (${t.MAX_RETRY_ATTEMPTS}) reached.`));const o=e??this.getBackoffTimeInMillis();this.retryTimeoutId=window.setTimeout((async()=>{try{await this.connect()}catch(t){}}),o),null==(i=this.logger)||i.debug(`Retrying connection in ${Math.floor(o/1e3)}s. Retry attempt ${this.retryAttempt+1} of ${t.MAX_RETRY_ATTEMPTS}.`),this.retryAttempt++}clearRetryTimeout(){null!==this.retryTimeoutId&&(window.clearTimeout(this.retryTimeoutId),this.retryTimeoutId=null)}};s(w,"BACKOFF_JITTER_FACTOR",.5),s(w,"MAX_RETRY_ATTEMPTS",5),s(w,"BACKOFF_INTERVALS_IN_MILLIS",[3e4,6e4,12e4,24e4,48e4]),s(w,"RETRY_AFTER_KEY","Retry-After");let A=w;class ${constructor(t=10){s(this,"outstandingRequestsMap",new Map),s(this,"completedTransactionsQueue",[]),s(this,"completedTransactionsToKeep"),this.completedTransactionsToKeep=t}addOutstandingRequest(t,e){if(this.outstandingRequestsMap.has(t))throw new Error(`Transaction [${t}] already has an outstanding request`);const s={transactionId:t,request:e,response:null,start:new Date,end:null};this.outstandingRequestsMap.set(t,s)}addResponse(t,e){const s=this.outstandingRequestsMap.get(t);if(void 0===s)throw new Error(`Transaction [${t}] does not have an outstanding request`);s.response=e,s.end=new Date,this.outstandingRequestsMap.delete(t),this.addCompletedTransaction(s)}get outstandingRequests(){return Array.from(this.outstandingRequestsMap.values())}get completedTransactions(){return this.completedTransactionsQueue}clearOutstandingRequests(){this.outstandingRequestsMap.clear()}addCompletedTransaction(t){this.completedTransactionsQueue.push(t),this.completedTransactionsQueue.length>this.completedTransactionsToKeep&&this.completedTransactionsQueue.shift()}}function k(t){return function(t){if(t.event===o.NewMessage){const e=t.data;return e.created||(e.created=(new Date).toISOString()),{...t,data:e}}return t}(t)}const R=class t extends A{constructor(t){super(t),s(this,"pingIntervalId",null),s(this,"messageEventListeners",[]),s(this,"config",null),s(this,"pingTransactionManager",new $)}onOpen(t){return this.pingTransactionManager.clearOutstandingRequests(),this.restartPingInterval(),this.sendGetConfig(),this.sendSubscribe(),Promise.resolve()}onMessageReceived(e){if("action"in e&&e.action===i.Ping){const t=e;this.sendPong(t)}if("response"in e&&"pong"===e.response){const t=e;this.pingTransactionManager.addResponse(t.tid,t),this.pingTransactionManager.clearOutstandingRequests()}if("response"in e&&"config"===e.response){const t=e;this.setConfig(t.data)}if("event"in e&&t.isInboxMessageEvent(e.event)){const t=k(e);for(const e of this.messageEventListeners)e(t)}return this.restartPingInterval(),Promise.resolve()}onClose(t){return this.clearPingInterval(),this.clearMessageEventListeners(),this.pingTransactionManager.clearOutstandingRequests(),Promise.resolve()}onError(t){return Promise.resolve()}sendSubscribe(){const t={channel:this.userId,event:"*"};this.subTenantId&&(t.accountId=this.subTenantId);const e={tid:u.nanoid(),action:n.Subscribe,data:t};this.send(e)}sendUnsubscribe(){const t={tid:u.nanoid(),action:n.Unsubscribe,data:{channel:this.userId}};this.send(t)}addMessageEventListener(t){this.messageEventListeners.push(t)}sendPing(){var t;if(this.pingTransactionManager.outstandingRequests.length>=this.maxOutstandingPings)return null==(t=this.logger)||t.debug("Max outstanding pings reached, retrying connection."),this.close(f,"Max outstanding pings reached, retrying connection."),void this.retryConnection();const e={tid:u.nanoid(),action:n.Ping};this.send(e),this.pingTransactionManager.addOutstandingRequest(e.tid,e)}sendPong(t){const e={tid:t.tid,action:n.Pong};this.send(e)}sendGetConfig(){const t={tid:u.nanoid(),action:n.GetConfig};this.send(t)}restartPingInterval(){this.clearPingInterval(),this.pingIntervalId=window.setInterval((()=>{this.sendPing()}),this.pingInterval)}clearPingInterval(){this.pingIntervalId&&window.clearInterval(this.pingIntervalId)}get pingInterval(){return this.config?1e3*this.config.pingInterval:t.DEFAULT_PING_INTERVAL_MILLIS}get maxOutstandingPings(){return this.config?this.config.maxOutstandingPings:t.DEFAULT_MAX_OUTSTANDING_PINGS}setConfig(t){this.config=t}clearMessageEventListeners(){this.messageEventListeners=[]}static isInboxMessageEvent(t){return Object.values(o).includes(t)}};s(R,"DEFAULT_PING_INTERVAL_MILLIS",6e4),s(R,"DEFAULT_MAX_OUTSTANDING_PINGS",3);let E=R;class C extends T{constructor(t){super(t),s(this,"socket"),this.socket=new E(t)}async getMessages(t){const e=`\n query GetInboxMessages(\n $params: FilterParamsInput = { ${this.options.tenantId?`accountId: "${this.options.tenantId}"`:""} }\n $limit: Int = ${(null==t?void 0:t.paginationLimit)??24}\n $after: String ${(null==t?void 0:t.startCursor)?`= "${t.startCursor}"`:""}\n ) {\n count(params: $params)\n messages(params: $params, limit: $limit, after: $after) {\n totalCount\n pageInfo {\n startCursor\n hasNextPage\n }\n nodes {\n messageId\n read\n archived\n created\n opened\n title\n preview\n data\n tags\n trackingIds {\n clickTrackingId\n }\n actions {\n content\n data\n href\n }\n }\n }\n }\n `;return await v({options:this.options,query:e,headers:{"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`},url:this.options.apiUrls.inbox.graphql})}async getArchivedMessages(t){const e=`\n query GetInboxMessages(\n $params: FilterParamsInput = { ${this.options.tenantId?`accountId: "${this.options.tenantId}"`:""}, archived: true }\n $limit: Int = ${(null==t?void 0:t.paginationLimit)??24}\n $after: String ${(null==t?void 0:t.startCursor)?`= "${t.startCursor}"`:""}\n ) {\n count(params: $params)\n messages(params: $params, limit: $limit, after: $after) {\n totalCount\n pageInfo {\n startCursor\n hasNextPage\n }\n nodes {\n messageId\n read\n archived\n created\n opened\n title\n preview\n data\n tags\n trackingIds {\n clickTrackingId\n }\n actions {\n content\n data\n href\n }\n }\n }\n }\n `;return v({options:this.options,query:e,headers:{"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`},url:this.options.apiUrls.inbox.graphql})}async getUnreadMessageCount(){var t;const e=`\n query GetMessages {\n count(params: { status: "unread" ${this.options.tenantId?`, accountId: "${this.options.tenantId}"`:""} })\n }\n `;return(null==(t=(await v({options:this.options,query:e,headers:{"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`},url:this.options.apiUrls.inbox.graphql})).data)?void 0:t.count)??0}async click(t){const e=`\n mutation TrackEvent {\n clicked(messageId: "${t.messageId}", trackingId: "${t.trackingId}")\n }\n `,s={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(s["x-courier-client-source-id"]=this.options.connectionId),await v({options:this.options,query:e,headers:s,url:this.options.apiUrls.inbox.graphql})}async read(t){const e=`\n mutation TrackEvent {\n read(messageId: "${t.messageId}")\n }\n `,s={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(s["x-courier-client-source-id"]=this.options.connectionId),await v({options:this.options,query:e,headers:s,url:this.options.apiUrls.inbox.graphql})}async unread(t){const e=`\n mutation TrackEvent {\n unread(messageId: "${t.messageId}")\n }\n `,s={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(s["x-courier-client-source-id"]=this.options.connectionId),await v({options:this.options,query:e,headers:s,url:this.options.apiUrls.inbox.graphql})}async open(t){const e=`\n mutation TrackEvent {\n opened(messageId: "${t.messageId}")\n }\n `,s={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(s["x-courier-client-source-id"]=this.options.connectionId),await v({options:this.options,query:e,headers:s,url:this.options.apiUrls.inbox.graphql})}async archive(t){const e=`\n mutation TrackEvent {\n archive(messageId: "${t.messageId}")\n }\n `,s={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(s["x-courier-client-source-id"]=this.options.connectionId),await v({options:this.options,query:e,headers:s,url:this.options.apiUrls.inbox.graphql})}async unarchive(t){const e=`\n mutation TrackEvent {\n unarchive(messageId: "${t.messageId}")\n }\n `,s={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(s["x-courier-client-source-id"]=this.options.connectionId),await v({options:this.options,query:e,headers:s,url:this.options.apiUrls.inbox.graphql})}async readAll(){const t={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(t["x-courier-client-source-id"]=this.options.connectionId),await v({options:this.options,query:"\n mutation TrackEvent {\n markAllRead\n }\n ",headers:t,url:this.options.apiUrls.inbox.graphql})}async archiveRead(){const t={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(t["x-courier-client-source-id"]=this.options.connectionId),await v({options:this.options,query:"\n mutation TrackEvent {\n archiveRead\n }\n ",headers:t,url:this.options.apiUrls.inbox.graphql})}async archiveAll(){const t={"x-courier-user-id":this.options.userId,Authorization:`Bearer ${this.options.accessToken}`};this.options.connectionId&&(t["x-courier-client-source-id"]=this.options.connectionId),await v({options:this.options,query:"\n mutation TrackEvent {\n archiveAll\n }\n ",headers:t,url:this.options.apiUrls.inbox.graphql})}}class S{transformItem(t){return{topicId:t.topic_id,topicName:t.topic_name,sectionId:t.section_id,sectionName:t.section_name,status:t.status,defaultStatus:t.default_status,hasCustomRouting:t.has_custom_routing,customRouting:t.custom_routing||[]}}*transform(t){for(const e of t)yield this.transformItem(e)}}class U extends T{constructor(){super(...arguments),s(this,"transformer",new S)}async getUserPreferences(t){let e=`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/preferences`;(null==t?void 0:t.paginationCursor)&&(e+=`?cursor=${t.paginationCursor}`);const s=await y({options:this.options,url:e,method:"GET",headers:{Authorization:`Bearer ${this.options.accessToken}`}});return{items:[...this.transformer.transform(s.items)],paging:s.paging}}async getUserPreferenceTopic(t){const e=await y({options:this.options,url:`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/preferences/${t.topicId}`,method:"GET",headers:{Authorization:`Bearer ${this.options.accessToken}`}});return this.transformer.transformItem(e.topic)}async putUserPreferenceTopic(t){const e={topic:{status:t.status,has_custom_routing:t.hasCustomRouting,custom_routing:t.customRouting}};await y({options:this.options,url:`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/preferences/${t.topicId}`,method:"PUT",headers:{Authorization:`Bearer ${this.options.accessToken}`},body:e})}getNotificationCenterUrl(t){return`https://view.notificationcenter.app/p/${function(t){const e=new Uint8Array(t.length);for(let s=0;s<t.length;s++)e[s]=t.charCodeAt(s);return btoa(String.fromCharCode(...e))}(`${function(t){const e=atob(t),s=new Uint8Array(e.length);for(let n=0;n<e.length;n++)s[n]=e.charCodeAt(n);return String.fromCharCode(...s)}(t.clientKey)}#${this.options.userId}${this.options.tenantId?`#${this.options.tenantId}`:""}#false`)}`}}class x extends T{async putUserToken(t){const e={provider_key:t.provider,...t.device&&{device:{app_id:t.device.appId,ad_id:t.device.adId,device_id:t.device.deviceId,platform:t.device.platform,manufacturer:t.device.manufacturer,model:t.device.model}}};await y({options:this.options,url:`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/tokens/${t.token}`,method:"PUT",headers:{Authorization:`Bearer ${this.options.accessToken}`},body:e,validCodes:[200,204]})}async deleteUserToken(t){await y({options:this.options,url:`${this.options.apiUrls.courier.rest}/users/${this.options.userId}/tokens/${t.token}`,method:"DELETE",headers:{Authorization:`Bearer ${this.options.accessToken}`},validCodes:[200,204]})}}class P extends T{async putSubscription(t){return await y({url:`${this.options.apiUrls.courier.rest}/lists/${t.listId}/subscriptions/${this.options.userId}`,options:this.options,method:"PUT",headers:{Authorization:`Bearer ${this.options.accessToken}`}})}async deleteSubscription(t){return await y({url:`${this.options.apiUrls.courier.rest}/lists/${t.listId}/subscriptions/${this.options.userId}`,options:this.options,method:"DELETE",headers:{Authorization:`Bearer ${this.options.accessToken}`}})}}class q extends T{async postInboundCourier(t){return await y({url:`${this.options.apiUrls.courier.rest}/inbound/courier`,options:this.options,method:"POST",headers:{Authorization:`Bearer ${this.options.accessToken}`},body:{...t,userId:this.options.userId},validCodes:[200,202]})}async postTrackingUrl(t){return await y({url:t.url,options:this.options,method:"POST",body:{event:t.event}})}}class _{constructor(t,e,s){this.clientId=t,this.sdkName=e,this.sdkVersion=s}getUserAgentInfo(){return{[d]:this.sdkName,[l]:this.sdkVersion,[p]:this.clientId}}toHttpHeaderValue(){return Object.entries(this.getUserAgentInfo()).map((([t,e])=>`${t}=${e}`)).join(",")}}const L=class t extends T{constructor(e){var n,i;const o=void 0!==e.showLogs&&e.showLogs,c=u.nanoid(),h={...e,showLogs:o,connectionId:c,apiUrls:e.apiUrls||r(),accessToken:e.jwt??e.publicApiKey},d=new _(c,e.courierUserAgentName||t.COURIER_JS_NAME,e.courierUserAgentVersion||t.COURIER_JS_VERSION);super({...h,logger:new a(h.showLogs),courierUserAgent:d,apiUrls:r(h.apiUrls)}),s(this,"tokens"),s(this,"brands"),s(this,"preferences"),s(this,"inbox"),s(this,"lists"),s(this,"tracking"),this.tokens=new x(this.options),this.brands=new b(this.options),this.preferences=new U(this.options),this.inbox=new C(this.options),this.lists=new P(this.options),this.tracking=new q(this.options),this.options.jwt||this.options.publicApiKey||this.options.logger.warn("Courier Client initialized with no authentication method. Please provide a JWT or public API key."),this.options.publicApiKey&&(null==(n=this.options.logger)||n.warn("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.")),this.options.jwt&&this.options.publicApiKey&&(null==(i=this.options.logger)||i.warn("Courier Warning: Both a JWT and a Public API Key were provided. The Public API Key will be ignored."))}};s(L,"COURIER_JS_NAME","courier-js"),s(L,"COURIER_JS_VERSION","2.1.0");let M=L;class O{constructor(t){s(this,"callback"),this.callback=t}remove(){B.shared.removeAuthenticationListener(this)}}const N=class t{constructor(){s(this,"id",u.nanoid()),s(this,"instanceClient"),s(this,"courierUserAgentName"),s(this,"courierUserAgentVersion"),s(this,"_paginationLimit",24),s(this,"authenticationListeners",[])}get paginationLimit(){return this._paginationLimit}set paginationLimit(t){this._paginationLimit=Math.min(Math.max(t,1),100)}get client(){return this.instanceClient}static get shared(){return t.instance||(t.instance=new t),t.instance}signIn(t){this.instanceClient&&(this.instanceClient.options.logger.warn("Sign in called but there is already a user signed in. Signing out the current user."),this.signOut()),this.instanceClient=new M({...t,courierUserAgentName:this.courierUserAgentName,courierUserAgentVersion:this.courierUserAgentVersion}),this.notifyAuthenticationListeners({userId:t.userId})}signOut(){var t,e;null==(e=null==(t=this.instanceClient)?void 0:t.inbox.socket)||e.close(),this.instanceClient=void 0,this.notifyAuthenticationListeners({userId:void 0})}addAuthenticationListener(t){var e;null==(e=this.instanceClient)||e.options.logger.info("Adding authentication listener");const s=new O(t);return this.authenticationListeners.push(s),s}removeAuthenticationListener(t){var e;null==(e=this.instanceClient)||e.options.logger.info("Removing authentication listener"),this.authenticationListeners=this.authenticationListeners.filter((e=>e!==t))}notifyAuthenticationListeners(t){this.authenticationListeners.forEach((e=>e.callback(t)))}};s(N,"instance");let B=N;t.BrandClient=b,t.Courier=B,t.CourierClient=M,t.InboxClient=C,t.InboxMessageEvent=o,t.ListClient=P,t.PreferenceClient=U,t.TokenClient=x,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})}));
2
2
  //# sourceMappingURL=index.js.map