diff --git a/MindboxSdk.podspec b/MindboxSdk.podspec index fdb1db1..25bf63c 100644 --- a/MindboxSdk.podspec +++ b/MindboxSdk.podspec @@ -10,12 +10,12 @@ Pod::Spec.new do |s| s.license = package["license"] s.authors = package["author"] - s.platforms = { :ios => "12.0" } + s.platforms = { :ios => "15.1" } s.source = { :git => "https://github.com/mindbox-moscow/react-native-sdk/.git", :tag => "#{s.version}" } s.source_files = "ios/**/*.{h,m,mm,swift}" - s.dependency "React-Core" + install_modules_dependencies(s) s.dependency "Mindbox", "2.15.0" s.dependency "MindboxNotifications", "2.15.0" diff --git a/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt b/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt index 570c31b..e4bd954 100644 --- a/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt +++ b/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt @@ -57,7 +57,6 @@ class MindboxSdkModule( } private var deviceUuidSubscription: String? = null - private var fmsTokenSubscription: String? = null private var getTokensSubscription: String? = null private fun emitPushFromDelivery(bundle: Bundle) { @@ -165,19 +164,6 @@ class MindboxSdkModule( } } - override fun getFMSToken(promise: Promise) { - try { - if (fmsTokenSubscription != null) { - Mindbox.disposePushTokenSubscription(fmsTokenSubscription!!) - } - fmsTokenSubscription = Mindbox.subscribePushToken { fmsToken -> - promise.resolve(fmsToken) - } - } catch (error: Throwable) { - promise.reject(error) - } - } - override fun getTokens(promise: Promise) { try { if (getTokensSubscription != null) { @@ -191,15 +177,6 @@ class MindboxSdkModule( } } - override fun updateFMSToken(token: String, promise: Promise) { - try { - Mindbox.updateNotificationPermissionStatus(reactApplicationContext.applicationContext) - promise.resolve(true) - } catch (error: Throwable) { - promise.reject(error) - } - } - override fun executeAsyncOperation(operationSystemName: String, operationBody: String, promise: Promise) { Mindbox.executeAsyncOperation( reactApplicationContext.applicationContext, diff --git a/example/exampleApp/ios/AppDelegate.swift b/example/exampleApp/ios/AppDelegate.swift index 27c3423..20d7e43 100644 --- a/example/exampleApp/ios/AppDelegate.swift +++ b/example/exampleApp/ios/AppDelegate.swift @@ -1,35 +1,21 @@ import UIKit import React +import React_RCTAppDelegate import UserNotifications import Mindbox import MindboxSdk - // https://developers.mindbox.ru/docs/ios-send-push-notifications-react-native -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { - - var window: UIWindow? - var bridge: RCTBridge! - - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { +@main +class AppDelegate: RCTAppDelegate, UNUserNotificationCenterDelegate { + override func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + moduleName = "exampleApp" + initialProps = [:] // Set the current instance of UNUserNotificationCenter's delegate to self. // This enables the AppDelegate to respond to notification events UNUserNotificationCenter.current().delegate = self - - // Setting up React Native bridge - bridge = RCTBridge(delegate: self, launchOptions: launchOptions) - let rootView = RCTRootView(bridge: bridge, moduleName: "exampleApp", initialProperties: nil) - - // Configuring the application window - self.window = UIWindow(frame: UIScreen.main.bounds) - let rootViewController = UIViewController() - rootViewController.view = rootView - self.window!.rootViewController = rootViewController - self.window!.makeKeyAndVisible() - // https://developers.mindbox.ru/docs/ios-app-start-tracking-react-native // Tracking app launch for analytics let trackVisitData = TrackVisitData() @@ -42,28 +28,33 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } else { UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum) } - - return true + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + func notifyReactNative() { - if let bridge = bridge, let eventEmitter = bridge.module(for: NotificationModule.self) as? NotificationModule { - eventEmitter.notifyReactNative() - } + if let eventEmitter = bridge?.module(for: NotificationModule.self) as? NotificationModule { + eventEmitter.notifyReactNative() + } } + // Handling remote notification fetch completion - func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + override func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable : Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { Mindbox.shared.application(application, performFetchWithCompletionHandler: completionHandler) notifyReactNative() } // Updating APNS token in Mindbox - func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { Mindbox.shared.apnsTokenUpdate(deviceToken: deviceToken) } // Handling Universal Links // https://developers.mindbox.ru/docs/ios-app-start-tracking-react-native - func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + override func application(_ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { let trackVisitData = TrackVisitData() trackVisitData.universalLink = userActivity Mindbox.shared.track(data: trackVisitData) @@ -78,34 +69,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // Handling push notification clicks func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - // https://developers.mindbox.ru/docs/ios-get-click-react-native Mindbox.shared.pushClicked(response: response) - // https://developers.mindbox.ru/docs/ios-app-start-tracking-react-native - // Tracking push notification clicks for analytics let trackVisitData = TrackVisitData() trackVisitData.push = response Mindbox.shared.track(data: trackVisitData) - - // Emitting event for further handling in JavaScript - // https://developers.mindbox.ru/docs/flutter-push-navigation-react-native MindboxJsDelivery.emitEvent(response) - completionHandler() } -} -extension AppDelegate: RCTBridgeDelegate { - func sourceURL(for bridge: RCTBridge!) -> URL! { + override func sourceURL(for bridge: RCTBridge!) -> URL! { + bundleURL() + } + + override func extraModules(for bridge: RCTBridge!) -> [RCTBridgeModule] { + [NotificationModule()] + } + + override func bundleURL() -> URL? { #if DEBUG - return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") + RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") #else - return Bundle.main.url(forResource: "main", withExtension: "jsbundle") + Bundle.main.url(forResource: "main", withExtension: "jsbundle") #endif } - - func extraModules(for bridge: RCTBridge!) -> [RCTBridgeModule]! { - var modules = [RCTBridgeModule]() - modules.append(NotificationModule()) - return modules - } } diff --git a/example/exampleApp/ios/Swift.swift b/example/exampleApp/ios/Swift.swift deleted file mode 100644 index 350c82f..0000000 --- a/example/exampleApp/ios/Swift.swift +++ /dev/null @@ -1,2 +0,0 @@ - -import Foundation diff --git a/example/exampleApp/ios/exampleApp.xcodeproj/project.pbxproj b/example/exampleApp/ios/exampleApp.xcodeproj/project.pbxproj index 83002a2..babf5b9 100644 --- a/example/exampleApp/ios/exampleApp.xcodeproj/project.pbxproj +++ b/example/exampleApp/ios/exampleApp.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 3A027BAE2C3BFC4D005415BB /* NotificationModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A027BAD2C3BFC4D005415BB /* NotificationModule.m */; }; 3A175C362C3D1B800027776A /* PushAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A175C322C3D16C70027776A /* PushAction.swift */; }; 3A175C372C3D1B800027776A /* MindboxRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A175C342C3D16F50027776A /* MindboxRemoteMessage.swift */; }; - 3A88FC072B6BDD900046E687 /* Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A88FC062B6BDD900046E687 /* Swift.swift */; }; 3A88FC092B6BDF360046E687 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A88FC082B6BDF360046E687 /* AppDelegate.swift */; }; 3ACCD5142B72826700C94F45 /* MindboxNotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3ACCD50D2B72826700C94F45 /* MindboxNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3ACCD51C2B728BD500C94F45 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACCD51B2B728BD500C94F45 /* NotificationService.swift */; }; @@ -69,7 +68,6 @@ 3A027BAD2C3BFC4D005415BB /* NotificationModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationModule.m; sourceTree = ""; }; 3A175C322C3D16C70027776A /* PushAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushAction.swift; sourceTree = ""; }; 3A175C342C3D16F50027776A /* MindboxRemoteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxRemoteMessage.swift; sourceTree = ""; }; - 3A88FC062B6BDD900046E687 /* Swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swift.swift; sourceTree = ""; }; 3A88FC082B6BDF360046E687 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 3A88FC0A2B6BF2840046E687 /* exampleApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "exampleApp-Bridging-Header.h"; sourceTree = ""; }; 3A88FC0B2B6CEE180046E687 /* exampleApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = exampleApp.entitlements; path = exampleApp/exampleApp.entitlements; sourceTree = ""; }; @@ -200,7 +198,6 @@ 3A027BAD2C3BFC4D005415BB /* NotificationModule.m */, 3A027BAB2C3BF24F005415BB /* NotificationModule.swift */, 3A88FC0A2B6BF2840046E687 /* exampleApp-Bridging-Header.h */, - 3A88FC062B6BDD900046E687 /* Swift.swift */, 13B07FAE1A68108700A75B9A /* exampleApp */, 832341AE1AAA6A7D00B99B32 /* Libraries */, 3ACCD50E2B72826700C94F45 /* MindboxNotificationServiceExtension */, @@ -532,7 +529,6 @@ files = ( 3A027BAE2C3BFC4D005415BB /* NotificationModule.m in Sources */, 3A88FC092B6BDF360046E687 /* AppDelegate.swift in Sources */, - 3A88FC072B6BDD900046E687 /* Swift.swift in Sources */, 3A027BAC2C3BF24F005415BB /* NotificationModule.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/MindboxJsDelivery.h b/ios/MindboxJsDelivery.h deleted file mode 100644 index aa5b605..0000000 --- a/ios/MindboxJsDelivery.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MindboxJsDelivery.h -// MindboxSdk -// -// Created by Nikolay Seleznev on 15.10.2021. -// Copyright © 2021 Facebook. All rights reserved. -// - -#import -#import - -@interface MindboxJsDelivery : RCTEventEmitter - -+ (void)emitEvent:(UNNotificationResponse *)response; - -+ (void)sendInappEvent:(NSString *)eventName eventId:(NSString *)eventId url:(NSString *)clickUrl payload:(NSString *)payload; -@end diff --git a/ios/MindboxJsDelivery.m b/ios/MindboxJsDelivery.m deleted file mode 100644 index 15c3171..0000000 --- a/ios/MindboxJsDelivery.m +++ /dev/null @@ -1,148 +0,0 @@ -// -// MindboxJsDelivery.m -// MindboxSdk -// -// Created by Nikolay Seleznev on 15.10.2021. -// Copyright © 2021 Facebook. All rights reserved. -// - -#import "MindboxJsDelivery.h" - -#import - -@implementation MindboxJsDelivery - -RCT_EXPORT_MODULE(); - -static bool hasListeners = NO; -static NSDictionary *storedEventDetails; - -- (NSArray *)supportedEvents { - return @[@"pushNotificationClicked", @"Click", @"Dismiss"]; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)startObserving { - hasListeners = YES; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(emitEventInternal:) name:@"event-emitted" object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inappActionReceived:)name:@"MindboxInappAction" object:nil]; - - if (storedEventDetails != NULL) { - [[NSNotificationCenter defaultCenter] postNotificationName:@"event-emitted" object:self userInfo:storedEventDetails]; - } -} - -- (void)stopObserving { - hasListeners = NO; - - [[NSNotificationCenter defaultCenter] removeObserver:self]; - - if (storedEventDetails != NULL) { - storedEventDetails = NULL; - } -} - -- (void)emitEventInternal:(NSNotification *)notification { - NSArray *eventDetails = [notification.userInfo valueForKey:@"detail"]; - NSString *eventName = [eventDetails objectAtIndex:0]; - NSString *actionIdentifier = [eventDetails objectAtIndex:1]; - NSDictionary *userInfo = [eventDetails objectAtIndex:2]; - NSString *clickUrl = @""; - NSString *pushPayload = @""; - - if ([actionIdentifier isEqual:UNNotificationDefaultActionIdentifier]) { - clickUrl = [userInfo objectForKey:@"clickUrl"]; - pushPayload = [userInfo objectForKey:@"payload"]; - - if ([clickUrl length] == 0) { - NSDictionary *aps = [userInfo objectForKey:@"aps"]; - clickUrl = [aps objectForKey:@"clickUrl"]; - pushPayload = [aps objectForKey:@"payload"]; - } - } else { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"uniqueKey == %@", actionIdentifier]; - NSArray *filteredArray = [[userInfo objectForKey:@"buttons"] filteredArrayUsingPredicate:predicate]; - - if (filteredArray.count > 0) { - clickUrl = [filteredArray.firstObject objectForKey:@"url"]; - } - - if ([clickUrl length] == 0) { - NSDictionary *aps = [userInfo objectForKey:@"aps"]; - NSArray *apsButtons = [aps objectForKey:@"buttons"]; - filteredArray = [apsButtons filteredArrayUsingPredicate:predicate]; - if (filteredArray.count > 0) { - clickUrl = [filteredArray.firstObject objectForKey:@"url"]; - } - } - - pushPayload = [userInfo objectForKey:@"payload"]; - if ([pushPayload length] == 0) { - NSDictionary *aps = [userInfo objectForKey:@"aps"]; - pushPayload = [aps objectForKey:@"payload"]; - } - } - - NSDictionary *dict = @{ - @"pushUrl": clickUrl ? clickUrl : [NSNull null], - @"pushPayload": pushPayload ? pushPayload : [NSNull null] - }; - NSError *error; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error]; - NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - - [self sendEventWithName:eventName body:jsonString]; - - if (storedEventDetails != NULL) { - storedEventDetails = NULL; - } -} - -+ (void)emitEvent:(UNNotificationResponse *)response { - NSDictionary *userInfo = response.notification.request.content.userInfo; - NSString *name = @"pushNotificationClicked"; - NSString *actionIdentifier = response.actionIdentifier; - NSDictionary *eventDetails = @{@"detail":@[name,actionIdentifier,userInfo]}; - if (hasListeners) { - [[NSNotificationCenter defaultCenter] postNotificationName:@"event-emitted" object:self userInfo:eventDetails]; - } else { - storedEventDetails = eventDetails; - } -} - -+ (void)sendInappEvent:(NSString *)eventName eventId:(NSString *)eventId url:(NSString *)clickUrl payload:(NSString *)payload { - if (hasListeners) { - NSMutableDictionary *bodyDict = [[NSMutableDictionary alloc] init]; - bodyDict[@"id"] = eventId; - - if (clickUrl != nil) { - bodyDict[@"redirectUrl"] = clickUrl; - } - - if (payload != nil) { - bodyDict[@"payload"] = payload; - } - - NSDictionary *userInfo = @{ - @"eventName": eventName, - @"body": bodyDict - }; - - [[NSNotificationCenter defaultCenter] postNotificationName:@"MindboxInappAction" - object:self - userInfo:userInfo]; - } -} - -- (void)inappActionReceived:(NSNotification *)notification { - NSString *eventName = notification.userInfo[@"eventName"]; - NSDictionary *body = notification.userInfo[@"body"]; - [self sendEventWithName:eventName body:body]; -} - -@end diff --git a/ios/MindboxJsDelivery.swift b/ios/MindboxJsDelivery.swift new file mode 100644 index 0000000..13ebba1 --- /dev/null +++ b/ios/MindboxJsDelivery.swift @@ -0,0 +1,63 @@ +import Foundation +import UserNotifications + +@objc(MindboxJsDelivery) +public final class MindboxJsDelivery: NSObject { + + @objc public static func emitEvent(_ response: UNNotificationResponse) { + let userInfo = response.notification.request.content.userInfo + let actionIdentifier = response.actionIdentifier + let pushUrl = resolvePushUrl(userInfo: userInfo, actionIdentifier: actionIdentifier) + let pushPayload = resolvePushPayload(userInfo: userInfo) + MindboxSdkImpl.emitPushClick(pushUrl: pushUrl, pushPayload: pushPayload) + } + + @objc public static func sendInappEvent(_ eventName: String, eventId: String, url: String?, payload: String?) { + switch eventName { + case "Click": + MindboxSdkImpl.emitInAppClick(id: eventId, redirectUrl: url, payload: payload) + case "Dismiss": + MindboxSdkImpl.emitInAppDismiss(id: eventId) + default: + break + } + } + + private static func resolvePushUrl(userInfo: [AnyHashable: Any], actionIdentifier: String) -> String { + if actionIdentifier == UNNotificationDefaultActionIdentifier { + return readString(userInfo: userInfo, key: "clickUrl") + ?? readString(userInfo: readDictionary(userInfo: userInfo, key: "aps"), key: "clickUrl") + ?? "" + } + let buttonUrl = readButtonUrl(userInfo: userInfo, uniqueKey: actionIdentifier) + if let buttonUrl = buttonUrl, !buttonUrl.isEmpty { + return buttonUrl + } + let aps = readDictionary(userInfo: userInfo, key: "aps") + return readButtonUrl(userInfo: aps, uniqueKey: actionIdentifier) ?? "" + } + + private static func resolvePushPayload(userInfo: [AnyHashable: Any]) -> String { + return readString(userInfo: userInfo, key: "payload") + ?? readString(userInfo: readDictionary(userInfo: userInfo, key: "aps"), key: "payload") + ?? "" + } + + private static func readButtonUrl(userInfo: [AnyHashable: Any], uniqueKey: String) -> String? { + guard let buttons = userInfo["buttons"] as? [[String: Any]] else { + return nil + } + return buttons.first(where: { ($0["uniqueKey"] as? String) == uniqueKey })?["url"] as? String + } + + private static func readDictionary(userInfo: [AnyHashable: Any], key: String) -> [AnyHashable: Any] { + guard let dictionary = userInfo[key] as? [AnyHashable: Any] else { + return [:] + } + return dictionary + } + + private static func readString(userInfo: [AnyHashable: Any], key: String) -> String? { + return userInfo[key] as? String + } +} diff --git a/ios/MindboxSdk-Bridging-Header.h b/ios/MindboxSdk-Bridging-Header.h deleted file mode 100644 index 8992b22..0000000 --- a/ios/MindboxSdk-Bridging-Header.h +++ /dev/null @@ -1,3 +0,0 @@ -#import -#import -#import diff --git a/ios/MindboxSdk.m b/ios/MindboxSdk.m deleted file mode 100644 index 2ebf228..0000000 --- a/ios/MindboxSdk.m +++ /dev/null @@ -1,31 +0,0 @@ -#import - -@interface RCT_EXTERN_MODULE(MindboxSdk, NSObject) - -RCT_EXTERN_METHOD(initialize:(NSString)payloadString resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(getDeviceUUID:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(getAPNSToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(updateAPNSToken:(NSString)token resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(executeAsyncOperation:(NSString)operationSystemName operationBody:(NSString)operationBody resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(executeSyncOperation:(NSString)operationSystemName operationBody:(NSString)operationBody resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(registerCallbacks:(NSArray)callbacks) - -RCT_EXTERN_METHOD(setLogLevel:(NSInteger)level) - -RCT_EXTERN_METHOD(getSdkVersion:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) - -RCT_EXTERN_METHOD(pushDelivered:(NSString)uniqKey) - -RCT_EXTERN_METHOD(refreshNotificationPermissionStatus) - -RCT_EXTERN_METHOD(writeNativeLog:(NSString)message level:(NSInteger)level) - -RCT_EXTERN_METHOD(getTokens:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) - -@end diff --git a/ios/MindboxSdk.mm b/ios/MindboxSdk.mm new file mode 100644 index 0000000..ff98782 --- /dev/null +++ b/ios/MindboxSdk.mm @@ -0,0 +1,109 @@ +#import "MindboxSdk-Swift.h" +#import + +#if __has_include() +#import +#elif __has_include("MindboxSdkSpec.h") +#import "MindboxSdkSpec.h" +#else +#error "MindboxSdkSpec.h not found. Ensure the React Native codegen spec has been generated and the New Architecture/codegen integration is enabled" +#endif + +@interface MindboxSdk : NativeMindboxSdkSpecBase +@end + +@implementation MindboxSdk { + MindboxSdkImpl *_impl; +} + +RCT_EXPORT_MODULE(MindboxSdk) + ++ (BOOL)requiresMainQueueSetup { + return YES; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _impl = [MindboxSdkImpl new]; + __weak MindboxSdk *weakSelf = self; + _impl.eventEmitHandler = ^(NSString *eventName, NSDictionary *body) { + MindboxSdk *strongSelf = weakSelf; + if (!strongSelf) return; + if ([eventName isEqualToString:@"onPushNotificationClicked"]) { + [strongSelf emitOnPushNotificationClicked:body]; + } else if ([eventName isEqualToString:@"onInAppClick"]) { + [strongSelf emitOnInAppClick:body]; + } else if ([eventName isEqualToString:@"onInAppDismiss"]) { + [strongSelf emitOnInAppDismiss:body]; + } + }; + } + return self; +} + +- (void)initialize:(NSString *)payloadString + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl initialize:payloadString resolve:resolve reject:reject]; +} + +- (void)registerCallbacks:(NSArray *)callbacks { + [_impl registerCallbacks:callbacks]; +} + +- (void)getDeviceUUID:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl getDeviceUUID:resolve reject:reject]; +} + +- (void)getTokens:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl getTokens:resolve reject:reject]; +} + +- (void)executeAsyncOperation:(NSString *)operationSystemName + operationBody:(NSString *)operationBody + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl executeAsyncOperation:operationSystemName operationBody:operationBody resolve:resolve reject:reject]; +} + +- (void)executeSyncOperation:(NSString *)operationSystemName + operationBody:(NSString *)operationBody + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl executeSyncOperation:operationSystemName operationBody:operationBody resolve:resolve reject:reject]; +} + +- (void)onPushClickedIsRegistered:(BOOL)isRegistered { + [_impl onPushClickedIsRegistered:isRegistered]; +} + +- (void)setLogLevel:(double)level { + [_impl setLogLevel:level]; +} + +- (void)getSdkVersion:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + [_impl getSdkVersion:resolve reject:reject]; +} + +- (void)pushDelivered:(NSString *)uniqKey { + [_impl pushDelivered:uniqKey]; +} + +- (void)refreshNotificationPermissionStatus { + [_impl refreshNotificationPermissionStatus]; +} + +- (void)writeNativeLog:(NSString *)message + logLevel:(double)logLevel { + [_impl writeNativeLog:message logLevel:logLevel]; +} + +- (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { + return std::make_shared(params); +} + +@end diff --git a/ios/MindboxSdk.swift b/ios/MindboxSdk.swift deleted file mode 100644 index 8779d91..0000000 --- a/ios/MindboxSdk.swift +++ /dev/null @@ -1,220 +0,0 @@ -import Mindbox -import MindboxLogger - -enum CustomError: Error { - case tokenAPNSisNull -} - -extension CustomError: LocalizedError { - public var errorDescription: String? { - switch self { - case .tokenAPNSisNull: - return NSLocalizedString("APNS token cannot be nullable", comment: "APNS token is null") - } - } -} - -struct PayloadData: Codable { - var domain: String - var endpointId: String - var subscribeCustomerIfCreated: Bool? - var shouldCreateCustomer: Bool? - var previousInstallId: String? - var previousUuid: String? -} - -@objc(MindboxSdk) -class MindboxSdk: NSObject { - - private var urlInappDelegate: URLInappMessageDelegate? - private var copyInappDelegate: CopyInappMessageDelegate? - private var emptyInappDelegate: InAppMessagesDelegate? - private var customClass: InAppMessagesDelegate? - private var compositeDelegate: CompositeInappMessageDelegate? - - @objc - static func requiresMainQueueSetup() -> Bool { - return true - } - - @objc(initialize:resolve:rejecter:) - func initialize(_ payloadString: String, resolve: @escaping RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void { - do { - let payload = try JSONDecoder().decode(PayloadData.self, from: payloadString.data(using: .utf8)!) - - let configuration = try MBConfiguration( - endpoint: payload.endpointId, - domain: payload.domain, - previousInstallationId: payload.previousInstallId, - previousDeviceUUID: payload.previousUuid, - subscribeCustomerIfCreated: payload.subscribeCustomerIfCreated ?? false, - shouldCreateCustomer: payload.shouldCreateCustomer ?? true - ) - - Mindbox.shared.initialization(configuration: configuration) - - resolve(true) - } catch { - reject("Error", error.localizedDescription, error) - } - } - - @objc(getDeviceUUID:rejecter:) - func getDeviceUUID(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void { - Mindbox.shared.getDeviceUUID{ - deviceUUID in resolve(deviceUUID) - } - } - - @objc(getAPNSToken:rejecter:) - func getAPNSToken(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void { - Mindbox.shared.getAPNSToken{ - ApnsToken in resolve(ApnsToken) - } - } - - @objc(getTokens:rejecter:) - func getTokens(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void { - Mindbox.shared.getAPNSToken{ - ApnsToken in resolve("{\"APNS\":\"\(ApnsToken)\"}") - } - } - - @objc func registerCallbacks(_ callbacks: [String]) { - var cb = [InAppMessagesDelegate]() - - for callback in callbacks { - switch callback { - case "urlInAppCallback": - urlInappDelegate = URLInappDelegate() - if let urlInappDelegate = urlInappDelegate { - cb.append(urlInappDelegate) - } - case "copyPayloadInAppCallback": - copyInappDelegate = CopyInappDelegate() - if let copyInappDelegate = copyInappDelegate { - cb.append(copyInappDelegate) - } - case "emptyInAppCallback": - emptyInappDelegate = EmptyInappDelegate() - if let emptyInappDelegate = emptyInappDelegate { - cb.append(emptyInappDelegate) - } - default: - customClass = CustomInappDelegate() - if let customClass = customClass { - cb.append(customClass) - } - } - } - - compositeDelegate = CompositeInappDelegate() - compositeDelegate?.delegates = cb - Mindbox.shared.inAppMessagesDelegate = compositeDelegate - } - - @objc(updateAPNSToken:resolve:rejecter:) - func updateAPNSToken(_ token: String, resolve: @escaping RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void { - do { - guard let tokenData = token.data(using: .utf8) else { throw CustomError.tokenAPNSisNull } - - Mindbox.shared.apnsTokenUpdate(deviceToken: tokenData) - - resolve(true) - } catch { - reject("Error", error.localizedDescription, error) - } - } - - @objc(executeAsyncOperation:operationBody:resolve:rejecter:) - func executeAsyncOperation(_ operationSystemName: String, operationBody: String, resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { - Mindbox.shared.executeAsyncOperation(operationSystemName: operationSystemName, json: operationBody) - resolve(true) - } - - @objc(executeSyncOperation:operationBody:resolve:rejecter:) - func executeSyncOperation(_ operationSystemName: String, operationBody: String, resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { - Mindbox.shared.executeSyncOperation(operationSystemName: operationSystemName, json: operationBody) { result in - switch result { - case .success(let response): - resolve(response.createJSON()) - case .failure(let error): - resolve(error.createJSON()) - } - } - } - - @objc - static func moduleName() -> String { - return "MindboxSdk" - } - - @objc - func constantsToExport() -> [AnyHashable: Any] { - return [:] - } - - @objc(setLogLevel:) - func setLogLevel(_ level: Int) -> Void { - switch (level) { - case 0: - Mindbox.logger.logLevel = .debug - case 1: - Mindbox.logger.logLevel = .info - case 2: - Mindbox.logger.logLevel = .default - case 3: - Mindbox.logger.logLevel = .error - case 4: - Mindbox.logger.logLevel = .fault - default: - Mindbox.logger.logLevel = .none - } - } - - @objc - func getSdkVersion() -> String { - return Mindbox.shared.sdkVersion - } - - @objc(getSdkVersion:rejecter:) - func getSdkVersion(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) { - do { - resolve(Mindbox.shared.sdkVersion) - } catch { - reject("Error", error.localizedDescription, error) - } - } - - @objc(pushDelivered:) - func pushDelivered(_ uniqKey: String) { - Mindbox.shared.pushDelivered(uniqueKey: uniqKey) - } - - @objc - func refreshNotificationPermissionStatus() { - Mindbox.shared.refreshNotificationPermissionStatus() - } - - @objc - func writeNativeLog(_ message: String, level: Int) { - - let logLevel: LogLevel - - switch level { - case 0: - logLevel = .debug - case 1: - logLevel = .info - case 2: - logLevel = .default - case 3: - logLevel = .error - case 4: - logLevel = .fault - default: - logLevel = .none - } - Mindbox.logger.log(level: logLevel, message: message) - } -} diff --git a/ios/MindboxSdk.xcodeproj/project.pbxproj b/ios/MindboxSdk.xcodeproj/project.pbxproj index 4fa4356..6205b75 100644 --- a/ios/MindboxSdk.xcodeproj/project.pbxproj +++ b/ios/MindboxSdk.xcodeproj/project.pbxproj @@ -7,9 +7,11 @@ objects = { /* Begin PBXBuildFile section */ - 61249CFA2719673B00FC4033 /* MindboxJsDelivery.m in Sources */ = {isa = PBXBuildFile; fileRef = 61249CF92719673B00FC4033 /* MindboxJsDelivery.m */; }; + 61249CFA2719673B00FC4033 /* MindboxJsDelivery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61249CF92719673B00FC4033 /* MindboxJsDelivery.swift */; }; D0F7B0012B1C4A1A00A1B2C3 /* MindboxJsDeliveryBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7B0002B1C4A1A00A1B2C3 /* MindboxJsDeliveryBridge.swift */; }; - F4FF95D7245B92E800C19C63 /* MindboxSdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D6245B92E800C19C63 /* MindboxSdk.swift */; }; + E0A1A1A12B2D2D2D00A1B2C3 /* MindboxSdk.mm in Sources */ = {isa = PBXBuildFile; fileRef = E0A1A1A02B2D2D2D00A1B2C3 /* MindboxSdk.mm */; }; + E0A1A1A32B2D2D2D00A1B2C3 /* MindboxSdkNewArchGuard.mm in Sources */ = {isa = PBXBuildFile; fileRef = E0A1A1A22B2D2D2D00A1B2C3 /* MindboxSdkNewArchGuard.mm */; }; + F4FF95D7245B92E800C19C63 /* MindboxSdkImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D6245B92E800C19C63 /* MindboxSdkImpl.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -26,12 +28,11 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libMindboxSdk.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMindboxSdk.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 61249CF82719673B00FC4033 /* MindboxJsDelivery.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MindboxJsDelivery.h; sourceTree = ""; }; - 61249CF92719673B00FC4033 /* MindboxJsDelivery.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MindboxJsDelivery.m; sourceTree = ""; }; - B3E7B5891CC2AC0600A0062D /* MindboxSdk.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MindboxSdk.m; sourceTree = ""; }; + 61249CF92719673B00FC4033 /* MindboxJsDelivery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxJsDelivery.swift; sourceTree = ""; }; D0F7B0002B1C4A1A00A1B2C3 /* MindboxJsDeliveryBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxJsDeliveryBridge.swift; sourceTree = ""; }; - F4FF95D5245B92E700C19C63 /* MindboxSdk-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MindboxSdk-Bridging-Header.h"; sourceTree = ""; }; - F4FF95D6245B92E800C19C63 /* MindboxSdk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxSdk.swift; sourceTree = ""; }; + E0A1A1A02B2D2D2D00A1B2C3 /* MindboxSdk.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MindboxSdk.mm; sourceTree = ""; }; + E0A1A1A22B2D2D2D00A1B2C3 /* MindboxSdkNewArchGuard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MindboxSdkNewArchGuard.mm; sourceTree = ""; }; + F4FF95D6245B92E800C19C63 /* MindboxSdkImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxSdkImpl.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,12 +57,11 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 61249CF82719673B00FC4033 /* MindboxJsDelivery.h */, - 61249CF92719673B00FC4033 /* MindboxJsDelivery.m */, + 61249CF92719673B00FC4033 /* MindboxJsDelivery.swift */, D0F7B0002B1C4A1A00A1B2C3 /* MindboxJsDeliveryBridge.swift */, - F4FF95D6245B92E800C19C63 /* MindboxSdk.swift */, - B3E7B5891CC2AC0600A0062D /* MindboxSdk.m */, - F4FF95D5245B92E700C19C63 /* MindboxSdk-Bridging-Header.h */, + E0A1A1A22B2D2D2D00A1B2C3 /* MindboxSdkNewArchGuard.mm */, + F4FF95D6245B92E800C19C63 /* MindboxSdkImpl.swift */, + E0A1A1A02B2D2D2D00A1B2C3 /* MindboxSdk.mm */, 134814211AA4EA7D00B7C361 /* Products */, ); sourceTree = ""; @@ -123,9 +123,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 61249CFA2719673B00FC4033 /* MindboxJsDelivery.m in Sources */, + 61249CFA2719673B00FC4033 /* MindboxJsDelivery.swift in Sources */, D0F7B0012B1C4A1A00A1B2C3 /* MindboxJsDeliveryBridge.swift in Sources */, - F4FF95D7245B92E800C19C63 /* MindboxSdk.swift in Sources */, + E0A1A1A12B2D2D2D00A1B2C3 /* MindboxSdk.mm in Sources */, + E0A1A1A32B2D2D2D00A1B2C3 /* MindboxSdkNewArchGuard.mm in Sources */, + F4FF95D7245B92E800C19C63 /* MindboxSdkImpl.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -238,7 +240,6 @@ OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = MindboxSdk; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "MindboxSdk-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; @@ -257,7 +258,6 @@ OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = MindboxSdk; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "MindboxSdk-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Release; diff --git a/ios/MindboxSdkImpl.swift b/ios/MindboxSdkImpl.swift new file mode 100644 index 0000000..341821a --- /dev/null +++ b/ios/MindboxSdkImpl.swift @@ -0,0 +1,215 @@ +import Foundation +import Mindbox +import MindboxLogger + +struct PayloadData: Codable { + var domain: String + var endpointId: String + var subscribeCustomerIfCreated: Bool? + var shouldCreateCustomer: Bool? + var previousInstallId: String? + var previousUuid: String? +} + +public typealias ResolveBlock = (Any?) -> Void +public typealias RejectBlock = (String?, String?, NSError?) -> Void +public typealias EventEmitHandler = (_ eventName: String, _ body: [String: String]) -> Void + +@objc(MindboxSdkImpl) +public final class MindboxSdkImpl: NSObject { + + private var urlInappDelegate: URLInappMessageDelegate? + private var copyInappDelegate: CopyInappMessageDelegate? + private var emptyInappDelegate: InAppMessagesDelegate? + private var customClass: InAppMessagesDelegate? + private var compositeDelegate: CompositeInappMessageDelegate? + + @objc var isPushListenerRegistered: Bool = false + @objc static var pendingPushPayload: [String: String]? + @objc public var eventEmitHandler: EventEmitHandler? + + private static let stateQueue = DispatchQueue(label: "com.mindboxsdk.MindboxSdkImpl.state") + private static weak var activeInstance: MindboxSdkImpl? + + @objc public override init() { + super.init() + MindboxSdkImpl.stateQueue.sync { + MindboxSdkImpl.activeInstance = self + } + } + + @objc public func initialize(_ payloadString: String, resolve: @escaping ResolveBlock, reject: @escaping RejectBlock) { + do { + guard let payloadData = payloadString.data(using: .utf8) else { + reject("Error", "Initialization payload must be UTF-8 encoded", nil) + return + } + let payload = try JSONDecoder().decode(PayloadData.self, from: payloadData) + let configuration = try MBConfiguration( + endpoint: payload.endpointId, + domain: payload.domain, + previousInstallationId: payload.previousInstallId, + previousDeviceUUID: payload.previousUuid, + subscribeCustomerIfCreated: payload.subscribeCustomerIfCreated ?? false, + shouldCreateCustomer: payload.shouldCreateCustomer ?? true + ) + Mindbox.shared.initialization(configuration: configuration) + resolve(true) + } catch { + reject("Error", error.localizedDescription, error as NSError) + } + } + + @objc public func getDeviceUUID(_ resolve: @escaping ResolveBlock, reject: @escaping RejectBlock) { + Mindbox.shared.getDeviceUUID { deviceUUID in + resolve(deviceUUID) + } + } + + @objc public func getTokens(_ resolve: @escaping ResolveBlock, reject: @escaping RejectBlock) { + Mindbox.shared.getAPNSToken { apnsToken in + do { + let tokens: [String: String] = ["APNS": apnsToken] + let data = try JSONSerialization.data(withJSONObject: tokens) + guard let json = String(data: data, encoding: .utf8) else { + reject("Error", "Failed to encode APNS token payload", nil) + return + } + resolve(json) + } catch { + reject("Error", error.localizedDescription, error as NSError) + } + } + } + + @objc public func registerCallbacks(_ callbacks: [String]) { + var cb = [InAppMessagesDelegate]() + for callback in callbacks { + switch callback { + case "urlInAppCallback": + urlInappDelegate = URLInappDelegate() + if let urlInappDelegate = urlInappDelegate { + cb.append(urlInappDelegate) + } + case "copyPayloadInAppCallback": + copyInappDelegate = CopyInappDelegate() + if let copyInappDelegate = copyInappDelegate { + cb.append(copyInappDelegate) + } + case "emptyInAppCallback": + emptyInappDelegate = EmptyInappDelegate() + if let emptyInappDelegate = emptyInappDelegate { + cb.append(emptyInappDelegate) + } + default: + customClass = CustomInappDelegate() + if let customClass = customClass { + cb.append(customClass) + } + } + } + compositeDelegate = CompositeInappDelegate() + compositeDelegate?.delegates = cb + Mindbox.shared.inAppMessagesDelegate = compositeDelegate + } + + @objc public func executeAsyncOperation(_ operationSystemName: String, operationBody: String, resolve: @escaping ResolveBlock, reject: @escaping RejectBlock) { + Mindbox.shared.executeAsyncOperation(operationSystemName: operationSystemName, json: operationBody) + resolve(true) + } + + @objc public func executeSyncOperation(_ operationSystemName: String, operationBody: String, resolve: @escaping ResolveBlock, reject: @escaping RejectBlock) { + Mindbox.shared.executeSyncOperation(operationSystemName: operationSystemName, json: operationBody) { result in + switch result { + case .success(let response): + resolve(response.createJSON()) + case .failure(let error): + resolve(error.createJSON()) + } + } + } + + @objc public func onPushClickedIsRegistered(_ isRegistered: Bool) { + let pendingPayload: [String: String]? = MindboxSdkImpl.stateQueue.sync { + isPushListenerRegistered = isRegistered + guard isRegistered, let pendingPayload = MindboxSdkImpl.pendingPushPayload else { + return nil + } + MindboxSdkImpl.pendingPushPayload = nil + return pendingPayload + } + if let pendingPayload = pendingPayload { + eventEmitHandler?("onPushNotificationClicked", pendingPayload) + } + } + + @objc public func setLogLevel(_ level: Double) { + switch Int(level) { + case 0: Mindbox.logger.logLevel = .debug + case 1: Mindbox.logger.logLevel = .info + case 2: Mindbox.logger.logLevel = .default + case 3: Mindbox.logger.logLevel = .error + case 4: Mindbox.logger.logLevel = .fault + default: Mindbox.logger.logLevel = .none + } + } + + @objc public func getSdkVersion(_ resolve: @escaping ResolveBlock, reject: @escaping RejectBlock) { + resolve(Mindbox.shared.sdkVersion) + } + + @objc public func pushDelivered(_ uniqKey: String) { + Mindbox.shared.pushDelivered(uniqueKey: uniqKey) + } + + @objc public func refreshNotificationPermissionStatus() { + Mindbox.shared.refreshNotificationPermissionStatus() + } + + @objc public func writeNativeLog(_ message: String, logLevel: Double) { + let mappedLogLevel: LogLevel + switch Int(logLevel) { + case 0: mappedLogLevel = .debug + case 1: mappedLogLevel = .info + case 2: mappedLogLevel = .default + case 3: mappedLogLevel = .error + case 4: mappedLogLevel = .fault + default: mappedLogLevel = .none + } + Mindbox.logger.log(level: mappedLogLevel, message: message) + } + + @objc static func emitPushClick(pushUrl: String, pushPayload: String) { + let payload: [String: String] = [ + "pushUrl": pushUrl, + "pushPayload": pushPayload + ] + let instance: MindboxSdkImpl? = MindboxSdkImpl.stateQueue.sync { + guard let activeInstance = MindboxSdkImpl.activeInstance, activeInstance.isPushListenerRegistered else { + MindboxSdkImpl.pendingPushPayload = payload + return nil + } + return activeInstance + } + if let instance = instance { + instance.eventEmitHandler?("onPushNotificationClicked", payload) + } + } + + @objc static func emitInAppClick(id: String, redirectUrl: String?, payload: String?) { + guard let instance = MindboxSdkImpl.stateQueue.sync(execute: { MindboxSdkImpl.activeInstance }) else { return } + var body: [String: String] = ["id": id] + if let redirectUrl = redirectUrl { + body["redirectUrl"] = redirectUrl + } + if let payload = payload { + body["payload"] = payload + } + instance.eventEmitHandler?("onInAppClick", body) + } + + @objc static func emitInAppDismiss(id: String) { + guard let instance = MindboxSdkImpl.stateQueue.sync(execute: { MindboxSdkImpl.activeInstance }) else { return } + instance.eventEmitHandler?("onInAppDismiss", ["id": id]) + } +} diff --git a/ios/MindboxSdkNewArchGuard.mm b/ios/MindboxSdkNewArchGuard.mm new file mode 100644 index 0000000..b766303 --- /dev/null +++ b/ios/MindboxSdkNewArchGuard.mm @@ -0,0 +1,3 @@ +#if defined(RCT_NEW_ARCH_ENABLED) && RCT_NEW_ARCH_ENABLED != 1 +#error "MindboxSdk requires React Native New Architecture. Set RCT_NEW_ARCH_ENABLED=1." +#endif diff --git a/src/NativeMindboxSdk.ts b/src/NativeMindboxSdk.ts index 9c7bfda..3d784b3 100644 --- a/src/NativeMindboxSdk.ts +++ b/src/NativeMindboxSdk.ts @@ -11,9 +11,7 @@ export interface Spec extends TurboModule { initialize(payloadString: string): Promise registerCallbacks(callbacks: Array): void getDeviceUUID(): Promise - getFMSToken(): Promise getTokens(): Promise - updateFMSToken(token: string): Promise executeAsyncOperation(operationSystemName: string, operationBody: string): Promise executeSyncOperation(operationSystemName: string, operationBody: string): Promise onPushClickedIsRegistered(isRegistered: boolean): void diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 12de382..1f98bf4 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -31,42 +31,10 @@ jest.mock('react-native', () => { resolve('UUID') }) ), - getAPNSToken: jest.fn( - () => - new Promise((resolve) => { - resolve('APNS') - }) - ), - getFMSToken: jest.fn( - () => - new Promise((resolve) => { - resolve('FMS') - }) - ), getTokens: jest.fn( () => new Promise((resolve) => { - resolve('Tokens') - }) - ), - updateAPNSToken: jest.fn( - (payloadString: string) => - new Promise((resolve, reject) => { - if (payloadString && typeof payloadString === 'string') { - resolve(true) - } else { - reject(new Error('Error')) - } - }) - ), - updateFMSToken: jest.fn( - (payloadString: string) => - new Promise((resolve, reject) => { - if (payloadString && typeof payloadString === 'string') { - resolve(true) - } else { - reject(new Error('Error')) - } + resolve(JSON.stringify({ APNS: 'APNS', FCM: 'FMS' })) }) ), executeAsyncOperation: jest.fn( @@ -255,76 +223,6 @@ describe('Testing Mindbox RN SDK', () => { }) }) - describe('Testing getAPNSToken method', () => { - it('resolves successfully with string payload', async () => { - expect.assertions(1) - - await expect(MindboxSdk.getAPNSToken()).resolves.toEqual('APNS') - }) - }) - - describe('Testing getFMSToken method', () => { - it('resolves successfully with string payload', async () => { - expect.assertions(1) - - await expect(MindboxSdk.getFMSToken()).resolves.toEqual('FMS') - }) - }) - - describe('Testing updateAPNSToken method', () => { - it('throws error when no paylaod passed', async () => { - expect.assertions(1) - - await expect(MindboxSdk.updateAPNSToken()).rejects.toThrow('Error') - }) - - it('throws error when non string payload passed', async () => { - expect.assertions(1) - - const wrongPaylaod = { - one: 'one', - two: 'two', - } - - await expect(MindboxSdk.updateAPNSToken(wrongPaylaod)).rejects.toThrow('Error') - }) - - it('resolves successfully with string payload passed', async () => { - expect.assertions(1) - - const payloadString = 'NewFMSToken' - - await expect(MindboxSdk.updateAPNSToken(payloadString)).resolves.toBeTruthy() - }) - }) - - describe('Testing updateFMSToken method', () => { - it('throws error when no paylaod passed', async () => { - expect.assertions(1) - - await expect(MindboxSdk.updateFMSToken()).rejects.toThrow('Error') - }) - - it('throws error when non string payload passed', async () => { - expect.assertions(1) - - const wrongPaylaod = { - one: 'one', - two: 'two', - } - - await expect(MindboxSdk.updateFMSToken(wrongPaylaod)).rejects.toThrow('Error') - }) - - it('resolves successfully with string payload passed', async () => { - expect.assertions(1) - - const payloadString = 'NewFMSToken' - - await expect(MindboxSdk.updateFMSToken(payloadString)).resolves.toBeTruthy() - }) - }) - describe('Testing executeAsyncOperation method', () => { it('throws error when no payload passed', async () => { expect.assertions(2) @@ -492,23 +390,6 @@ describe('Testing Mindbox RN SDK', () => { await MindboxSdk.initialize(initializationData) }) - it('getToken method works correctly', async () => { - const MindboxSdk = require('../index').default - await MindboxSdk.initialize(initializationData) - - expect.assertions(2) - - MindboxSdk.getToken((token: string) => { - expect(token).toEqual('APNS') - }) - - Platform.OS = 'android' - - MindboxSdk.getToken((token: string) => { - expect(token).toEqual('FMS') - }) - }) - it('getTokens method works correctly', async () => { const MindboxSdk = require('../index').default await MindboxSdk.initialize(initializationData) @@ -516,22 +397,15 @@ describe('Testing Mindbox RN SDK', () => { expect.assertions(2) MindboxSdk.getTokens((token: string) => { - expect(token).toEqual('Tokens') + expect(token).toEqual(JSON.stringify({ APNS: 'APNS', FCM: 'FMS' })) }) Platform.OS = 'android' MindboxSdk.getTokens((token: string) => { - expect(token).toEqual('Tokens') + expect(token).toEqual(JSON.stringify({ APNS: 'APNS', FCM: 'FMS' })) }) - }) - - it('updateToken method resolves successfully', async () => { - const MindboxSdk = require('../index').default - - expect.assertions(1) - - await expect(MindboxSdk.updateToken('newToken')).resolves.toBeUndefined() + Platform.OS = 'ios' }) it('onPushClickReceived method works correctly', () => { diff --git a/src/index.tsx b/src/index.tsx index 3c2347c..bb60785 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,3 @@ -import { Platform } from 'react-native' - import type { InitializationData, ExecuteSyncOperationPayload, ExecuteAsyncOperationPayload } from './types' import type { InAppCallback } from './InAppCallback' import MindboxSdkNative from './NativeMindboxSdk' @@ -25,11 +23,6 @@ type PushNotificationClickedPayload = { pushPayload: string } -type MindboxSdkIosNativeModule = { - getAPNSToken(): Promise - updateAPNSToken(token: string): Promise -} - class MindboxSdkClass { private _initialized: boolean private _initializing: boolean @@ -180,46 +173,6 @@ class MindboxSdkClass { } } - /** - * @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 = () => { - const iosNativeModule = MindboxSdkNative as unknown as MindboxSdkIosNativeModule - let promise: Promise - - switch (Platform.OS) { - case 'ios': - promise = iosNativeModule.getAPNSToken() - break - - case 'android': - promise = MindboxSdkNative.getFMSToken() - break - - default: - promise = iosNativeModule.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 . @@ -244,34 +197,6 @@ class MindboxSdkClass { } } - /** - * @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': - await (MindboxSdkNative as unknown as MindboxSdkIosNativeModule).updateAPNSToken(token) - break - - case 'android': - await MindboxSdkNative.updateFMSToken(token) - break - - default: - await (MindboxSdkNative as unknown as MindboxSdkIosNativeModule).updateAPNSToken(token) - break - } - } - /** * @name onPushClickReceived * @description Listens if push notification or push notification button were pressed. @@ -293,10 +218,8 @@ class MindboxSdkClass { callback(event.pushUrl || null, event.pushPayload || null) }) - if (Platform.OS === 'android') { - this.writeNativeLog('Register push click listener for android', LogLevel.INFO) - MindboxSdkNative.onPushClickedIsRegistered(true) - } + this.writeNativeLog('Register push click listener', LogLevel.INFO) + MindboxSdkNative.onPushClickedIsRegistered(true) } /** @@ -310,9 +233,7 @@ class MindboxSdkClass { if (this._pushSubscription) { this._pushSubscription.remove() this._pushSubscription = undefined - if (Platform.OS === 'android') { - MindboxSdkNative.onPushClickedIsRegistered(false) - } + MindboxSdkNative.onPushClickedIsRegistered(false) } }