@virex-tech/paywallo-sdk 2.0.0 → 2.0.2
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.
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +85 -85
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +72 -72
- package/dist/index.mjs.map +1 -1
- package/ios/PaywalloNotifications.m +4 -1
- package/ios/PaywalloNotifications.swift +60 -73
- package/package.json +1 -1
- package/ios/PaywalloNotificationsSwizzle.swift +0 -126
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
@interface RCT_EXTERN_MODULE(PaywalloNotifications, RCTEventEmitter)
|
|
5
5
|
|
|
6
|
-
RCT_EXTERN_METHOD(requestPermission:(NSDictionary *)options
|
|
6
|
+
RCT_EXTERN_METHOD(requestPermission:(nonnull NSDictionary *)options
|
|
7
7
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
8
8
|
reject:(RCTPromiseRejectBlock)reject)
|
|
9
9
|
|
|
@@ -13,6 +13,9 @@ RCT_EXTERN_METHOD(hasPermission:(RCTPromiseResolveBlock)resolve
|
|
|
13
13
|
RCT_EXTERN_METHOD(getToken:(RCTPromiseResolveBlock)resolve
|
|
14
14
|
reject:(RCTPromiseRejectBlock)reject)
|
|
15
15
|
|
|
16
|
+
RCT_EXTERN_METHOD(registerForPush:(RCTPromiseResolveBlock)resolve
|
|
17
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
18
|
+
|
|
16
19
|
RCT_EXTERN_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve
|
|
17
20
|
reject:(RCTPromiseRejectBlock)reject)
|
|
18
21
|
|
|
@@ -10,9 +10,7 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
10
10
|
private static let messageReceivedEvent = "PaywalloMessageReceived"
|
|
11
11
|
private static let notificationOpenedEvent = "PaywalloNotificationOpened"
|
|
12
12
|
|
|
13
|
-
// MARK: - Pre-subscribe buffer
|
|
14
|
-
// hasListeners is read/written from bufferQueue to avoid races between
|
|
15
|
-
// startObserving (main thread) and bufferOrEmit (NotificationCenter delivery thread).
|
|
13
|
+
// MARK: - Pre-subscribe buffer
|
|
16
14
|
private var _hasListeners = false
|
|
17
15
|
private var preSubscribeBuffer: [[String: Any]] = []
|
|
18
16
|
private let bufferQueue = DispatchQueue(label: "com.paywallo.notifications.buffer")
|
|
@@ -24,7 +22,7 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
24
22
|
|
|
25
23
|
// MARK: - RCTEventEmitter Overrides
|
|
26
24
|
|
|
27
|
-
override static func requiresMainQueueSetup() -> Bool { return
|
|
25
|
+
override static func requiresMainQueueSetup() -> Bool { return true }
|
|
28
26
|
|
|
29
27
|
override func supportedEvents() -> [String] {
|
|
30
28
|
return [
|
|
@@ -46,13 +44,8 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
46
44
|
// MARK: - Buffer Management
|
|
47
45
|
|
|
48
46
|
private func bufferOrEmit(eventName: String, body: [String: Any]) {
|
|
49
|
-
// Determine whether to emit or buffer inside the lock, but never call
|
|
50
|
-
// sendEvent (which may dispatch back to main) while holding the lock —
|
|
51
|
-
// that would risk deadlock or re-entrancy on a sync queue.
|
|
52
47
|
let shouldEmit: Bool = bufferQueue.sync {
|
|
53
|
-
if _hasListeners {
|
|
54
|
-
return true
|
|
55
|
-
}
|
|
48
|
+
if _hasListeners { return true }
|
|
56
49
|
var entry = body
|
|
57
50
|
entry["_eventName"] = eventName
|
|
58
51
|
if preSubscribeBuffer.count >= PaywalloNotifications.maxBufferSize {
|
|
@@ -62,12 +55,13 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
62
55
|
return false
|
|
63
56
|
}
|
|
64
57
|
if shouldEmit {
|
|
65
|
-
|
|
58
|
+
DispatchQueue.main.async {
|
|
59
|
+
self.sendEvent(withName: eventName, body: body)
|
|
60
|
+
}
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
63
|
|
|
69
64
|
private func drainPreSubscribeBuffer() {
|
|
70
|
-
// Copy and clear while holding the lock; emit outside the lock.
|
|
71
65
|
let drained: [[String: Any]] = bufferQueue.sync {
|
|
72
66
|
let copy = preSubscribeBuffer
|
|
73
67
|
preSubscribeBuffer.removeAll()
|
|
@@ -76,13 +70,16 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
76
70
|
for entry in drained {
|
|
77
71
|
var body = entry
|
|
78
72
|
guard let eventName = body.removeValue(forKey: "_eventName") as? String else { continue }
|
|
79
|
-
|
|
73
|
+
DispatchQueue.main.async {
|
|
74
|
+
self.sendEvent(withName: eventName, body: body)
|
|
75
|
+
}
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
// MARK: - Public Methods (exposed to JS)
|
|
84
80
|
|
|
85
|
-
@objc
|
|
81
|
+
@objc(requestPermission:resolve:reject:)
|
|
82
|
+
func requestPermission(
|
|
86
83
|
_ options: NSDictionary,
|
|
87
84
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
88
85
|
reject: @escaping RCTPromiseRejectBlock
|
|
@@ -95,52 +92,70 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
95
92
|
authOptions.insert(.provisional)
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
center.requestAuthorization(options: authOptions) {
|
|
99
|
-
|
|
100
|
-
reject("
|
|
95
|
+
center.requestAuthorization(options: authOptions) { [weak self] _, error in
|
|
96
|
+
guard let self = self else {
|
|
97
|
+
DispatchQueue.main.async { reject("MODULE_RELEASED", "Module was released", nil) }
|
|
101
98
|
return
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
if let error = error {
|
|
102
|
+
DispatchQueue.main.async { reject("PERMISSION_ERROR", error.localizedDescription, error) }
|
|
103
|
+
return
|
|
106
104
|
}
|
|
107
105
|
|
|
108
|
-
// Return status string matching SDK's PushPermissionStatus type
|
|
109
106
|
self.getPermissionStatusString { status in
|
|
110
|
-
resolve(status)
|
|
107
|
+
DispatchQueue.main.async { resolve(status) }
|
|
111
108
|
}
|
|
112
109
|
}
|
|
113
110
|
}
|
|
114
111
|
|
|
115
|
-
@objc
|
|
112
|
+
@objc(hasPermission:reject:)
|
|
113
|
+
func hasPermission(
|
|
116
114
|
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
117
115
|
reject: @escaping RCTPromiseRejectBlock
|
|
118
116
|
) {
|
|
119
117
|
getPermissionStatusString { status in
|
|
120
|
-
resolve(status)
|
|
118
|
+
DispatchQueue.main.async { resolve(status) }
|
|
121
119
|
}
|
|
122
120
|
}
|
|
123
121
|
|
|
124
|
-
@objc
|
|
122
|
+
@objc(getToken:reject:)
|
|
123
|
+
func getToken(
|
|
125
124
|
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
126
125
|
reject: @escaping RCTPromiseRejectBlock
|
|
127
126
|
) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
DispatchQueue.main.async {
|
|
128
|
+
if let token = PaywalloNotifications.latestAPNSToken {
|
|
129
|
+
resolve(token)
|
|
130
|
+
} else {
|
|
131
|
+
resolve(nil)
|
|
132
|
+
}
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
@objc
|
|
136
|
+
@objc(registerForPush:reject:)
|
|
137
|
+
func registerForPush(
|
|
136
138
|
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
137
139
|
reject: @escaping RCTPromiseRejectBlock
|
|
138
140
|
) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
resolve(
|
|
142
|
-
}
|
|
143
|
-
|
|
141
|
+
DispatchQueue.main.async {
|
|
142
|
+
UIApplication.shared.registerForRemoteNotifications()
|
|
143
|
+
resolve(true)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@objc(getInitialNotification:reject:)
|
|
148
|
+
func getInitialNotification(
|
|
149
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
150
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
151
|
+
) {
|
|
152
|
+
DispatchQueue.main.async {
|
|
153
|
+
if let notification = PaywalloNotifications.initialNotification {
|
|
154
|
+
PaywalloNotifications.initialNotification = nil
|
|
155
|
+
resolve(notification)
|
|
156
|
+
} else {
|
|
157
|
+
resolve(nil)
|
|
158
|
+
}
|
|
144
159
|
}
|
|
145
160
|
}
|
|
146
161
|
|
|
@@ -157,44 +172,10 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
157
172
|
case .ephemeral: status = "ephemeral"
|
|
158
173
|
@unknown default: status = "notDetermined"
|
|
159
174
|
}
|
|
160
|
-
completion(status)
|
|
175
|
+
DispatchQueue.main.async { completion(status) }
|
|
161
176
|
}
|
|
162
177
|
}
|
|
163
178
|
|
|
164
|
-
// MARK: - Static methods called from swizzled AppDelegate
|
|
165
|
-
|
|
166
|
-
@objc static func didRegisterForRemoteNotifications(deviceToken: Data) {
|
|
167
|
-
let token = deviceToken.map { String(format: "%02x", $0) }.joined()
|
|
168
|
-
latestAPNSToken = token
|
|
169
|
-
// Emit to JS — need to find the module instance through the bridge
|
|
170
|
-
// This is handled via NotificationCenter since we can't access the bridge statically
|
|
171
|
-
NotificationCenter.default.post(
|
|
172
|
-
name: NSNotification.Name("PaywalloAPNSTokenReceived"),
|
|
173
|
-
object: nil,
|
|
174
|
-
userInfo: ["token": token]
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
@objc static func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) {
|
|
179
|
-
let payload = extractPayload(from: userInfo)
|
|
180
|
-
NotificationCenter.default.post(
|
|
181
|
-
name: NSNotification.Name("PaywalloRemoteNotificationReceived"),
|
|
182
|
-
object: nil,
|
|
183
|
-
userInfo: ["payload": payload]
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
@objc static func didOpenNotification(userInfo: [AnyHashable: Any]) {
|
|
188
|
-
let payload = extractPayload(from: userInfo)
|
|
189
|
-
// If module not ready yet, store as initial notification
|
|
190
|
-
initialNotification = payload
|
|
191
|
-
NotificationCenter.default.post(
|
|
192
|
-
name: NSNotification.Name("PaywalloNotificationOpened"),
|
|
193
|
-
object: nil,
|
|
194
|
-
userInfo: ["payload": payload]
|
|
195
|
-
)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
179
|
// MARK: - Payload Extraction
|
|
199
180
|
|
|
200
181
|
private static func extractPayload(from userInfo: [AnyHashable: Any]) -> [String: Any] {
|
|
@@ -209,7 +190,6 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
209
190
|
}
|
|
210
191
|
}
|
|
211
192
|
|
|
212
|
-
// Extract custom data (everything except "aps")
|
|
213
193
|
var data: [String: String] = [:]
|
|
214
194
|
for (key, value) in userInfo {
|
|
215
195
|
guard let key = key as? String, key != "aps" else { continue }
|
|
@@ -222,11 +202,10 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
222
202
|
return payload
|
|
223
203
|
}
|
|
224
204
|
|
|
225
|
-
// MARK: -
|
|
205
|
+
// MARK: - Init: observe token from AppDelegate
|
|
226
206
|
|
|
227
207
|
override init() {
|
|
228
208
|
super.init()
|
|
229
|
-
PaywalloNotificationsSwizzle.swizzleIfNeeded()
|
|
230
209
|
|
|
231
210
|
NotificationCenter.default.addObserver(
|
|
232
211
|
self, selector: #selector(onAPNSTokenReceived(_:)),
|
|
@@ -240,6 +219,11 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
240
219
|
self, selector: #selector(onNotificationOpened(_:)),
|
|
241
220
|
name: NSNotification.Name("PaywalloNotificationOpened"), object: nil
|
|
242
221
|
)
|
|
222
|
+
|
|
223
|
+
// Check if a token was already captured before this observer registered (race condition fix)
|
|
224
|
+
if let existing = PaywalloNotifications.latestAPNSToken {
|
|
225
|
+
bufferOrEmit(eventName: PaywalloNotifications.tokenRefreshEvent, body: ["token": existing])
|
|
226
|
+
}
|
|
243
227
|
}
|
|
244
228
|
|
|
245
229
|
deinit {
|
|
@@ -248,7 +232,10 @@ class PaywalloNotifications: RCTEventEmitter {
|
|
|
248
232
|
|
|
249
233
|
@objc private func onAPNSTokenReceived(_ notification: Notification) {
|
|
250
234
|
guard let token = notification.userInfo?["token"] as? String else { return }
|
|
251
|
-
|
|
235
|
+
DispatchQueue.main.async {
|
|
236
|
+
PaywalloNotifications.latestAPNSToken = token
|
|
237
|
+
self.bufferOrEmit(eventName: PaywalloNotifications.tokenRefreshEvent, body: ["token": token])
|
|
238
|
+
}
|
|
252
239
|
}
|
|
253
240
|
|
|
254
241
|
@objc private func onRemoteNotificationReceived(_ notification: Notification) {
|
package/package.json
CHANGED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import UIKit
|
|
3
|
-
|
|
4
|
-
/// Swizzles UIApplicationDelegate methods to intercept APNS callbacks.
|
|
5
|
-
/// Runs automatically at load time via swizzleIfNeeded() called from PaywalloNotifications.init().
|
|
6
|
-
/// Same approach used by OneSignal, Firebase, and other push SDKs.
|
|
7
|
-
@objc class PaywalloNotificationsSwizzle: NSObject {
|
|
8
|
-
|
|
9
|
-
private static var hasSwizzled = false
|
|
10
|
-
|
|
11
|
-
/// Call once at module init time to swizzle AppDelegate APNS methods.
|
|
12
|
-
@objc static func swizzleIfNeeded() {
|
|
13
|
-
guard !hasSwizzled else { return }
|
|
14
|
-
|
|
15
|
-
// Defer to main queue — UIApplication.shared.delegate is set after app launch.
|
|
16
|
-
// Set hasSwizzled only after the delegate is confirmed, so a nil-delegate
|
|
17
|
-
// early call doesn't permanently suppress swizzling.
|
|
18
|
-
DispatchQueue.main.async {
|
|
19
|
-
guard let delegate = UIApplication.shared.delegate else { return }
|
|
20
|
-
guard !hasSwizzled else { return }
|
|
21
|
-
hasSwizzled = true
|
|
22
|
-
let delegateClass: AnyClass = type(of: delegate)
|
|
23
|
-
|
|
24
|
-
swizzleDidRegisterForRemoteNotifications(delegateClass)
|
|
25
|
-
swizzleDidFailToRegisterForRemoteNotifications(delegateClass)
|
|
26
|
-
swizzleDidReceiveRemoteNotification(delegateClass)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// MARK: - didRegisterForRemoteNotificationsWithDeviceToken
|
|
31
|
-
|
|
32
|
-
private static func swizzleDidRegisterForRemoteNotifications(_ cls: AnyClass) {
|
|
33
|
-
let original = #selector(UIApplicationDelegate.application(_:didRegisterForRemoteNotificationsWithDeviceToken:))
|
|
34
|
-
let swizzled = #selector(PaywalloNotificationsSwizzle.pw_didRegisterForRemoteNotifications(_:deviceToken:))
|
|
35
|
-
swizzle(cls: cls, original: original, swizzled: swizzled)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// MARK: - didFailToRegisterForRemoteNotificationsWithError
|
|
39
|
-
|
|
40
|
-
private static func swizzleDidFailToRegisterForRemoteNotifications(_ cls: AnyClass) {
|
|
41
|
-
let original = #selector(UIApplicationDelegate.application(_:didFailToRegisterForRemoteNotificationsWithError:))
|
|
42
|
-
let swizzled = #selector(PaywalloNotificationsSwizzle.pw_didFailToRegisterForRemoteNotifications(_:error:))
|
|
43
|
-
swizzle(cls: cls, original: original, swizzled: swizzled)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// MARK: - didReceiveRemoteNotification (with completion handler)
|
|
47
|
-
|
|
48
|
-
private static func swizzleDidReceiveRemoteNotification(_ cls: AnyClass) {
|
|
49
|
-
let original = #selector(UIApplicationDelegate.application(_:didReceiveRemoteNotification:fetchCompletionHandler:))
|
|
50
|
-
let swizzled = #selector(PaywalloNotificationsSwizzle.pw_didReceiveRemoteNotification(_:userInfo:completionHandler:))
|
|
51
|
-
swizzle(cls: cls, original: original, swizzled: swizzled)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// MARK: - Core swizzle helper
|
|
55
|
-
|
|
56
|
-
/// Track whether the original method existed before swizzle for each selector,
|
|
57
|
-
/// so the pw_ implementations know whether to forward (exchange case) or not (add case).
|
|
58
|
-
private static var didExchange: [String: Bool] = [:]
|
|
59
|
-
|
|
60
|
-
/// Exchanges implementations if the method exists on cls, otherwise adds it directly.
|
|
61
|
-
private static func swizzle(cls: AnyClass, original: Selector, swizzled: Selector) {
|
|
62
|
-
guard let swizzledMethod = class_getInstanceMethod(PaywalloNotificationsSwizzle.self, swizzled) else {
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let key = NSStringFromSelector(original)
|
|
67
|
-
|
|
68
|
-
if let originalMethod = class_getInstanceMethod(cls, original) {
|
|
69
|
-
// Method exists on the AppDelegate — exchange so both run.
|
|
70
|
-
// After exchange: calling `original` runs our pw_ body,
|
|
71
|
-
// calling `pw_` from within pw_ runs the ORIGINAL body (not recursion).
|
|
72
|
-
method_exchangeImplementations(originalMethod, swizzledMethod)
|
|
73
|
-
didExchange[key] = true
|
|
74
|
-
} else {
|
|
75
|
-
// Method not implemented in the host AppDelegate — add our impl directly.
|
|
76
|
-
// pw_ methods must NOT call themselves here (no original to forward to).
|
|
77
|
-
let types = method_getTypeEncoding(swizzledMethod)
|
|
78
|
-
class_addMethod(cls, original, method_getImplementation(swizzledMethod), types)
|
|
79
|
-
didExchange[key] = false
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// MARK: - Swizzled implementations
|
|
84
|
-
// After method_exchangeImplementations:
|
|
85
|
-
// - The original selector on AppDelegate points to our pw_ body
|
|
86
|
-
// - Calling pw_ from within itself calls the ORIGINAL AppDelegate implementation
|
|
87
|
-
// (because the IMP under pw_ was exchanged to the original). Not infinite recursion.
|
|
88
|
-
//
|
|
89
|
-
// When class_addMethod was used instead (no original method):
|
|
90
|
-
// - There is no original to forward to, so we skip the self-call to avoid
|
|
91
|
-
// actual infinite recursion / stack overflow.
|
|
92
|
-
|
|
93
|
-
// All pw_ methods are called on the main thread (UIApplicationDelegate callbacks
|
|
94
|
-
// are main-thread-only). didExchange is written on main by swizzleIfNeeded, so
|
|
95
|
-
// reading it here is safe without additional locking.
|
|
96
|
-
|
|
97
|
-
@objc func pw_didRegisterForRemoteNotifications(_ application: UIApplication, deviceToken: Data) {
|
|
98
|
-
PaywalloNotifications.didRegisterForRemoteNotifications(deviceToken: deviceToken)
|
|
99
|
-
let key = NSStringFromSelector(#selector(UIApplicationDelegate.application(_:didRegisterForRemoteNotificationsWithDeviceToken:)))
|
|
100
|
-
if PaywalloNotificationsSwizzle.didExchange[key] == true {
|
|
101
|
-
pw_didRegisterForRemoteNotifications(application, deviceToken: deviceToken)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
@objc func pw_didFailToRegisterForRemoteNotifications(_ application: UIApplication, error: Error) {
|
|
106
|
-
let key = NSStringFromSelector(#selector(UIApplicationDelegate.application(_:didFailToRegisterForRemoteNotificationsWithError:)))
|
|
107
|
-
if PaywalloNotificationsSwizzle.didExchange[key] == true {
|
|
108
|
-
pw_didFailToRegisterForRemoteNotifications(application, error: error)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
@objc func pw_didReceiveRemoteNotification(
|
|
113
|
-
_ application: UIApplication,
|
|
114
|
-
userInfo: [AnyHashable: Any],
|
|
115
|
-
completionHandler: @escaping (UIBackgroundFetchResult) -> Void
|
|
116
|
-
) {
|
|
117
|
-
PaywalloNotifications.didReceiveRemoteNotification(userInfo: userInfo)
|
|
118
|
-
let key = NSStringFromSelector(#selector(UIApplicationDelegate.application(_:didReceiveRemoteNotification:fetchCompletionHandler:)))
|
|
119
|
-
if PaywalloNotificationsSwizzle.didExchange[key] == true {
|
|
120
|
-
pw_didReceiveRemoteNotification(application, userInfo: userInfo, completionHandler: completionHandler)
|
|
121
|
-
} else {
|
|
122
|
-
// No original handler — we must call the completion handler ourselves
|
|
123
|
-
completionHandler(.noData)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|