@dashx/react-native 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,94 +1,108 @@
1
- import FirebaseCore
2
- import FirebaseMessaging
3
1
  import Foundation
4
2
  import UIKit
5
3
  import DashX
4
+ #if canImport(FirebaseCore)
5
+ import FirebaseCore
6
+ #endif
7
+ #if canImport(FirebaseMessaging)
8
+ import FirebaseMessaging
9
+ #endif
6
10
 
7
11
  @objc(DashXRCTAppDelegate)
8
- open class DashXRCTAppDelegate: RCTAppDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
9
- // MARK: Getters
10
- private var app: UIApplication = .shared
12
+ open class DashXRCTAppDelegate: RCTDefaultReactNativeFactoryDelegate, UIApplicationDelegate, UNUserNotificationCenterDelegate {
11
13
 
12
- // MARK: Init
14
+ public var window: UIWindow?
15
+ private var reactNativeFactory: RCTReactNativeFactory?
13
16
 
14
- public override init() {
15
- super.init()
17
+ /// The React Native module name. Defaults to "main". Override to match your app's registered component name.
18
+ open var moduleName: String { "main" }
16
19
 
17
- UNUserNotificationCenter.current().delegate = self
20
+ // MARK: - UIApplicationDelegate
18
21
 
19
- // Register to ensure device token can be fetched
20
- app.registerForRemoteNotifications()
21
- }
22
-
23
- // MARK: MessagingDelegate
24
-
25
- open override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
22
+ open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
23
+ #if canImport(FirebaseCore)
26
24
  FirebaseApp.configure()
25
+ #endif
26
+ #if canImport(FirebaseMessaging)
27
27
  Messaging.messaging().delegate = self
28
+ #endif
29
+
30
+ UNUserNotificationCenter.current().delegate = self
28
31
 
29
- // Requesting Push Notifications Permission
30
32
  DashX.requestNotificationPermission { authorizationStatus in
31
33
  switch authorizationStatus {
32
34
  case .authorized:
33
35
  DashXLog.d(tag: #function, "permission authorized to receive push notifications")
36
+ DispatchQueue.main.async {
37
+ application.registerForRemoteNotifications()
38
+ }
34
39
  default:
35
40
  DashXLog.d(tag: #function, "permission denied to receive push notifications")
36
41
  }
37
42
  }
38
43
 
39
- return super.application(application, didFinishLaunchingWithOptions: launchOptions)
44
+ let factory = RCTReactNativeFactory(delegate: self)
45
+ reactNativeFactory = factory
46
+
47
+ window = UIWindow(frame: UIScreen.main.bounds)
48
+ factory.startReactNative(withModuleName: moduleName, in: window, launchOptions: launchOptions)
49
+
50
+ return true
40
51
  }
41
52
 
42
53
  // MARK: - APNS Token Management
43
54
 
44
- public override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
55
+ open func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
45
56
  DashXLog.d(tag: #function, "Unable to register for remote notifications: \(error.localizedDescription)")
46
57
  }
47
58
 
48
- public override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
59
+ open func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
60
+ #if canImport(FirebaseMessaging)
49
61
  Messaging.messaging().setAPNSToken(deviceToken, type: .unknown)
50
- }
51
-
52
- public func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
53
- guard let token = fcmToken else {
54
- DashXLog.d(tag: #function, "FCM Token is empty")
55
- return
62
+ Messaging.messaging().token { token, error in
63
+ if let token = token {
64
+ DashX.setFCMToken(to: token)
65
+ }
56
66
  }
67
+ #endif
68
+ }
57
69
 
58
- DashXLog.d(tag: #function, "FCM Token: \(token)")
70
+ // MARK: - Universal Links
59
71
 
60
- DashX.setFCMToken(to: token)
72
+ open func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
73
+ DashX.handleUserActivity(userActivity: userActivity)
74
+ return true
61
75
  }
62
76
 
63
77
  // MARK: - Push Notifications
64
78
 
65
- public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
79
+ open func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
66
80
  let message = notification.request.content.userInfo
67
81
 
68
- // Pass notification reciept information to Firebase
82
+ #if canImport(FirebaseMessaging)
69
83
  Messaging.messaging().appDidReceiveMessage(message)
84
+ #endif
70
85
 
71
- DashX.trackNotification(message: message, event: .delivered)
86
+ DashX.trackMessage(message: message, event: .delivered)
72
87
 
73
88
  DashXEventEmitter.instance.dispatch(name: "messageReceived", body: bridgeSafePayload(from: message))
74
89
 
75
- let presentationOptions = notificationDeliveredInForeground(message: message)
76
-
77
- completionHandler(presentationOptions)
90
+ completionHandler(notificationDeliveredInForeground(message: message))
78
91
  }
79
92
 
80
- public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
93
+ open func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
81
94
  let message = response.notification.request.content.userInfo
82
95
 
83
- // Pass notification reciept information to Firebase
96
+ #if canImport(FirebaseMessaging)
84
97
  Messaging.messaging().appDidReceiveMessage(message)
98
+ #endif
85
99
 
86
100
  DashXEventEmitter.instance.dispatch(name: "messageReceived", body: bridgeSafePayload(from: message))
87
101
 
88
102
  if response.actionIdentifier == UNNotificationDismissActionIdentifier {
89
- DashX.trackNotification(message: message, event: .dismissed)
103
+ DashX.trackMessage(message: message, event: .dismissed)
90
104
  } else {
91
- DashX.trackNotification(message: message, event: .clicked)
105
+ DashX.trackMessage(message: message, event: .clicked)
92
106
 
93
107
  if let url = message.dashxNotificationUrl() {
94
108
  handleLink(url: url)
@@ -100,9 +114,10 @@ open class DashXRCTAppDelegate: RCTAppDelegate, MessagingDelegate, UNUserNotific
100
114
  completionHandler()
101
115
  }
102
116
 
103
- public override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
104
- // Pass notification reciept information to Firebase
117
+ open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
118
+ #if canImport(FirebaseMessaging)
105
119
  Messaging.messaging().appDidReceiveMessage(userInfo)
120
+ #endif
106
121
 
107
122
  DashXEventEmitter.instance.dispatch(name: "messageReceived", body: bridgeSafePayload(from: userInfo))
108
123
 
@@ -153,15 +168,16 @@ open class DashXRCTAppDelegate: RCTAppDelegate, MessagingDelegate, UNUserNotific
153
168
  completionHandler(.newData)
154
169
  }
155
170
 
156
- // MARK: - Push Notifications handlers
171
+ // MARK: - Override points
157
172
 
158
- open func notificationDeliveredInForeground(message: [AnyHashable: Any]) -> UNNotificationPresentationOptions { return [] }
173
+ open func notificationDeliveredInForeground(message: [AnyHashable: Any]) -> UNNotificationPresentationOptions { return [.banner, .sound] }
159
174
 
160
175
  open func notificationClicked(message: [AnyHashable: Any], actionIdentifier: String) {}
161
176
 
162
177
  open func handleLink(url: URL) {}
163
178
 
164
- /// Converts notification userInfo to a dictionary safe for the React Native bridge (string keys, JSON-serializable values).
179
+ // MARK: - Helpers
180
+
165
181
  private func bridgeSafePayload(from userInfo: [AnyHashable: Any]) -> [String: Any] {
166
182
  userInfo.reduce(into: [String: Any]()) { result, pair in
167
183
  let key = String(describing: pair.key)
@@ -178,7 +194,7 @@ open class DashXRCTAppDelegate: RCTAppDelegate, MessagingDelegate, UNUserNotific
178
194
  if let num = item as? NSNumber { return num }
179
195
  if let d = item as? [AnyHashable: Any] { return bridgeSafePayload(from: d) }
180
196
  if let a = item as? [Any] {
181
- return a.map { sub in
197
+ return a.map { sub -> Any in
182
198
  if let s = sub as? String { return s }
183
199
  if let n = sub as? NSNumber { return n }
184
200
  if let sd = sub as? [AnyHashable: Any] { return bridgeSafePayload(from: sd) }
@@ -216,16 +232,13 @@ extension DashXRCTAppDelegate {
216
232
  return
217
233
  }
218
234
 
219
- // Create a temporary file URL to save the downloaded image
220
235
  let tmpDirectoryURL = FileManager.default.temporaryDirectory
221
236
  let uuid = UUID().uuidString
222
237
  let tmpFileURL = tmpDirectoryURL.appendingPathComponent(uuid + ".png")
223
238
 
224
239
  do {
225
- // Move the downloaded file to the temporary file URL
226
240
  try FileManager.default.moveItem(at: location, to: tmpFileURL)
227
241
 
228
- // Create the notification attachment from the temporary file URL
229
242
  let attachment = try UNNotificationAttachment(identifier: "\(id)-attachment", url: tmpFileURL, options: nil)
230
243
  content.attachments = [attachment]
231
244
  self.createNotification(id: id, content: content)
@@ -236,3 +249,18 @@ extension DashXRCTAppDelegate {
236
249
  task.resume()
237
250
  }
238
251
  }
252
+
253
+ #if canImport(FirebaseMessaging)
254
+ extension DashXRCTAppDelegate: MessagingDelegate {
255
+ open func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
256
+ guard let token = fcmToken else {
257
+ DashXLog.d(tag: #function, "FCM Token is empty")
258
+ return
259
+ }
260
+
261
+ DashXLog.d(tag: #function, "FCM Token: \(token)")
262
+
263
+ DashX.setFCMToken(to: token)
264
+ }
265
+ }
266
+ #endif
@@ -1,3 +1,5 @@
1
1
  #import <React/RCTBridgeModule.h>
2
2
  #import <React/RCTEventEmitter.h>
3
3
  #import <React/RCTViewManager.h>
4
+ #import <React_RCTAppDelegate/RCTDefaultReactNativeFactoryDelegate.h>
5
+ #import <React_RCTAppDelegate/RCTReactNativeFactory.h>
@@ -0,0 +1 @@
1
+ #import "DashXRCTAppDelegate.h"
@@ -29,4 +29,16 @@ RCT_EXTERN_METHOD(unsubscribe);
29
29
 
30
30
  RCT_EXTERN_METHOD(setLogLevel:(int *)level);
31
31
 
32
+ RCT_EXTERN_METHOD(uploadAsset:(NSString *)filePath resource:(NSString *)resource attribute:(NSString *)attribute resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject);
33
+
34
+ RCT_EXTERN_METHOD(fetchAsset:(NSString *)assetId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject);
35
+
36
+ RCT_EXTERN_METHOD(enableLifecycleTracking);
37
+
38
+ RCT_EXTERN_METHOD(enableAdTracking);
39
+
40
+ RCT_EXTERN_METHOD(requestNotificationPermission:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject);
41
+
42
+ RCT_EXTERN_METHOD(getNotificationPermissionStatus:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject);
43
+
32
44
  @end
@@ -1,13 +1,14 @@
1
1
  import Foundation
2
2
  import DashX
3
3
 
4
- typealias CBU = CallbackUtils
5
-
6
4
  @objc(DashXReactNative)
7
5
  class DashXReactNative: RCTEventEmitter {
8
6
  override init() {
9
7
  super.init()
10
8
  DashXEventEmitter.instance.registerEventEmitter(eventEmitter: self)
9
+ DashX.linkHandler = { url in
10
+ DashXEventEmitter.instance.dispatch(name: "linkReceived", body: url.absoluteString)
11
+ }
11
12
  }
12
13
 
13
14
  override static func requiresMainQueueSetup() -> Bool {
@@ -15,7 +16,7 @@ class DashXReactNative: RCTEventEmitter {
15
16
  }
16
17
 
17
18
  override func supportedEvents() -> [String] {
18
- return ["messageReceived"]
19
+ return ["messageReceived", "linkReceived"]
19
20
  }
20
21
 
21
22
  @objc(configure:)
@@ -35,7 +36,8 @@ class DashXReactNative: RCTEventEmitter {
35
36
 
36
37
  @objc(identify:)
37
38
  func identify(_ options: NSDictionary) {
38
- try? DashX.identify(withOptions: options)
39
+ let stringOptions = (options as? [String: Any])?.compactMapValues { ($0 as? CustomStringConvertible)?.description } ?? [:]
40
+ try? DashX.identify(options: stringOptions)
39
41
  }
40
42
 
41
43
  @objc(setIdentity:token:)
@@ -50,39 +52,74 @@ class DashXReactNative: RCTEventEmitter {
50
52
 
51
53
  @objc(track:data:)
52
54
  func track(_ event: String, _ data: NSDictionary?) {
53
- DashX.track(event, withData: data)
55
+ DashX.track(event, withData: data as? [String: Any])
54
56
  }
55
57
 
56
58
  @objc(screen:data:)
57
59
  func screen(_ screenName: String, _ data: NSDictionary?) {
58
- DashX.screen(screenName, withData: data)
60
+ DashX.screen(screenName, withData: data as? [String: Any])
59
61
  }
60
62
 
61
63
  @objc(fetchRecord:options:resolver:rejecter:)
62
64
  func fetchRecord(_ urn: String, options: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
63
- reject("EUNIMPLEMENTED", "fetchRecord is not yet implemented on iOS", nil)
65
+ DashXClient.instance.fetchRecord(
66
+ urn: urn,
67
+ preview: options?["preview"] as? Bool,
68
+ language: options?["language"] as? String,
69
+ fields: options?["fields"] as? [[String: Any]],
70
+ include: options?["include"] as? [[String: Any]],
71
+ exclude: options?["exclude"] as? [[String: Any]]
72
+ ) { result in
73
+ switch result {
74
+ case .success(let record): resolve(record)
75
+ case .failure(let error): reject("EUNSPECIFIED", error.localizedDescription, error)
76
+ }
77
+ }
64
78
  }
65
79
 
66
80
  @objc(searchRecords:options:resolver:rejecter:)
67
81
  func searchRecords(_ resource: String, options: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
68
- reject("EUNIMPLEMENTED", "searchRecords is not yet implemented on iOS", nil)
82
+ DashXClient.instance.searchRecords(
83
+ resource: resource,
84
+ filter: options?["filter"] as? [String: Any],
85
+ order: options?["order"] as? [[String: Any]],
86
+ limit: options?["limit"] as? Int,
87
+ page: options?["page"] as? Int,
88
+ preview: options?["preview"] as? Bool,
89
+ language: options?["language"] as? String,
90
+ fields: options?["fields"] as? [[String: Any]],
91
+ include: options?["include"] as? [[String: Any]],
92
+ exclude: options?["exclude"] as? [[String: Any]]
93
+ ) { result in
94
+ switch result {
95
+ case .success(let records): resolve(records)
96
+ case .failure(let error): reject("EUNSPECIFIED", error.localizedDescription, error)
97
+ }
98
+ }
69
99
  }
70
100
 
71
101
  @objc(fetchStoredPreferences:rejecter:)
72
102
  func fetchStoredPreferences(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
73
- DashX.fetchStoredPreferences(
74
- successCallback: CBU.resolveToSuccessCallback(resolve),
75
- failureCallback: CBU.rejectToFailureCallback(reject)
76
- )
103
+ DashXClient.instance.fetchStoredPreferences { result in
104
+ switch result {
105
+ case .success(let prefs): resolve(prefs)
106
+ case .failure(let error): reject("EUNSPECIFIED", error.localizedDescription, error)
107
+ }
108
+ }
77
109
  }
78
110
 
79
- @objc(saveStoredPreferences:resolve:rejecter:)
80
- func saveStoredPreferences(_ preferenceData: NSDictionary, _ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
81
- DashX.saveStoredPreferences(
82
- preferenceData: preferenceData,
83
- successCallback: CBU.resolveToSuccessCallback(resolve),
84
- failureCallback: CBU.rejectToFailureCallback(reject)
85
- )
111
+ @objc(saveStoredPreferences:resolver:rejecter:)
112
+ func saveStoredPreferences(_ preferenceData: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
113
+ guard let dict = preferenceData as? [String: Any] else {
114
+ reject("EINVAL", "preferenceData must be a plain object", nil)
115
+ return
116
+ }
117
+ DashXClient.instance.saveStoredPreferences(preferenceData: dict) { result in
118
+ switch result {
119
+ case .success(let success): resolve(success)
120
+ case .failure(let error): reject("EUNSPECIFIED", error.localizedDescription, error)
121
+ }
122
+ }
86
123
  }
87
124
 
88
125
  @objc
@@ -100,18 +137,59 @@ class DashXReactNative: RCTEventEmitter {
100
137
  let logLevel = DashXLog.LogLevel(rawValue: level) ?? DashXLog.LogLevel.off
101
138
  DashXLog.setLogLevel(to: logLevel)
102
139
  }
103
- }
104
140
 
105
- class CallbackUtils {
106
- static func resolveToSuccessCallback(_ resolve: @escaping RCTPromiseResolveBlock) -> SuccessCallback {
107
- return resolve
141
+ @objc(uploadAsset:resource:attribute:resolver:rejecter:)
142
+ func uploadAsset(_ filePath: String, resource: String, attribute: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
143
+ let fileURL = URL(fileURLWithPath: filePath)
144
+ DashXClient.instance.uploadAsset(fileURL: fileURL, resource: resource, attribute: attribute) { result in
145
+ switch result {
146
+ case .success(let assetResponse):
147
+ if let data = try? JSONEncoder().encode(assetResponse),
148
+ let dict = try? JSONSerialization.jsonObject(with: data) as? NSDictionary {
149
+ resolve(dict)
150
+ } else {
151
+ resolve(nil)
152
+ }
153
+ case .failure(let error):
154
+ reject("EUNSPECIFIED", error.localizedDescription, error)
155
+ }
156
+ }
108
157
  }
109
158
 
110
- static func rejectToFailureCallback(_ reject: @escaping RCTPromiseRejectBlock) -> FailureCallback {
111
- let result: FailureCallback = { (error) in
112
- reject("\((error as NSError).code)", error.localizedDescription, error)
159
+ @objc(fetchAsset:resolver:rejecter:)
160
+ func fetchAsset(_ assetId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
161
+ DashXClient.instance.fetchAsset(assetId: assetId) { result in
162
+ switch result {
163
+ case .success(let assetResponse):
164
+ if let data = try? JSONEncoder().encode(assetResponse),
165
+ let dict = try? JSONSerialization.jsonObject(with: data) as? NSDictionary {
166
+ resolve(dict)
167
+ } else {
168
+ resolve(nil)
169
+ }
170
+ case .failure(let error):
171
+ reject("EUNSPECIFIED", error.localizedDescription, error)
172
+ }
113
173
  }
174
+ }
175
+
176
+ @objc
177
+ func enableLifecycleTracking() {
178
+ DispatchQueue.main.async { DashX.enableLifecycleTracking() }
179
+ }
180
+
181
+ @objc
182
+ func enableAdTracking() {
183
+ DashX.enableAdTracking()
184
+ }
185
+
186
+ @objc(requestNotificationPermission:rejecter:)
187
+ func requestNotificationPermission(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
188
+ DashX.requestNotificationPermission { status in resolve(status.rawValue) }
189
+ }
114
190
 
115
- return result
191
+ @objc(getNotificationPermissionStatus:rejecter:)
192
+ func getNotificationPermissionStatus(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
193
+ DashX.getNotificationPermissionStatus { status in resolve(status.rawValue) }
116
194
  }
117
195
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dashx/react-native",
3
3
  "title": "DashX React Native",
4
- "version": "1.1.2",
4
+ "version": "1.1.3",
5
5
  "description": "DashX SDK for React Native",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
@@ -25,7 +25,8 @@
25
25
  "README.md"
26
26
  ],
27
27
  "scripts": {
28
- "test": "echo \"Error: no test specified\" && exit 1"
28
+ "test": "jest",
29
+ "lint": "eslint index.js"
29
30
  },
30
31
  "repository": {
31
32
  "type": "git",
@@ -37,7 +38,7 @@
37
38
  "android"
38
39
  ],
39
40
  "author": {
40
- "name": "DashXDev",
41
+ "name": "Team DashX",
41
42
  "email": "dev@dashx.com"
42
43
  },
43
44
  "license": "MIT",
@@ -74,6 +75,9 @@
74
75
  "@react-native",
75
76
  "prettier"
76
77
  ],
78
+ "plugins": [
79
+ "prettier"
80
+ ],
77
81
  "rules": {
78
82
  "prettier/prettier": [
79
83
  "error",
@@ -95,5 +99,14 @@
95
99
  "quoteProps": "consistent",
96
100
  "singleQuote": true,
97
101
  "trailingComma": "es5"
102
+ },
103
+ "jest": {
104
+ "testEnvironment": "node",
105
+ "transform": {
106
+ "^.+\\.js$": "babel-jest"
107
+ },
108
+ "transformIgnorePatterns": [
109
+ "node_modules/(?!react-native)"
110
+ ]
98
111
  }
99
112
  }