@usergist/feedback-react-native 0.1.0
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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/UserGistFeedback.podspec +31 -0
- package/UserGistFeedbackExtension.podspec +23 -0
- package/android/build.gradle +92 -0
- package/android/consumer-rules.pro +5 -0
- package/android/src/main/AndroidManifest.xml +31 -0
- package/android/src/main/java/studio/usergist/feedback/UserGistFirebaseMessagingService.kt +153 -0
- package/android/src/main/java/studio/usergist/feedback/UserGistPushModule.kt +344 -0
- package/android/src/main/java/studio/usergist/feedback/UserGistPushPackage.kt +18 -0
- package/dist/NativeUserGistPush.d.ts +35 -0
- package/dist/NativeUserGistPush.d.ts.map +1 -0
- package/dist/NativeUserGistPush.js +13 -0
- package/dist/NativeUserGistPush.js.map +1 -0
- package/dist/Provider.d.ts +7 -0
- package/dist/Provider.d.ts.map +1 -0
- package/dist/Provider.js +300 -0
- package/dist/Provider.js.map +1 -0
- package/dist/Push.d.ts +103 -0
- package/dist/Push.d.ts.map +1 -0
- package/dist/Push.js +225 -0
- package/dist/Push.js.map +1 -0
- package/dist/UserGist.d.ts +210 -0
- package/dist/UserGist.d.ts.map +1 -0
- package/dist/UserGist.js +1246 -0
- package/dist/UserGist.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/consent.d.ts +17 -0
- package/dist/internal/consent.d.ts.map +1 -0
- package/dist/internal/consent.js +98 -0
- package/dist/internal/consent.js.map +1 -0
- package/dist/internal/context.d.ts +12 -0
- package/dist/internal/context.d.ts.map +1 -0
- package/dist/internal/context.js +70 -0
- package/dist/internal/context.js.map +1 -0
- package/dist/internal/debug.d.ts +14 -0
- package/dist/internal/debug.d.ts.map +1 -0
- package/dist/internal/debug.js +73 -0
- package/dist/internal/debug.js.map +1 -0
- package/dist/internal/engine.d.ts +93 -0
- package/dist/internal/engine.d.ts.map +1 -0
- package/dist/internal/engine.js +904 -0
- package/dist/internal/engine.js.map +1 -0
- package/dist/internal/events.d.ts +49 -0
- package/dist/internal/events.d.ts.map +1 -0
- package/dist/internal/events.js +71 -0
- package/dist/internal/events.js.map +1 -0
- package/dist/internal/frequency-cap.d.ts +15 -0
- package/dist/internal/frequency-cap.d.ts.map +1 -0
- package/dist/internal/frequency-cap.js +84 -0
- package/dist/internal/frequency-cap.js.map +1 -0
- package/dist/internal/identity.d.ts +14 -0
- package/dist/internal/identity.d.ts.map +1 -0
- package/dist/internal/identity.js +113 -0
- package/dist/internal/identity.js.map +1 -0
- package/dist/internal/inapp-matcher.d.ts +13 -0
- package/dist/internal/inapp-matcher.d.ts.map +1 -0
- package/dist/internal/inapp-matcher.js +65 -0
- package/dist/internal/inapp-matcher.js.map +1 -0
- package/dist/internal/inapp-rules-cache.d.ts +16 -0
- package/dist/internal/inapp-rules-cache.d.ts.map +1 -0
- package/dist/internal/inapp-rules-cache.js +88 -0
- package/dist/internal/inapp-rules-cache.js.map +1 -0
- package/dist/internal/instruction-dedupe.d.ts +16 -0
- package/dist/internal/instruction-dedupe.d.ts.map +1 -0
- package/dist/internal/instruction-dedupe.js +52 -0
- package/dist/internal/instruction-dedupe.js.map +1 -0
- package/dist/internal/lifecycle.d.ts +12 -0
- package/dist/internal/lifecycle.d.ts.map +1 -0
- package/dist/internal/lifecycle.js +97 -0
- package/dist/internal/lifecycle.js.map +1 -0
- package/dist/internal/modal-coordinator.d.ts +4 -0
- package/dist/internal/modal-coordinator.d.ts.map +1 -0
- package/dist/internal/modal-coordinator.js +43 -0
- package/dist/internal/modal-coordinator.js.map +1 -0
- package/dist/internal/modal-queue.d.ts +9 -0
- package/dist/internal/modal-queue.d.ts.map +1 -0
- package/dist/internal/modal-queue.js +32 -0
- package/dist/internal/modal-queue.js.map +1 -0
- package/dist/internal/mutation-queue.d.ts +23 -0
- package/dist/internal/mutation-queue.d.ts.map +1 -0
- package/dist/internal/mutation-queue.js +66 -0
- package/dist/internal/mutation-queue.js.map +1 -0
- package/dist/internal/prompt-answers.d.ts +6 -0
- package/dist/internal/prompt-answers.d.ts.map +1 -0
- package/dist/internal/prompt-answers.js +10 -0
- package/dist/internal/prompt-answers.js.map +1 -0
- package/dist/internal/prompt-flow.d.ts +8 -0
- package/dist/internal/prompt-flow.d.ts.map +1 -0
- package/dist/internal/prompt-flow.js +26 -0
- package/dist/internal/prompt-flow.js.map +1 -0
- package/dist/internal/queue.d.ts +17 -0
- package/dist/internal/queue.d.ts.map +1 -0
- package/dist/internal/queue.js +172 -0
- package/dist/internal/queue.js.map +1 -0
- package/dist/internal/requests.d.ts +22 -0
- package/dist/internal/requests.d.ts.map +1 -0
- package/dist/internal/requests.js +146 -0
- package/dist/internal/requests.js.map +1 -0
- package/dist/internal/rules-cache.d.ts +16 -0
- package/dist/internal/rules-cache.d.ts.map +1 -0
- package/dist/internal/rules-cache.js +86 -0
- package/dist/internal/rules-cache.js.map +1 -0
- package/dist/internal/storage.d.ts +36 -0
- package/dist/internal/storage.d.ts.map +1 -0
- package/dist/internal/storage.js +213 -0
- package/dist/internal/storage.js.map +1 -0
- package/dist/internal/survey-completion.d.ts +10 -0
- package/dist/internal/survey-completion.d.ts.map +1 -0
- package/dist/internal/survey-completion.js +11 -0
- package/dist/internal/survey-completion.js.map +1 -0
- package/dist/internal/survey-matcher.d.ts +21 -0
- package/dist/internal/survey-matcher.d.ts.map +1 -0
- package/dist/internal/survey-matcher.js +139 -0
- package/dist/internal/survey-matcher.js.map +1 -0
- package/dist/internal/survey-rules-cache.d.ts +17 -0
- package/dist/internal/survey-rules-cache.d.ts.map +1 -0
- package/dist/internal/survey-rules-cache.js +91 -0
- package/dist/internal/survey-rules-cache.js.map +1 -0
- package/dist/internal/survey-store.d.ts +19 -0
- package/dist/internal/survey-store.d.ts.map +1 -0
- package/dist/internal/survey-store.js +59 -0
- package/dist/internal/survey-store.js.map +1 -0
- package/dist/internal/transport.d.ts +210 -0
- package/dist/internal/transport.d.ts.map +1 -0
- package/dist/internal/transport.js +498 -0
- package/dist/internal/transport.js.map +1 -0
- package/dist/internal/trigger-matcher.d.ts +21 -0
- package/dist/internal/trigger-matcher.d.ts.map +1 -0
- package/dist/internal/trigger-matcher.js +114 -0
- package/dist/internal/trigger-matcher.js.map +1 -0
- package/dist/internal/types.d.ts +89 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/internal/user-state.d.ts +11 -0
- package/dist/internal/user-state.d.ts.map +1 -0
- package/dist/internal/user-state.js +118 -0
- package/dist/internal/user-state.js.map +1 -0
- package/dist/native/events.d.ts +27 -0
- package/dist/native/events.d.ts.map +1 -0
- package/dist/native/events.js +12 -0
- package/dist/native/events.js.map +1 -0
- package/dist/native/push-bridge.d.ts +20 -0
- package/dist/native/push-bridge.d.ts.map +1 -0
- package/dist/native/push-bridge.js +92 -0
- package/dist/native/push-bridge.js.map +1 -0
- package/dist/preview/PromptShell.d.ts +9 -0
- package/dist/preview/PromptShell.d.ts.map +1 -0
- package/dist/preview/PromptShell.js +128 -0
- package/dist/preview/PromptShell.js.map +1 -0
- package/dist/preview/SurveyShell.d.ts +15 -0
- package/dist/preview/SurveyShell.d.ts.map +1 -0
- package/dist/preview/SurveyShell.js +197 -0
- package/dist/preview/SurveyShell.js.map +1 -0
- package/dist/preview/index.d.ts +4 -0
- package/dist/preview/index.d.ts.map +1 -0
- package/dist/preview/index.js +16 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/ui/InAppMessageView.d.ts +12 -0
- package/dist/ui/InAppMessageView.d.ts.map +1 -0
- package/dist/ui/InAppMessageView.js +260 -0
- package/dist/ui/InAppMessageView.js.map +1 -0
- package/dist/ui/PromptSheet.d.ts +12 -0
- package/dist/ui/PromptSheet.d.ts.map +1 -0
- package/dist/ui/PromptSheet.js +285 -0
- package/dist/ui/PromptSheet.js.map +1 -0
- package/dist/ui/QuestionImageHeader.d.ts +12 -0
- package/dist/ui/QuestionImageHeader.d.ts.map +1 -0
- package/dist/ui/QuestionImageHeader.js +22 -0
- package/dist/ui/QuestionImageHeader.js.map +1 -0
- package/dist/ui/SurveyView.d.ts +25 -0
- package/dist/ui/SurveyView.d.ts.map +1 -0
- package/dist/ui/SurveyView.js +478 -0
- package/dist/ui/SurveyView.js.map +1 -0
- package/dist/ui/animations.d.ts +4 -0
- package/dist/ui/animations.d.ts.map +1 -0
- package/dist/ui/animations.js +24 -0
- package/dist/ui/animations.js.map +1 -0
- package/dist/ui/questions/MultipleChoiceQuestion.d.ts +12 -0
- package/dist/ui/questions/MultipleChoiceQuestion.d.ts.map +1 -0
- package/dist/ui/questions/MultipleChoiceQuestion.js +41 -0
- package/dist/ui/questions/MultipleChoiceQuestion.js.map +1 -0
- package/dist/ui/questions/NpsQuestion.d.ts +14 -0
- package/dist/ui/questions/NpsQuestion.d.ts.map +1 -0
- package/dist/ui/questions/NpsQuestion.js +73 -0
- package/dist/ui/questions/NpsQuestion.js.map +1 -0
- package/dist/ui/questions/RatingQuestion.d.ts +21 -0
- package/dist/ui/questions/RatingQuestion.d.ts.map +1 -0
- package/dist/ui/questions/RatingQuestion.js +91 -0
- package/dist/ui/questions/RatingQuestion.js.map +1 -0
- package/dist/ui/questions/ShortTextQuestion.d.ts +12 -0
- package/dist/ui/questions/ShortTextQuestion.d.ts.map +1 -0
- package/dist/ui/questions/ShortTextQuestion.js +25 -0
- package/dist/ui/questions/ShortTextQuestion.js.map +1 -0
- package/dist/ui/requests/BoardView.d.ts +9 -0
- package/dist/ui/requests/BoardView.d.ts.map +1 -0
- package/dist/ui/requests/BoardView.js +270 -0
- package/dist/ui/requests/BoardView.js.map +1 -0
- package/dist/ui/requests/CommentsSection.d.ts +7 -0
- package/dist/ui/requests/CommentsSection.d.ts.map +1 -0
- package/dist/ui/requests/CommentsSection.js +254 -0
- package/dist/ui/requests/CommentsSection.js.map +1 -0
- package/dist/ui/requests/DetailView.d.ts +8 -0
- package/dist/ui/requests/DetailView.d.ts.map +1 -0
- package/dist/ui/requests/DetailView.js +206 -0
- package/dist/ui/requests/DetailView.js.map +1 -0
- package/dist/ui/requests/RequestsHost.d.ts +12 -0
- package/dist/ui/requests/RequestsHost.d.ts.map +1 -0
- package/dist/ui/requests/RequestsHost.js +58 -0
- package/dist/ui/requests/RequestsHost.js.map +1 -0
- package/dist/ui/requests/StatusPill.d.ts +6 -0
- package/dist/ui/requests/StatusPill.d.ts.map +1 -0
- package/dist/ui/requests/StatusPill.js +25 -0
- package/dist/ui/requests/StatusPill.js.map +1 -0
- package/dist/ui/requests/SubmitView.d.ts +8 -0
- package/dist/ui/requests/SubmitView.d.ts.map +1 -0
- package/dist/ui/requests/SubmitView.js +94 -0
- package/dist/ui/requests/SubmitView.js.map +1 -0
- package/dist/ui/requests/shared.d.ts +21 -0
- package/dist/ui/requests/shared.d.ts.map +1 -0
- package/dist/ui/requests/shared.js +74 -0
- package/dist/ui/requests/shared.js.map +1 -0
- package/dist/ui/survey-questions.d.ts +20 -0
- package/dist/ui/survey-questions.d.ts.map +1 -0
- package/dist/ui/survey-questions.js +211 -0
- package/dist/ui/survey-questions.js.map +1 -0
- package/dist/ui/theme.d.ts +15 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +31 -0
- package/dist/ui/theme.js.map +1 -0
- package/ios/Extension/UserGistNotificationService.swift +292 -0
- package/ios/UserGistPush.h +16 -0
- package/ios/UserGistPush.mm +158 -0
- package/ios/UserGistPushImpl.swift +399 -0
- package/ios/UserGistPushSwizzle.m +168 -0
- package/package.json +89 -0
- package/react-native.config.js +16 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import UserNotifications
|
|
4
|
+
|
|
5
|
+
/// Self-contained iOS push handler for `@usergist/feedback-react-native`.
|
|
6
|
+
///
|
|
7
|
+
/// Owns:
|
|
8
|
+
/// 1. UNUserNotificationCenter authorization + APNs registration.
|
|
9
|
+
/// 2. Capturing the APNs device token via swizzling so consumers don't
|
|
10
|
+
/// have to edit `AppDelegate`.
|
|
11
|
+
/// 3. Foreground notification banners + tap delivery via swizzled
|
|
12
|
+
/// `UNUserNotificationCenterDelegate`.
|
|
13
|
+
/// 4. Forwarding every signal (`tokenReceived`, `notificationReceived`,
|
|
14
|
+
/// `notificationOpened`, `tokenError`) to the JS bridge.
|
|
15
|
+
///
|
|
16
|
+
/// `UserGistPushImpl` is a singleton. The Obj-C `UserGistPush` class binds an
|
|
17
|
+
/// `RCTEventEmitter` reference at module-init time so the Swift side can
|
|
18
|
+
/// emit events without owning the RN bridge type.
|
|
19
|
+
@objc(UserGistPushImpl)
|
|
20
|
+
public final class UserGistPushImpl: NSObject {
|
|
21
|
+
|
|
22
|
+
// MARK: - Singleton
|
|
23
|
+
|
|
24
|
+
@objc public static let shared = UserGistPushImpl()
|
|
25
|
+
|
|
26
|
+
private weak var emitter: AnyObject?
|
|
27
|
+
private let stateLock = NSLock()
|
|
28
|
+
private var hasJsListeners: Bool = false
|
|
29
|
+
|
|
30
|
+
/// Buffer of events that arrived before JS was ready to listen. Drained
|
|
31
|
+
/// the moment `startObserving` fires.
|
|
32
|
+
private var pendingEvents: [(name: String, body: [String: Any])] = []
|
|
33
|
+
|
|
34
|
+
/// The APNs token captured during the most recent registration cycle.
|
|
35
|
+
/// Cached so the JS layer can ask for it without forcing a re-prompt.
|
|
36
|
+
private var lastApnsTokenHex: String?
|
|
37
|
+
|
|
38
|
+
/// The notification (if any) the user tapped while the app was killed
|
|
39
|
+
/// — surfaced via `getInitialNotification` so the host can navigate.
|
|
40
|
+
private var initialNotification: [String: Any]?
|
|
41
|
+
|
|
42
|
+
private override init() {
|
|
43
|
+
super.init()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Exposed to Obj-C so UserGistPushSwizzle.m's setDelegate: interceptor
|
|
47
|
+
/// can fetch the shared UN delegate and reinstall it whenever some
|
|
48
|
+
/// other SDK (FirebaseAnalytics, OneSignal, etc.) tries to take over.
|
|
49
|
+
@objc public static func usergistUNDelegate() -> AnyObject {
|
|
50
|
+
return UserGistUNDelegateSwizzler.sharedDelegateAccessor
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// MARK: - Bridge wiring
|
|
54
|
+
|
|
55
|
+
/// Called by `UserGistPush.mm -init`. Swift never imports React-Core, so we
|
|
56
|
+
/// take an opaque reference and reflect back through `@objc selector`s.
|
|
57
|
+
@objc public func bindEmitter(_ emitter: AnyObject) {
|
|
58
|
+
stateLock.lock()
|
|
59
|
+
self.emitter = emitter
|
|
60
|
+
stateLock.unlock()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@objc public func setHasJsListeners(_ value: Bool) {
|
|
64
|
+
stateLock.lock()
|
|
65
|
+
self.hasJsListeners = value
|
|
66
|
+
stateLock.unlock()
|
|
67
|
+
if value {
|
|
68
|
+
drainPendingEvents()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// MARK: - Exported (called from UserGistPush.mm)
|
|
73
|
+
|
|
74
|
+
@objc public func enablePush(options: [String: Any],
|
|
75
|
+
resolver: @escaping (Any?) -> Void,
|
|
76
|
+
rejecter: @escaping (String, String, Error?) -> Void) {
|
|
77
|
+
NSLog("[UserGistPush] enablePush called")
|
|
78
|
+
let installDelegateProxy = options["installDelegateProxy"] as? Bool ?? true
|
|
79
|
+
if installDelegateProxy {
|
|
80
|
+
Self.installUNDelegateSwizzleIfNeeded()
|
|
81
|
+
if let swizzler: AnyClass = NSClassFromString("UserGistPushSwizzle") {
|
|
82
|
+
let selector = NSSelectorFromString("installIfNeeded")
|
|
83
|
+
let target = swizzler as AnyObject
|
|
84
|
+
if target.responds(to: selector) {
|
|
85
|
+
_ = target.perform(selector)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
UNUserNotificationCenter.current().requestAuthorization(
|
|
90
|
+
options: [.alert, .badge, .sound, .providesAppNotificationSettings]
|
|
91
|
+
) { [weak self] granted, error in
|
|
92
|
+
NSLog("[UserGistPush] requestAuthorization callback granted=\(granted) error=\(String(describing: error))")
|
|
93
|
+
if let error = error {
|
|
94
|
+
rejecter("auth_error", error.localizedDescription, error)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
let status: String = granted ? "authorized" : "denied"
|
|
98
|
+
DispatchQueue.main.async {
|
|
99
|
+
if granted {
|
|
100
|
+
NSLog("[UserGistPush] calling UIApplication.registerForRemoteNotifications")
|
|
101
|
+
UIApplication.shared.registerForRemoteNotifications()
|
|
102
|
+
}
|
|
103
|
+
// Token arrives asynchronously via the swizzled
|
|
104
|
+
// `application:didRegisterForRemoteNotificationsWithDeviceToken:`
|
|
105
|
+
// callback. Resolve immediately with whatever we have cached so the
|
|
106
|
+
// caller has a result even if the token is still in flight; the
|
|
107
|
+
// `tokenReceived` event will fire shortly with the value.
|
|
108
|
+
resolver([
|
|
109
|
+
"granted": granted,
|
|
110
|
+
"status": status,
|
|
111
|
+
"token": self?.readLastApnsToken() as Any,
|
|
112
|
+
"platform": "ios"
|
|
113
|
+
])
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@objc public func disablePush(resolver: @escaping (Any?) -> Void,
|
|
119
|
+
rejecter: @escaping (String, String, Error?) -> Void) {
|
|
120
|
+
DispatchQueue.main.async {
|
|
121
|
+
UIApplication.shared.unregisterForRemoteNotifications()
|
|
122
|
+
self.stateLock.lock()
|
|
123
|
+
self.lastApnsTokenHex = nil
|
|
124
|
+
self.stateLock.unlock()
|
|
125
|
+
resolver(nil)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@objc public func getPermissionStatus(resolver: @escaping (Any?) -> Void,
|
|
130
|
+
rejecter: @escaping (String, String, Error?) -> Void) {
|
|
131
|
+
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
|
132
|
+
let s: String
|
|
133
|
+
switch settings.authorizationStatus {
|
|
134
|
+
case .authorized: s = "authorized"
|
|
135
|
+
case .denied: s = "denied"
|
|
136
|
+
case .notDetermined: s = "not_determined"
|
|
137
|
+
case .provisional: s = "provisional"
|
|
138
|
+
case .ephemeral: s = "authorized"
|
|
139
|
+
@unknown default: s = "not_determined"
|
|
140
|
+
}
|
|
141
|
+
resolver(s)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@objc public func setBadgeCount(_ count: Double,
|
|
146
|
+
resolver: @escaping (Any?) -> Void,
|
|
147
|
+
rejecter: @escaping (String, String, Error?) -> Void) {
|
|
148
|
+
DispatchQueue.main.async {
|
|
149
|
+
let center = UNUserNotificationCenter.current()
|
|
150
|
+
let n = Int(count)
|
|
151
|
+
if #available(iOS 16.0, *) {
|
|
152
|
+
center.setBadgeCount(n) { error in
|
|
153
|
+
if let error = error {
|
|
154
|
+
rejecter("badge_failed", error.localizedDescription, error)
|
|
155
|
+
} else {
|
|
156
|
+
resolver(nil)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
UIApplication.shared.applicationIconBadgeNumber = n
|
|
161
|
+
resolver(nil)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@objc public func getInitialNotification(resolver: @escaping (Any?) -> Void,
|
|
167
|
+
rejecter: @escaping (String, String, Error?) -> Void) {
|
|
168
|
+
stateLock.lock()
|
|
169
|
+
let notification = initialNotification
|
|
170
|
+
initialNotification = nil
|
|
171
|
+
stateLock.unlock()
|
|
172
|
+
resolver(notification as Any?)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// MARK: - Internal — emission helpers (called from swizzles)
|
|
176
|
+
|
|
177
|
+
@objc public func recordToken(deviceToken: Data) {
|
|
178
|
+
let hex = deviceToken.map { String(format: "%02x", $0) }.joined()
|
|
179
|
+
NSLog("[UserGistPush] APNs token received (\(hex.count) hex chars) — emitting tokenReceived")
|
|
180
|
+
stateLock.lock()
|
|
181
|
+
lastApnsTokenHex = hex
|
|
182
|
+
stateLock.unlock()
|
|
183
|
+
emit(name: "UserGistPush:tokenReceived", body: ["token": hex, "platform": "ios"])
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@objc public func recordTokenError(error: Error) {
|
|
187
|
+
NSLog("[UserGistPush] APNs registration FAILED: \(error.localizedDescription)")
|
|
188
|
+
emit(name: "UserGistPush:tokenError",
|
|
189
|
+
body: ["error": error.localizedDescription])
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func recordNotificationReceived(userInfo: [AnyHashable: Any]) {
|
|
193
|
+
let normalized = normalizeUserInfo(userInfo)
|
|
194
|
+
emit(name: "UserGistPush:notificationReceived", body: normalized)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
func recordNotificationOpened(userInfo: [AnyHashable: Any], actionIdentifier: String?) {
|
|
198
|
+
var body = normalizeUserInfo(userInfo)
|
|
199
|
+
if let actionIdentifier = actionIdentifier, actionIdentifier != "com.apple.UNNotificationDefaultActionIdentifier" {
|
|
200
|
+
body["actionIdentifier"] = actionIdentifier
|
|
201
|
+
}
|
|
202
|
+
stateLock.lock()
|
|
203
|
+
if initialNotification == nil {
|
|
204
|
+
// First open captured — surface via getInitialNotification.
|
|
205
|
+
initialNotification = body
|
|
206
|
+
}
|
|
207
|
+
stateLock.unlock()
|
|
208
|
+
emit(name: "UserGistPush:notificationOpened", body: body)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// MARK: - Internal — emit + normalize
|
|
212
|
+
|
|
213
|
+
private func emit(name: String, body: [String: Any]) {
|
|
214
|
+
stateLock.lock()
|
|
215
|
+
guard hasJsListeners else {
|
|
216
|
+
NSLog("[UserGistPush] emit BUFFER \(name) — JS not listening yet (pending=\(pendingEvents.count + 1))")
|
|
217
|
+
if pendingEvents.count >= 100 { pendingEvents.removeFirst() }
|
|
218
|
+
pendingEvents.append((name, body))
|
|
219
|
+
stateLock.unlock()
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
stateLock.unlock()
|
|
223
|
+
NSLog("[UserGistPush] emit SEND \(name)")
|
|
224
|
+
sendToEmitter(name: name, body: body)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private func drainPendingEvents() {
|
|
228
|
+
stateLock.lock()
|
|
229
|
+
let buffered = pendingEvents
|
|
230
|
+
pendingEvents = []
|
|
231
|
+
stateLock.unlock()
|
|
232
|
+
for ev in buffered {
|
|
233
|
+
sendToEmitter(name: ev.name, body: ev.body)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private func sendToEmitter(name: String, body: [String: Any]) {
|
|
238
|
+
stateLock.lock()
|
|
239
|
+
let target = emitter
|
|
240
|
+
stateLock.unlock()
|
|
241
|
+
guard let emitter = target else { return }
|
|
242
|
+
// RCTEventEmitter exposes `sendEventWithName:body:`. Reflect to call
|
|
243
|
+
// it without importing React-Core in Swift (keeps the Swift module
|
|
244
|
+
// free of React headers, which simplifies new-arch interop).
|
|
245
|
+
let sel = NSSelectorFromString("sendEventWithName:body:")
|
|
246
|
+
guard emitter.responds(to: sel) else { return }
|
|
247
|
+
_ = emitter.perform(sel, with: name, with: body as NSDictionary)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private func readLastApnsToken() -> String? {
|
|
251
|
+
stateLock.lock()
|
|
252
|
+
defer { stateLock.unlock() }
|
|
253
|
+
return lastApnsTokenHex
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private func normalizeUserInfo(_ userInfo: [AnyHashable: Any]) -> [String: Any] {
|
|
257
|
+
var out: [String: Any] = [:]
|
|
258
|
+
var aps: [String: Any]?
|
|
259
|
+
var data: [String: Any] = [:]
|
|
260
|
+
for (k, v) in userInfo {
|
|
261
|
+
guard let key = k as? String else { continue }
|
|
262
|
+
if key == "aps", let dict = v as? [String: Any] {
|
|
263
|
+
aps = dict
|
|
264
|
+
} else {
|
|
265
|
+
data[key] = v
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if let alert = aps?["alert"] as? [String: Any] {
|
|
269
|
+
if let title = alert["title"] as? String { out["title"] = title }
|
|
270
|
+
if let body = alert["body"] as? String { out["body"] = body }
|
|
271
|
+
} else if let alertString = aps?["alert"] as? String {
|
|
272
|
+
out["body"] = alertString
|
|
273
|
+
}
|
|
274
|
+
out["data"] = data
|
|
275
|
+
// delivery_id can land in three places depending on payload shape:
|
|
276
|
+
// 1. flat data["usergist_delivery_id"] — FCM data-only path
|
|
277
|
+
// 2. flat data["delivery_id"] — legacy / generic path
|
|
278
|
+
// 3. nested data["usergist"]["deliveryId"] — current APNs payload
|
|
279
|
+
// Without fallback (3), iOS opens land with deliveryId=nil and the
|
|
280
|
+
// server-side patcher early-returns → opened_at never updates.
|
|
281
|
+
let nestedUserGist = data["usergist"] as? [String: Any]
|
|
282
|
+
if let deliveryId =
|
|
283
|
+
(data["delivery_id"] as? String)
|
|
284
|
+
?? (data["usergist_delivery_id"] as? String)
|
|
285
|
+
?? (nestedUserGist?["deliveryId"] as? String)
|
|
286
|
+
{
|
|
287
|
+
out["deliveryId"] = deliveryId
|
|
288
|
+
}
|
|
289
|
+
if let actionId = nestedUserGist?["actionIdentifier"] as? String {
|
|
290
|
+
// Carry a server-set action id through if present (none today, future-proof).
|
|
291
|
+
out["actionIdentifier"] = actionId
|
|
292
|
+
}
|
|
293
|
+
return out
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// MARK: - UN delegate swizzle install
|
|
297
|
+
|
|
298
|
+
private static var unDelegateInstalled = false
|
|
299
|
+
|
|
300
|
+
private static func installUNDelegateSwizzleIfNeeded() {
|
|
301
|
+
guard !unDelegateInstalled else { return }
|
|
302
|
+
unDelegateInstalled = true
|
|
303
|
+
// Install synchronously after enablePush opts into automatic integration.
|
|
304
|
+
UserGistUNDelegateSwizzler.install()
|
|
305
|
+
// Re-install whenever the app foregrounds. Belt-and-braces against
|
|
306
|
+
// any SDK that re-takes the delegate while the app is backgrounded.
|
|
307
|
+
NotificationCenter.default.addObserver(
|
|
308
|
+
forName: UIApplication.didBecomeActiveNotification,
|
|
309
|
+
object: nil,
|
|
310
|
+
queue: .main,
|
|
311
|
+
) { _ in
|
|
312
|
+
UserGistUNDelegateSwizzler.reinstallIfHijacked()
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// MARK: - AppDelegate swizzler (APNs token capture)
|
|
318
|
+
|
|
319
|
+
// AppDelegate APNs swizzle has moved to UserGistPushSwizzle.m (pure Obj-C,
|
|
320
|
+
// installed via +load + UIApplicationDidFinishLaunchingNotification so it
|
|
321
|
+
// lands before any iOS push registration cycle). The Obj-C bridge calls
|
|
322
|
+
// `[UserGistPushImpl.shared recordTokenWithDeviceToken:]` /
|
|
323
|
+
// `recordTokenErrorWithError:` from the swizzled IMP.
|
|
324
|
+
|
|
325
|
+
// MARK: - UNUserNotificationCenterDelegate swizzler (foreground + open)
|
|
326
|
+
|
|
327
|
+
@objc private final class UserGistUNDelegateSwizzler: NSObject, UNUserNotificationCenterDelegate {
|
|
328
|
+
|
|
329
|
+
fileprivate static let sharedDelegate = UserGistUNDelegateSwizzler()
|
|
330
|
+
/// Public access to the shared instance for the Obj-C swizzle bridge.
|
|
331
|
+
@objc fileprivate static var sharedDelegateAccessor: AnyObject { return sharedDelegate }
|
|
332
|
+
private weak var existingDelegate: UNUserNotificationCenterDelegate?
|
|
333
|
+
|
|
334
|
+
fileprivate static func install() {
|
|
335
|
+
let center = UNUserNotificationCenter.current()
|
|
336
|
+
if let existing = center.delegate, existing !== sharedDelegate {
|
|
337
|
+
sharedDelegate.existingDelegate = existing
|
|
338
|
+
}
|
|
339
|
+
center.delegate = sharedDelegate
|
|
340
|
+
NSLog("[UserGistPush] UN delegate installed (chained=\(sharedDelegate.existingDelegate != nil))")
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/// Re-install if some other SDK has stolen the delegate since we last set it.
|
|
344
|
+
/// Called on every foreground transition.
|
|
345
|
+
fileprivate static func reinstallIfHijacked() {
|
|
346
|
+
let center = UNUserNotificationCenter.current()
|
|
347
|
+
guard center.delegate !== sharedDelegate else { return }
|
|
348
|
+
NSLog("[UserGistPush] delegate hijacked by \(String(describing: type(of: center.delegate))) — reinstalling")
|
|
349
|
+
if let existing = center.delegate, existing !== sharedDelegate {
|
|
350
|
+
sharedDelegate.existingDelegate = existing
|
|
351
|
+
}
|
|
352
|
+
center.delegate = sharedDelegate
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// Resolve the foreign delegate to chain to: prefer the most recent one
|
|
356
|
+
/// captured by the Obj-C setDelegate: interceptor (catches Firebase /
|
|
357
|
+
/// OneSignal / etc. that install AFTER our swizzle ran), fall back to
|
|
358
|
+
/// whatever was on the center when our swizzle first installed.
|
|
359
|
+
private func chainTarget() -> UNUserNotificationCenterDelegate? {
|
|
360
|
+
if let foreign = _UserGistGetLastForeignDelegate() as? UNUserNotificationCenterDelegate,
|
|
361
|
+
foreign !== self {
|
|
362
|
+
return foreign
|
|
363
|
+
}
|
|
364
|
+
return existingDelegate
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Foreground delivery
|
|
368
|
+
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
|
369
|
+
willPresent notification: UNNotification,
|
|
370
|
+
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
371
|
+
UserGistPushImpl.shared.recordNotificationReceived(userInfo: notification.request.content.userInfo)
|
|
372
|
+
if let existing = chainTarget(),
|
|
373
|
+
existing.responds(to: #selector(UNUserNotificationCenterDelegate.userNotificationCenter(_:willPresent:withCompletionHandler:))) {
|
|
374
|
+
existing.userNotificationCenter?(center, willPresent: notification, withCompletionHandler: completionHandler)
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
if #available(iOS 14.0, *) {
|
|
378
|
+
completionHandler([.banner, .list, .sound, .badge])
|
|
379
|
+
} else {
|
|
380
|
+
completionHandler([.alert, .sound, .badge])
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Tap / action
|
|
385
|
+
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
|
386
|
+
didReceive response: UNNotificationResponse,
|
|
387
|
+
withCompletionHandler completionHandler: @escaping () -> Void) {
|
|
388
|
+
UserGistPushImpl.shared.recordNotificationOpened(
|
|
389
|
+
userInfo: response.notification.request.content.userInfo,
|
|
390
|
+
actionIdentifier: response.actionIdentifier
|
|
391
|
+
)
|
|
392
|
+
if let existing = chainTarget(),
|
|
393
|
+
existing.responds(to: #selector(UNUserNotificationCenterDelegate.userNotificationCenter(_:didReceive:withCompletionHandler:))) {
|
|
394
|
+
existing.userNotificationCenter?(center, didReceive: response, withCompletionHandler: completionHandler)
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
completionHandler()
|
|
398
|
+
}
|
|
399
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Pure Obj-C swizzle for capturing the APNs device token from the host
|
|
2
|
+
// app's UIApplicationDelegate without requiring any AppDelegate edits.
|
|
3
|
+
//
|
|
4
|
+
// Why Obj-C: Swift methods even with `@objc dynamic` go through Swift's
|
|
5
|
+
// vtable thunking. When method_getImplementation returns the IMP, calling
|
|
6
|
+
// it on a `self` of a different class can hit Swift-specific guards that
|
|
7
|
+
// silently no-op the call. A bare Obj-C IMP installed via
|
|
8
|
+
// `imp_implementationWithBlock` has the standard objc_msgSend ABI and runs
|
|
9
|
+
// on any NSObject subclass without surprises.
|
|
10
|
+
//
|
|
11
|
+
// Installation is explicit and happens only after the host calls enablePush.
|
|
12
|
+
// At that point UIApplication already has a delegate, and interception is in
|
|
13
|
+
// place before registerForRemoteNotifications starts the APNs token cycle.
|
|
14
|
+
|
|
15
|
+
#import <Foundation/Foundation.h>
|
|
16
|
+
#import <UIKit/UIKit.h>
|
|
17
|
+
#import <UserNotifications/UserNotifications.h>
|
|
18
|
+
#import <objc/runtime.h>
|
|
19
|
+
#import <objc/message.h>
|
|
20
|
+
|
|
21
|
+
#if __has_include("UserGistFeedback-Swift.h")
|
|
22
|
+
#import "UserGistFeedback-Swift.h"
|
|
23
|
+
#else
|
|
24
|
+
#import <UserGistFeedback/UserGistFeedback-Swift.h>
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
@interface UserGistPushSwizzle : NSObject
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
@implementation UserGistPushSwizzle
|
|
31
|
+
|
|
32
|
+
+ (void)load { }
|
|
33
|
+
|
|
34
|
+
+ (void)installIfNeeded {
|
|
35
|
+
static dispatch_once_t onceToken;
|
|
36
|
+
dispatch_once(&onceToken, ^{
|
|
37
|
+
[self installUNDelegateSetterSwizzle];
|
|
38
|
+
|
|
39
|
+
// Force-instantiate the Swift singleton so its `init` runs and our
|
|
40
|
+
// shared UN delegate is available the very first time the setter
|
|
41
|
+
// swizzle fires. Otherwise the first foreign `setDelegate:` call
|
|
42
|
+
// (typically Firebase) would still hit the original setter because
|
|
43
|
+
// usergistUNDelegate() returned nil.
|
|
44
|
+
Class implCls = NSClassFromString(@"UserGistFeedback.UserGistPushImpl");
|
|
45
|
+
if (!implCls) implCls = NSClassFromString(@"UserGistPushImpl");
|
|
46
|
+
if (implCls) {
|
|
47
|
+
SEL sharedSel = NSSelectorFromString(@"shared");
|
|
48
|
+
if ([implCls respondsToSelector:sharedSel]) {
|
|
49
|
+
(void)((id (*)(id, SEL))objc_msgSend)(implCls, sharedSel);
|
|
50
|
+
NSLog(@"[UserGistPush] +load: UserGistPushImpl.shared force-initialized");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[self _usergistInstallSwizzles:nil];
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
+ (void)_usergistInstallSwizzles:(NSNotification *)note {
|
|
59
|
+
Class delegateClass = [[[UIApplication sharedApplication] delegate] class];
|
|
60
|
+
if (!delegateClass) {
|
|
61
|
+
NSLog(@"[UserGistPush] opt-in swizzle: no AppDelegate — skipping");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
NSLog(@"[UserGistPush] opt-in swizzle: target class = %@", NSStringFromClass(delegateClass));
|
|
65
|
+
|
|
66
|
+
[self installDidRegister:delegateClass];
|
|
67
|
+
[self installDidFail:delegateClass];
|
|
68
|
+
|
|
69
|
+
NSLog(@"[UserGistPush] opt-in swizzle: complete");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Holds whoever the host (or another SDK like FirebaseAnalytics) most
|
|
73
|
+
// recently tried to install as the UNUserNotificationCenter delegate.
|
|
74
|
+
// Read by UserGistUNDelegateSwizzler so it can chain didReceive / willPresent
|
|
75
|
+
// to the displaced delegate.
|
|
76
|
+
static __weak id<NSObject> _usergist_lastForeignDelegate = nil;
|
|
77
|
+
|
|
78
|
+
// Swizzle UNUserNotificationCenter.setDelegate: so any code that tries to
|
|
79
|
+
// install its own delegate (FirebaseAnalytics, OneSignal, Notifee, the
|
|
80
|
+
// host app itself) is intercepted. Their delegate is captured as the
|
|
81
|
+
// "foreign" chain target; our shared delegate stays installed.
|
|
82
|
+
+ (void)installUNDelegateSetterSwizzle {
|
|
83
|
+
Class cls = [UNUserNotificationCenter class];
|
|
84
|
+
SEL setterSel = @selector(setDelegate:);
|
|
85
|
+
Method existing = class_getInstanceMethod(cls, setterSel);
|
|
86
|
+
if (!existing) {
|
|
87
|
+
NSLog(@"[UserGistPush] setDelegate: not found on UNUserNotificationCenter — skipping");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
IMP origImp = method_getImplementation(existing);
|
|
91
|
+
IMP newImp = imp_implementationWithBlock(
|
|
92
|
+
^(UNUserNotificationCenter *_self, id<NSObject> incoming) {
|
|
93
|
+
// Pull the Swift-side shared delegate via the public Swift class.
|
|
94
|
+
// UserGistFeedback-Swift.h exposes UserGistPushImpl; we route through
|
|
95
|
+
// it to install + record the foreign chain target.
|
|
96
|
+
Class implCls = NSClassFromString(@"UserGistFeedback.UserGistPushImpl");
|
|
97
|
+
if (!implCls) implCls = NSClassFromString(@"UserGistPushImpl");
|
|
98
|
+
id sharedDelegate = nil;
|
|
99
|
+
if (implCls) {
|
|
100
|
+
SEL usergistSharedSel = NSSelectorFromString(@"usergistUNDelegate");
|
|
101
|
+
if ([implCls respondsToSelector:usergistSharedSel]) {
|
|
102
|
+
sharedDelegate = ((id (*)(id, SEL))objc_msgSend)(implCls, usergistSharedSel);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (incoming && sharedDelegate && incoming != sharedDelegate) {
|
|
106
|
+
NSLog(@"[UserGistPush] setDelegate: foreign=%@, keeping ours; chain target captured",
|
|
107
|
+
NSStringFromClass([incoming class]));
|
|
108
|
+
_usergist_lastForeignDelegate = incoming;
|
|
109
|
+
((void (*)(id, SEL, id))origImp)(_self, setterSel, sharedDelegate);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
((void (*)(id, SEL, id))origImp)(_self, setterSel, incoming);
|
|
113
|
+
});
|
|
114
|
+
method_setImplementation(existing, newImp);
|
|
115
|
+
NSLog(@"[UserGistPush] UN setDelegate swizzle installed");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Exposed to Swift via the bridging header — the Swift swizzler reads this
|
|
119
|
+
// when chaining didReceive / willPresent.
|
|
120
|
+
NSObject *_UserGistGetLastForeignDelegate(void) {
|
|
121
|
+
return (NSObject *)_usergist_lastForeignDelegate;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
+ (void)installDidRegister:(Class)cls {
|
|
125
|
+
SEL sel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:);
|
|
126
|
+
IMP newImp = imp_implementationWithBlock(^(id _self, UIApplication *app, NSData *deviceToken) {
|
|
127
|
+
NSLog(@"[UserGistPush] swizzled didRegister fired (token len=%lu)",
|
|
128
|
+
(unsigned long)deviceToken.length);
|
|
129
|
+
[[UserGistPushImpl shared] recordTokenWithDeviceToken:deviceToken];
|
|
130
|
+
// Chain — if the original delegate had its own impl we replaced, the
|
|
131
|
+
// exchanged IMP lives under sel `_usergist_orig_didRegister:`.
|
|
132
|
+
SEL chainSel = NSSelectorFromString(@"_usergist_orig_application:didRegisterForRemoteNotificationsWithDeviceToken:");
|
|
133
|
+
if ([_self respondsToSelector:chainSel]) {
|
|
134
|
+
((void (*)(id, SEL, UIApplication *, NSData *))objc_msgSend)(_self, chainSel, app, deviceToken);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
Method existing = class_getInstanceMethod(cls, sel);
|
|
138
|
+
if (existing) {
|
|
139
|
+
// Save the original under a different selector so we can chain.
|
|
140
|
+
SEL chainSel = NSSelectorFromString(@"_usergist_orig_application:didRegisterForRemoteNotificationsWithDeviceToken:");
|
|
141
|
+
class_addMethod(cls, chainSel, method_getImplementation(existing), method_getTypeEncoding(existing));
|
|
142
|
+
method_setImplementation(existing, newImp);
|
|
143
|
+
} else {
|
|
144
|
+
class_addMethod(cls, sel, newImp, "v@:@@");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
+ (void)installDidFail:(Class)cls {
|
|
149
|
+
SEL sel = @selector(application:didFailToRegisterForRemoteNotificationsWithError:);
|
|
150
|
+
IMP newImp = imp_implementationWithBlock(^(id _self, UIApplication *app, NSError *err) {
|
|
151
|
+
NSLog(@"[UserGistPush] swizzled didFail fired: %@", err.localizedDescription);
|
|
152
|
+
[[UserGistPushImpl shared] recordTokenErrorWithError:err];
|
|
153
|
+
SEL chainSel = NSSelectorFromString(@"_usergist_orig_application:didFailToRegisterForRemoteNotificationsWithError:");
|
|
154
|
+
if ([_self respondsToSelector:chainSel]) {
|
|
155
|
+
((void (*)(id, SEL, UIApplication *, NSError *))objc_msgSend)(_self, chainSel, app, err);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
Method existing = class_getInstanceMethod(cls, sel);
|
|
159
|
+
if (existing) {
|
|
160
|
+
SEL chainSel = NSSelectorFromString(@"_usergist_orig_application:didFailToRegisterForRemoteNotificationsWithError:");
|
|
161
|
+
class_addMethod(cls, chainSel, method_getImplementation(existing), method_getTypeEncoding(existing));
|
|
162
|
+
method_setImplementation(existing, newImp);
|
|
163
|
+
} else {
|
|
164
|
+
class_addMethod(cls, sel, newImp, "v@:@@");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@end
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usergist/feedback-react-native",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "userGist feedback + push SDK for React Native (self-contained, no Firebase/OneSignal/WonderPush required)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://usergist.com/docs/sdks/react-native",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Future-Picnic/usergist-js.git",
|
|
10
|
+
"directory": "packages/sdk-react-native"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Future-Picnic/usergist-js/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"react-native",
|
|
17
|
+
"feedback",
|
|
18
|
+
"surveys",
|
|
19
|
+
"push-notifications",
|
|
20
|
+
"in-app-messaging",
|
|
21
|
+
"feature-requests"
|
|
22
|
+
],
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"module": "./dist/index.js",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./preview": {
|
|
34
|
+
"types": "./dist/preview/index.d.ts",
|
|
35
|
+
"import": "./dist/preview/index.js",
|
|
36
|
+
"default": "./dist/preview/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"!dist/Ritmus*",
|
|
43
|
+
"!dist/NativeRitmusPush*",
|
|
44
|
+
"ios",
|
|
45
|
+
"android",
|
|
46
|
+
"!android/build",
|
|
47
|
+
"UserGistFeedback.podspec",
|
|
48
|
+
"UserGistFeedbackExtension.podspec",
|
|
49
|
+
"react-native.config.js",
|
|
50
|
+
"README.md",
|
|
51
|
+
"CHANGELOG.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@react-native-async-storage/async-storage": ">=1.19",
|
|
56
|
+
"react": ">=18",
|
|
57
|
+
"react-native": ">=0.72",
|
|
58
|
+
"react-native-safe-area-context": ">=4"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@usergist/sdk-core": "^0.1.0"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/react": "^18.3.3",
|
|
68
|
+
"react": "18.2.0",
|
|
69
|
+
"react-native": "0.74.0",
|
|
70
|
+
"react-native-safe-area-context": "^4",
|
|
71
|
+
"vitest": "^2.1.0",
|
|
72
|
+
"typescript": "^5.5.4"
|
|
73
|
+
},
|
|
74
|
+
"codegenConfig": {
|
|
75
|
+
"name": "RNUserGistPushSpec",
|
|
76
|
+
"type": "modules",
|
|
77
|
+
"jsSrcsDir": "src",
|
|
78
|
+
"android": {
|
|
79
|
+
"javaPackageName": "studio.usergist.feedback"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"build": "tsc",
|
|
84
|
+
"dev": "tsc --watch",
|
|
85
|
+
"test": "vitest run",
|
|
86
|
+
"typecheck": "tsc --noEmit",
|
|
87
|
+
"pack:check": "pnpm publish --dry-run --no-git-checks"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// React Native autolinking entry. RN 0.60+ reads this to discover the
|
|
2
|
+
// native iOS Pod and Android Gradle module without consumer-side edits.
|
|
3
|
+
export default {
|
|
4
|
+
dependency: {
|
|
5
|
+
platforms: {
|
|
6
|
+
ios: {
|
|
7
|
+
// The podspec at repo root pulls ios/ sources via s.source_files.
|
|
8
|
+
},
|
|
9
|
+
android: {
|
|
10
|
+
sourceDir: 'android',
|
|
11
|
+
packageImportPath: 'import studio.usergist.feedback.UserGistPushPackage;',
|
|
12
|
+
packageInstance: 'new UserGistPushPackage()',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
}
|