react-native-alarms 0.0.6 → 0.0.8

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/AlarmService.ts CHANGED
@@ -1,208 +1,408 @@
1
1
  /**
2
- * AlarmService - High-level alarm management API
2
+ * High-level alarm management API.
3
3
  *
4
- * This module provides a convenient wrapper around the native alarm module
5
- * with additional features like event listeners and state management.
4
+ * Provides a convenient wrapper around the native `Alarm` module for
5
+ * scheduling and managing alarms, handling permissions, querying alarm
6
+ * state, and subscribing to native alarm events.
7
+ *
8
+ * This API can also be used for time-sensitive reminders and custom
9
+ * scheduled experiences supported by the native alarm presentation flow.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { AlarmService } from 'react-native-alarms/service';
14
+ *
15
+ * const permission = await AlarmService.checkPermission();
16
+ *
17
+ * if (permission.status !== 'granted') {
18
+ * await AlarmService.requestPermission();
19
+ * }
20
+ *
21
+ * const alarmId = await AlarmService.scheduleAlarm({
22
+ * timestamp: Date.now() + 60_000,
23
+ * title: 'Reminder',
24
+ * body: 'Your scheduled event is ready.',
25
+ * });
26
+ * ```
6
27
  */
7
-
8
- import { NativeEventEmitter, NativeModules } from 'react-native';
28
+ import { NativeEventEmitter, type EmitterSubscription } from 'react-native';
9
29
  import type {
10
30
  AlarmConfig,
11
- Alarm,
31
+ AlarmProp,
12
32
  AlarmPermission,
13
33
  AlarmEvent,
14
- } from './specs/NativeAlarm';
34
+ AlarmEventMap,
35
+ AlarmEventName,
36
+ } from './src/types';
37
+ import { AlarmRegistry } from './src/utils';
38
+ import Alarm, { getNativeAlarmModule } from './specs/NativeAlarm';
15
39
 
16
- const { Alarm: NativeAlarm } = NativeModules;
40
+ /**
41
+ * Resolves the native module when available and keeps the lazy proxy as a
42
+ * fallback so missing native linking is reported when an operation is called.
43
+ */
44
+ const nativeAlarm = getNativeAlarmModule();
45
+ const alarm = nativeAlarm ?? Alarm;
17
46
 
18
- // Guard against module not being linked
19
- const alarm = NativeAlarm ? require('./specs/NativeAlarm').default : null;
47
+ /**
48
+ * Native event emitter used to subscribe to alarm lifecycle events.
49
+ */
50
+ const alarmEmitter = nativeAlarm ? new NativeEventEmitter(nativeAlarm) : null;
20
51
 
21
- const alarmEmitter = NativeAlarm ? new NativeEventEmitter(NativeAlarm) : null;
52
+ /**
53
+ * Callback invoked when a native alarm event is received.
54
+ *
55
+ * @param alarm Event data emitted by the native alarm module.
56
+ */
57
+ type AlarmEventListener<K extends AlarmEventName = AlarmEventName> = (
58
+ alarm: AlarmEventMap[K],
59
+ ) => void;
22
60
 
23
- type AlarmEventListener = (alarm: AlarmEvent) => void;
61
+ const ALARM_EVENT_NAMES = [
62
+ 'onAlarmFired',
63
+ 'onAlarmDismissed',
64
+ 'onAlarmSnoozed',
65
+ ] as const satisfies readonly AlarmEventName[];
66
+
67
+ function isAlarmEvent(value: Object): value is AlarmEvent {
68
+ return (
69
+ 'alarmId' in value &&
70
+ typeof value.alarmId === 'string' &&
71
+ 'timestamp' in value &&
72
+ typeof value.timestamp === 'number' &&
73
+ 'title' in value &&
74
+ typeof value.title === 'string' &&
75
+ 'body' in value &&
76
+ typeof value.body === 'string' &&
77
+ 'data' in value &&
78
+ typeof value.data === 'string' &&
79
+ (!('image' in value) || typeof value.image === 'string')
80
+ );
81
+ }
24
82
 
