@sigx/lynx-notifications 0.4.0 → 0.4.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/README.md +103 -31
- package/android/com/sigx/notifications/NotificationsModule.kt +105 -4
- package/android/com/sigx/notifications/PushActivityHook.kt +66 -0
- package/android/com/sigx/notifications/PushEventBus.kt +148 -0
- package/android/com/sigx/notifications/PushPublisher.kt +42 -0
- package/android/com/sigx/notifications/SigxFirebaseMessagingService.kt +115 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -25
- package/dist/index.js.map +1 -1
- package/dist/notifications.d.ts +85 -11
- package/dist/notifications.d.ts.map +1 -1
- package/dist/notifications.js +114 -0
- package/dist/notifications.js.map +1 -0
- package/dist/push.d.ts +30 -0
- package/dist/push.d.ts.map +1 -0
- package/dist/push.js +59 -0
- package/dist/push.js.map +1 -0
- package/ios/NotificationsModule.swift +68 -2
- package/ios/PushAppDelegateHook.swift +155 -0
- package/ios/PushEventBus.swift +164 -0
- package/ios/PushPublisher.swift +30 -0
- package/package.json +8 -5
- package/signalx-module.json +41 -3
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import UserNotifications
|
|
4
|
+
|
|
5
|
+
/// AppDelegate hook for remote push.
|
|
6
|
+
///
|
|
7
|
+
/// Discovered by the auto-linker via `signalx-module.json`'s
|
|
8
|
+
/// `ios.appDelegateHook` field; the generated `GeneratedAppDelegateHooks` calls
|
|
9
|
+
/// these static methods at the matching `UIApplicationDelegate` callbacks.
|
|
10
|
+
///
|
|
11
|
+
/// Responsibilities:
|
|
12
|
+
/// - `didFinishLaunching` — install the singleton `UNUserNotificationCenter`
|
|
13
|
+
/// delegate so foreground banners and tap callbacks reach JS. Also captures
|
|
14
|
+
/// a cold-start notification payload (`launchOptions[.remoteNotification]`)
|
|
15
|
+
/// for `Notifications.getInitialNotification()`.
|
|
16
|
+
/// - `didRegisterForRemoteNotificationsWithDeviceToken` — converts the APNs
|
|
17
|
+
/// `Data` token to its canonical hex form and publishes via `PushEventBus`.
|
|
18
|
+
/// - `didFailToRegisterForRemoteNotificationsWithError` — surfaces the error
|
|
19
|
+
/// to JS so apps can show a "push unavailable" state instead of hanging.
|
|
20
|
+
@objc public class PushAppDelegateHook: NSObject {
|
|
21
|
+
|
|
22
|
+
@objc public static func didFinishLaunching(
|
|
23
|
+
_ application: UIApplication,
|
|
24
|
+
launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
25
|
+
) {
|
|
26
|
+
// Take ownership of the notification-center delegate. Apps that need
|
|
27
|
+
// to interpose their own delegate should set it AFTER our hook runs
|
|
28
|
+
// and forward to PushNotificationDelegate.shared from their
|
|
29
|
+
// implementation. (Standard pattern for Firebase / OneSignal / etc.)
|
|
30
|
+
UNUserNotificationCenter.current().delegate = PushNotificationDelegate.shared
|
|
31
|
+
|
|
32
|
+
// Open the cold-start capture window. The first `didReceive` tap
|
|
33
|
+
// after launch (which is how local notifications launched from a
|
|
34
|
+
// terminated state arrive — they never appear in launchOptions) will
|
|
35
|
+
// be stashed as the initial payload instead of fired on the response
|
|
36
|
+
// channel. JS drains it via `getInitialNotification()`.
|
|
37
|
+
PushEventBus.shared.beginColdStartWindow()
|
|
38
|
+
|
|
39
|
+
// Cold-start REMOTE push: iOS also pre-populates
|
|
40
|
+
// `launchOptions[.remoteNotification]` for these. Capture the payload
|
|
41
|
+
// here too — same destination, just a different source.
|
|
42
|
+
if let userInfo = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
|
|
43
|
+
let (notificationId, data) = extractData(from: userInfo)
|
|
44
|
+
PushEventBus.shared.captureInitialResponse(
|
|
45
|
+
notificationId: notificationId,
|
|
46
|
+
data: data,
|
|
47
|
+
actionIdentifier: UNNotificationDefaultActionIdentifier
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@objc public static func didRegisterForRemoteNotificationsWithDeviceToken(
|
|
53
|
+
_ application: UIApplication,
|
|
54
|
+
deviceToken: Data
|
|
55
|
+
) {
|
|
56
|
+
let hex = deviceToken.map { String(format: "%02x", $0) }.joined()
|
|
57
|
+
PushEventBus.shared.publishToken(hex, platform: "apns")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@objc public static func didFailToRegisterForRemoteNotificationsWithError(
|
|
61
|
+
_ application: UIApplication,
|
|
62
|
+
error: Error
|
|
63
|
+
) {
|
|
64
|
+
PushEventBus.shared.publishTokenError(error.localizedDescription)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Pull a notification id and string-coerced data dict out of an APNs
|
|
68
|
+
/// userInfo payload. Notification id falls back to a generated UUID when
|
|
69
|
+
/// the payload doesn't carry one (APNs doesn't require it; some servers
|
|
70
|
+
/// set it as `apns-collapse-id` or in a custom field).
|
|
71
|
+
static func extractData(from userInfo: [AnyHashable: Any]) -> (String, [String: String]) {
|
|
72
|
+
var data: [String: String] = [:]
|
|
73
|
+
var notificationId = UUID().uuidString
|
|
74
|
+
for (k, v) in userInfo {
|
|
75
|
+
guard let key = k as? String else { continue }
|
|
76
|
+
// `aps` carries title/body/sound — strip it from the JS-visible
|
|
77
|
+
// data dict; the relevant fields are surfaced as title/body
|
|
78
|
+
// already by the foreground / response handlers.
|
|
79
|
+
if key == "aps" { continue }
|
|
80
|
+
if key == "notification_id" || key == "notificationId" {
|
|
81
|
+
if let s = v as? String { notificationId = s }
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
data[key] = "\(v)"
|
|
85
|
+
}
|
|
86
|
+
return (notificationId, data)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Singleton `UNUserNotificationCenterDelegate`. Forwards foreground deliveries
|
|
91
|
+
/// (`willPresent`) and tap responses (`didReceive`) to `PushEventBus`. Owned by
|
|
92
|
+
/// `PushAppDelegateHook.didFinishLaunching`; the app keeps it as
|
|
93
|
+
/// `UNUserNotificationCenter.current().delegate`.
|
|
94
|
+
final class PushNotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
|
|
95
|
+
|
|
96
|
+
static let shared = PushNotificationDelegate()
|
|
97
|
+
|
|
98
|
+
/// Foreground delivery. Pass `[.banner, .list, .sound, .badge]` so the OS
|
|
99
|
+
/// shows the banner even when the app is open — without this, iOS suppresses
|
|
100
|
+
/// foreground notifications entirely (the historical complaint in the
|
|
101
|
+
/// previous local-only README).
|
|
102
|
+
func userNotificationCenter(
|
|
103
|
+
_ center: UNUserNotificationCenter,
|
|
104
|
+
willPresent notification: UNNotification,
|
|
105
|
+
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
106
|
+
) {
|
|
107
|
+
let content = notification.request.content
|
|
108
|
+
let (_, data) = PushAppDelegateHook.extractData(from: content.userInfo)
|
|
109
|
+
PushEventBus.shared.publishMessage(
|
|
110
|
+
title: content.title.isEmpty ? nil : content.title,
|
|
111
|
+
body: content.body.isEmpty ? nil : content.body,
|
|
112
|
+
data: data,
|
|
113
|
+
foreground: true
|
|
114
|
+
)
|
|
115
|
+
if #available(iOS 14.0, *) {
|
|
116
|
+
completionHandler([.banner, .list, .sound, .badge])
|
|
117
|
+
} else {
|
|
118
|
+
completionHandler([.alert, .sound, .badge])
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// User tapped a notification (remote OR local). This is the path the
|
|
123
|
+
/// previous README flagged as "Tap callbacks aren't surfaced in JS yet" —
|
|
124
|
+
/// now they are.
|
|
125
|
+
///
|
|
126
|
+
/// If we're still in the cold-start window opened by
|
|
127
|
+
/// `didFinishLaunching`, the first tap is stashed as the initial payload
|
|
128
|
+
/// (drained later by `getInitialNotification()`) instead of fired on the
|
|
129
|
+
/// response channel — this covers local notifications launched from a
|
|
130
|
+
/// terminated state, where `launchOptions[.remoteNotification]` is empty.
|
|
131
|
+
func userNotificationCenter(
|
|
132
|
+
_ center: UNUserNotificationCenter,
|
|
133
|
+
didReceive response: UNNotificationResponse,
|
|
134
|
+
withCompletionHandler completionHandler: @escaping () -> Void
|
|
135
|
+
) {
|
|
136
|
+
let content = response.notification.request.content
|
|
137
|
+
let (idFromPayload, data) = PushAppDelegateHook.extractData(from: content.userInfo)
|
|
138
|
+
let notificationId = response.notification.request.identifier.isEmpty
|
|
139
|
+
? idFromPayload
|
|
140
|
+
: response.notification.request.identifier
|
|
141
|
+
let captured = PushEventBus.shared.captureInitialResponseIfColdStart(
|
|
142
|
+
notificationId: notificationId,
|
|
143
|
+
data: data,
|
|
144
|
+
actionIdentifier: response.actionIdentifier
|
|
145
|
+
)
|
|
146
|
+
if !captured {
|
|
147
|
+
PushEventBus.shared.publishResponse(
|
|
148
|
+
notificationId: notificationId,
|
|
149
|
+
data: data,
|
|
150
|
+
actionIdentifier: response.actionIdentifier
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
completionHandler()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Process-wide pub/sub bus that converts APNs + UNUserNotificationCenter
|
|
4
|
+
/// callbacks into JS-side event payloads. The AppDelegate hook and the
|
|
5
|
+
/// notification-center delegate write here; per-LynxView `PushPublisher`
|
|
6
|
+
/// instances read.
|
|
7
|
+
///
|
|
8
|
+
/// Mirrors the `WebSocketEventBus` pattern. Native lifecycle is decoupled from
|
|
9
|
+
/// any specific LynxView so events that arrive before a LynxView is built
|
|
10
|
+
/// (cold-start payloads, token registrations) can be replayed to subscribers
|
|
11
|
+
/// when they attach.
|
|
12
|
+
final class PushEventBus {
|
|
13
|
+
|
|
14
|
+
static let shared = PushEventBus()
|
|
15
|
+
|
|
16
|
+
/// JSON-encodable payload. Wire shape matches the JS-side types in
|
|
17
|
+
/// `src/push.ts` (channel name carried as `__channel`).
|
|
18
|
+
typealias Payload = [String: Any]
|
|
19
|
+
typealias Listener = (String, Payload) -> Void
|
|
20
|
+
|
|
21
|
+
private let queue = DispatchQueue(label: "com.sigx.notifications.bus")
|
|
22
|
+
private var listeners: [(token: UUID, fn: Listener)] = []
|
|
23
|
+
|
|
24
|
+
/// Cached cold-start tap. Cleared after the JS shim retrieves it via
|
|
25
|
+
/// `Notifications.getInitialNotification()`. Stored regardless of whether
|
|
26
|
+
/// a LynxView has attached yet — the JS shim polls on first call.
|
|
27
|
+
private(set) var initialResponse: Payload?
|
|
28
|
+
|
|
29
|
+
/// Cached most-recent device token so a JS subscriber that attaches after
|
|
30
|
+
/// registration completes still receives it on subscribe. APNs returns
|
|
31
|
+
/// the same token across registrations, so caching is safe.
|
|
32
|
+
private(set) var lastToken: Payload?
|
|
33
|
+
|
|
34
|
+
/// `true` between `application(_:didFinishLaunchingWithOptions:)` and the
|
|
35
|
+
/// first time JS asks for the initial notification. While set, the FIRST
|
|
36
|
+
/// `didReceive` tap is also captured as the cold-start payload — covers
|
|
37
|
+
/// the local-notification cold-start path where there's no
|
|
38
|
+
/// `launchOptions[.remoteNotification]` to read.
|
|
39
|
+
private(set) var inColdStartWindow = false
|
|
40
|
+
|
|
41
|
+
@discardableResult
|
|
42
|
+
func addListener(_ fn: @escaping Listener) -> UUID {
|
|
43
|
+
let token = UUID()
|
|
44
|
+
// Snapshot the cached token in the same critical section as the
|
|
45
|
+
// listener append so we either replay-then-deliver or skip-then-deliver
|
|
46
|
+
// — no torn-state where a concurrent publishToken slips between the
|
|
47
|
+
// two and the listener sees the token twice. The replay itself runs
|
|
48
|
+
// OUTSIDE the queue so a listener that re-enters the bus (e.g.
|
|
49
|
+
// immediately subscribes a second listener, or calls publishToken
|
|
50
|
+
// recursively) doesn't deadlock on the serial queue.
|
|
51
|
+
let cached: Payload? = queue.sync {
|
|
52
|
+
listeners.append((token, fn))
|
|
53
|
+
return lastToken
|
|
54
|
+
}
|
|
55
|
+
if let cached = cached {
|
|
56
|
+
fn(PushEventChannel.token, cached)
|
|
57
|
+
}
|
|
58
|
+
return token
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func removeListener(_ token: UUID) {
|
|
62
|
+
queue.sync { listeners.removeAll { $0.token == token } }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// MARK: - Publish
|
|
66
|
+
|
|
67
|
+
func publishToken(_ token: String, platform: String = "apns") {
|
|
68
|
+
let payload: Payload = ["token": token, "platform": platform]
|
|
69
|
+
queue.sync { lastToken = payload }
|
|
70
|
+
emit(channel: PushEventChannel.token, payload: payload)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func publishTokenError(_ message: String) {
|
|
74
|
+
emit(channel: PushEventChannel.tokenError, payload: ["error": message])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func publishMessage(title: String?, body: String?, data: [String: String], foreground: Bool) {
|
|
78
|
+
var payload: Payload = [
|
|
79
|
+
"data": data,
|
|
80
|
+
"foreground": foreground,
|
|
81
|
+
]
|
|
82
|
+
if let title = title { payload["title"] = title }
|
|
83
|
+
if let body = body { payload["body"] = body }
|
|
84
|
+
emit(channel: PushEventChannel.message, payload: payload)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func publishResponse(notificationId: String, data: [String: String], actionIdentifier: String) {
|
|
88
|
+
let payload: Payload = [
|
|
89
|
+
"notificationId": notificationId,
|
|
90
|
+
"data": data,
|
|
91
|
+
"actionIdentifier": actionIdentifier,
|
|
92
|
+
]
|
|
93
|
+
emit(channel: PushEventChannel.response, payload: payload)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Open the cold-start capture window. Called from
|
|
97
|
+
/// `application(_:didFinishLaunchingWithOptions:)`. The window stays
|
|
98
|
+
/// open until either the first `consumeInitialResponse()` call or a
|
|
99
|
+
/// successful capture — whichever comes first.
|
|
100
|
+
func beginColdStartWindow() {
|
|
101
|
+
queue.sync { inColdStartWindow = true }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Stash a cold-start tap. `application(_:didFinishLaunchingWithOptions:)`
|
|
105
|
+
/// runs before any LynxView exists; the payload is held until JS asks for
|
|
106
|
+
/// it via `getInitialNotification()`.
|
|
107
|
+
func captureInitialResponse(notificationId: String, data: [String: String], actionIdentifier: String) {
|
|
108
|
+
queue.sync {
|
|
109
|
+
initialResponse = [
|
|
110
|
+
"notificationId": notificationId,
|
|
111
|
+
"data": data,
|
|
112
|
+
"actionIdentifier": actionIdentifier,
|
|
113
|
+
]
|
|
114
|
+
inColdStartWindow = false
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Capture a tap as the cold-start payload IF we're still in the
|
|
119
|
+
/// cold-start window AND nothing's been captured yet. Returns `true` if
|
|
120
|
+
/// the call stashed the payload — the caller should NOT also publish the
|
|
121
|
+
/// same tap on the response channel (the JS shim drains it via
|
|
122
|
+
/// `getInitialNotification`).
|
|
123
|
+
func captureInitialResponseIfColdStart(
|
|
124
|
+
notificationId: String,
|
|
125
|
+
data: [String: String],
|
|
126
|
+
actionIdentifier: String,
|
|
127
|
+
) -> Bool {
|
|
128
|
+
return queue.sync {
|
|
129
|
+
guard inColdStartWindow, initialResponse == nil else { return false }
|
|
130
|
+
initialResponse = [
|
|
131
|
+
"notificationId": notificationId,
|
|
132
|
+
"data": data,
|
|
133
|
+
"actionIdentifier": actionIdentifier,
|
|
134
|
+
]
|
|
135
|
+
inColdStartWindow = false
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Drain and return the cold-start payload (one-shot — subsequent calls
|
|
141
|
+
/// return nil). Also closes the cold-start window so any subsequent tap
|
|
142
|
+
/// goes through the regular response channel.
|
|
143
|
+
func consumeInitialResponse() -> Payload? {
|
|
144
|
+
return queue.sync {
|
|
145
|
+
let p = initialResponse
|
|
146
|
+
initialResponse = nil
|
|
147
|
+
inColdStartWindow = false
|
|
148
|
+
return p
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private func emit(channel: String, payload: Payload) {
|
|
153
|
+
let snapshot = queue.sync { listeners }
|
|
154
|
+
for (_, fn) in snapshot { fn(channel, payload) }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Stable event-channel names. These also appear in the JS shim — keep in sync.
|
|
159
|
+
enum PushEventChannel {
|
|
160
|
+
static let token = "__sigxPushToken"
|
|
161
|
+
static let tokenError = "__sigxPushTokenError"
|
|
162
|
+
static let message = "__sigxPushMessage"
|
|
163
|
+
static let response = "__sigxNotificationResponse"
|
|
164
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Lynx
|
|
3
|
+
|
|
4
|
+
/// Per-`LynxView` publisher that pumps `PushEventBus` payloads into JS via
|
|
5
|
+
/// `LynxView.sendGlobalEvent(name, withParams:)`.
|
|
6
|
+
///
|
|
7
|
+
/// One instance per LynxView; instantiated by the generated
|
|
8
|
+
/// `GeneratedLifecyclePublishers.attachAll(to:)` and retained for the
|
|
9
|
+
/// LynxView's lifetime. The bus is global so a remote push or tap that fires
|
|
10
|
+
/// before the LynxView's JS heap is ready will be replayed (for `__sigxPushToken`)
|
|
11
|
+
/// or delivered on the next message via cold-start retrieval.
|
|
12
|
+
final class PushPublisher {
|
|
13
|
+
|
|
14
|
+
private weak var lynxView: LynxView?
|
|
15
|
+
private var token: UUID?
|
|
16
|
+
|
|
17
|
+
init(lynxView: LynxView) {
|
|
18
|
+
self.lynxView = lynxView
|
|
19
|
+
self.token = PushEventBus.shared.addListener { [weak self] channel, payload in
|
|
20
|
+
guard let view = self?.lynxView else { return }
|
|
21
|
+
view.sendGlobalEvent(channel, withParams: [payload])
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
deinit {
|
|
26
|
+
if let token = token {
|
|
27
|
+
PushEventBus.shared.removeListener(token)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigx/lynx-notifications",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Local push notifications for sigx-lynx",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
"signalx-module.json"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@sigx/lynx-core": "^0.4.
|
|
22
|
+
"@sigx/lynx-core": "^0.4.2"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
+
"@typescript/native-preview": "7.0.0-dev.20260521.1",
|
|
25
26
|
"typescript": "^6.0.3",
|
|
26
|
-
"
|
|
27
|
+
"vitest": "^4.1.7"
|
|
27
28
|
},
|
|
28
29
|
"author": "Andreas Ekdahl",
|
|
29
30
|
"license": "MIT",
|
|
@@ -49,7 +50,9 @@
|
|
|
49
50
|
"notifications"
|
|
50
51
|
],
|
|
51
52
|
"scripts": {
|
|
52
|
-
"build": "
|
|
53
|
-
"dev": "
|
|
53
|
+
"build": "node ../../scripts/clean.mjs dist && tsgo",
|
|
54
|
+
"dev": "tsgo --watch",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"clean": "node ../../scripts/clean.mjs dist .turbo"
|
|
54
57
|
}
|
|
55
58
|
}
|
package/signalx-module.json
CHANGED
|
@@ -1,16 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Notifications",
|
|
3
3
|
"package": "@sigx/lynx-notifications",
|
|
4
|
-
"description": "Local push notifications",
|
|
4
|
+
"description": "Local + remote push notifications (APNs / FCM)",
|
|
5
5
|
"platforms": ["android", "ios"],
|
|
6
6
|
"ios": {
|
|
7
7
|
"moduleClass": "NotificationsModule",
|
|
8
|
+
"publisherClass": "PushPublisher",
|
|
9
|
+
"appDelegateHook": {
|
|
10
|
+
"class": "PushAppDelegateHook",
|
|
11
|
+
"methods": [
|
|
12
|
+
"didFinishLaunching",
|
|
13
|
+
"didRegisterForRemoteNotificationsWithDeviceToken",
|
|
14
|
+
"didFailToRegisterForRemoteNotificationsWithError"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
8
17
|
"sourceDir": "ios",
|
|
9
|
-
"methods": [
|
|
18
|
+
"methods": [
|
|
19
|
+
"schedule",
|
|
20
|
+
"cancel",
|
|
21
|
+
"cancelAll",
|
|
22
|
+
"requestPermission",
|
|
23
|
+
"getPermissionStatus",
|
|
24
|
+
"registerForPushNotifications",
|
|
25
|
+
"unregisterForPushNotifications",
|
|
26
|
+
"setBadgeCount",
|
|
27
|
+
"getBadgeCount",
|
|
28
|
+
"getInitialNotification"
|
|
29
|
+
],
|
|
30
|
+
"backgroundModes": ["remote-notification"]
|
|
10
31
|
},
|
|
11
32
|
"android": {
|
|
12
33
|
"moduleClass": "com.sigx.notifications.NotificationsModule",
|
|
34
|
+
"publisherClass": "com.sigx.notifications.PushPublisher",
|
|
35
|
+
"activityHook": {
|
|
36
|
+
"class": "com.sigx.notifications.PushActivityHook",
|
|
37
|
+
"methods": ["onCreate", "onNewIntent"]
|
|
38
|
+
},
|
|
13
39
|
"sourceDir": "android",
|
|
14
|
-
"permissions": ["android.permission.POST_NOTIFICATIONS"]
|
|
40
|
+
"permissions": ["android.permission.POST_NOTIFICATIONS"],
|
|
41
|
+
"dependencies": [
|
|
42
|
+
"com.google.firebase:firebase-messaging:24.0.0",
|
|
43
|
+
"com.google.firebase:firebase-common-ktx:21.0.0",
|
|
44
|
+
"com.google.android.gms:play-services-base:18.5.0"
|
|
45
|
+
],
|
|
46
|
+
"services": [
|
|
47
|
+
{
|
|
48
|
+
"name": "com.sigx.notifications.SigxFirebaseMessagingService",
|
|
49
|
+
"exported": false,
|
|
50
|
+
"actions": ["com.google.firebase.MESSAGING_EVENT"]
|
|
51
|
+
}
|
|
52
|
+
]
|
|
15
53
|
}
|
|
16
54
|
}
|