import { EmitterSubscription, NativeEventEmitter, NativeModules, Platform } from 'react-native' import type { InitializationData, ExecuteSyncOperationPayload, ExecuteAsyncOperationPayload } from './types' import type { InAppCallback } from './InAppCallback' import { LogLevel } from './LogLevel' const { MindboxSdk: MindboxSdkNative, MindboxJsDelivery } = NativeModules class MindboxSdkClass { private _initialized: boolean private _initializing: boolean private _callbacks: Array<() => void> private _mindboxJsDeliveryEvents: NativeEventEmitter private _emitterSubscribtion?: EmitterSubscription private readonly _prefix: string = '[RN]' constructor() { this._initialized = false this._initializing = false this._callbacks = [] this._mindboxJsDeliveryEvents = new NativeEventEmitter(MindboxJsDelivery) } /** * @name initialized * @type {boolean} * @description Is MindboxSdk already initialized. */ get initialized() { return this._initialized } /** * @name subscribedForPushClickedEvent * @type {boolean} * @description Is there any subscription on push notification tapped. */ get subscribedForPushClickedEvent() { return !!this._emitterSubscribtion } public registerInAppCallbacks(callbacks: Array) { let customCallback: InAppCallback | undefined const callbackNames = callbacks.map((callback) => { const name = callback.getName() switch (name) { case 'urlInAppCallback': { break } case 'copyPayloadInAppCallback': { break } case 'emptyInAppCallback': { break } default: { customCallback = callback } } return name }) this._mindboxJsDeliveryEvents.addListener('Click', (event) => { customCallback?.onInAppClick(event.id, event.redirectUrl, event.payload) }) this._mindboxJsDeliveryEvents.addListener('Dismiss', (event) => { customCallback?.onInAppDismissed(event.id) }) MindboxSdkNative.registerCallbacks(callbackNames) } /** * @name initialize * @description Initialization of MindboxSdk. It is recommended to do this on app's launch. You can call this method multiple times to set new configuration params. * @param {InitializationData} initializationData Initialization data * * @example * await MindboxSdk.initialize({ * domain: 'api.mindbox.ru', * endpointId: 'your-endpoint-id-here', * subscribeCustomerIfCreated: true, * shouldCreateCustomer: true, * previousInstallId: '', * previousUuid: '', * }); */ public async initialize(initializationData: InitializationData) { if (this._initializing) { console.warn('MindboxSdk is already initializing') return } this._initializing = true if (!initializationData || typeof initializationData !== 'object') { this._initializing = false throw new Error('Wrong initialization data!') } const { domain, endpointId, subscribeCustomerIfCreated, shouldCreateCustomer, previousInstallId, previousUuid } = initializationData if (!domain || !endpointId) { this._initializing = false throw new Error('Wrong initialization data!') } const payload: InitializationData = { domain, endpointId, } if (typeof shouldCreateCustomer !== 'undefined') { payload.shouldCreateCustomer = shouldCreateCustomer } if (typeof subscribeCustomerIfCreated !== 'undefined') { payload.subscribeCustomerIfCreated = subscribeCustomerIfCreated } if (typeof previousInstallId !== 'undefined' && previousInstallId.length > 0) { payload.previousInstallId = previousInstallId } if (typeof previousUuid !== 'undefined' && previousUuid.length > 0) { payload.previousUuid = previousUuid } try { const payloadString = JSON.stringify(payload) this._initialized = await MindboxSdkNative.initialize(payloadString) this._initializing = false this.writeNativeLog('Init in RN ', LogLevel.INFO) for (let i = 0; i < this._callbacks.length; i++) { this._callbacks[i]() } this._callbacks = [] } catch (error) { this._initializing = false throw error } } /** * @name getDeviceUUID * @description Requires a callback that will return device UUID. * @param {function(deviceUUID: String): void} callback Callback will return device UUID * * @example * MindboxSdk.getDeviceUUID((uuid: string) => { ... }); */ public getDeviceUUID(callback: (deviceUUID: string) => void) { if (!callback || typeof callback !== 'function') { throw new Error('callback is required!') } const callbackHandler = () => { MindboxSdkNative.getDeviceUUID().then((deviceUUID: string) => callback(deviceUUID)) } if (this._initialized) { callbackHandler() } else { this._callbacks.push(callbackHandler) } } /** * @name getToken * @description Requires a callback that will return FMS (Android) / APNS (iOS) token. * @param {function(token: String): void} callback Callback will return FMS (Android) / APNS (iOS) token * @deprecated since version 2.12.0. Use getTokens * @example * MindboxSdk.getToken((token: string) => { ... }); */ public getToken(callback: (token: string) => void) { if (!callback || typeof callback !== 'function') { throw new Error('callback is required!') } const callbackHandler = () => { let promise = null switch (Platform.OS) { case 'ios': promise = MindboxSdkNative.getAPNSToken() break case 'android': promise = MindboxSdkNative.getFMSToken() break default: promise = MindboxSdkNative.getAPNSToken() break } promise.then((token: string) => callback(token)) } if (this._initialized) { callbackHandler() } else { this._callbacks.push(callbackHandler) } } /** * @name getTokens * @description Requires a callback that will return FMS (Android) / APNS (iOS) token . * method return jsin string like {"FCM":"token1","HMS":"token2","RuStore":"token3"} * @param {function(token: String): void} callback Callback will return FMS/HCM/RuStore (Android) / APNS (iOS) token * @example * MindboxSdk.getTokens((token: string) => { ... }); */ public getTokens(callback: (token: string) => void) { if (!callback || typeof callback !== 'function') { throw new Error('callback is required!') } const callbackHandler = () => { let promise = null promise = MindboxSdkNative.getTokens() promise.then((token: string) => callback(token)) } if (this._initialized) { callbackHandler() } else { this._callbacks.push(callbackHandler) } } /** * @name updateToken * @description Updates your FMS/APNS token. * @param {String} token Your new fms/apns token * @deprecated since version 2.12.0. Use native methods * @example * await MindboxSdk.updateToken('your-fms/apns-token'); */ public async updateToken(token: string) { if (!token || typeof token !== 'string') { throw new Error('token is required!') } switch (Platform.OS) { case 'ios': try { await MindboxSdkNative.updateAPNSToken(token) } catch (error) { throw error } break case 'android': try { await MindboxSdkNative.updateFMSToken(token) } catch (error) { throw error } break default: try { await MindboxSdkNative.updateAPNSToken(token) } catch (error) { throw error } break } } /** * @name onPushClickReceived * @description Listens if push notification or push notification button were pressed. * @param {function(pushUrl: String, pushPayload: String): void} callback Callback will return push notification link or push notification button link * * @example * MindboxSdk.onPushClickReceived((pushClickRecievedData: string) => { ... }); */ public onPushClickReceived(callback: (pushUrl: string | null, pushPayload: string | null) => void) { if (!callback || typeof callback !== 'function') { this.writeNativeLog('PushClick callback is not set', LogLevel.ERROR) throw new Error('callback is required!') } this.removeOnPushClickReceived() this.writeNativeLog(`Set push click listener`, LogLevel.INFO) this._emitterSubscribtion = this._mindboxJsDeliveryEvents.addListener('pushNotificationClicked', (dataString: string) => { const data = JSON.parse(dataString) callback(data.pushUrl || null, data.pushPayload || null) }) if (Platform.OS === 'android') { this.writeNativeLog('Register push click listener for android', LogLevel.INFO) MindboxSdkNative.onPushClickedIsRegistered(true) } } /** * @name removeOnPushClickReceived * @description Removes onPushClickReceived subscribtion and event listener. * * @example * MindboxSdk.removeOnPushClickReceived(); */ public removeOnPushClickReceived() { if (this._emitterSubscribtion) { this._emitterSubscribtion.remove() this._emitterSubscribtion = undefined if (Platform.OS === 'android') { MindboxSdkNative.onPushClickedIsRegistered(false) } } } /** * @name executeAsyncOperation * @description Makes request to backend API without waiting any response. * @param {ExecuteAsyncOperationPayload} payload Payload with data * @param {String} payload.operationSystemName System name of the async operation * @param {Object} payload.operationBody Data for operation. Will be passed to the backed API * * @example * MindboxSdk.executeAsyncOperation({ * operationSystemName: '--YOUR SYSTEM NAME HERE--', * operationBody: { ... }, * }); */ public executeAsyncOperation(payload: ExecuteAsyncOperationPayload) { if (!payload || typeof payload !== 'object') { throw new Error('payload is required!') } const { operationSystemName, operationBody } = payload if (!operationSystemName || typeof operationSystemName !== 'string') { throw new Error('operationSystemName is required and must be a string!') } if (!operationBody || typeof operationBody !== 'object') { throw new Error('operationBody is required!') } const jsonStringPayload = JSON.stringify(operationBody) MindboxSdkNative.executeAsyncOperation(operationSystemName, jsonStringPayload) } /** * @name executeSyncOperation * @description Makes request to backend API and waits response. * @param {ExecuteSyncOperationPayload} payload Payload with data * @param {String} payload.operationSystemName System name of the async operation * @param {Object} payload.operationBody Data for operation. Will be passed to the backed API * @param {function(data: ExecuteSyncOperationSuccess): void} payload.onSuccess Callback will return data in case of successfull request * @param {function(error: ExecuteSyncOperationError): void} payload.onError Callback will return error data in case of unsuccessfull request * * @example * MindboxSdk.executeSyncOperation({ * operationSystemName: '--YOUR SYSTEM NAME HERE--', * operationBody: { ... }, * onSuccess: (data) => { ... }, * onError: (error) => { ... }, * }); */ public executeSyncOperation(payload: ExecuteSyncOperationPayload) { if (!payload || typeof payload !== 'object') { throw new Error('payload is required!') } const { operationSystemName, operationBody, onSuccess, onError } = payload if (!operationSystemName || typeof operationSystemName !== 'string') { throw new Error('operationSystemName is required and must be a string!') } if (!operationBody || typeof operationBody !== 'object') { throw new Error('operationBody is required!') } if (!onSuccess || typeof onSuccess !== 'function') { throw new Error('onSuccess callback is required!') } if (onError && typeof onError !== 'function') { throw new Error('onError callback must be a function!') } const jsonStringPayload = JSON.stringify(operationBody) MindboxSdkNative.executeSyncOperation(operationSystemName, jsonStringPayload).then((data: string) => { const response = JSON.parse(data) if (response.type && ['MindboxError', 'NetworkError', 'InternalError'].includes(response.type)) { onError(response) } else { onSuccess(response) } }) } /** * Method for managing sdk logging * * @param level - is used for showing Mindbox logs starts from [LogLevel]. Default * is [LogLevel.WARN]. [LogLevel.NONE] turns off all logs. */ public setLogLevel(level: LogLevel) { MindboxSdkNative.setLogLevel(level) } /** * Returns SDK version */ public getSdkVersion(callback: (sdkVersion: string) => void) { return MindboxSdkNative.getSdkVersion().then((version: string) => callback(version)) } /** * * Creates and deliveries event of "Push delivered". Recommended call this method from * background thread. * * Use this method only if you have custom push handling you don't use [Mindbox.handleRemoteMessage]. * You must not call it otherwise. * * @param uniqKey unique identifier of push notification * @deprecated since version 2.10.3. Use native methods */ public pushDelivered(uniqKey: string) { this.writeNativeLog('Used deprecated method pushDelivered. Use native methods', LogLevel.WARN) return MindboxSdkNative.pushDelivered(uniqKey) } /** * This method is kept for backward compatibility. The `granted` argument is ignored. * The SDK reads the current system authorization status and, if it differs * from the last known value, sends an update to the backend. * * @param granted current permission status * @deprecated Use `refreshNotificationPermissionStatus()` instead. */ public updateNotificationPermissionStatus(granted: Boolean) { console.warn(`updateNotificationPermissionStatus(granted=${String(granted)}) is deprecated. Use refreshNotificationPermissionStatus instead.`) return MindboxSdkNative.refreshNotificationPermissionStatus() } /** * Checks the current system authorization status for push notifications * and reports any changes to Mindbox. * * @example * MindboxSdk.refreshNotificationPermissionStatus() */ public refreshNotificationPermissionStatus() { return MindboxSdkNative.refreshNotificationPermissionStatus() } /** * Writes a log message to the native Mindbox logging system. * * Usage example: * * MindboxSdk..writeNativeLog( * message: "This is a debug message", * logLevel: LogLevel.DEBUG, * ); * * @param message: The message to be logged. * @param logLevel: The severity level of the log message [LogLevel]. */ public writeNativeLog(message: String, logLevel: LogLevel) { const prefixedMessage = `${this._prefix} ${message}` return MindboxSdkNative.writeNativeLog(prefixedMessage, logLevel) } } const MindboxSdk = new MindboxSdkClass() export default MindboxSdk export { LogLevel } from './LogLevel' export { InAppCallback, CopyPayloadInAppCallback, EmptyInAppCallback, UrlInAppCallback } from './InAppCallback'