25
83
  /**
26
- * High-level alarm service for managing alarms
84
+ * High-level service for scheduling and managing native alarms.
85
+ *
86
+ * All methods are static, so the service can be used directly without
87
+ * creating an instance.
88
+ *
89
+ * The service maintains JavaScript event listeners and forwards events
90
+ * received from the native module to registered callbacks.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const unsubscribe = AlarmService.onAlarmFired((alarm) => {
95
+ * console.log('Alarm fired:', alarm.title);
96
+ * });
97
+ *
98
+ * // Remove this listener when it is no longer needed.
99
+ * unsubscribe();
100
+ * ```
27
101
  */
28
102
  export class AlarmService {
103
+ /**
104
+ * Registered JavaScript listeners grouped by event type.
105
+ *
106
+ * A `Set` prevents the same callback reference from being registered
107
+ * more than once for the same event.
108
+ */
29
109
  private static listeners: {
30
- fired: Set<AlarmEventListener>;
31
- dismissed: Set<AlarmEventListener>;
32
- snoozed: Set<AlarmEventListener>;
110
+ [K in AlarmEventName]: Set<AlarmEventListener<K>>;
33
111
  } = {
34
- fired: new Set(),
35
- dismissed: new Set(),
36
- snoozed: new Set(),
112
+ onAlarmFired: new Set(),
113
+ onAlarmDismissed: new Set(),
114
+ onAlarmSnoozed: new Set(),
37
115
  };
38
116
 
39
- private static subscriptions: any[] = [];
117
+ /**
118
+ * Native event subscriptions created by {@link initialize}.
119
+ */
120
+ private static subscriptions: EmitterSubscription[] = [];
40
121
 
41
122
  /**
42
- * Initialize the alarm service and set up event listeners
123
+ * Initializes native event forwarding.
124
+ *
125
+ * Registers listeners for alarm firing, dismissal, and snoozing.
126
+ * Calling this method when subscriptions already exist does not
127
+ * register duplicate native subscriptions.
128
+ *
129
+ * This method is called automatically by the event subscription helpers.
43
130
  */
44
- static initialize() {
45
- if (!alarmEmitter || !this.subscriptions.length) {
46
- this.subscriptions = [
47
- alarmEmitter?.addListener('onAlarmFired', (data: any) => {
48
- this.listeners.fired.forEach(listener =>
49
- listener(data as AlarmEvent),
50
- );
51
- }),
52
- alarmEmitter?.addListener('onAlarmDismissed', (data: any) => {
53
- this.listeners.dismissed.forEach(listener =>
54
- listener(data as AlarmEvent),
131
+ static initialize(): void {
132
+ if (!alarmEmitter || this.subscriptions.length) return;
133
+ this.subscriptions = ALARM_EVENT_NAMES.map(eventName =>
134
+ alarmEmitter.addListener(eventName, (event: Object) => {
135
+ if (!isAlarmEvent(event)) {
136
+ console.error(
137
+ `[react-native-alarms] Ignoring invalid ${eventName} event payload`,
55
138
  );
56
- }),
57
- alarmEmitter?.addListener('onAlarmSnoozed', (data: any) => {
58
- this.listeners.snoozed.forEach(listener =>
59
- listener(data as AlarmEvent),
60
- );
61
- }),
62
- ].filter(Boolean);
63
- }
139
+ return;
140
+ }
141
+ this.listeners[eventName].forEach(listener => listener(event));
142
+ }),
143
+ );
64
144
  }
65
145
 
66
146
  /**
67
- * Cleanup event listeners
147
+ * Removes native event subscriptions created by the service.
148
+ *
149
+ * This stops forwarding native events to JavaScript listeners until
150
+ * {@link initialize} is called again.
151
+ *
152
+ * Registered JavaScript callbacks remain in their respective sets.
153
+ * Use the unsubscribe functions returned by the event helpers to
154
+ * remove individual callbacks.
68
155
  */
69
- static cleanup() {
70
- this.subscriptions.forEach(sub => sub?.remove());
156
+ static cleanup(): void {
157
+ this.subscriptions.forEach(sub => sub.remove());
71
158
  this.subscriptions = [];
72
159
  }
73
160
 
74
161
  /**
75
- * Register a listener for when alarms fire
162
+ * Subscribes to alarms when they fire.
163
+ *
164
+ * The service initializes native event forwarding automatically.
165
+ *
166
+ * @param listener Callback invoked with the fired alarm's event data.
167
+ * @returns A function that unregisters this callback.
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const unsubscribe = AlarmService.onAlarmFired(alarm => {
172
+ * console.log('Fired:', alarm.alarmId, alarm.title);
173
+ * });
174
+ *
175
+ * // Later:
176
+ * unsubscribe();
177
+ * ```
76
178
  */
77
- static onAlarmFired(listener: AlarmEventListener) {
78
- this.initialize();
79
- this.listeners.fired.add(listener);
80
- return () => this.listeners.fired.delete(listener);
179
+ static onAlarmFired(
180
+ listener: AlarmEventListener<'onAlarmFired'>,
181
+ ): () => void {
182
+ return this.subscribe('onAlarmFired', listener);
81
183
  }
82
184
 
83
185
  /**
84
- * Register a listener for when alarms are dismissed
186
+ * Subscribes to alarms when they are dismissed.
187
+ *
188
+ * @param listener Callback invoked with the dismissed alarm's event data.
189
+ * @returns A function that unregisters this callback.
85
190
  */
86
- static onAlarmDismissed(listener: AlarmEventListener) {
87
- this.initialize();
88
- this.listeners.dismissed.add(listener);
89
- return () => this.listeners.dismissed.delete(listener);
191
+ static onAlarmDismissed(
192
+ listener: AlarmEventListener<'onAlarmDismissed'>,
193
+ ): () => void {
194
+ return this.subscribe('onAlarmDismissed', listener);
90
195
  }
91
196
 
92
197
  /**
93
- * Register a listener for when alarms are snoozed
198
+ * Subscribes to alarms when they are snoozed.
199
+ *
200
+ * @param listener Callback invoked with the snoozed alarm's event data.
201
+ * @returns A function that unregisters this callback.
94
202
  */
95
- static onAlarmSnoozed(listener: AlarmEventListener) {
203
+ static onAlarmSnoozed(
204
+ listener: AlarmEventListener<'onAlarmSnoozed'>,
205
+ ): () => void {
206
+ return this.subscribe('onAlarmSnoozed', listener);
207
+ }
208
+
209
+ private static subscribe<K extends AlarmEventName>(
210
+ eventName: K,
211
+ listener: AlarmEventListener<K>,
212
+ ): () => void {
96
213
  this.initialize();
97
- this.listeners.snoozed.add(listener);
98
- return () => this.listeners.snoozed.delete(listener);
214
+ this.listeners[eventName].add(listener);
215
+ return () => {
216
+ this.listeners[eventName].delete(listener);
217
+ };
99
218
  }
100
219
 
101
220
  /**
102
- * Check the current alarm permissions
221
+ * Retrieves the current alarm permission and capability status.
222
+ *
223
+ * @returns A promise resolving to the current permission status.
224
+ * @throws Error if the native alarm module is not linked.
103
225
  */
104
226
  static async checkPermission(): Promise<AlarmPermission> {
105
- if (!alarm) throw new Error('Alarm module not linked');
106
227
  return alarm.checkPermission();
107
228
  }
108
229
 
109
230
  /**
110
- * Request alarm permissions
231
+ * Requests the permissions required for alarm scheduling.
232
+ *
233
+ * The permissions requested depend on the platform and its capabilities.
234
+ *
235
+ * @returns A promise resolving to the updated permission status.
236
+ * @throws Error if the native alarm module is not linked.
111
237
  */
112
238
  static async requestPermission(): Promise<AlarmPermission> {
113
- if (!alarm) throw new Error('Alarm module not linked');
114
239
  return alarm.requestPermission();
115
240
  }
116
241
 
117
242
  /**
118
- * Schedule a new alarm
243
+ * Schedules a new alarm or time-based reminder.
244
+ *
245
+ * @param config Configuration containing the trigger timestamp,
246
+ * title, optional message, repeat settings, and custom data.
247
+ * @returns A promise resolving to the scheduled alarm ID.
248
+ * @throws Error if the native alarm module is not linked.
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * const alarmId = await AlarmService.scheduleAlarm({
253
+ * timestamp: Date.now() + 5 * 60_000,
254
+ * title: 'Take a break',
255
+ * body: 'Your scheduled break starts now.',
256
+ * data: JSON.stringify({ type: 'break-reminder' }),
257
+ * });
258
+ * ```
119
259
  */
120
260
  static async scheduleAlarm(config: AlarmConfig): Promise<string> {
121
- if (!alarm) throw new Error('Alarm module not linked');
122
- return alarm.scheduleAlarm(config);
261
+ return alarm.scheduleAlarm(this.toNativeConfig(config));
123
262
  }
124
263
 
125
264
  /**
126
- * Update an existing alarm
265
+ * Updates an existing scheduled alarm.
266
+ *
267
+ * @param alarmId Identifier of the alarm to update.
268
+ * @param config Updated alarm configuration.
269
+ * @returns A promise resolving to `true` if the update succeeds.
270
+ * @throws Error if the native alarm module is not linked.
127
271
  */
128
272
  static async updateAlarm(
129
273
  alarmId: string,
130
274
  config: AlarmConfig,
131
275
  ): Promise<boolean> {
132
- if (!alarm) throw new Error('Alarm module not linked');
133
- return alarm.updateAlarm(alarmId, config);
276
+ return alarm.updateAlarm(alarmId, this.toNativeConfig(config));
277
+ }
278
+
279
+ private static toNativeConfig<T extends AlarmConfig>(
280
+ config: T,
281
+ ): T & { componentName: string; componentProps: Record<string, unknown> } {
282
+ return {
283
+ ...config,
284
+ componentName: AlarmRegistry.resolve(config.presentation?.type),
285
+ componentProps: config.presentation?.props ?? {},
286
+ };
134
287
  }
135
288
 
136
289
  /**
137
- * Cancel an alarm
290
+ * Cancels a specific scheduled alarm.
291
+ *
292
+ * @param alarmId Identifier of the alarm to cancel.
293
+ * @returns A promise resolving to whether cancellation succeeded.
294
+ * @throws Error if the native alarm module is not linked.
138
295
  */
139
296
  static async cancelAlarm(alarmId: string): Promise<boolean> {
140
- if (!alarm) throw new Error('Alarm module not linked');
141
297
  return alarm.cancelAlarm(alarmId);
142
298
  }
143
299
 
144
300
  /**
145
- * Cancel all alarms
301
+ * Cancels all scheduled alarms managed by the native module.
302
+ *
303
+ * @returns A promise that resolves when the operation completes.
304
+ * @throws Error if the native alarm module is not linked.
146
305
  */
147
306
  static async cancelAllAlarms(): Promise<void> {
148
- if (!alarm) throw new Error('Alarm module not linked');
149
307
  return alarm.cancelAllAlarms();
150
308
  }
151
309
 
152
310
  /**
153
- * Stop the currently ringing alarm
311
+ * Stops a currently ringing alarm.
312
+ *
313
+ * @param alarmId Identifier of the ringing alarm.
314
+ * @returns A promise that resolves when the operation completes.
315
+ * @throws Error if the native alarm module is not linked.
154
316
  */
155
317
  static async stopAlarm(alarmId: string): Promise<void> {
156
- if (!alarm) throw new Error('Alarm module not linked');
157
318
  return alarm.stopAlarm(alarmId);
158
319
  }
159
320
 
160
321
  /**
161
- * Snooze the currently ringing alarm
322
+ * Snoozes a currently ringing alarm.
323
+ *
324
+ * @param alarmId Identifier of the ringing alarm.
325
+ * @param minutes Snooze duration in minutes. Defaults to `5`.
326
+ * @returns A promise that resolves when the operation completes.
327
+ * @throws Error if the native alarm module is not linked.
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * await AlarmService.snoozeAlarm(alarmId, 10);
332
+ * ```
162
333
  */
163
334
  static async snoozeAlarm(alarmId: string, minutes?: number): Promise<void> {
164
- if (!alarm) throw new Error('Alarm module not linked');
165
335
  return alarm.snoozeAlarm(alarmId, minutes ?? 5);
166
336
  }
167
337
 
168
338
  /**
169
- * Get the currently ringing alarm (if any)
339
+ * Retrieves the alarm that is currently ringing.
340
+ *
341
+ * @returns A promise resolving to the ringing alarm, or `null`
342
+ * if no alarm is currently ringing.
343
+ * @throws Error if the native alarm module is not linked.
170
344
  */
171
- static async getRingingAlarm(): Promise<Alarm | null> {
172
- if (!alarm) throw new Error('Alarm module not linked');
345
+ static async getRingingAlarm(): Promise<AlarmProp | null> {
173
346
  return alarm.getRingingAlarm();
174
347
  }
175
348
 
176
349
  /**
177
- * Get a specific alarm by ID
350
+ * Retrieves a scheduled alarm by its identifier.
351
+ *
352
+ * @param alarmId Identifier of the alarm to retrieve.
353
+ * @returns A promise resolving to the alarm, or `null` if not found.
354
+ * @throws Error if the native alarm module is not linked.
178
355
  */
179
- static async getAlarm(alarmId: string): Promise<Alarm | null> {
180
- if (!alarm) throw new Error('Alarm module not linked');
356
+ static async getAlarm(alarmId: string): Promise<AlarmProp | null> {
181
357
  return alarm.getAlarm(alarmId);
182
358
  }
183
359
 
184
360
  /**
185
- * Get all scheduled alarms
361
+ * Retrieves all alarms managed by the native module.
362
+ *
363
+ * @returns A promise resolving to the list of scheduled alarms.
364
+ * @throws Error if the native alarm module is not linked.
186
365
  */
187
- static async getAllAlarms(): Promise<Alarm[]> {
188
- if (!alarm) throw new Error('Alarm module not linked');
366
+ static async getAllAlarms(): Promise<AlarmProp[]> {
189
367
  return alarm.getAllAlarms();
190
368
  }
191
369
 
192
370
  /**
193
- * Check if an alarm is scheduled
371
+ * Checks whether an alarm is currently scheduled.
372
+ *
373
+ * @param alarmId Identifier of the alarm to check.
374
+ * @returns A promise resolving to `true` if scheduled, otherwise `false`.
375
+ * @throws Error if the native alarm module is not linked.
194
376
  */
195
377
  static async isScheduled(alarmId: string): Promise<boolean> {
196
- if (!alarm) throw new Error('Alarm module not linked');
197
378
  return alarm.isScheduled(alarmId);
198
379
  }
199
380
 
200
- /** Opens the vendor Autostart screen on Android; resolves false on iOS. */
381
+ /**
382
+ * Opens the device manufacturer's Auto Start settings screen.
383
+ *
384
+ * This is primarily intended for Android devices whose manufacturers
385
+ * provide additional background-execution settings.
386
+ *
387
+ * On iOS, the native implementation resolves to `false`.
388
+ *
389
+ * @returns A promise resolving to `true` if the settings screen was
390
+ * opened, or `false` if unsupported or unavailable.
391
+ * @throws Error if the native alarm module is not linked.
392
+ */
201
393
  static async openAutoStartSettings(): Promise<boolean> {
202
- if (!alarm) throw new Error('Alarm module not linked');
203
394
  return alarm.openAutoStartSettings();
204
395
  }
205
396
  }
206
397
 
207
- // Export a singleton instance
398
+ /**
399
+ * Default export for convenient service access.
400
+ *
401
+ * @example
402
+ * ```typescript
403
+ * import AlarmService from 'react-native-alarms/service';
404
+ *
405
+ * const alarms = await AlarmService.getAllAlarms();
406
+ * ```
407
+ */
208
408
  export default AlarmService;