From 8ab2f365132ad41491e57201f4e0dfe232aaefb1 Mon Sep 17 00:00:00 2001 From: Gabriel Vinhaes Date: Tue, 17 Mar 2026 20:49:46 -0300 Subject: [PATCH] feat: add EU endpoint presets to React Native SDK --- Docs/1_Authentication.md | 12 + Docs/5_Client.md | 1 + README.md | 14 + .../courierreactnative/CourierClientModule.kt | 1 + .../courierreactnative/CourierSharedModule.kt | 3 + .../main/java/com/courierreactnative/Utils.kt | 10 + babel.config.js | 2 +- ios/CourierClientModule.swift | 1 + ios/CourierReactNativeModule.m | 1 + ios/CourierSharedModule.swift | 13 +- ios/Utils.swift | 10 + package.json | 2 +- src/CourierApiUrls.tsx | 30 ++ src/__tests__/api-urls.test.tsx | 76 ++++ src/__tests__/index.test.tsx | 6 +- src/client/CourierClient.tsx | 33 +- src/index.tsx | 325 +++++++++++------- 17 files changed, 405 insertions(+), 135 deletions(-) create mode 100644 src/CourierApiUrls.tsx create mode 100644 src/__tests__/api-urls.test.tsx diff --git a/Docs/1_Authentication.md b/Docs/1_Authentication.md index 0bb2628..9f9f8f8 100644 --- a/Docs/1_Authentication.md +++ b/Docs/1_Authentication.md @@ -94,6 +94,18 @@ const userId = "your_user_id"; await Courier.shared.signIn({ userId: userId, accessToken: jwt }); ``` +For EU-hosted workspaces, pass the built-in EU endpoint preset: + +```javascript +import Courier, { getCourierApiUrlsForRegion } from "@trycourier/courier-react-native"; + +await Courier.shared.signIn({ + userId: userId, + accessToken: jwt, + apiUrls: getCourierApiUrlsForRegion("eu") +}); +``` + If the token is expired, you can generate a new one from your endpoint and call `Courier.shared.signIn(...)` again. You will need to check the token manually for expiration or generate a new one when the user views a specific screen in your app. It is up to you to handle token expiration and refresh based on your security needs. ## 4. Sign your user out diff --git a/Docs/5_Client.md b/Docs/5_Client.md index 960f8ee..1f7504c 100644 --- a/Docs/5_Client.md +++ b/Docs/5_Client.md @@ -15,6 +15,7 @@ const client = new CourierClient({ clientKey: "...", // Optional. Used only for Inbox tenantId: .., // Optional. Used for scoping a client to a specific tenant connectionId: "...", // Optional. Used for inbox websocket + apiUrls: getCourierApiUrlsForRegion("eu"), // Optional. Use for EU-hosted workspaces }); // Details about the client diff --git a/README.md b/README.md index 3b4ce3a..67167af 100644 --- a/README.md +++ b/README.md @@ -259,3 +259,17 @@ Starter projects using this SDK. We want to make this the best SDK for managing notifications! Have an idea or feedback about our SDKs? Let us know! [Courier React Native Issues](https://github.com/trycourier/courier-react-native/issues) + +## EU endpoints + +If your workspace uses EU-hosted Courier endpoints, pass the built-in EU preset through `apiUrls`. + +```tsx +import Courier, { getCourierApiUrlsForRegion } from "@trycourier/courier-react-native"; + +await Courier.shared.signIn({ + userId: "your_user_id", + accessToken: jwt, + apiUrls: getCourierApiUrlsForRegion("eu") +}); +``` diff --git a/android/src/main/java/com/courierreactnative/CourierClientModule.kt b/android/src/main/java/com/courierreactnative/CourierClientModule.kt index 6d518ed..3463204 100644 --- a/android/src/main/java/com/courierreactnative/CourierClientModule.kt +++ b/android/src/main/java/com/courierreactnative/CourierClientModule.kt @@ -50,6 +50,7 @@ class CourierClientModule( userId = userId, connectionId = options.getString("connectionId"), tenantId = options.getString("tenantId"), + apiUrls = options.getMap("apiUrls")?.toApiUrls() ?: CourierClient.ApiUrls(), showLogs = showLogs ) diff --git a/android/src/main/java/com/courierreactnative/CourierSharedModule.kt b/android/src/main/java/com/courierreactnative/CourierSharedModule.kt index 185eeb6..21a0ffd 100644 --- a/android/src/main/java/com/courierreactnative/CourierSharedModule.kt +++ b/android/src/main/java/com/courierreactnative/CourierSharedModule.kt @@ -1,6 +1,7 @@ package com.courierreactnative import com.courier.android.Courier +import com.courier.android.client.CourierClient import com.courier.android.models.CourierAuthenticationListener import com.courier.android.models.CourierInboxListener import com.courier.android.models.InboxMessageSet @@ -92,6 +93,7 @@ class CourierSharedModule( clientKey: String?, userId: String, tenantId: String?, + apiUrls: com.facebook.react.bridge.ReadableMap?, showLogs: Boolean, promise: Promise ) { @@ -101,6 +103,7 @@ class CourierSharedModule( tenantId = tenantId, accessToken = accessToken, clientKey = clientKey, + apiUrls = apiUrls?.toApiUrls() ?: CourierClient.ApiUrls(), showLogs = showLogs ) promise.resolve(null) diff --git a/android/src/main/java/com/courierreactnative/Utils.kt b/android/src/main/java/com/courierreactnative/Utils.kt index 58c9789..e26ca08 100644 --- a/android/src/main/java/com/courierreactnative/Utils.kt +++ b/android/src/main/java/com/courierreactnative/Utils.kt @@ -76,6 +76,16 @@ internal fun ReadableMap.toInfoViewStyle(context: Context): CourierStyles.InfoVi } +internal fun ReadableMap.toApiUrls(): com.courier.android.client.CourierClient.ApiUrls { + val defaults = com.courier.android.client.CourierClient.ApiUrls() + return com.courier.android.client.CourierClient.ApiUrls( + rest = getString("rest") ?: defaults.rest, + graphql = getString("graphql") ?: defaults.graphql, + inboxGraphql = getString("inboxGraphql") ?: defaults.inboxGraphql, + inboxWebSocket = getString("inboxWebSocket") ?: defaults.inboxWebSocket + ) +} + internal fun Map?.toWritableMap(): WritableMap { val map = Arguments.createMap() this?.forEach { (key, value) -> diff --git a/babel.config.js b/babel.config.js index f842b77..f7b3da3 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,3 @@ module.exports = { - presets: ['module:metro-react-native-babel-preset'], + presets: ['module:@react-native/babel-preset'], }; diff --git a/ios/CourierClientModule.swift b/ios/CourierClientModule.swift index e524f9f..8155349 100644 --- a/ios/CourierClientModule.swift +++ b/ios/CourierClientModule.swift @@ -25,6 +25,7 @@ internal class CourierClientModule: CourierReactNativeEventEmitter { userId: userId, connectionId: options["connectionId"] as? String, tenantId: options["tenantId"] as? String, + baseUrls: (options["apiUrls"] as? NSDictionary)?.toCourierApiUrls() ?? CourierClient.ApiUrls(), showLogs: showLogs ) diff --git a/ios/CourierReactNativeModule.m b/ios/CourierReactNativeModule.m index e6695b4..88850bc 100644 --- a/ios/CourierReactNativeModule.m +++ b/ios/CourierReactNativeModule.m @@ -37,6 +37,7 @@ @interface RCT_EXTERN_MODULE(CourierSharedModule, NSObject) withClientKey: (NSString*)clientKey withUserId: (NSString*)userId withTenantId: (NSString*)tenantId + withApiUrls: (NSDictionary*)apiUrls withShowLogs: (BOOL*)showLogs withResolver: (RCTPromiseResolveBlock)resolve withRejecter: (RCTPromiseRejectBlock)reject diff --git a/ios/CourierSharedModule.swift b/ios/CourierSharedModule.swift index 0b8e415..673ccf4 100644 --- a/ios/CourierSharedModule.swift +++ b/ios/CourierSharedModule.swift @@ -40,7 +40,13 @@ class CourierSharedModule: CourierReactNativeEventEmitter { "userId": options.userId as Any, "connectionId": options.connectionId as Any, "tenantId": options.tenantId as Any, - "showLogs": options.showLogs as Any + "showLogs": options.showLogs as Any, + "apiUrls": [ + "rest": options.apiUrls.rest, + "graphql": options.apiUrls.graphql, + "inboxGraphql": options.apiUrls.inboxGraphql, + "inboxWebSocket": options.apiUrls.inboxWebSocket + ] as Any ] .compactMapValues { $0 } @@ -90,8 +96,8 @@ class CourierSharedModule: CourierReactNativeEventEmitter { } } - @objc(signIn:withClientKey:withUserId:withTenantId:withShowLogs:withResolver:withRejecter:) - func signIn(accessToken: String, clientKey: String?, userId: String, tenantId: String?, showLogs: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void { + @objc(signIn:withClientKey:withUserId:withTenantId:withApiUrls:withShowLogs:withResolver:withRejecter:) + func signIn(accessToken: String, clientKey: String?, userId: String, tenantId: String?, apiUrls: NSDictionary?, showLogs: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void { Task { @@ -100,6 +106,7 @@ class CourierSharedModule: CourierReactNativeEventEmitter { tenantId: tenantId, accessToken: accessToken, clientKey: clientKey, + baseUrls: apiUrls?.toCourierApiUrls() ?? CourierClient.ApiUrls(), showLogs: showLogs ) diff --git a/ios/Utils.swift b/ios/Utils.swift index 427ef1f..d16a89d 100644 --- a/ios/Utils.swift +++ b/ios/Utils.swift @@ -157,6 +157,16 @@ internal extension NSDictionary { ) } + + func toCourierApiUrls() -> CourierClient.ApiUrls { + let defaults = CourierClient.ApiUrls() + return CourierClient.ApiUrls( + rest: self["rest"] as? String ?? defaults.rest, + graphql: self["graphql"] as? String ?? defaults.graphql, + inboxGraphql: self["inboxGraphql"] as? String ?? defaults.inboxGraphql, + inboxWebSocket: self["inboxWebSocket"] as? String ?? defaults.inboxWebSocket + ) + } } diff --git a/package.json b/package.json index 271b843..298bfe5 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "quoteProps": "consistent", "singleQuote": true, "tabWidth": 2, - "trailingComma": "off", + "trailingComma": "none", "useTabs": false } ] diff --git a/src/CourierApiUrls.tsx b/src/CourierApiUrls.tsx new file mode 100644 index 0000000..2806868 --- /dev/null +++ b/src/CourierApiUrls.tsx @@ -0,0 +1,30 @@ +export interface CourierApiUrls { + rest: string; + graphql: string; + inboxGraphql: string; + inboxWebSocket: string; +} + +export type CourierApiRegion = 'us' | 'eu'; + +export const DEFAULT_COURIER_API_URLS: CourierApiUrls = { + rest: 'https://api.courier.com', + graphql: 'https://api.courier.com/client/q', + inboxGraphql: 'https://inbox.courier.io/q', + inboxWebSocket: 'wss://realtime.courier.io' +}; + +export const EU_COURIER_API_URLS: CourierApiUrls = { + rest: 'https://api.eu.courier.com', + graphql: 'https://api.eu.courier.com/client/q', + inboxGraphql: 'https://inbox.eu.courier.io/q', + inboxWebSocket: 'wss://realtime.eu.courier.io' +}; + +export function getCourierApiUrlsForRegion( + region: CourierApiRegion +): CourierApiUrls { + return region === 'eu' + ? { ...EU_COURIER_API_URLS } + : { ...DEFAULT_COURIER_API_URLS }; +} diff --git a/src/__tests__/api-urls.test.tsx b/src/__tests__/api-urls.test.tsx new file mode 100644 index 0000000..b7357a1 --- /dev/null +++ b/src/__tests__/api-urls.test.tsx @@ -0,0 +1,76 @@ +jest.mock('../Modules', () => ({ + Modules: { + getNativeComponent: jest.fn(() => 'mock-native-component'), + Client: { + addClient: jest.fn(() => 'client-id'), + removeClient: jest.fn(() => 'client-id') + }, + Shared: { + attachEmitter: jest.fn(() => Promise.resolve('emitter-id')), + signIn: jest.fn(() => Promise.resolve()) + }, + System: { + setIOSForegroundPresentationOptions: jest.fn(() => 'ok') + } + } +})); + +jest.mock('../Broadcaster', () => + jest.fn().mockImplementation(() => ({ + addListener: jest.fn(() => Promise.resolve(undefined)) + })) +); + +import Courier, { + CourierClient, + DEFAULT_COURIER_API_URLS, + EU_COURIER_API_URLS, + getCourierApiUrlsForRegion +} from '../index'; +import { Modules } from '../Modules'; + +describe('CourierApiUrls', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns a cloned EU preset', () => { + const apiUrls = getCourierApiUrlsForRegion('eu'); + + expect(apiUrls).toEqual(EU_COURIER_API_URLS); + expect(apiUrls).not.toBe(EU_COURIER_API_URLS); + }); + + it('passes apiUrls when creating a client', () => { + const client = new CourierClient({ + userId: 'user-123', + showLogs: true, + apiUrls: EU_COURIER_API_URLS + }); + + expect(client.options.apiUrls).toEqual(EU_COURIER_API_URLS); + expect(Modules.Client.addClient).toHaveBeenCalledWith( + expect.objectContaining({ + userId: 'user-123', + apiUrls: EU_COURIER_API_URLS + }) + ); + }); + + it('passes apiUrls during shared sign in', async () => { + await Courier.shared.signIn({ + accessToken: 'jwt', + userId: 'user-123', + apiUrls: DEFAULT_COURIER_API_URLS + }); + + expect(Modules.Shared.signIn).toHaveBeenCalledWith( + 'jwt', + undefined, + 'user-123', + undefined, + DEFAULT_COURIER_API_URLS, + expect.any(Boolean) + ); + }); +}); diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index bf84291..489447b 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -1 +1,5 @@ -it.todo('write a test'); +describe('placeholder', () => { + it('keeps the test suite active', () => { + expect(true).toBe(true); + }); +}); diff --git a/src/client/CourierClient.tsx b/src/client/CourierClient.tsx index 065bf2b..0250ad6 100644 --- a/src/client/CourierClient.tsx +++ b/src/client/CourierClient.tsx @@ -1,9 +1,10 @@ -import { BrandClient } from ".."; -import { ClientModule } from "./ClientModule"; -import { InboxClient } from "./InboxClient"; -import { PreferenceClient } from "./PreferenceClient"; -import { TokenClient } from "./TokenClient"; -import { TrackingClient } from "./TrackingClient"; +import { BrandClient } from '..'; +import { ClientModule } from './ClientModule'; +import { InboxClient } from './InboxClient'; +import { PreferenceClient } from './PreferenceClient'; +import { TokenClient } from './TokenClient'; +import { TrackingClient } from './TrackingClient'; +import { CourierApiUrls } from '../CourierApiUrls'; export interface CourierClientOptions { userId: string; @@ -12,10 +13,10 @@ export interface CourierClientOptions { clientKey?: string; connectionId?: string; tenantId?: string; + apiUrls?: CourierApiUrls; } export class CourierClient extends ClientModule { - public readonly options: CourierClientOptions; public readonly tokens: TokenClient; public readonly brands: BrandClient; @@ -23,8 +24,15 @@ export class CourierClient extends ClientModule { public readonly preferences: PreferenceClient; public readonly tracking: TrackingClient; - constructor(props: { userId: string, jwt?: string, clientKey?: string, connectionId?: string, tenantId?: string, showLogs?: boolean }) { - + constructor(props: { + userId: string; + jwt?: string; + clientKey?: string; + connectionId?: string; + tenantId?: string; + apiUrls?: CourierApiUrls; + showLogs?: boolean; + }) { const options = { userId: props.userId, showLogs: props.showLogs ?? __DEV__, @@ -32,18 +40,17 @@ export class CourierClient extends ClientModule { clientKey: props.clientKey, connectionId: props.clientKey, tenantId: props.tenantId, + apiUrls: props.apiUrls ? { ...props.apiUrls } : undefined }; super(options); - this.options = options + this.options = options; this.tokens = new TokenClient(this.clientId); this.brands = new BrandClient(this.clientId); this.inbox = new InboxClient(this.clientId); this.preferences = new PreferenceClient(this.clientId); this.tracking = new TrackingClient(this.clientId); - } - -} \ No newline at end of file +} diff --git a/src/index.tsx b/src/index.tsx index 1582dd9..adb542d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,4 @@ -import { - Platform, - EmitterSubscription, -} from 'react-native'; +import { Platform, EmitterSubscription } from 'react-native'; // Imports import { CourierInboxListener } from './models/CourierInboxListener'; @@ -11,12 +8,25 @@ import { InboxMessage } from './models/InboxMessage'; import { CourierPushProvider } from './models/CourierPushProvider'; import { Modules } from './Modules'; import Broadcaster from './Broadcaster'; +import { + CourierApiRegion, + CourierApiUrls, + DEFAULT_COURIER_API_URLS, + EU_COURIER_API_URLS, + getCourierApiUrlsForRegion +} from './CourierApiUrls'; import { CourierClient } from './client/CourierClient'; import { Events, CourierUtils } from './utils'; import { InboxMessageFeed } from './models/InboxMessageFeed'; import { InboxMessageEvent } from './models/InboxMessageEvent'; -export { CourierClient } from './client/CourierClient' ; +export { CourierClient } from './client/CourierClient'; +export type { CourierApiUrls, CourierApiRegion }; +export { + DEFAULT_COURIER_API_URLS, + EU_COURIER_API_URLS, + getCourierApiUrlsForRegion +}; export { BrandClient } from './client/BrandClient'; export { CourierBrandResponse } from './models/CourierBrand'; export { CourierDevice } from './models/CourierDevice'; @@ -40,18 +50,33 @@ export { iOS_CourierSheet } from './models/iOS_CourierSheet'; export { InboxMessage } from './models/InboxMessage'; export { InboxMessageFeed } from './models/InboxMessageFeed'; export { InboxMessageEvent } from './models/InboxMessageEvent'; -export { CourierInboxButtonStyle, CourierInboxTextStyle, CourierInboxUnreadIndicatorStyle, CourierInboxTheme } from './models/CourierInboxTheme'; -export { CourierPreferencesTheme, CourierPreferencesMode, CourierPreferencesChannel } from './models/CourierPreferencesTheme'; -export type iOSForegroundPresentationOptions = 'sound' | 'badge' | 'list' | 'banner'; +export { + CourierInboxButtonStyle, + CourierInboxTextStyle, + CourierInboxUnreadIndicatorStyle, + CourierInboxTheme +} from './models/CourierInboxTheme'; +export { + CourierPreferencesTheme, + CourierPreferencesMode, + CourierPreferencesChannel +} from './models/CourierPreferencesTheme'; +export type iOSForegroundPresentationOptions = + | 'sound' + | 'badge' + | 'list' + | 'banner'; export { CourierUtils } from './utils'; class Courier { - // Singleton private static _sharedInstance: Courier; // Listeners - private authenticationListeners = new Map(); + private authenticationListeners = new Map< + string, + CourierAuthenticationListener + >(); private inboxListeners = new Map(); private pushListeners = new Map(); @@ -62,7 +87,6 @@ class Courier { private pushNotificationDeliveredEmitter: EmitterSubscription | undefined; public constructor() { - // Sets the initial SDK values // will show all foreground notification styles in iOS Courier.setIOSForegroundPresentationOptions({ @@ -71,18 +95,15 @@ class Courier { // Attach the push notification listeners this.attachPushNotificationListeners(); - } // Returns the public shared instance public static get shared(): Courier { - if (!this._sharedInstance) { this._sharedInstance = new Courier(); } return this._sharedInstance; - } // Debugging @@ -99,39 +120,47 @@ class Courier { // System (Static) private async attachPushNotificationListeners() { - // Remove existing listeners // Only allows one subscription to be active this.pushNotificationClickedEmitter?.remove(); this.pushNotificationDeliveredEmitter?.remove(); // When a push notification is clicked - this.pushNotificationClickedEmitter = await this.systemBroadcaster.addListener(Events.Push.CLICKED, (event) => { - try { - const message = JSON.parse(event); - this.pushListeners.forEach(listener => { - if (listener.onPushNotificationClicked) { - listener.onPushNotificationClicked(message); - } - }); - } catch (error) { - Courier.log(`Error parsing push notification clicked event: ${error}`); - } - }); + this.pushNotificationClickedEmitter = + await this.systemBroadcaster.addListener(Events.Push.CLICKED, (event) => { + try { + const message = JSON.parse(event); + this.pushListeners.forEach((listener) => { + if (listener.onPushNotificationClicked) { + listener.onPushNotificationClicked(message); + } + }); + } catch (error) { + Courier.log( + `Error parsing push notification clicked event: ${error}` + ); + } + }); // When a push notification is delivered - this.pushNotificationDeliveredEmitter = await this.systemBroadcaster.addListener(Events.Push.DELIVERED, (event) => { - try { - const message = JSON.parse(event); - this.pushListeners.forEach(listener => { - if (listener.onPushNotificationDelivered) { - listener.onPushNotificationDelivered(message); + this.pushNotificationDeliveredEmitter = + await this.systemBroadcaster.addListener( + Events.Push.DELIVERED, + (event) => { + try { + const message = JSON.parse(event); + this.pushListeners.forEach((listener) => { + if (listener.onPushNotificationDelivered) { + listener.onPushNotificationDelivered(message); + } + }); + } catch (error) { + Courier.log( + `Error parsing push notification delivered event: ${error}` + ); } - }); - } catch (error) { - Courier.log(`Error parsing push notification delivered event: ${error}`); - } - }); + } + ); } /** @@ -140,16 +169,16 @@ class Courier { * @param props An object containing an array of iOSForegroundPresentationOptions. * @returns A string indicating the result of the operation. Returns 'unsupported' on non-iOS platforms. */ - public static setIOSForegroundPresentationOptions(props: { options: iOSForegroundPresentationOptions[] }): string { - + public static setIOSForegroundPresentationOptions(props: { + options: iOSForegroundPresentationOptions[]; + }): string { // Only works on iOS if (Platform.OS !== 'ios') return 'unsupported'; const normalizedParams = Array.from(new Set(props.options)); return Modules.System.setIOSForegroundPresentationOptions({ - options: normalizedParams, + options: normalizedParams }); - } /** @@ -183,8 +212,7 @@ class Courier { * @returns {Promise} The current CourierClient instance, or undefined if not initialized. */ public async getClient(): Promise { - - const client = await Modules.Shared.getClient() ?? undefined; + const client = (await Modules.Shared.getClient()) ?? undefined; if (!client) { return undefined; @@ -199,8 +227,8 @@ class Courier { clientKey: clientObj.clientKey, connectionId: clientObj.connectionId, tenantId: clientObj.tenantId, + apiUrls: clientObj.apiUrls }); - } // Authentication @@ -210,7 +238,7 @@ class Courier { * @returns {Promise} The current user ID, or undefined if not set. */ public async getUserId(): Promise { - return await Modules.Shared.getUserId() ?? undefined; + return (await Modules.Shared.getUserId()) ?? undefined; } /** @@ -218,7 +246,7 @@ class Courier { * @returns {Promise} The current tenant ID, or undefined if not set. */ public async getTenantId(): Promise { - return await Modules.Shared.getTenantId() ?? undefined; + return (await Modules.Shared.getTenantId()) ?? undefined; } /** @@ -226,7 +254,8 @@ class Courier { * @returns {Promise} True if a user is signed in, false otherwise. */ public async isUserSignedIn(): Promise { - const isSignedIn: string = await Modules.Shared.getIsUserSignedIn() ?? 'false'; + const isSignedIn: string = + (await Modules.Shared.getIsUserSignedIn()) ?? 'false'; return isSignedIn.toLowerCase() === 'true'; } @@ -248,13 +277,21 @@ class Courier { * @param {boolean} [props.showLogs] - Whether to show debug logs (defaults to __DEV__). * @returns {Promise} A promise that resolves when the sign-in process is complete. */ - public async signIn(props: { accessToken: string, clientKey?: string, userId: string, tenantId?: string, showLogs?: boolean }): Promise { + public async signIn(props: { + accessToken: string; + clientKey?: string; + userId: string; + tenantId?: string; + apiUrls?: CourierApiUrls; + showLogs?: boolean; + }): Promise { this.isDebugging = props.showLogs ?? __DEV__; return await Modules.Shared.signIn( props.accessToken, props.clientKey, props.userId, props.tenantId, + props.apiUrls ?? null, this.isDebugging ); } @@ -265,19 +302,22 @@ class Courier { * @param {function} props.onUserChanged - Callback function triggered when the user changes. * @returns {CourierAuthenticationListener} The created authentication listener. */ - public async addAuthenticationListener(props: { onUserChanged: (userId?: string) => void }): Promise { - + public async addAuthenticationListener(props: { + onUserChanged: (userId?: string) => void; + }): Promise { // Create a listener const listenerId = `authentication_${CourierUtils.generateUUID()}`; // Attach the listener const listener = new CourierAuthenticationListener(listenerId); - listener.onUserChanged = await this.sharedBroadcaster.addListener(listenerId, (event) => props.onUserChanged(event)); + listener.onUserChanged = await this.sharedBroadcaster.addListener( + listenerId, + (event) => props.onUserChanged(event) + ); const id = await Modules.Shared.addAuthenticationListener(listenerId); this.authenticationListeners.set(id, listener); return listener; - } /** @@ -286,8 +326,9 @@ class Courier { * @param {string} props.listenerId - The ID of the listener to remove. * @returns {Promise} A promise that resolves to the ID of the removed listener. */ - public async removeAuthenticationListener(props: { listenerId: string }): Promise { - + public async removeAuthenticationListener(props: { + listenerId: string; + }): Promise { // Remove the native listener await Modules.Shared.removeAuthenticationListener(props.listenerId); @@ -299,7 +340,6 @@ class Courier { } return props.listenerId; - } /** @@ -307,7 +347,6 @@ class Courier { * This method clears all registered authentication listeners, both native and JavaScript. */ public async removeAllAuthenticationListeners() { - // Remove all native listeners await Modules.Shared.removeAllAuthenticationListeners(); @@ -318,7 +357,6 @@ class Courier { // Clear the map of authentication listeners this.authenticationListeners.clear(); - } // Push @@ -352,7 +390,9 @@ class Courier { * @param {CourierPushProvider} props.provider - The push notification provider. * @returns {Promise} A promise that resolves to the token or undefined if not found. */ - public async getTokenForProvider(props: { provider: CourierPushProvider }): Promise { + public async getTokenForProvider(props: { + provider: CourierPushProvider; + }): Promise { return await Modules.Shared.getToken(props.provider); } @@ -363,7 +403,7 @@ class Courier { * @param {string} props.token - The push notification token. * @returns {Promise} A promise that resolves when the token is set. */ - public async setToken(props: { key: string, token: string }): Promise { + public async setToken(props: { key: string; token: string }): Promise { return await Modules.Shared.setToken(props.key, props.token); } @@ -374,7 +414,10 @@ class Courier { * @param {string} props.token - The push notification token. * @returns {Promise} A promise that resolves when the token is set. */ - public async setTokenForProvider(props: { provider: CourierPushProvider, token: string }): Promise { + public async setTokenForProvider(props: { + provider: CourierPushProvider; + token: string; + }): Promise { return await Modules.Shared.setToken(props.provider, props.token); } @@ -385,8 +428,10 @@ class Courier { * @param {function} [props.onPushNotificationDelivered] - Callback function triggered when a push notification is delivered. * @returns {CourierPushListener} The created push notification listener. */ - public addPushNotificationListener(props: { onPushNotificationClicked?: (push: any) => void, onPushNotificationDelivered?: (push: any) => void }): CourierPushListener { - + public addPushNotificationListener(props: { + onPushNotificationClicked?: (push: any) => void; + onPushNotificationDelivered?: (push: any) => void; + }): CourierPushListener { const listenerId = `push_${CourierUtils.generateUUID()}`; const pushListener = new CourierPushListener( @@ -405,7 +450,6 @@ class Courier { Modules.System.registerPushNotificationClickedOnKilledState(); return pushListener; - } /** @@ -414,7 +458,9 @@ class Courier { * @param {string} props.listenerId - The ID of the listener to remove. * @returns {string} The ID of the removed listener. */ - public async removePushNotificationListener(props: { listenerId: string }): Promise { + public async removePushNotificationListener(props: { + listenerId: string; + }): Promise { if (this.pushListeners.has(props.listenerId)) { this.pushListeners.delete(props.listenerId); } @@ -523,15 +569,28 @@ class Courier { * @returns {CourierInboxListener} A listener object that can be used to remove the listener later. */ public async addInboxListener(props: { - onLoading?: (isRefresh: boolean) => void, - onError?: (error: string) => void, - onUnreadCountChanged?: (unreadCount: number) => void, - onTotalCountChanged?: (totalCount: number, feed: string) => void, - onMessagesChanged?: (messages: InboxMessage[], canPaginate: boolean, feed: InboxMessageFeed) => void, - onPageAdded?: (messages: InboxMessage[], canPaginate: boolean, isFirstPage: boolean, feed: InboxMessageFeed) => void, - onMessageEvent?: (message: InboxMessage, index: number, feed: InboxMessageFeed, eventName: InboxMessageEvent) => void + onLoading?: (isRefresh: boolean) => void; + onError?: (error: string) => void; + onUnreadCountChanged?: (unreadCount: number) => void; + onTotalCountChanged?: (totalCount: number, feed: string) => void; + onMessagesChanged?: ( + messages: InboxMessage[], + canPaginate: boolean, + feed: InboxMessageFeed + ) => void; + onPageAdded?: ( + messages: InboxMessage[], + canPaginate: boolean, + isFirstPage: boolean, + feed: InboxMessageFeed + ) => void; + onMessageEvent?: ( + message: InboxMessage, + index: number, + feed: InboxMessageFeed, + eventName: InboxMessageEvent + ) => void; }): Promise { - // Generate a unique ID for the listener const listenerId = `inbox_${CourierUtils.generateUUID()}`; @@ -543,56 +602,91 @@ class Courier { totalCount: `inbox_total_count_${CourierUtils.generateUUID()}`, messagesChanged: `inbox_messages_changed_${CourierUtils.generateUUID()}`, pageAdded: `inbox_page_added_${CourierUtils.generateUUID()}`, - messageEvent: `inbox_message_event_${CourierUtils.generateUUID()}`, + messageEvent: `inbox_message_event_${CourierUtils.generateUUID()}` }; // Create the CourierInboxListener instance const listener = new CourierInboxListener(listenerId); // 1) onLoading - listener.onLoading = await this.sharedBroadcaster.addListener(listenerIds.loading, (event: any) => { - // event is simply the boolean "isRefresh" - props.onLoading?.(event); - }); + listener.onLoading = await this.sharedBroadcaster.addListener( + listenerIds.loading, + (event: any) => { + // event is simply the boolean "isRefresh" + props.onLoading?.(event); + } + ); // 2) onError - listener.onError = await this.sharedBroadcaster.addListener(listenerIds.error, (event: any) => { - // event is the error message (string) - props.onError?.(event); - }); + listener.onError = await this.sharedBroadcaster.addListener( + listenerIds.error, + (event: any) => { + // event is the error message (string) + props.onError?.(event); + } + ); // 3) onUnreadCountChanged - listener.onUnreadCountChanged = await this.sharedBroadcaster.addListener(listenerIds.unreadCount, (event: any) => { - // event is the unread count (number) - props.onUnreadCountChanged?.(event); - }); + listener.onUnreadCountChanged = await this.sharedBroadcaster.addListener( + listenerIds.unreadCount, + (event: any) => { + // event is the unread count (number) + props.onUnreadCountChanged?.(event); + } + ); // 4) onTotalCountChanged - listener.onTotalCountChanged = await this.sharedBroadcaster.addListener(listenerIds.totalCount, (event: any) => { - // event => { feed: "feed"|"archive", totalCount: number } - props.onTotalCountChanged?.(event.totalCount, event.feed); - }); + listener.onTotalCountChanged = await this.sharedBroadcaster.addListener( + listenerIds.totalCount, + (event: any) => { + // event => { feed: "feed"|"archive", totalCount: number } + props.onTotalCountChanged?.(event.totalCount, event.feed); + } + ); // 5) onMessagesChanged - listener.onMessagesChanged = await this.sharedBroadcaster.addListener(listenerIds.messagesChanged, (event: any) => { - // event => { feed: "feed"|"archive", messages: any[], totalMessageCount: number, canPaginate: boolean } - const convertedMessages = this.convertMessages(event.messages); - props.onMessagesChanged?.(convertedMessages, event.canPaginate, event.feed); - }); + listener.onMessagesChanged = await this.sharedBroadcaster.addListener( + listenerIds.messagesChanged, + (event: any) => { + // event => { feed: "feed"|"archive", messages: any[], totalMessageCount: number, canPaginate: boolean } + const convertedMessages = this.convertMessages(event.messages); + props.onMessagesChanged?.( + convertedMessages, + event.canPaginate, + event.feed + ); + } + ); // 6) onPageAdded - listener.onPageAdded = await this.sharedBroadcaster.addListener(listenerIds.pageAdded, (event: any) => { - // event => { feed: "feed"|"archive", messages: any[], totalMessageCount: number, canPaginate: boolean, isFirstPage: boolean } - const convertedMessages = this.convertMessages(event.messages); - props.onPageAdded?.(convertedMessages, event.canPaginate, event.isFirstPage, event.feed); - }); + listener.onPageAdded = await this.sharedBroadcaster.addListener( + listenerIds.pageAdded, + (event: any) => { + // event => { feed: "feed"|"archive", messages: any[], totalMessageCount: number, canPaginate: boolean, isFirstPage: boolean } + const convertedMessages = this.convertMessages(event.messages); + props.onPageAdded?.( + convertedMessages, + event.canPaginate, + event.isFirstPage, + event.feed + ); + } + ); // 7) onMessageEvent - listener.onMessageEvent = await this.sharedBroadcaster.addListener(listenerIds.messageEvent, (event: any) => { - // event => { feed: "feed"|"archive", index: number, event: string, message: any } - const convertedMessage = InboxMessage.fromJson(event.message); - props.onMessageEvent?.(convertedMessage, event.index, event.feed, event.event); - }); + listener.onMessageEvent = await this.sharedBroadcaster.addListener( + listenerIds.messageEvent, + (event: any) => { + // event => { feed: "feed"|"archive", index: number, event: string, message: any } + const convertedMessage = InboxMessage.fromJson(event.message); + props.onMessageEvent?.( + convertedMessage, + event.index, + event.feed, + event.event + ); + } + ); // Attach the listener to native iOS/Android via bridging const id = await Modules.Shared.addInboxListener( @@ -613,7 +707,7 @@ class Courier { } private convertMessages(messages: string[]): InboxMessage[] { - return messages.map(jsonString => InboxMessage.fromJson(jsonString)); + return messages.map((jsonString) => InboxMessage.fromJson(jsonString)); } /** @@ -622,14 +716,14 @@ class Courier { * @param {string} props.listenerId - The ID of the listener to remove. * @returns {string} The ID of the removed listener. */ - public async removeInboxListener(props: { listenerId: string }): Promise { - + public async removeInboxListener(props: { + listenerId: string; + }): Promise { // Call native code await Modules.Shared.removeInboxListener(props.listenerId); // Remove the listener if (this.inboxListeners.has(props.listenerId)) { - // Remove emitters const listener = this.inboxListeners.get(props.listenerId); listener?.onLoading?.remove(); @@ -642,18 +736,15 @@ class Courier { // Remove the listener this.inboxListeners.delete(props.listenerId); - } return props.listenerId; - } /** * Removes all inbox listeners. */ public async removeAllInboxListeners() { - // Call native code await Modules.Shared.removeAllInboxListeners(); @@ -669,7 +760,6 @@ class Courier { }); this.inboxListeners.clear(); - } /** @@ -685,8 +775,12 @@ class Courier { * Fetches the next page of inbox messages. * @returns {Promise} A promise that resolves with an array of fetched inbox messages. */ - public async fetchNextPageOfMessages(props: { inboxMessageFeed: InboxMessageFeed }): Promise { - const data = await Modules.Shared.fetchNextPageOfMessages(props.inboxMessageFeed); + public async fetchNextPageOfMessages(props: { + inboxMessageFeed: InboxMessageFeed; + }): Promise { + const data = await Modules.Shared.fetchNextPageOfMessages( + props.inboxMessageFeed + ); const parsedData = data ? JSON.parse(data) : undefined; return parsedData?.messages ?? []; } @@ -699,7 +793,6 @@ class Courier { Modules.Shared.setIsUITestsActive(isActive); } } - } export default Courier;