@idealyst/notifications 1.2.114
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 +77 -0
- package/package.json +82 -0
- package/src/constants.ts +86 -0
- package/src/errors.ts +92 -0
- package/src/index.native.ts +120 -0
- package/src/index.ts +123 -0
- package/src/index.web.ts +2 -0
- package/src/local/local.native.ts +571 -0
- package/src/local/local.web.ts +360 -0
- package/src/local/useLocalNotifications.ts +122 -0
- package/src/permissions/permissions.native.ts +148 -0
- package/src/permissions/permissions.web.ts +49 -0
- package/src/permissions/useNotificationPermissions.ts +108 -0
- package/src/push/push.native.ts +248 -0
- package/src/push/push.web.ts +250 -0
- package/src/push/usePushNotifications.ts +152 -0
- package/src/types.ts +417 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
UsePushNotificationsOptions,
|
|
4
|
+
UsePushNotificationsResult,
|
|
5
|
+
PushToken,
|
|
6
|
+
NotificationError,
|
|
7
|
+
PermissionResult,
|
|
8
|
+
RequestPermissionOptions,
|
|
9
|
+
MessageHandler,
|
|
10
|
+
TokenRefreshHandler,
|
|
11
|
+
} from '../types';
|
|
12
|
+
import { createNotificationError } from '../errors';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Factory that creates a usePushNotifications hook bound to platform-specific functions.
|
|
16
|
+
* Each platform entry point calls this with the correct implementations.
|
|
17
|
+
*/
|
|
18
|
+
export function createUsePushNotificationsHook(fns: {
|
|
19
|
+
requestPushPermission: (options?: RequestPermissionOptions) => Promise<PermissionResult>;
|
|
20
|
+
getPushToken: () => Promise<PushToken>;
|
|
21
|
+
deletePushToken: () => Promise<void>;
|
|
22
|
+
onForegroundMessage: (handler: MessageHandler) => () => void;
|
|
23
|
+
onNotificationOpened: (handler: MessageHandler) => () => void;
|
|
24
|
+
onTokenRefresh: (handler: TokenRefreshHandler) => () => void;
|
|
25
|
+
subscribeToTopic: (topic: string) => Promise<void>;
|
|
26
|
+
unsubscribeFromTopic: (topic: string) => Promise<void>;
|
|
27
|
+
}) {
|
|
28
|
+
return function usePushNotifications(
|
|
29
|
+
options: UsePushNotificationsOptions = {},
|
|
30
|
+
): UsePushNotificationsResult {
|
|
31
|
+
const { autoRegister = false, onMessage, onNotificationOpened: onOpened, onTokenRefresh } = options;
|
|
32
|
+
|
|
33
|
+
const [token, setToken] = useState<PushToken | null>(null);
|
|
34
|
+
const [isRegistered, setIsRegistered] = useState(false);
|
|
35
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
36
|
+
const [error, setError] = useState<NotificationError | null>(null);
|
|
37
|
+
const mountedRef = useRef(true);
|
|
38
|
+
const initializedRef = useRef(false);
|
|
39
|
+
|
|
40
|
+
const register = useCallback(async (): Promise<PushToken> => {
|
|
41
|
+
setIsLoading(true);
|
|
42
|
+
setError(null);
|
|
43
|
+
try {
|
|
44
|
+
await fns.requestPushPermission();
|
|
45
|
+
const pushToken = await fns.getPushToken();
|
|
46
|
+
if (mountedRef.current) {
|
|
47
|
+
setToken(pushToken);
|
|
48
|
+
setIsRegistered(true);
|
|
49
|
+
}
|
|
50
|
+
return pushToken;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
const notifError = err as NotificationError;
|
|
53
|
+
if (mountedRef.current) setError(notifError);
|
|
54
|
+
throw notifError;
|
|
55
|
+
} finally {
|
|
56
|
+
if (mountedRef.current) setIsLoading(false);
|
|
57
|
+
}
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
const unregister = useCallback(async (): Promise<void> => {
|
|
61
|
+
setError(null);
|
|
62
|
+
try {
|
|
63
|
+
await fns.deletePushToken();
|
|
64
|
+
if (mountedRef.current) {
|
|
65
|
+
setToken(null);
|
|
66
|
+
setIsRegistered(false);
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const notifError = err as NotificationError;
|
|
70
|
+
if (mountedRef.current) setError(notifError);
|
|
71
|
+
throw notifError;
|
|
72
|
+
}
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
const getToken = useCallback(async (): Promise<PushToken | null> => {
|
|
76
|
+
try {
|
|
77
|
+
const pushToken = await fns.getPushToken();
|
|
78
|
+
if (mountedRef.current) setToken(pushToken);
|
|
79
|
+
return pushToken;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const subscribeToTopic = useCallback(async (topic: string): Promise<void> => {
|
|
86
|
+
try {
|
|
87
|
+
await fns.subscribeToTopic(topic);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const notifError = err as NotificationError;
|
|
90
|
+
if (mountedRef.current) setError(notifError);
|
|
91
|
+
throw notifError;
|
|
92
|
+
}
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
const unsubscribeFromTopic = useCallback(async (topic: string): Promise<void> => {
|
|
96
|
+
try {
|
|
97
|
+
await fns.unsubscribeFromTopic(topic);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
const notifError = err as NotificationError;
|
|
100
|
+
if (mountedRef.current) setError(notifError);
|
|
101
|
+
throw notifError;
|
|
102
|
+
}
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
const clearError = useCallback(() => setError(null), []);
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
mountedRef.current = true;
|
|
109
|
+
const unsubs: (() => void)[] = [];
|
|
110
|
+
|
|
111
|
+
if (onMessage) {
|
|
112
|
+
unsubs.push(fns.onForegroundMessage(onMessage));
|
|
113
|
+
}
|
|
114
|
+
if (onOpened) {
|
|
115
|
+
unsubs.push(fns.onNotificationOpened(onOpened));
|
|
116
|
+
}
|
|
117
|
+
if (onTokenRefresh) {
|
|
118
|
+
unsubs.push(
|
|
119
|
+
fns.onTokenRefresh((newToken) => {
|
|
120
|
+
if (mountedRef.current) setToken(newToken);
|
|
121
|
+
onTokenRefresh(newToken);
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (autoRegister && !initializedRef.current) {
|
|
127
|
+
initializedRef.current = true;
|
|
128
|
+
register().catch(() => {
|
|
129
|
+
// Error is captured in state via the register function
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return () => {
|
|
134
|
+
mountedRef.current = false;
|
|
135
|
+
unsubs.forEach((u) => u());
|
|
136
|
+
};
|
|
137
|
+
}, [autoRegister, onMessage, onOpened, onTokenRefresh, register]);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
token,
|
|
141
|
+
isRegistered,
|
|
142
|
+
isLoading,
|
|
143
|
+
error,
|
|
144
|
+
register,
|
|
145
|
+
unregister,
|
|
146
|
+
getToken,
|
|
147
|
+
subscribeToTopic,
|
|
148
|
+
unsubscribeFromTopic,
|
|
149
|
+
clearError,
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Permission Types
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
export type PermissionStatus =
|
|
6
|
+
| 'granted'
|
|
7
|
+
| 'denied'
|
|
8
|
+
| 'undetermined'
|
|
9
|
+
| 'provisional'
|
|
10
|
+
| 'blocked';
|
|
11
|
+
|
|
12
|
+
export interface PermissionResult {
|
|
13
|
+
/** Current permission status. */
|
|
14
|
+
status: PermissionStatus;
|
|
15
|
+
/** Whether permission is sufficient to show notifications. */
|
|
16
|
+
canNotify: boolean;
|
|
17
|
+
/** iOS only: specific iOS authorization status details. */
|
|
18
|
+
ios?: {
|
|
19
|
+
alert: boolean;
|
|
20
|
+
badge: boolean;
|
|
21
|
+
sound: boolean;
|
|
22
|
+
criticalAlert: boolean;
|
|
23
|
+
provisional: boolean;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RequestPermissionOptions {
|
|
28
|
+
/** iOS: request provisional (silent) authorization. */
|
|
29
|
+
provisional?: boolean;
|
|
30
|
+
/** iOS: request critical alert permission (requires entitlement). */
|
|
31
|
+
criticalAlert?: boolean;
|
|
32
|
+
/** iOS: request permission for specific notification types. */
|
|
33
|
+
ios?: {
|
|
34
|
+
alert?: boolean;
|
|
35
|
+
badge?: boolean;
|
|
36
|
+
sound?: boolean;
|
|
37
|
+
criticalAlert?: boolean;
|
|
38
|
+
provisional?: boolean;
|
|
39
|
+
carPlay?: boolean;
|
|
40
|
+
announcement?: boolean;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Push Notification Types
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
export type PushTokenType = 'fcm' | 'apns' | 'web';
|
|
49
|
+
|
|
50
|
+
export interface PushToken {
|
|
51
|
+
/** The device token string. */
|
|
52
|
+
token: string;
|
|
53
|
+
/** Token type (fcm for Android, apns/fcm for iOS, web for browser). */
|
|
54
|
+
type: PushTokenType;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface RemoteMessage {
|
|
58
|
+
/** Unique message identifier. */
|
|
59
|
+
messageId: string;
|
|
60
|
+
/** Message payload data (key-value pairs). */
|
|
61
|
+
data: Record<string, string>;
|
|
62
|
+
/** Notification payload (title/body for display). */
|
|
63
|
+
notification?: {
|
|
64
|
+
title?: string;
|
|
65
|
+
body?: string;
|
|
66
|
+
imageUrl?: string;
|
|
67
|
+
};
|
|
68
|
+
/** FCM topic name if message was sent to a topic. */
|
|
69
|
+
topic?: string;
|
|
70
|
+
/** FCM collapse key. */
|
|
71
|
+
collapseKey?: string;
|
|
72
|
+
/** Message sent time (ms since epoch). */
|
|
73
|
+
sentTime?: number;
|
|
74
|
+
/** Time-to-live in seconds. */
|
|
75
|
+
ttl?: number;
|
|
76
|
+
/** Where the message was received. */
|
|
77
|
+
origin: 'foreground' | 'background' | 'quit';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type MessageHandler = (message: RemoteMessage) => void | Promise<void>;
|
|
81
|
+
|
|
82
|
+
export type TokenRefreshHandler = (token: PushToken) => void;
|
|
83
|
+
|
|
84
|
+
/** Web push configuration (web only). */
|
|
85
|
+
export interface WebPushConfig {
|
|
86
|
+
/** VAPID public key (base64 string). */
|
|
87
|
+
vapidKey: string;
|
|
88
|
+
/** Path to the Service Worker file. Default: '/sw.js'. */
|
|
89
|
+
serviceWorkerPath?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Local Notification Types
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
export type NotificationImportance =
|
|
97
|
+
| 'none'
|
|
98
|
+
| 'min'
|
|
99
|
+
| 'low'
|
|
100
|
+
| 'default'
|
|
101
|
+
| 'high'
|
|
102
|
+
| 'max';
|
|
103
|
+
|
|
104
|
+
export type NotificationVisibility =
|
|
105
|
+
| 'private'
|
|
106
|
+
| 'public'
|
|
107
|
+
| 'secret';
|
|
108
|
+
|
|
109
|
+
export interface NotificationAction {
|
|
110
|
+
/** Unique action identifier. */
|
|
111
|
+
id: string;
|
|
112
|
+
/** Button label shown to user. */
|
|
113
|
+
title: string;
|
|
114
|
+
/** Icon name (Android only, optional). */
|
|
115
|
+
icon?: string;
|
|
116
|
+
/** Whether this action opens the app. */
|
|
117
|
+
foreground?: boolean;
|
|
118
|
+
/** Whether this action is destructive (iOS: red, Android: dismiss). */
|
|
119
|
+
destructive?: boolean;
|
|
120
|
+
/** Whether this action accepts text input. */
|
|
121
|
+
input?: boolean;
|
|
122
|
+
/** Placeholder text for input actions. */
|
|
123
|
+
inputPlaceholder?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface NotificationCategory {
|
|
127
|
+
/** Unique category identifier. */
|
|
128
|
+
id: string;
|
|
129
|
+
/** Actions to display with notifications of this category. */
|
|
130
|
+
actions: NotificationAction[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface AndroidChannel {
|
|
134
|
+
/** Unique channel identifier. */
|
|
135
|
+
id: string;
|
|
136
|
+
/** Channel display name. */
|
|
137
|
+
name: string;
|
|
138
|
+
/** Channel description (shown in system settings). */
|
|
139
|
+
description?: string;
|
|
140
|
+
/** Importance level. */
|
|
141
|
+
importance?: NotificationImportance;
|
|
142
|
+
/** Enable vibration. */
|
|
143
|
+
vibration?: boolean;
|
|
144
|
+
/** Vibration pattern (ms). */
|
|
145
|
+
vibrationPattern?: number[];
|
|
146
|
+
/** Enable notification light. */
|
|
147
|
+
lights?: boolean;
|
|
148
|
+
/** Light color (hex). */
|
|
149
|
+
lightColor?: string;
|
|
150
|
+
/** Sound file name (without extension) in res/raw. */
|
|
151
|
+
sound?: string;
|
|
152
|
+
/** Badge (show dot on app icon). */
|
|
153
|
+
badge?: boolean;
|
|
154
|
+
/** Show on lock screen. */
|
|
155
|
+
visibility?: NotificationVisibility;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type RepeatFrequency =
|
|
159
|
+
| 'none'
|
|
160
|
+
| 'hourly'
|
|
161
|
+
| 'daily'
|
|
162
|
+
| 'weekly';
|
|
163
|
+
|
|
164
|
+
export interface NotificationTrigger {
|
|
165
|
+
/** Type of trigger. */
|
|
166
|
+
type: 'timestamp' | 'interval';
|
|
167
|
+
/** For timestamp trigger: the date/time to fire (ms since epoch). */
|
|
168
|
+
timestamp?: number;
|
|
169
|
+
/** For interval trigger: interval in minutes. */
|
|
170
|
+
interval?: number;
|
|
171
|
+
/** Repeat frequency. 'none' = fire once. */
|
|
172
|
+
repeatFrequency?: RepeatFrequency;
|
|
173
|
+
/** Whether the notification should fire even if app is in low-power mode (Android). */
|
|
174
|
+
alarmManager?: boolean;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface NotificationContent {
|
|
178
|
+
/** Notification title. */
|
|
179
|
+
title: string;
|
|
180
|
+
/** Notification body text. */
|
|
181
|
+
body?: string;
|
|
182
|
+
/** Subtitle (iOS) or sub-text (Android). */
|
|
183
|
+
subtitle?: string;
|
|
184
|
+
/** Custom data payload (passed to press handlers). */
|
|
185
|
+
data?: Record<string, string>;
|
|
186
|
+
/** Category ID (for action buttons). */
|
|
187
|
+
categoryId?: string;
|
|
188
|
+
/** Badge count (iOS) or badge number (Android). */
|
|
189
|
+
badge?: number;
|
|
190
|
+
/** Sound file name (without extension). Use 'default' for system sound. */
|
|
191
|
+
sound?: string;
|
|
192
|
+
/** Image URL for big picture style. */
|
|
193
|
+
imageUrl?: string;
|
|
194
|
+
/** Thread identifier (iOS) for notification grouping. */
|
|
195
|
+
threadId?: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export type AndroidNotificationStyle =
|
|
199
|
+
| { type: 'bigPicture'; picture: string; title?: string; summary?: string }
|
|
200
|
+
| { type: 'bigText'; text: string; title?: string; summary?: string }
|
|
201
|
+
| { type: 'inbox'; lines: string[]; title?: string; summary?: string }
|
|
202
|
+
| {
|
|
203
|
+
type: 'messaging';
|
|
204
|
+
person: { name: string; icon?: string };
|
|
205
|
+
messages: Array<{ text: string; timestamp: number }>;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export interface DisplayNotificationOptions extends NotificationContent {
|
|
209
|
+
/** Unique notification identifier. Auto-generated if omitted. */
|
|
210
|
+
id?: string;
|
|
211
|
+
/** Android-specific options. */
|
|
212
|
+
android?: {
|
|
213
|
+
channelId?: string;
|
|
214
|
+
smallIcon?: string;
|
|
215
|
+
largeIcon?: string;
|
|
216
|
+
color?: string;
|
|
217
|
+
pressAction?: { id: string; launchActivity?: string };
|
|
218
|
+
importance?: NotificationImportance;
|
|
219
|
+
ongoing?: boolean;
|
|
220
|
+
autoCancel?: boolean;
|
|
221
|
+
groupId?: string;
|
|
222
|
+
groupSummary?: boolean;
|
|
223
|
+
progress?: { max: number; current: number; indeterminate?: boolean };
|
|
224
|
+
actions?: NotificationAction[];
|
|
225
|
+
style?: AndroidNotificationStyle;
|
|
226
|
+
};
|
|
227
|
+
/** iOS-specific options. */
|
|
228
|
+
ios?: {
|
|
229
|
+
categoryId?: string;
|
|
230
|
+
interruptionLevel?: 'passive' | 'active' | 'timeSensitive' | 'critical';
|
|
231
|
+
relevanceScore?: number;
|
|
232
|
+
targetContentId?: string;
|
|
233
|
+
attachments?: Array<{
|
|
234
|
+
url: string;
|
|
235
|
+
id?: string;
|
|
236
|
+
thumbnailHidden?: boolean;
|
|
237
|
+
}>;
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface ScheduleNotificationOptions extends DisplayNotificationOptions {
|
|
242
|
+
/** Trigger configuration. */
|
|
243
|
+
trigger: NotificationTrigger;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface DisplayedNotification {
|
|
247
|
+
/** Notification identifier. */
|
|
248
|
+
id: string;
|
|
249
|
+
/** Notification title. */
|
|
250
|
+
title?: string;
|
|
251
|
+
/** Notification body. */
|
|
252
|
+
body?: string;
|
|
253
|
+
/** Custom data. */
|
|
254
|
+
data?: Record<string, string>;
|
|
255
|
+
/** Date displayed (ms since epoch). */
|
|
256
|
+
date?: number;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface PendingNotification {
|
|
260
|
+
/** Notification identifier. */
|
|
261
|
+
id: string;
|
|
262
|
+
/** Notification title. */
|
|
263
|
+
title?: string;
|
|
264
|
+
/** Notification body. */
|
|
265
|
+
body?: string;
|
|
266
|
+
/** Trigger details. */
|
|
267
|
+
trigger: NotificationTrigger;
|
|
268
|
+
/** Custom data. */
|
|
269
|
+
data?: Record<string, string>;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export type NotificationEventType =
|
|
273
|
+
| 'press'
|
|
274
|
+
| 'action_press'
|
|
275
|
+
| 'dismissed';
|
|
276
|
+
|
|
277
|
+
export interface NotificationEvent {
|
|
278
|
+
/** Event type. */
|
|
279
|
+
type: NotificationEventType;
|
|
280
|
+
/** Notification identifier. */
|
|
281
|
+
id: string;
|
|
282
|
+
/** Notification content. */
|
|
283
|
+
notification: DisplayedNotification;
|
|
284
|
+
/** Action ID (only for 'action_press' events). */
|
|
285
|
+
actionId?: string;
|
|
286
|
+
/** Input text (only for input actions). */
|
|
287
|
+
input?: string;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export type NotificationEventHandler = (event: NotificationEvent) => void | Promise<void>;
|
|
291
|
+
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// Error Types
|
|
294
|
+
// ============================================================================
|
|
295
|
+
|
|
296
|
+
export type NotificationErrorCode =
|
|
297
|
+
| 'not_available'
|
|
298
|
+
| 'not_supported'
|
|
299
|
+
| 'permission_denied'
|
|
300
|
+
| 'token_failed'
|
|
301
|
+
| 'display_failed'
|
|
302
|
+
| 'schedule_failed'
|
|
303
|
+
| 'cancel_failed'
|
|
304
|
+
| 'channel_failed'
|
|
305
|
+
| 'topic_failed'
|
|
306
|
+
| 'unknown';
|
|
307
|
+
|
|
308
|
+
export interface NotificationError {
|
|
309
|
+
code: NotificationErrorCode;
|
|
310
|
+
message: string;
|
|
311
|
+
/** Original error from underlying library. */
|
|
312
|
+
originalError?: unknown;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ============================================================================
|
|
316
|
+
// Hook Types: usePushNotifications
|
|
317
|
+
// ============================================================================
|
|
318
|
+
|
|
319
|
+
export interface UsePushNotificationsOptions {
|
|
320
|
+
/** Auto-request permission and register on mount. */
|
|
321
|
+
autoRegister?: boolean;
|
|
322
|
+
/** Called when a foreground message arrives. */
|
|
323
|
+
onMessage?: MessageHandler;
|
|
324
|
+
/** Called when user opens a notification (from background/quit). */
|
|
325
|
+
onNotificationOpened?: MessageHandler;
|
|
326
|
+
/** Called when a new push token is issued. */
|
|
327
|
+
onTokenRefresh?: TokenRefreshHandler;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export interface UsePushNotificationsResult {
|
|
331
|
+
/** Current push token, or null if not registered. */
|
|
332
|
+
token: PushToken | null;
|
|
333
|
+
/** Whether the device is registered for push. */
|
|
334
|
+
isRegistered: boolean;
|
|
335
|
+
/** Whether registration is in progress. */
|
|
336
|
+
isLoading: boolean;
|
|
337
|
+
/** Current error, if any. */
|
|
338
|
+
error: NotificationError | null;
|
|
339
|
+
|
|
340
|
+
/** Request permission and register for push notifications. */
|
|
341
|
+
register: () => Promise<PushToken>;
|
|
342
|
+
/** Unregister from push (delete token). */
|
|
343
|
+
unregister: () => Promise<void>;
|
|
344
|
+
/** Get the current token without registering. */
|
|
345
|
+
getToken: () => Promise<PushToken | null>;
|
|
346
|
+
/** Subscribe to an FCM topic (native only). */
|
|
347
|
+
subscribeToTopic: (topic: string) => Promise<void>;
|
|
348
|
+
/** Unsubscribe from an FCM topic (native only). */
|
|
349
|
+
unsubscribeFromTopic: (topic: string) => Promise<void>;
|
|
350
|
+
/** Clear the current error. */
|
|
351
|
+
clearError: () => void;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ============================================================================
|
|
355
|
+
// Hook Types: useLocalNotifications
|
|
356
|
+
// ============================================================================
|
|
357
|
+
|
|
358
|
+
export interface UseLocalNotificationsOptions {
|
|
359
|
+
/** Called when user presses a notification or action button. */
|
|
360
|
+
onEvent?: NotificationEventHandler;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export interface UseLocalNotificationsResult {
|
|
364
|
+
/** Display a notification immediately. Returns notification ID. */
|
|
365
|
+
displayNotification: (options: DisplayNotificationOptions) => Promise<string>;
|
|
366
|
+
/** Schedule a notification. Returns notification ID. */
|
|
367
|
+
scheduleNotification: (options: ScheduleNotificationOptions) => Promise<string>;
|
|
368
|
+
/** Cancel a specific notification by ID. */
|
|
369
|
+
cancelNotification: (id: string) => Promise<void>;
|
|
370
|
+
/** Cancel all notifications (displayed + pending). */
|
|
371
|
+
cancelAllNotifications: () => Promise<void>;
|
|
372
|
+
|
|
373
|
+
/** Create an Android notification channel. */
|
|
374
|
+
createChannel: (channel: AndroidChannel) => Promise<void>;
|
|
375
|
+
/** Delete an Android notification channel. */
|
|
376
|
+
deleteChannel: (channelId: string) => Promise<void>;
|
|
377
|
+
/** Get all Android notification channels. */
|
|
378
|
+
getChannels: () => Promise<AndroidChannel[]>;
|
|
379
|
+
|
|
380
|
+
/** Set notification categories (iOS action buttons). */
|
|
381
|
+
setCategories: (categories: NotificationCategory[]) => Promise<void>;
|
|
382
|
+
|
|
383
|
+
/** Get all currently displayed notifications. */
|
|
384
|
+
getDisplayedNotifications: () => Promise<DisplayedNotification[]>;
|
|
385
|
+
/** Get all pending (scheduled) notifications. */
|
|
386
|
+
getPendingNotifications: () => Promise<PendingNotification[]>;
|
|
387
|
+
|
|
388
|
+
/** Set the app badge count. */
|
|
389
|
+
setBadgeCount: (count: number) => Promise<void>;
|
|
390
|
+
/** Get the current app badge count. */
|
|
391
|
+
getBadgeCount: () => Promise<number>;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ============================================================================
|
|
395
|
+
// Hook Types: useNotificationPermissions
|
|
396
|
+
// ============================================================================
|
|
397
|
+
|
|
398
|
+
export interface UseNotificationPermissionsOptions {
|
|
399
|
+
/** Auto-check permission on mount. */
|
|
400
|
+
autoCheck?: boolean;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export interface UseNotificationPermissionsResult {
|
|
404
|
+
/** Current permission status. */
|
|
405
|
+
status: PermissionStatus;
|
|
406
|
+
/** Detailed permission result. */
|
|
407
|
+
permission: PermissionResult | null;
|
|
408
|
+
/** Whether checking/requesting is in progress. */
|
|
409
|
+
isLoading: boolean;
|
|
410
|
+
|
|
411
|
+
/** Check current permission without prompting. */
|
|
412
|
+
checkPermission: () => Promise<PermissionResult>;
|
|
413
|
+
/** Request notification permission. */
|
|
414
|
+
requestPermission: (options?: RequestPermissionOptions) => Promise<PermissionResult>;
|
|
415
|
+
/** Open system settings for this app (to change permissions). */
|
|
416
|
+
openSettings: () => Promise<void>;
|
|
417
|
+
}
|