@sigx/lynx-notifications 0.4.1 → 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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- 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 +80 -4
- package/dist/notifications.js.map +1 -1
- 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 +6 -4
- package/signalx-module.json +41 -3
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package com.sigx.notifications
|
|
2
|
+
|
|
3
|
+
import android.app.NotificationChannel
|
|
4
|
+
import android.app.NotificationManager
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import androidx.core.app.NotificationCompat
|
|
8
|
+
import com.google.firebase.messaging.FirebaseMessagingService
|
|
9
|
+
import com.google.firebase.messaging.RemoteMessage
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* FCM entry point. Registered in AndroidManifest via the auto-linker
|
|
13
|
+
* (`android.services` in `signalx-module.json`).
|
|
14
|
+
*
|
|
15
|
+
* Handles two callbacks:
|
|
16
|
+
* - [onNewToken]: token rotated. Forwarded to JS via [PushEventBus] so the
|
|
17
|
+
* app can re-register with its backend (Azure Notification Hubs, etc.).
|
|
18
|
+
* - [onMessageReceived]: incoming push. Forwarded to JS regardless of app
|
|
19
|
+
* state. When the app is BACKGROUNDED and the payload carries a
|
|
20
|
+
* title/body, we additionally pop a system notification so the user has
|
|
21
|
+
* a visible entry point back into the app — without this, FCM data-only
|
|
22
|
+
* messages received while backgrounded silently land in JS but never
|
|
23
|
+
* surface in the system tray. When the app is foreground, we skip the
|
|
24
|
+
* system notif: the JS heap is alive and apps are expected to render
|
|
25
|
+
* their own in-app UI from `addPushListener`.
|
|
26
|
+
*/
|
|
27
|
+
class SigxFirebaseMessagingService : FirebaseMessagingService() {
|
|
28
|
+
|
|
29
|
+
override fun onNewToken(token: String) {
|
|
30
|
+
super.onNewToken(token)
|
|
31
|
+
PushEventBus.publishToken(token, platform = "fcm")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun onMessageReceived(message: RemoteMessage) {
|
|
35
|
+
super.onMessageReceived(message)
|
|
36
|
+
val notification = message.notification
|
|
37
|
+
val title = notification?.title
|
|
38
|
+
val body = notification?.body
|
|
39
|
+
val data = message.data
|
|
40
|
+
// foreground=true here is "the JS heap is alive". FCM service runs
|
|
41
|
+
// regardless of activity foreground state, but the JS shim and apps
|
|
42
|
+
// typically treat `foreground` as "received while app was in use" —
|
|
43
|
+
// we approximate by checking the process importance.
|
|
44
|
+
val foreground = isAppInForeground()
|
|
45
|
+
PushEventBus.publishMessage(
|
|
46
|
+
title = title,
|
|
47
|
+
body = body,
|
|
48
|
+
data = data,
|
|
49
|
+
foreground = foreground,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if (!foreground && title != null) {
|
|
53
|
+
showSystemNotification(title, body ?: "", data)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private fun isAppInForeground(): Boolean {
|
|
58
|
+
val appProcessInfo = android.app.ActivityManager.RunningAppProcessInfo()
|
|
59
|
+
android.app.ActivityManager.getMyMemoryState(appProcessInfo)
|
|
60
|
+
return appProcessInfo.importance == android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND ||
|
|
61
|
+
appProcessInfo.importance == android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private fun showSystemNotification(title: String, body: String, data: Map<String, String>) {
|
|
65
|
+
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
66
|
+
// The FCM service can fire BEFORE NotificationsModule is ever
|
|
67
|
+
// instantiated (the JS bridge may not have loaded yet — particularly
|
|
68
|
+
// for data-only messages that wake the app from a fully terminated
|
|
69
|
+
// state). Ensure the notification channel exists here too so
|
|
70
|
+
// `notify()` on O+ doesn't silently drop the post.
|
|
71
|
+
ensureChannel(manager)
|
|
72
|
+
// Prefer the app's launcher icon over the stock dialog icon — Android
|
|
73
|
+
// renders small-icons as monochrome silhouettes, so a generic system
|
|
74
|
+
// shape would show up as a plain square. `applicationInfo.icon` is
|
|
75
|
+
// the manifest <application android:icon>, which apps always set.
|
|
76
|
+
val smallIcon = applicationInfo.icon.takeIf { it != 0 } ?: android.R.drawable.ic_dialog_info
|
|
77
|
+
val builder = NotificationCompat.Builder(this, NotificationsModule.CHANNEL_ID)
|
|
78
|
+
.setContentTitle(title)
|
|
79
|
+
.setContentText(body)
|
|
80
|
+
.setSmallIcon(smallIcon)
|
|
81
|
+
.setAutoCancel(true)
|
|
82
|
+
|
|
83
|
+
// Stash data on the launch intent so [PushActivityHook.onNewIntent]
|
|
84
|
+
// can route the tap to [PushEventBus.publishResponse] / capture as
|
|
85
|
+
// the cold-start payload.
|
|
86
|
+
val pm = packageManager
|
|
87
|
+
val launchIntent = pm.getLaunchIntentForPackage(packageName)?.apply {
|
|
88
|
+
putExtra(PushActivityHook.EXTRA_NOTIFICATION_TAP, true)
|
|
89
|
+
for ((k, v) in data) putExtra("sigx_push_data_$k", v)
|
|
90
|
+
}
|
|
91
|
+
val notificationId = (data["notification_id"] ?: data["notificationId"] ?: System.currentTimeMillis().toString())
|
|
92
|
+
if (launchIntent != null) {
|
|
93
|
+
val pending = android.app.PendingIntent.getActivity(
|
|
94
|
+
this,
|
|
95
|
+
notificationId.hashCode(),
|
|
96
|
+
launchIntent,
|
|
97
|
+
android.app.PendingIntent.FLAG_UPDATE_CURRENT or android.app.PendingIntent.FLAG_IMMUTABLE,
|
|
98
|
+
)
|
|
99
|
+
builder.setContentIntent(pending)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
manager.notify(notificationId.hashCode(), builder.build())
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private fun ensureChannel(manager: NotificationManager) {
|
|
106
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
|
107
|
+
if (manager.getNotificationChannel(NotificationsModule.CHANNEL_ID) != null) return
|
|
108
|
+
val channel = NotificationChannel(
|
|
109
|
+
NotificationsModule.CHANNEL_ID,
|
|
110
|
+
"sigx-lynx-go",
|
|
111
|
+
NotificationManager.IMPORTANCE_DEFAULT,
|
|
112
|
+
)
|
|
113
|
+
manager.createNotificationChannel(channel)
|
|
114
|
+
}
|
|
115
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Notifications } from './notifications.js';
|
|
2
|
-
export type { NotificationContent, ScheduleOptions } from './notifications.js';
|
|
2
|
+
export type { NotificationContent, ScheduleOptions, RegisterPushResult, UnregisterPushResult, NotificationResponse, PushTokenEvent, PushTokenError, RemoteMessage, } from './notifications.js';
|
|
3
|
+
export { addTokenListener, addTokenErrorListener, addPushListener, addNotificationResponseListener, } from './push.js';
|
|
3
4
|
export type { PermissionResponse } from '@sigx/lynx-core';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EACR,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,aAAa,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACH,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,+BAA+B,GAClC,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAWnD,OAAO,EACH,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,+BAA+B,GAClC,MAAM,WAAW,CAAC"}
|
package/dist/notifications.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { PermissionResponse } from '@sigx/lynx-core';
|
|
2
|
+
import { addTokenListener, addTokenErrorListener, addPushListener, addNotificationResponseListener, type NotificationResponse, type PushTokenEvent, type PushTokenError, type RemoteMessage } from './push.js';
|
|
2
3
|
export interface NotificationContent {
|
|
3
4
|
title: string;
|
|
4
5
|
body: string;
|
|
5
|
-
/** Optional data payload */
|
|
6
|
+
/** Optional data payload. Round-tripped to JS on tap responses. */
|
|
6
7
|
data?: Record<string, string>;
|
|
7
8
|
}
|
|
8
9
|
export interface ScheduleOptions {
|
|
@@ -11,27 +12,100 @@ export interface ScheduleOptions {
|
|
|
11
12
|
/** Repeat interval: 'minute', 'hour', 'day', 'week' */
|
|
12
13
|
repeat?: 'minute' | 'hour' | 'day' | 'week';
|
|
13
14
|
}
|
|
15
|
+
export interface RegisterPushResult {
|
|
16
|
+
token?: string;
|
|
17
|
+
platform?: 'apns' | 'fcm';
|
|
18
|
+
/** iOS resolves with `{ dispatched: true }` — the real token arrives via `addTokenListener`. */
|
|
19
|
+
dispatched?: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface UnregisterPushResult {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
14
26
|
/**
|
|
15
|
-
* Local notification APIs.
|
|
27
|
+
* Local + remote notification APIs.
|
|
16
28
|
*
|
|
17
29
|
* @example
|
|
18
30
|
* ```ts
|
|
19
31
|
* import { Notifications } from '@sigx/lynx-notifications';
|
|
20
32
|
*
|
|
21
33
|
* const { status } = await Notifications.requestPermission();
|
|
22
|
-
* if (status
|
|
23
|
-
*
|
|
24
|
-
*
|
|
34
|
+
* if (status !== 'granted') return;
|
|
35
|
+
*
|
|
36
|
+
* // Remote push registration. On iOS the real token arrives via addTokenListener;
|
|
37
|
+
* // on Android the token is in the promise result.
|
|
38
|
+
* Notifications.addTokenListener(({ token, platform }) => {
|
|
39
|
+
* // POST token + platform to your backend (Azure Notification Hubs / Firebase Admin / …)
|
|
40
|
+
* });
|
|
41
|
+
* Notifications.addPushListener((msg) => {
|
|
42
|
+
* console.log('push received', msg);
|
|
43
|
+
* });
|
|
44
|
+
* Notifications.addNotificationResponseListener((resp) => {
|
|
45
|
+
* // user tapped the notification — route them somewhere
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* await Notifications.registerForPushNotifications();
|
|
49
|
+
*
|
|
50
|
+
* // Cold-start tap (if any)
|
|
51
|
+
* const initial = await Notifications.getInitialNotification();
|
|
25
52
|
* ```
|
|
26
53
|
*/
|
|
27
54
|
export declare const Notifications: {
|
|
28
|
-
schedule(content: NotificationContent, options?: ScheduleOptions)
|
|
29
|
-
cancel(notificationId: string)
|
|
30
|
-
cancelAll()
|
|
55
|
+
readonly schedule: (content: NotificationContent, options?: ScheduleOptions) => Promise<string>;
|
|
56
|
+
readonly cancel: (notificationId: string) => Promise<void>;
|
|
57
|
+
readonly cancelAll: () => Promise<void>;
|
|
31
58
|
/** Request notification permission, showing the OS dialog if needed. */
|
|
32
|
-
requestPermission()
|
|
59
|
+
readonly requestPermission: () => Promise<PermissionResponse>;
|
|
33
60
|
/** Check current notification permission status without prompting. */
|
|
34
|
-
getPermissionStatus()
|
|
35
|
-
|
|
61
|
+
readonly getPermissionStatus: () => Promise<PermissionResponse>;
|
|
62
|
+
/**
|
|
63
|
+
* Trigger remote-push registration.
|
|
64
|
+
*
|
|
65
|
+
* iOS: dispatches `application.registerForRemoteNotifications()`. The
|
|
66
|
+
* token (or error) arrives asynchronously via `addTokenListener` /
|
|
67
|
+
* `addTokenErrorListener`. The promise resolves with `{ dispatched: true }`
|
|
68
|
+
* once the call has been made.
|
|
69
|
+
*
|
|
70
|
+
* Android: resolves directly with the FCM token. Also publishes the token
|
|
71
|
+
* via the listener channel so JS code that wires both paths sees one
|
|
72
|
+
* canonical event.
|
|
73
|
+
*/
|
|
74
|
+
readonly registerForPushNotifications: () => Promise<RegisterPushResult>;
|
|
75
|
+
/**
|
|
76
|
+
* Detach from APNs / FCM.
|
|
77
|
+
*
|
|
78
|
+
* iOS: synchronous — resolves with `{ ok: true }` after
|
|
79
|
+
* `unregisterForRemoteNotifications` is called.
|
|
80
|
+
* Android: awaits the FCM `deleteToken()` Task; resolves with
|
|
81
|
+
* `{ ok: false, error }` if the network call fails so the JS caller
|
|
82
|
+
* doesn't believe it's unregistered while the server keeps pushing.
|
|
83
|
+
* Failures also publish on the `addTokenErrorListener` channel.
|
|
84
|
+
*/
|
|
85
|
+
readonly unregisterForPushNotifications: () => Promise<UnregisterPushResult>;
|
|
86
|
+
/**
|
|
87
|
+
* iOS: app-icon badge count. iOS 16+ uses
|
|
88
|
+
* `UNUserNotificationCenter.setBadgeCount`; older falls back to
|
|
89
|
+
* `applicationIconBadgeNumber`.
|
|
90
|
+
*
|
|
91
|
+
* Android: no-op. Stock Android has no portable badging API (it's
|
|
92
|
+
* vendor-specific — Samsung's `ShortcutBadger`, etc.) and this call
|
|
93
|
+
* does NOT clear pending notifications — callers wanting that should
|
|
94
|
+
* use `cancelAll()` directly.
|
|
95
|
+
*/
|
|
96
|
+
readonly setBadgeCount: (count: number) => Promise<void>;
|
|
97
|
+
/** iOS: current badge number. Android: always 0 (no portable read API). */
|
|
98
|
+
readonly getBadgeCount: () => Promise<number>;
|
|
99
|
+
/**
|
|
100
|
+
* If the app was launched by a notification tap, returns the payload.
|
|
101
|
+
* One-shot: subsequent calls return null. Call exactly once during startup.
|
|
102
|
+
*/
|
|
103
|
+
readonly getInitialNotification: () => Promise<NotificationResponse | null>;
|
|
104
|
+
readonly addTokenListener: typeof addTokenListener;
|
|
105
|
+
readonly addTokenErrorListener: typeof addTokenErrorListener;
|
|
106
|
+
readonly addPushListener: typeof addPushListener;
|
|
107
|
+
readonly addNotificationResponseListener: typeof addNotificationResponseListener;
|
|
108
|
+
readonly isAvailable: () => boolean;
|
|
36
109
|
};
|
|
110
|
+
export type { NotificationResponse, PushTokenEvent, PushTokenError, RemoteMessage };
|
|
37
111
|
//# sourceMappingURL=notifications.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EACH,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,+BAA+B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,EACrB,MAAM,WAAW,CAAC;AAInB,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC5B,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;CAC/C;AAED,MAAM,WAAW,kBAAkB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC1B,gGAAgG;IAChG,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,aAAa;iCACJ,mBAAmB,YAAW,eAAe,KAAQ,OAAO,CAAC,MAAM,CAAC;sCAI/D,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;8BAIhC,OAAO,CAAC,IAAI,CAAC;IAI1B,wEAAwE;sCACnD,OAAO,CAAC,kBAAkB,CAAC;IAIhD,sEAAsE;wCAC/C,OAAO,CAAC,kBAAkB,CAAC;IAIlD;;;;;;;;;;;OAWG;iDAC6B,OAAO,CAAC,kBAAkB,CAAC;IAI3D;;;;;;;;;OASG;mDAC+B,OAAO,CAAC,oBAAoB,CAAC;IAI/D;;;;;;;;;OASG;oCACkB,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,2EAA2E;kCAC1D,OAAO,CAAC,MAAM,CAAC;IAIhC;;;OAGG;2CACuB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;;;;;gCAa/C,OAAO;CAGhB,CAAC;AAEX,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC"}
|
package/dist/notifications.js
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
import { callAsync, isModuleAvailable } from '@sigx/lynx-core';
|
|
2
|
+
import { addTokenListener, addTokenErrorListener, addPushListener, addNotificationResponseListener, } from './push.js';
|
|
2
3
|
const MODULE = 'Notifications';
|
|
3
4
|
/**
|
|
4
|
-
* Local notification APIs.
|
|
5
|
+
* Local + remote notification APIs.
|
|
5
6
|
*
|
|
6
7
|
* @example
|
|
7
8
|
* ```ts
|
|
8
9
|
* import { Notifications } from '@sigx/lynx-notifications';
|
|
9
10
|
*
|
|
10
11
|
* const { status } = await Notifications.requestPermission();
|
|
11
|
-
* if (status
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* if (status !== 'granted') return;
|
|
13
|
+
*
|
|
14
|
+
* // Remote push registration. On iOS the real token arrives via addTokenListener;
|
|
15
|
+
* // on Android the token is in the promise result.
|
|
16
|
+
* Notifications.addTokenListener(({ token, platform }) => {
|
|
17
|
+
* // POST token + platform to your backend (Azure Notification Hubs / Firebase Admin / …)
|
|
18
|
+
* });
|
|
19
|
+
* Notifications.addPushListener((msg) => {
|
|
20
|
+
* console.log('push received', msg);
|
|
21
|
+
* });
|
|
22
|
+
* Notifications.addNotificationResponseListener((resp) => {
|
|
23
|
+
* // user tapped the notification — route them somewhere
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* await Notifications.registerForPushNotifications();
|
|
27
|
+
*
|
|
28
|
+
* // Cold-start tap (if any)
|
|
29
|
+
* const initial = await Notifications.getInitialNotification();
|
|
14
30
|
* ```
|
|
15
31
|
*/
|
|
16
32
|
export const Notifications = {
|
|
@@ -31,8 +47,68 @@ export const Notifications = {
|
|
|
31
47
|
getPermissionStatus() {
|
|
32
48
|
return callAsync(MODULE, 'getPermissionStatus');
|
|
33
49
|
},
|
|
50
|
+
/**
|
|
51
|
+
* Trigger remote-push registration.
|
|
52
|
+
*
|
|
53
|
+
* iOS: dispatches `application.registerForRemoteNotifications()`. The
|
|
54
|
+
* token (or error) arrives asynchronously via `addTokenListener` /
|
|
55
|
+
* `addTokenErrorListener`. The promise resolves with `{ dispatched: true }`
|
|
56
|
+
* once the call has been made.
|
|
57
|
+
*
|
|
58
|
+
* Android: resolves directly with the FCM token. Also publishes the token
|
|
59
|
+
* via the listener channel so JS code that wires both paths sees one
|
|
60
|
+
* canonical event.
|
|
61
|
+
*/
|
|
62
|
+
registerForPushNotifications() {
|
|
63
|
+
return callAsync(MODULE, 'registerForPushNotifications');
|
|
64
|
+
},
|
|
65
|
+
/**
|
|
66
|
+
* Detach from APNs / FCM.
|
|
67
|
+
*
|
|
68
|
+
* iOS: synchronous — resolves with `{ ok: true }` after
|
|
69
|
+
* `unregisterForRemoteNotifications` is called.
|
|
70
|
+
* Android: awaits the FCM `deleteToken()` Task; resolves with
|
|
71
|
+
* `{ ok: false, error }` if the network call fails so the JS caller
|
|
72
|
+
* doesn't believe it's unregistered while the server keeps pushing.
|
|
73
|
+
* Failures also publish on the `addTokenErrorListener` channel.
|
|
74
|
+
*/
|
|
75
|
+
unregisterForPushNotifications() {
|
|
76
|
+
return callAsync(MODULE, 'unregisterForPushNotifications');
|
|
77
|
+
},
|
|
78
|
+
/**
|
|
79
|
+
* iOS: app-icon badge count. iOS 16+ uses
|
|
80
|
+
* `UNUserNotificationCenter.setBadgeCount`; older falls back to
|
|
81
|
+
* `applicationIconBadgeNumber`.
|
|
82
|
+
*
|
|
83
|
+
* Android: no-op. Stock Android has no portable badging API (it's
|
|
84
|
+
* vendor-specific — Samsung's `ShortcutBadger`, etc.) and this call
|
|
85
|
+
* does NOT clear pending notifications — callers wanting that should
|
|
86
|
+
* use `cancelAll()` directly.
|
|
87
|
+
*/
|
|
88
|
+
setBadgeCount(count) {
|
|
89
|
+
return callAsync(MODULE, 'setBadgeCount', count);
|
|
90
|
+
},
|
|
91
|
+
/** iOS: current badge number. Android: always 0 (no portable read API). */
|
|
92
|
+
getBadgeCount() {
|
|
93
|
+
return callAsync(MODULE, 'getBadgeCount');
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* If the app was launched by a notification tap, returns the payload.
|
|
97
|
+
* One-shot: subsequent calls return null. Call exactly once during startup.
|
|
98
|
+
*/
|
|
99
|
+
getInitialNotification() {
|
|
100
|
+
return callAsync(MODULE, 'getInitialNotification');
|
|
101
|
+
},
|
|
102
|
+
// ── Event subscriptions ─────────────────────────────────────────────────
|
|
103
|
+
// Re-exported so consumers can chain `Notifications.addTokenListener(...)`
|
|
104
|
+
// without a second import. Each returns an unsubscribe function.
|
|
105
|
+
addTokenListener,
|
|
106
|
+
addTokenErrorListener,
|
|
107
|
+
addPushListener,
|
|
108
|
+
addNotificationResponseListener,
|
|
34
109
|
isAvailable() {
|
|
35
110
|
return isModuleAvailable(MODULE);
|
|
36
111
|
},
|
|
37
112
|
};
|
|
113
|
+
// `UnregisterPushResult` is declared above; re-export via index.ts.
|
|
38
114
|
//# sourceMappingURL=notifications.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,EACH,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,+BAA+B,GAKlC,MAAM,WAAW,CAAC;AAEnB,MAAM,MAAM,GAAG,eAAe,CAAC;AA6B/B;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,QAAQ,CAAC,OAA4B,EAAE,OAAO,GAAoB,EAAE;QAChE,OAAO,SAAS,CAAS,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,CAAC,cAAsB;QACzB,OAAO,SAAS,CAAO,MAAM,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC7D,CAAC;IAED,SAAS;QACL,OAAO,SAAS,CAAO,MAAM,EAAE,WAAW,CAAC,CAAC;IAChD,CAAC;IAED,wEAAwE;IACxE,iBAAiB;QACb,OAAO,SAAS,CAAqB,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACtE,CAAC;IAED,sEAAsE;IACtE,mBAAmB;QACf,OAAO,SAAS,CAAqB,MAAM,EAAE,qBAAqB,CAAC,CAAC;IACxE,CAAC;IAED;;;;;;;;;;;OAWG;IACH,4BAA4B;QACxB,OAAO,SAAS,CAAqB,MAAM,EAAE,8BAA8B,CAAC,CAAC;IACjF,CAAC;IAED;;;;;;;;;OASG;IACH,8BAA8B;QAC1B,OAAO,SAAS,CAAuB,MAAM,EAAE,gCAAgC,CAAC,CAAC;IACrF,CAAC;IAED;;;;;;;;;OASG;IACH,aAAa,CAAC,KAAa;QACvB,OAAO,SAAS,CAAO,MAAM,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;IAC3D,CAAC;IAED,2EAA2E;IAC3E,aAAa;QACT,OAAO,SAAS,CAAS,MAAM,EAAE,eAAe,CAAC,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,sBAAsB;QAClB,OAAO,SAAS,CAA8B,MAAM,EAAE,wBAAwB,CAAC,CAAC;IACpF,CAAC;IAED,2EAA2E;IAC3E,2EAA2E;IAC3E,iEAAiE;IAEjE,gBAAgB;IAChB,qBAAqB;IACrB,eAAe;IACf,+BAA+B;IAE/B,WAAW;QACP,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;CACK,CAAC;AAGX,oEAAoE"}
|
package/dist/push.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote-push event subscriptions. Backed by `GlobalEventEmitter` on four
|
|
3
|
+
* native channels: `__sigxPushToken`, `__sigxPushTokenError`,
|
|
4
|
+
* `__sigxPushMessage`, `__sigxNotificationResponse`. The native side carries
|
|
5
|
+
* the same channel names — JS shims here just adapt the listener-bag API.
|
|
6
|
+
*/
|
|
7
|
+
export interface PushTokenEvent {
|
|
8
|
+
token: string;
|
|
9
|
+
platform: 'apns' | 'fcm';
|
|
10
|
+
}
|
|
11
|
+
export interface PushTokenError {
|
|
12
|
+
error: string;
|
|
13
|
+
}
|
|
14
|
+
export interface RemoteMessage {
|
|
15
|
+
title?: string;
|
|
16
|
+
body?: string;
|
|
17
|
+
data: Record<string, string>;
|
|
18
|
+
foreground: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface NotificationResponse {
|
|
21
|
+
notificationId: string;
|
|
22
|
+
data: Record<string, string>;
|
|
23
|
+
/** 'default' for the standard tap; custom action ids when categories ship. */
|
|
24
|
+
actionIdentifier: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function addTokenListener(cb: (event: PushTokenEvent) => void): () => void;
|
|
27
|
+
export declare function addTokenErrorListener(cb: (event: PushTokenError) => void): () => void;
|
|
28
|
+
export declare function addPushListener(cb: (event: RemoteMessage) => void): () => void;
|
|
29
|
+
export declare function addNotificationResponseListener(cb: (event: NotificationResponse) => void): () => void;
|
|
30
|
+
//# sourceMappingURL=push.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,MAAM,WAAW,cAAc;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC3B,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AA+CD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI,CAEhF;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI,CAErF;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI,CAE9E;AAED,wBAAgB,+BAA+B,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAErG"}
|
package/dist/push.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote-push event subscriptions. Backed by `GlobalEventEmitter` on four
|
|
3
|
+
* native channels: `__sigxPushToken`, `__sigxPushTokenError`,
|
|
4
|
+
* `__sigxPushMessage`, `__sigxNotificationResponse`. The native side carries
|
|
5
|
+
* the same channel names — JS shims here just adapt the listener-bag API.
|
|
6
|
+
*/
|
|
7
|
+
const TOKEN_CHANNEL = '__sigxPushToken';
|
|
8
|
+
const TOKEN_ERROR_CHANNEL = '__sigxPushTokenError';
|
|
9
|
+
const MESSAGE_CHANNEL = '__sigxPushMessage';
|
|
10
|
+
const RESPONSE_CHANNEL = '__sigxNotificationResponse';
|
|
11
|
+
function emitter() {
|
|
12
|
+
if (typeof lynx === 'undefined')
|
|
13
|
+
return undefined;
|
|
14
|
+
const obj = lynx;
|
|
15
|
+
return obj.getJSModule?.('GlobalEventEmitter');
|
|
16
|
+
}
|
|
17
|
+
function subscribe(channel, cb) {
|
|
18
|
+
const e = emitter();
|
|
19
|
+
if (!e) {
|
|
20
|
+
// Web / SSR / test fallback: no native bridge, just return a no-op
|
|
21
|
+
// unsubscribe. The shim is a one-way data path — there's nothing
|
|
22
|
+
// useful to emulate.
|
|
23
|
+
return () => { };
|
|
24
|
+
}
|
|
25
|
+
const wrapped = (raw) => {
|
|
26
|
+
const event = (typeof raw === 'string' ? safeParse(raw) : raw);
|
|
27
|
+
if (event === undefined)
|
|
28
|
+
return;
|
|
29
|
+
try {
|
|
30
|
+
cb(event);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
console.warn(`[notifications] listener for ${channel} threw:`, err);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
e.addListener(channel, wrapped);
|
|
37
|
+
return () => e.removeListener(channel, wrapped);
|
|
38
|
+
}
|
|
39
|
+
function safeParse(s) {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(s);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function addTokenListener(cb) {
|
|
48
|
+
return subscribe(TOKEN_CHANNEL, cb);
|
|
49
|
+
}
|
|
50
|
+
export function addTokenErrorListener(cb) {
|
|
51
|
+
return subscribe(TOKEN_ERROR_CHANNEL, cb);
|
|
52
|
+
}
|
|
53
|
+
export function addPushListener(cb) {
|
|
54
|
+
return subscribe(MESSAGE_CHANNEL, cb);
|
|
55
|
+
}
|
|
56
|
+
export function addNotificationResponseListener(cb) {
|
|
57
|
+
return subscribe(RESPONSE_CHANNEL, cb);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=push.js.map
|
package/dist/push.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,aAAa,GAAG,iBAAiB,CAAC;AACxC,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AACnD,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAC5C,MAAM,gBAAgB,GAAG,4BAA4B,CAAC;AAoCtD,SAAS,OAAO;IACZ,IAAI,OAAO,IAAI,KAAK,WAAW;QAAE,OAAO,SAAS,CAAC;IAClD,MAAM,GAAG,GAAG,IAA2B,CAAC;IACxC,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC,oBAAoB,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,SAAS,CACd,OAAe,EACf,EAAsB;IAEtB,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IACpB,IAAI,CAAC,CAAC,EAAE,CAAC;QACL,mEAAmE;QACnE,iEAAiE;QACjE,qBAAqB;QACrB,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IACpB,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,GAAY,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAkB,CAAC;QAChF,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAChC,IAAI,CAAC;YACD,EAAE,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,gCAAgC,OAAO,SAAS,EAAE,GAAG,CAAC,CAAC;QACxE,CAAC;IACL,CAAC,CAAC;IACF,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,SAAS,CAAC,CAAS;IACxB,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,SAAS,CAAC;IAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAmC;IAChE,OAAO,SAAS,CAAiB,aAAa,EAAE,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,EAAmC;IACrE,OAAO,SAAS,CAAiB,mBAAmB,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,EAAkC;IAC9D,OAAO,SAAS,CAAgB,eAAe,EAAE,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,EAAyC;IACrF,OAAO,SAAS,CAAuB,gBAAgB,EAAE,EAAE,CAAC,CAAC;AACjE,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
import UIKit
|
|
2
3
|
import UserNotifications
|
|
3
4
|
import Lynx
|
|
4
5
|
|
|
5
|
-
/// Local notifications module.
|
|
6
|
-
/// JS usage: NativeModules.Notifications
|
|
6
|
+
/// Local + remote notifications module.
|
|
7
|
+
/// JS usage: NativeModules.Notifications.<method>(...)
|
|
7
8
|
class NotificationsModule: NSObject, LynxModule {
|
|
8
9
|
|
|
9
10
|
@objc static var name: String { "Notifications" }
|
|
@@ -15,6 +16,11 @@ class NotificationsModule: NSObject, LynxModule {
|
|
|
15
16
|
"cancelAll": NSStringFromSelector(#selector(cancelAll(_:))),
|
|
16
17
|
"requestPermission": NSStringFromSelector(#selector(requestPermission(_:))),
|
|
17
18
|
"getPermissionStatus": NSStringFromSelector(#selector(getPermissionStatus(_:))),
|
|
19
|
+
"registerForPushNotifications": NSStringFromSelector(#selector(registerForPushNotifications(_:))),
|
|
20
|
+
"unregisterForPushNotifications": NSStringFromSelector(#selector(unregisterForPushNotifications(_:))),
|
|
21
|
+
"setBadgeCount": NSStringFromSelector(#selector(setBadgeCount(_:callback:))),
|
|
22
|
+
"getBadgeCount": NSStringFromSelector(#selector(getBadgeCount(_:))),
|
|
23
|
+
"getInitialNotification": NSStringFromSelector(#selector(getInitialNotification(_:))),
|
|
18
24
|
]
|
|
19
25
|
}
|
|
20
26
|
|
|
@@ -25,6 +31,7 @@ class NotificationsModule: NSObject, LynxModule {
|
|
|
25
31
|
let title = content?["title"] as? String ?? "Notification"
|
|
26
32
|
let body = content?["body"] as? String ?? ""
|
|
27
33
|
let delay = options?["delay"] as? TimeInterval ?? 0
|
|
34
|
+
let data = content?["data"] as? [String: String] ?? [:]
|
|
28
35
|
|
|
29
36
|
let notificationId = UUID().uuidString
|
|
30
37
|
|
|
@@ -32,6 +39,9 @@ class NotificationsModule: NSObject, LynxModule {
|
|
|
32
39
|
notificationContent.title = title
|
|
33
40
|
notificationContent.body = body
|
|
34
41
|
notificationContent.sound = .default
|
|
42
|
+
// Round-trip the data dict so a local tap surfaces it on
|
|
43
|
+
// didReceiveResponse → publishResponse — same wire shape as remote.
|
|
44
|
+
notificationContent.userInfo = data
|
|
35
45
|
|
|
36
46
|
let trigger: UNNotificationTrigger
|
|
37
47
|
if delay > 0 {
|
|
@@ -91,4 +101,60 @@ class NotificationsModule: NSObject, LynxModule {
|
|
|
91
101
|
callback?(["status": status])
|
|
92
102
|
}
|
|
93
103
|
}
|
|
104
|
+
|
|
105
|
+
// MARK: - Remote push
|
|
106
|
+
|
|
107
|
+
/// Ask iOS to contact APNs. The token (or registration failure) arrives
|
|
108
|
+
/// asynchronously via the AppDelegate hook → `PushEventBus` → JS event.
|
|
109
|
+
/// The callback only confirms that `registerForRemoteNotifications` was
|
|
110
|
+
/// dispatched — it does NOT block on token receipt.
|
|
111
|
+
@objc func registerForPushNotifications(_ callback: LynxCallbackBlock?) {
|
|
112
|
+
DispatchQueue.main.async {
|
|
113
|
+
UIApplication.shared.registerForRemoteNotifications()
|
|
114
|
+
callback?(["dispatched": true])
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@objc func unregisterForPushNotifications(_ callback: LynxCallbackBlock?) {
|
|
119
|
+
DispatchQueue.main.async {
|
|
120
|
+
UIApplication.shared.unregisterForRemoteNotifications()
|
|
121
|
+
callback?(["ok": true])
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@objc func setBadgeCount(_ count: NSNumber?, callback: LynxCallbackBlock?) {
|
|
126
|
+
let value = count?.intValue ?? 0
|
|
127
|
+
if #available(iOS 16.0, *) {
|
|
128
|
+
UNUserNotificationCenter.current().setBadgeCount(value) { error in
|
|
129
|
+
if let error = error {
|
|
130
|
+
callback?(["error": error.localizedDescription])
|
|
131
|
+
} else {
|
|
132
|
+
callback?(true)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
DispatchQueue.main.async {
|
|
137
|
+
UIApplication.shared.applicationIconBadgeNumber = value
|
|
138
|
+
callback?(true)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@objc func getBadgeCount(_ callback: LynxCallbackBlock?) {
|
|
144
|
+
DispatchQueue.main.async {
|
|
145
|
+
callback?(NSNumber(value: UIApplication.shared.applicationIconBadgeNumber))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// One-shot: return the payload that launched the app from a cold start,
|
|
150
|
+
/// or nil. Subsequent calls return nil even if the original payload was
|
|
151
|
+
/// non-nil — JS code that wants to react to launch payloads should call
|
|
152
|
+
/// this exactly once during startup.
|
|
153
|
+
@objc func getInitialNotification(_ callback: LynxCallbackBlock?) {
|
|
154
|
+
if let payload = PushEventBus.shared.consumeInitialResponse() {
|
|
155
|
+
callback?(payload)
|
|
156
|
+
} else {
|
|
157
|
+
callback?(NSNull())
|
|
158
|
+
}
|
|
159
|
+
}
|
|
94
160
|
}
|