UNPKG

@trycourier/courier-react-native

Version:

Inbox, Push Notifications, and Preferences for React Native

638 lines (566 loc) 28.2 kB
import { Platform } from 'react-native'; // Imports import { CourierInboxListener } from './models/CourierInboxListener'; import { CourierPushListener } from './models/CourierPushListener'; import { CourierAuthenticationListener } from './models/CourierAuthenticationListener'; import { InboxMessage } from './models/InboxMessage'; import { Modules } from './Modules'; import Broadcaster from './Broadcaster'; import { CourierClient } from './client/CourierClient'; import { Events, CourierUtils } from './utils'; export { CourierClient } from './client/CourierClient'; export { BrandClient } from './client/BrandClient'; export { CourierBrandResponse } from './models/CourierBrand'; export { CourierDevice } from './models/CourierDevice'; // Exports export { CourierInboxView } from './views/CourierInboxView'; export { CourierPreferencesView } from './views/CourierPreferencesView'; export { CourierInboxListener } from './models/CourierInboxListener'; export { CourierPushListener } from './models/CourierPushListener'; export { CourierUserPreferencesTopic } from './models/CourierUserPreferences'; export { CourierAuthenticationListener } from './models/CourierAuthenticationListener'; export { CourierUserPreferencesChannel } from './models/CourierUserPreferences'; export { CourierUserPreferencesStatus } from './models/CourierUserPreferences'; export { CourierTrackingEvent } from './models/CourierTrackingEvent'; export { CourierPushProvider } from './models/CourierPushProvider'; export { CourierFont } from './models/CourierFont'; export { CourierButton } from './models/CourierButton'; export { CourierInfoViewStyle } from './models/CourierInfoViewStyle'; export { iOS_CourierCell } from './models/iOS_CourierCell'; 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 { CourierUtils } from './utils'; class Courier { // Singleton // Listeners authenticationListeners = new Map(); inboxListeners = new Map(); pushListeners = new Map(); // Broadcasting systemBroadcaster = new Broadcaster(Modules.System); sharedBroadcaster = new Broadcaster(Modules.Shared); constructor() { // Sets the initial SDK values // will show all foreground notification styles in iOS Courier.setIOSForegroundPresentationOptions({ options: ['sound', 'badge', 'list', 'banner'] }); // Attach the push notification listeners this.attachPushNotificationListeners(); } // Returns the public shared instance static get shared() { if (!this._sharedInstance) { this._sharedInstance = new Courier(); } return this._sharedInstance; } // Debugging isDebugging = __DEV__; // Show a log to the console static log(message) { if (Courier.shared.isDebugging) { console.log(message); } } // System (Static) async attachPushNotificationListeners() { var _this$pushNotificatio, _this$pushNotificatio2; // Remove existing listeners // Only allows one subscription to be active (_this$pushNotificatio = this.pushNotificationClickedEmitter) === null || _this$pushNotificatio === void 0 ? void 0 : _this$pushNotificatio.remove(); (_this$pushNotificatio2 = this.pushNotificationDeliveredEmitter) === null || _this$pushNotificatio2 === void 0 ? void 0 : _this$pushNotificatio2.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}`); } }); // 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); } }); } catch (error) { Courier.log(`Error parsing push notification delivered event: ${error}`); } }); } /** * Sets the iOS foreground presentation options for push notifications. * This method only works on iOS devices. * @param props An object containing an array of iOSForegroundPresentationOptions. * @returns A string indicating the result of the operation. Returns 'unsupported' on non-iOS platforms. */ static setIOSForegroundPresentationOptions(props) { // Only works on iOS if (Platform.OS !== 'ios') return 'unsupported'; const normalizedParams = Array.from(new Set(props.options)); return Modules.System.setIOSForegroundPresentationOptions({ options: normalizedParams }); } /** * Retrieves the current notification permission status. * @returns A Promise that resolves to a string representing the current notification permission status. */ static async getNotificationPermissionStatus() { return await Modules.System.getNotificationPermissionStatus(); } /** * Requests permission to send push notifications to the user. * @returns A Promise that resolves to a string indicating the result of the permission request. */ static async requestNotificationPermission() { return await Modules.System.requestNotificationPermission(); } /** * Opens the settings page for the current app. * This can be used to direct users to enable notifications if they've previously denied permission. */ static openSettingsForApp() { Modules.System.openSettingsForApp(); } // Client /** * Gets the current CourierClient instance. * @returns {Promise<CourierClient | undefined>} The current CourierClient instance, or undefined if not initialized. */ async getClient() { const client = (await Modules.Shared.getClient()) ?? undefined; if (!client) { return undefined; } const clientObj = JSON.parse(client); return new CourierClient({ userId: clientObj.userId, showLogs: clientObj.showLogs, jwt: clientObj.jwt, clientKey: clientObj.clientKey, connectionId: clientObj.connectionId, tenantId: clientObj.tenantId }); } // Authentication /** * Gets the current user ID. * @returns {Promise<string | undefined>} The current user ID, or undefined if not set. */ async getUserId() { return (await Modules.Shared.getUserId()) ?? undefined; } /** * Gets the current tenant ID. * @returns {Promise<string | undefined>} The current tenant ID, or undefined if not set. */ async getTenantId() { return (await Modules.Shared.getTenantId()) ?? undefined; } /** * Checks if a user is currently signed in. * @returns {Promise<boolean>} True if a user is signed in, false otherwise. */ async isUserSignedIn() { const isSignedIn = (await Modules.Shared.getIsUserSignedIn()) ?? 'false'; return isSignedIn.toLowerCase() === 'true'; } /** * Signs out the current user. * @returns {Promise<void>} A promise that resolves when the sign out process is complete. */ async signOut() { return await Modules.Shared.signOut(); } /** * Signs in a user with the provided credentials. * @param {Object} props - The sign-in properties. * @param {string} props.accessToken - The access token for authentication. * @param {string} [props.clientKey] - The client key (optional). * @param {string} props.userId - The user ID. * @param {string} [props.tenantId] - The tenant ID (optional). * @param {boolean} [props.showLogs] - Whether to show debug logs (defaults to __DEV__). * @returns {Promise<void>} A promise that resolves when the sign-in process is complete. */ async signIn(props) { this.isDebugging = props.showLogs ?? __DEV__; return await Modules.Shared.signIn(props.accessToken, props.clientKey ?? null, props.userId, props.tenantId ?? null, this.isDebugging); } /** * Adds an authentication listener to monitor user changes. * @param {Object} props - The listener properties. * @param {function} props.onUserChanged - Callback function triggered when the user changes. * @returns {CourierAuthenticationListener} The created authentication listener. */ async addAuthenticationListener(props) { // 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)); const id = await Modules.Shared.addAuthenticationListener(listenerId); this.authenticationListeners.set(id, listener); return listener; } /** * Removes a specific authentication listener. * @param {Object} props - The removal properties. * @param {string} props.listenerId - The ID of the listener to remove. * @returns {Promise<string>} A promise that resolves to the ID of the removed listener. */ async removeAuthenticationListener(props) { // Remove the native listener await Modules.Shared.removeAuthenticationListener(props.listenerId); // Remove the listener if (this.authenticationListeners.has(props.listenerId)) { var _listener$onUserChang; const listener = this.authenticationListeners.get(props.listenerId); listener === null || listener === void 0 || (_listener$onUserChang = listener.onUserChanged) === null || _listener$onUserChang === void 0 ? void 0 : _listener$onUserChang.remove(); this.authenticationListeners.delete(props.listenerId); } return props.listenerId; } /** * Removes all authentication listeners. * This method clears all registered authentication listeners, both native and JavaScript. */ async removeAllAuthenticationListeners() { // Remove all native listeners await Modules.Shared.removeAllAuthenticationListeners(); // Iterate through all authentication listeners this.authenticationListeners.forEach(listener => { var _listener$onUserChang2; (_listener$onUserChang2 = listener.onUserChanged) === null || _listener$onUserChang2 === void 0 ? void 0 : _listener$onUserChang2.remove(); }); // Clear the map of authentication listeners this.authenticationListeners.clear(); } // Push /** * Retrieves all push notification tokens. * @returns {Promise<Map<string, string>>} A promise that resolves to a Map of provider keys to tokens. */ async getAllTokens() { const tokensObject = await Modules.Shared.getAllTokens(); const tokensMap = new Map(); for (const [key, value] of Object.entries(tokensObject)) { tokensMap.set(key, value); } return tokensMap; } /** * Retrieves the push notification token for a specific key. * @param {Object} props - The properties object. * @param {string} props.key - The key associated with the token. * @returns {Promise<string | undefined>} A promise that resolves to the token or undefined if not found. */ async getToken(props) { return await Modules.Shared.getToken(props.key); } /** * Retrieves the push notification token for a specific provider. * @param {Object} props - The properties object. * @param {CourierPushProvider} props.provider - The push notification provider. * @returns {Promise<string | undefined>} A promise that resolves to the token or undefined if not found. */ async getTokenForProvider(props) { return await Modules.Shared.getToken(props.provider); } /** * Sets the push notification token for a specific key. * @param {Object} props - The properties object. * @param {string} props.key - The key to associate with the token. * @param {string} props.token - The push notification token. * @returns {Promise<void>} A promise that resolves when the token is set. */ async setToken(props) { return await Modules.Shared.setToken(props.key, props.token); } /** * Sets the push notification token for a specific provider. * @param {Object} props - The properties object. * @param {CourierPushProvider} props.provider - The push notification provider. * @param {string} props.token - The push notification token. * @returns {Promise<void>} A promise that resolves when the token is set. */ async setTokenForProvider(props) { return await Modules.Shared.setToken(props.provider, props.token); } /** * Adds a push notification listener. * @param {Object} props - The properties object. * @param {function} [props.onPushNotificationClicked] - Callback function triggered when a push notification is clicked. * @param {function} [props.onPushNotificationDelivered] - Callback function triggered when a push notification is delivered. * @returns {CourierPushListener} The created push notification listener. */ addPushNotificationListener(props) { const listenerId = `push_${CourierUtils.generateUUID()}`; const pushListener = new CourierPushListener(listenerId, props.onPushNotificationClicked, props.onPushNotificationDelivered); // Cache the listener this.pushListeners.set(listenerId, pushListener); // When listener is registered // Attempt to fetch the last message that was clicked // This is needed for when the app is killed and the // user launched the app by clicking on a notifications Modules.System.registerPushNotificationClickedOnKilledState(); return pushListener; } /** * Removes a specific push notification listener. * @param {Object} props - The properties object. * @param {string} props.listenerId - The ID of the listener to remove. * @returns {string} The ID of the removed listener. */ async removePushNotificationListener(props) { if (this.pushListeners.has(props.listenerId)) { this.pushListeners.delete(props.listenerId); } return props.listenerId; } /** * Removes all push notification listeners. */ async removeAllPushNotificationListeners() { this.pushListeners.forEach(listener => { listener.remove(); }); this.pushListeners.clear(); } // Inbox /** * Gets the current pagination limit for inbox messages. * @returns {Promise<number>} A promise that resolves with the current pagination limit. * Default is 32. */ async getInboxPaginationLimit() { return await Modules.Shared.getInboxPaginationLimit(); } /** * Sets the pagination limit for inbox messages. * @param {number} limit - The new pagination limit to set. * @returns {Promise<void>} A promise that resolves when the limit is set. * Default is 32. */ async setInboxPaginationLimit(limit) { await Modules.Shared.setInboxPaginationLimit(limit); } /** * Opens a specific message in the inbox. * @param {Object} props - The properties object. * @param {string} props.messageId - The ID of the message to open. * @returns {Promise<void>} A promise that resolves when the message is opened. */ async openMessage(props) { return await Modules.Shared.openMessage(props.messageId); } /** * Registers a click event for a specific message in the inbox. * @param {Object} props - The properties object. * @param {string} props.messageId - The ID of the message that was clicked. * @returns {Promise<void>} A promise that resolves when the click is registered. */ async clickMessage(props) { return await Modules.Shared.clickMessage(props.messageId); } /** * Marks a specific message as read in the inbox. * @param {Object} props - The properties object. * @param {string} props.messageId - The ID of the message to mark as read. * @returns {Promise<void>} A promise that resolves when the message is marked as read. */ async readMessage(props) { return await Modules.Shared.readMessage(props.messageId); } /** * Marks a specific message as unread in the inbox. * @param {Object} props - The properties object. * @param {string} props.messageId - The ID of the message to mark as unread. * @returns {Promise<void>} A promise that resolves when the message is marked as unread. */ async unreadMessage(props) { return await Modules.Shared.unreadMessage(props.messageId); } /** * Archives a specific message in the inbox. * @param {Object} props - The properties object. * @param {string} props.messageId - The ID of the message to archive. * @returns {Promise<void>} A promise that resolves when the message is archived. */ async archiveMessage(props) { return await Modules.Shared.archiveMessage(props.messageId); } /** * Marks all messages in the inbox as read. * @returns {Promise<void>} A promise that resolves when all messages are marked as read. */ async readAllInboxMessages() { return await Modules.Shared.readAllInboxMessages(); } /** * Adds a listener for inbox changes (aligned with the updated native iOS/Swift callbacks). * @param {Object} props - The properties object. * @param {Function} [props.onLoading] - Called when loading or refreshing begins. Receives isRefresh (boolean). * @param {Function} [props.onError] - Called when an error occurs. Receives the error message (string). * @param {Function} [props.onUnreadCountChanged] - Called when unread count changes. Receives unreadCount (number). * @param {Function} [props.onTotalCountChanged] - Called when total message count changes. Receives totalCount (number) and feed ("feed" or "archive"). * @param {Function} [props.onMessagesChanged] - Called when messages in a feed change. Receives an InboxMessageSet and feed name. * @param {Function} [props.onPageAdded] - Called when a new page of messages is added. Receives an InboxMessageSet, feed name, and isFirstPage (boolean). * @param {Function} [props.onMessageEvent] - Called for message-level changes (add/remove/update). Receives a message, index, feed name, and event string. * @returns {CourierInboxListener} A listener object that can be used to remove the listener later. */ async addInboxListener(props) { // Generate a unique ID for the listener const listenerId = `inbox_${CourierUtils.generateUUID()}`; // Generate unique channel IDs for each callback const listenerIds = { loading: `inbox_loading_${CourierUtils.generateUUID()}`, error: `inbox_error_${CourierUtils.generateUUID()}`, unreadCount: `inbox_unread_count_${CourierUtils.generateUUID()}`, totalCount: `inbox_total_count_${CourierUtils.generateUUID()}`, messagesChanged: `inbox_messages_changed_${CourierUtils.generateUUID()}`, pageAdded: `inbox_page_added_${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 => { var _props$onLoading; // event is simply the boolean "isRefresh" (_props$onLoading = props.onLoading) === null || _props$onLoading === void 0 ? void 0 : _props$onLoading.call(props, event); }); // 2) onError listener.onError = await this.sharedBroadcaster.addListener(listenerIds.error, event => { var _props$onError; // event is the error message (string) (_props$onError = props.onError) === null || _props$onError === void 0 ? void 0 : _props$onError.call(props, event); }); // 3) onUnreadCountChanged listener.onUnreadCountChanged = await this.sharedBroadcaster.addListener(listenerIds.unreadCount, event => { var _props$onUnreadCountC; // event is the unread count (number) (_props$onUnreadCountC = props.onUnreadCountChanged) === null || _props$onUnreadCountC === void 0 ? void 0 : _props$onUnreadCountC.call(props, event); }); // 4) onTotalCountChanged listener.onTotalCountChanged = await this.sharedBroadcaster.addListener(listenerIds.totalCount, event => { var _props$onTotalCountCh; // event => { feed: "feed"|"archive", totalCount: number } (_props$onTotalCountCh = props.onTotalCountChanged) === null || _props$onTotalCountCh === void 0 ? void 0 : _props$onTotalCountCh.call(props, event.totalCount, event.feed); }); // 5) onMessagesChanged listener.onMessagesChanged = await this.sharedBroadcaster.addListener(listenerIds.messagesChanged, event => { var _props$onMessagesChan; // event => { feed: "feed"|"archive", messages: any[], totalMessageCount: number, canPaginate: boolean } const convertedMessages = this.convertMessages(event.messages); (_props$onMessagesChan = props.onMessagesChanged) === null || _props$onMessagesChan === void 0 ? void 0 : _props$onMessagesChan.call(props, convertedMessages, event.canPaginate, event.feed); }); // 6) onPageAdded listener.onPageAdded = await this.sharedBroadcaster.addListener(listenerIds.pageAdded, event => { var _props$onPageAdded; // event => { feed: "feed"|"archive", messages: any[], totalMessageCount: number, canPaginate: boolean, isFirstPage: boolean } const convertedMessages = this.convertMessages(event.messages); (_props$onPageAdded = props.onPageAdded) === null || _props$onPageAdded === void 0 ? void 0 : _props$onPageAdded.call(props, convertedMessages, event.canPaginate, event.isFirstPage, event.feed); }); // 7) onMessageEvent listener.onMessageEvent = await this.sharedBroadcaster.addListener(listenerIds.messageEvent, event => { var _props$onMessageEvent; // event => { feed: "feed"|"archive", index: number, event: string, message: any } const convertedMessage = InboxMessage.fromJson(event.message); (_props$onMessageEvent = props.onMessageEvent) === null || _props$onMessageEvent === void 0 ? void 0 : _props$onMessageEvent.call(props, convertedMessage, event.index, event.feed, event.event); }); // Attach the listener to native iOS/Android via bridging const id = await Modules.Shared.addInboxListener(listenerId, listenerIds.loading, listenerIds.error, listenerIds.unreadCount, listenerIds.totalCount, listenerIds.messagesChanged, listenerIds.pageAdded, listenerIds.messageEvent); // Keep track so we can remove it later this.inboxListeners.set(id, listener); return listener; } convertMessages(messages) { return messages.map(jsonString => InboxMessage.fromJson(jsonString)); } /** * Removes a specific inbox listener. * @param {Object} props - The properties object. * @param {string} props.listenerId - The ID of the listener to remove. * @returns {string} The ID of the removed listener. */ async removeInboxListener(props) { // Call native code await Modules.Shared.removeInboxListener(props.listenerId); // Remove the listener if (this.inboxListeners.has(props.listenerId)) { var _listener$onLoading, _listener$onError, _listener$onUnreadCou, _listener$onTotalCoun, _listener$onMessagesC, _listener$onPageAdded, _listener$onMessageEv; // Remove emitters const listener = this.inboxListeners.get(props.listenerId); listener === null || listener === void 0 || (_listener$onLoading = listener.onLoading) === null || _listener$onLoading === void 0 ? void 0 : _listener$onLoading.remove(); listener === null || listener === void 0 || (_listener$onError = listener.onError) === null || _listener$onError === void 0 ? void 0 : _listener$onError.remove(); listener === null || listener === void 0 || (_listener$onUnreadCou = listener.onUnreadCountChanged) === null || _listener$onUnreadCou === void 0 ? void 0 : _listener$onUnreadCou.remove(); listener === null || listener === void 0 || (_listener$onTotalCoun = listener.onTotalCountChanged) === null || _listener$onTotalCoun === void 0 ? void 0 : _listener$onTotalCoun.remove(); listener === null || listener === void 0 || (_listener$onMessagesC = listener.onMessagesChanged) === null || _listener$onMessagesC === void 0 ? void 0 : _listener$onMessagesC.remove(); listener === null || listener === void 0 || (_listener$onPageAdded = listener.onPageAdded) === null || _listener$onPageAdded === void 0 ? void 0 : _listener$onPageAdded.remove(); listener === null || listener === void 0 || (_listener$onMessageEv = listener.onMessageEvent) === null || _listener$onMessageEv === void 0 ? void 0 : _listener$onMessageEv.remove(); // Remove the listener this.inboxListeners.delete(props.listenerId); } return props.listenerId; } /** * Removes all inbox listeners. */ async removeAllInboxListeners() { // Call native code await Modules.Shared.removeAllInboxListeners(); // Remove all items from inboxListeners this.inboxListeners.forEach(listener => { var _listener$onLoading2, _listener$onError2, _listener$onUnreadCou2, _listener$onTotalCoun2, _listener$onMessagesC2, _listener$onPageAdded2, _listener$onMessageEv2; listener === null || listener === void 0 || (_listener$onLoading2 = listener.onLoading) === null || _listener$onLoading2 === void 0 ? void 0 : _listener$onLoading2.remove(); listener === null || listener === void 0 || (_listener$onError2 = listener.onError) === null || _listener$onError2 === void 0 ? void 0 : _listener$onError2.remove(); listener === null || listener === void 0 || (_listener$onUnreadCou2 = listener.onUnreadCountChanged) === null || _listener$onUnreadCou2 === void 0 ? void 0 : _listener$onUnreadCou2.remove(); listener === null || listener === void 0 || (_listener$onTotalCoun2 = listener.onTotalCountChanged) === null || _listener$onTotalCoun2 === void 0 ? void 0 : _listener$onTotalCoun2.remove(); listener === null || listener === void 0 || (_listener$onMessagesC2 = listener.onMessagesChanged) === null || _listener$onMessagesC2 === void 0 ? void 0 : _listener$onMessagesC2.remove(); listener === null || listener === void 0 || (_listener$onPageAdded2 = listener.onPageAdded) === null || _listener$onPageAdded2 === void 0 ? void 0 : _listener$onPageAdded2.remove(); listener === null || listener === void 0 || (_listener$onMessageEv2 = listener.onMessageEvent) === null || _listener$onMessageEv2 === void 0 ? void 0 : _listener$onMessageEv2.remove(); }); this.inboxListeners.clear(); } /** * Refreshes the inbox. * Useful for pull-to-refresh functionality. * @returns {Promise<void>} A promise that resolves when the inbox is refreshed. */ async refreshInbox() { return await Modules.Shared.refreshInbox(); } /** * Fetches the next page of inbox messages. * @returns {Promise<InboxMessage[]>} A promise that resolves with an array of fetched inbox messages. */ async fetchNextPageOfMessages(props) { const data = await Modules.Shared.fetchNextPageOfMessages(props.inboxMessageFeed); const parsedData = data ? JSON.parse(data) : undefined; return (parsedData === null || parsedData === void 0 ? void 0 : parsedData.messages) ?? []; } /** * Sets a flag to indicate if UI tests are active. */ static setIsUITestsActive(isActive) { if (Platform.OS === 'ios' || Platform.OS === 'android') { Modules.Shared.setIsUITestsActive(isActive); } } } export default Courier; //# sourceMappingURL=index.js.map