react-native-alarms 0.0.6 → 0.0.7

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.
@@ -8,12 +8,15 @@ import UIKit
8
8
  /// so this shows when the alarm fires while the app is in the foreground, or when the
9
9
  /// user opens the app by tapping the alarm notification.
10
10
  final class AlarmRingPresenter {
11
- static let componentName = "AlarmRing"
12
-
11
+ private static let defaultComponentName = "AlarmRing"
12
+
13
13
  private static weak var controller: UIViewController?
14
14
  private static var alarmId: String?
15
-
16
- static func present(_ alarm: AlarmData, bridge: RCTBridge?, attempt: Int = 0) {
15
+
16
+ static func present(_ alarm: AlarmData, bridge: RCTBridge?,
17
+ componentName: String = defaultComponentName,
18
+ componentProps: [String: Any] = [:],
19
+ attempt: Int = 0) {
17
20
  DispatchQueue.main.async {
18
21
  if controller != nil, alarmId == alarm.id {
19
22
  // Already showing (e.g. presented while backgrounded, now foregrounded):
@@ -23,7 +26,9 @@ final class AlarmRingPresenter {
23
26
  }
24
27
  // Cold start from a notification tap or the AlarmKit "Open" button: the root
25
28
  // surface or window may not be ready yet, so retry for up to ~15 s.
26
- guard let surface = makeSurface(bridge, props: alarm.toDictionary()),
29
+ guard let surface = makeSurface(bridge,
30
+ componentName: componentName,
31
+ props: alarm.toDictionary()),
27
32
  let top = topViewController() else {
28
33
  if attempt < 60 {
29
34
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
@@ -34,7 +39,7 @@ final class AlarmRingPresenter {
34
39
  }
35
40
  return
36
41
  }
37
-
42
+
38
43
  let vc = UIViewController()
39
44
  let root = RCTSurfaceHostingProxyRootView(surface: surface)
40
45
  root.backgroundColor = .systemBackground
@@ -42,7 +47,7 @@ final class AlarmRingPresenter {
42
47
  surface.start()
43
48
  vc.modalPresentationStyle = .fullScreen
44
49
  vc.isModalInPresentation = true
45
-
50
+
46
51
  let show = {
47
52
  top.present(vc, animated: true)
48
53
  controller = vc
@@ -56,7 +61,7 @@ final class AlarmRingPresenter {
56
61
  }
57
62
  }
58
63
  }
59
-
64
+
60
65
  /// The ring screen speaks the task via TTS, and iOS won't play it over the
61
66
  /// notification / AlarmKit sound, so stop that sound once the screen is up.
62
67
  /// Only while the app is frontmost: in the background the system alert is the
@@ -64,11 +69,11 @@ final class AlarmRingPresenter {
64
69
  private static func silenceIfActive(_ id: String) {
65
70
  guard UIApplication.shared.applicationState == .active else { return }
66
71
  NotificationBackend.shared.silence(id)
67
- #if canImport(AlarmKit)
72
+ #if canImport(AlarmKit)
68
73
  if #available(iOS 26.0, *) { AlarmKitBackend.shared.silence(id) }
69
- #endif
74
+ #endif
70
75
  }
71
-
76
+
72
77
  /// Called when ringing ends for any reason (stop, snooze, cancel).
73
78
  static func dismiss(_ id: String? = nil) {
74
79
  DispatchQueue.main.async {
@@ -78,18 +83,20 @@ final class AlarmRingPresenter {
78
83
  vc.dismiss(animated: true)
79
84
  }
80
85
  }
81
-
86
+
82
87
  private static let createSurface = NSSelectorFromString("createFabricSurfaceForModuleName:initialProperties:")
83
-
88
+
84
89
  /// `RCTSurfacePresenter` implements `createFabricSurfaceForModuleName:initialProperties:`
85
90
  /// without formally adopting `RCTSurfacePresenterStub`, and its header is ObjC++ (not
86
91
  /// importable from Swift), so call it by selector.
87
- private static func makeSurface(_ bridge: RCTBridge?, props: [String: Any]) -> RCTSurfaceProtocol? {
92
+ private static func makeSurface(_ bridge: RCTBridge?,
93
+ componentName: String,
94
+ props: [String: Any]) -> RCTSurfaceProtocol? {
88
95
  guard let presenter = surfacePresenter(bridge), presenter.responds(to: createSurface) else { return nil }
89
96
  return presenter.perform(createSurface, with: componentName, with: props)?
90
97
  .takeUnretainedValue() as? RCTSurfaceProtocol
91
98
  }
92
-
99
+
93
100
  /// Bridgeless mode skips `bridge.surfacePresenter` (and injection) for modules
94
101
  /// registered with `RCT_EXTERN_MODULE`, so fall back to the presenter of the app's
95
102
  /// root React surface, which `RCTFabricSurface` keeps in a weak `_surfacePresenter` ivar.
@@ -99,10 +106,10 @@ final class AlarmRingPresenter {
99
106
  let hosting = findHostingView(in: root),
100
107
  let surface = hosting.surface as AnyObject?,
101
108
  let ivar = class_getInstanceVariable(type(of: surface), "_surfacePresenter")
102
- else { return nil }
109
+ else { return nil }
103
110
  return object_getIvar(surface, ivar) as? NSObject
104
111
  }
105
-
112
+
106
113
  private static func findHostingView(in view: UIView) -> RCTSurfaceHostingView? {
107
114
  if let hosting = view as? RCTSurfaceHostingView { return hosting }
108
115
  for sub in view.subviews {
@@ -110,14 +117,14 @@ final class AlarmRingPresenter {
110
117
  }
111
118
  return nil
112
119
  }
113
-
120
+
114
121
  private static func keyWindow() -> UIWindow? {
115
122
  UIApplication.shared.connectedScenes
116
123
  .compactMap { $0 as? UIWindowScene }
117
124
  .flatMap { $0.windows }
118
125
  .first { $0.isKeyWindow }
119
126
  }
120
-
127
+
121
128
  private static func topViewController() -> UIViewController? {
122
129
  var top = keyWindow()?.rootViewController
123
130
  while let presented = top?.presentedViewController { top = presented }
@@ -4,45 +4,45 @@ import Foundation
4
4
  enum AlarmStore {
5
5
  private static let key = "RNAlarm.alarms"
6
6
  private static let lock = NSLock()
7
-
7
+
8
8
  static func all() -> [AlarmData] {
9
9
  lock.lock(); defer { lock.unlock() }
10
10
  return Array(load().values)
11
11
  }
12
-
12
+
13
13
  static func get(_ id: String) -> AlarmData? {
14
14
  lock.lock(); defer { lock.unlock() }
15
15
  return load()[id]
16
16
  }
17
-
17
+
18
18
  static func save(_ alarm: AlarmData) {
19
19
  lock.lock(); defer { lock.unlock() }
20
20
  var map = load()
21
21
  map[alarm.id] = alarm
22
22
  persist(map)
23
23
  }
24
-
24
+
25
25
  static func remove(_ id: String) {
26
26
  lock.lock(); defer { lock.unlock() }
27
27
  var map = load()
28
28
  map.removeValue(forKey: id)
29
29
  persist(map)
30
30
  }
31
-
31
+
32
32
  static func clear() {
33
33
  lock.lock(); defer { lock.unlock() }
34
34
  persist([:])
35
35
  }
36
-
36
+
37
37
  // MARK: - Private
38
-
38
+
39
39
  private static func load() -> [String: AlarmData] {
40
40
  guard let data = UserDefaults.standard.data(forKey: key),
41
41
  let map = try? JSONDecoder().decode([String: AlarmData].self, from: data)
42
- else { return [:] }
42
+ else { return [:] }
43
43
  return map
44
44
  }
45
-
45
+
46
46
  private static func persist(_ map: [String: AlarmData]) {
47
47
  if let data = try? JSONEncoder().encode(map) {
48
48
  UserDefaults.standard.set(data, forKey: key)
@@ -10,25 +10,25 @@ import UserNotifications
10
10
  @objc(RNAlarmNotificationBackend)
11
11
  final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
12
12
  static let shared = NotificationBackend()
13
-
13
+
14
14
  static let category = "RN_ALARM"
15
15
  static let actionSnooze = "RN_ALARM_SNOOZE"
16
16
  static let actionStop = "RN_ALARM_STOP"
17
17
  private static let prefix = "rnalarm:"
18
18
  /// How many `interval` occurrences to pre-schedule (iOS caps pending requests at 64).
19
19
  private static let intervalOccurrences = 12
20
-
20
+
21
21
  private let center = UNUserNotificationCenter.current()
22
-
22
+
23
23
  /// The alarm whose notification is currently being shown (foreground) or was tapped.
24
24
  private(set) var ringing: AlarmData?
25
25
  /// Wired by `AlarmModule` so native events reach JS.
26
26
  var onEvent: ((String, AlarmData) -> Void)?
27
-
27
+
28
28
  /// Delegate that was installed before us (e.g. Notifee); non-alarm notifications go to it.
29
29
  private weak var previousDelegate: UNUserNotificationCenterDelegate?
30
30
  private static var installed = false
31
-
31
+
32
32
  /// Installs the delegate and actions. Call once at launch so a tap on a
33
33
  /// notification that starts the app is delivered to the module.
34
34
  @objc static func install() {
@@ -51,9 +51,9 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
51
51
  ),
52
52
  ])
53
53
  }
54
-
54
+
55
55
  // MARK: - Permission
56
-
56
+
57
57
  /// 'granted' | 'denied' | 'notDetermined', same vocabulary as Android.
58
58
  func status(_ completion: @escaping (String) -> Void) {
59
59
  center.getNotificationSettings { settings in
@@ -64,20 +64,20 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
64
64
  }
65
65
  }
66
66
  }
67
-
67
+
68
68
  func request(_ completion: @escaping (Bool) -> Void) {
69
69
  center.requestAuthorization(options: [.alert, .sound, .badge]) { ok, _ in completion(ok) }
70
70
  }
71
-
71
+
72
72
  // MARK: - Scheduling
73
-
73
+
74
74
  private func ids(for alarmId: String) -> [String] {
75
75
  let base = Self.prefix + alarmId
76
76
  return [base, base + ":snooze"]
77
- + (1...7).map { base + ":wd\($0)" }
78
- + (0..<Self.intervalOccurrences).map { base + ":i\($0)" }
77
+ + (1...7).map { base + ":wd\($0)" }
78
+ + (0..<Self.intervalOccurrences).map { base + ":i\($0)" }
79
79
  }
80
-
80
+
81
81
  private func content(for a: AlarmData) -> UNMutableNotificationContent {
82
82
  let c = UNMutableNotificationContent()
83
83
  c.title = a.title.isEmpty ? "Alarm" : a.title
@@ -85,32 +85,32 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
85
85
  c.categoryIdentifier = Self.category
86
86
  c.userInfo = ["alarmId": a.id]
87
87
  c.sound = (a.sound.isEmpty || a.sound == "default")
88
- ? .default
89
- : UNNotificationSound(named: UNNotificationSoundName(rawValue: a.sound))
88
+ ? .default
89
+ : UNNotificationSound(named: UNNotificationSoundName(rawValue: a.sound))
90
90
  // Breaks through Focus when the Time Sensitive Notifications capability is enabled;
91
91
  // silently downgraded to a normal notification otherwise.
92
92
  c.interruptionLevel = .timeSensitive
93
93
  return c
94
94
  }
95
-
95
+
96
96
  private func request(_ id: String, _ a: AlarmData, _ trigger: UNNotificationTrigger) -> UNNotificationRequest {
97
97
  UNNotificationRequest(identifier: id, content: content(for: a), trigger: trigger)
98
98
  }
99
-
99
+
100
100
  func schedule(_ a: AlarmData, completion: @escaping (Error?) -> Void) {
101
101
  cancel(a.id)
102
102
  let cal = Calendar.current
103
103
  let base = Self.prefix + a.id
104
104
  let hm = cal.dateComponents([.hour, .minute], from: a.date)
105
105
  var requests: [UNNotificationRequest] = []
106
-
106
+
107
107
  switch a.repeatType {
108
108
  case "daily":
109
109
  var dc = DateComponents()
110
110
  dc.hour = hm.hour
111
111
  dc.minute = hm.minute
112
112
  requests.append(request(base, a, UNCalendarNotificationTrigger(dateMatching: dc, repeats: true)))
113
-
113
+
114
114
  case "weekly":
115
115
  let days = a.weekdays.isEmpty ? Array(1...7) : a.weekdays.filter { (1...7).contains($0) }
116
116
  for d in days {
@@ -120,7 +120,7 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
120
120
  dc.minute = hm.minute
121
121
  requests.append(request(base + ":wd\(d)", a, UNCalendarNotificationTrigger(dateMatching: dc, repeats: true)))
122
122
  }
123
-
123
+
124
124
  case "interval":
125
125
  // iOS repeating interval triggers count from "now", not from `timestamp`, so
126
126
  // pre-schedule the next N exact occurrences instead.
@@ -130,16 +130,16 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
130
130
  requests.append(request(base + ":i\(k)", a, exactTrigger(ms: t)))
131
131
  t += a.intervalMs
132
132
  }
133
-
133
+
134
134
  default:
135
135
  let next = a.nextTrigger()
136
136
  guard next > 0 else { return completion(nil) } // nothing in the future, same as Android
137
137
  requests.append(request(base, a, exactTrigger(ms: next)))
138
138
  }
139
-
139
+
140
140
  add(requests, completion: completion)
141
141
  }
142
-
142
+
143
143
  private func exactTrigger(ms: Double) -> UNCalendarNotificationTrigger {
144
144
  let dc = Calendar.current.dateComponents(
145
145
  [.year, .month, .day, .hour, .minute, .second],
@@ -147,7 +147,7 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
147
147
  )
148
148
  return UNCalendarNotificationTrigger(dateMatching: dc, repeats: false)
149
149
  }
150
-
150
+
151
151
  private func add(_ requests: [UNNotificationRequest], completion: @escaping (Error?) -> Void) {
152
152
  let group = DispatchGroup()
153
153
  var firstError: Error?
@@ -160,7 +160,7 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
160
160
  }
161
161
  group.notify(queue: .main) { completion(firstError) }
162
162
  }
163
-
163
+
164
164
  /// One-off re-ring in `minutes`, equivalent to Android's `AlarmScheduler.scheduleSnooze`.
165
165
  func snooze(_ a: AlarmData, minutes: Int) {
166
166
  center.removeDeliveredNotifications(withIdentifiers: ids(for: a.id))
@@ -168,19 +168,19 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
168
168
  center.add(request(Self.prefix + a.id + ":snooze", a, trigger))
169
169
  if ringing?.id == a.id { ringing = nil }
170
170
  }
171
-
171
+
172
172
  /// Stops the notification's sound (by clearing the delivered notification) while the
173
173
  /// in-app ring screen takes over; `ringing` stays set so the session is still live.
174
174
  func silence(_ id: String) {
175
175
  center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
176
176
  }
177
-
177
+
178
178
  func cancel(_ id: String) {
179
179
  center.removePendingNotificationRequests(withIdentifiers: ids(for: id))
180
180
  center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
181
181
  if ringing?.id == id { ringing = nil }
182
182
  }
183
-
183
+
184
184
  func cancelAll() {
185
185
  center.getPendingNotificationRequests { (requests: [UNNotificationRequest]) in
186
186
  let ours = requests.map { $0.identifier }.filter { $0.hasPrefix(Self.prefix) }
@@ -192,7 +192,7 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
192
192
  }
193
193
  ringing = nil
194
194
  }
195
-
195
+
196
196
  /// Dismiss: clear the shown notification and, like Android, drop a pending snooze.
197
197
  func stop(_ id: String) {
198
198
  center.removeDeliveredNotifications(withIdentifiers: ids(for: id))
@@ -202,14 +202,14 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
202
202
  onEvent?(AlarmModule.eventDismissed, a)
203
203
  }
204
204
  }
205
-
205
+
206
206
  // MARK: - UNUserNotificationCenterDelegate
207
-
207
+
208
208
  private func alarm(from notification: UNNotification) -> AlarmData? {
209
209
  guard let id = notification.request.content.userInfo["alarmId"] as? String else { return nil }
210
210
  return AlarmStore.get(id)
211
211
  }
212
-
212
+
213
213
  func userNotificationCenter(
214
214
  _ center: UNUserNotificationCenter,
215
215
  willPresent notification: UNNotification,
@@ -229,7 +229,7 @@ final class NotificationBackend: NSObject, UNUserNotificationCenterDelegate {
229
229
  // The ring screen takes over; keep the sound but skip the banner when it can show.
230
230
  completionHandler(onEvent == nil ? [.banner, .list, .sound] : [.list, .sound])
231
231
  }
232
-
232
+
233
233
  func userNotificationCenter(
234
234
  _ center: UNUserNotificationCenter,
235
235
  didReceive response: UNNotificationResponse,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-alarms",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "A powerful, cross-platform alarm management library for React Native",
5
5
  "private": false,
6
6
  "main": "index.ts",
@@ -1,97 +1,130 @@
1
1
  import type { TurboModule } from 'react-native';
2
2
  import { NativeModules, Platform, TurboModuleRegistry } from 'react-native';
3
+ import { AlarmPermission, AlarmConfig, AlarmProp } from '../src/types';
3
4
 
4
5
  /**
5
- * Shared contract of the native `Alarm` module.
6
- *
7
- * Both platforms register a legacy-style module named `Alarm` with these exact
8
- * method names (Android: AlarmModule.kt, iOS: ios/alarm/AlarmModule.swift) and
9
- * emit the same events through `NativeEventEmitter`:
10
- * onAlarmFired | onAlarmDismissed | onAlarmSnoozed -> AlarmEvent
6
+ * Native Alarm module interface.
11
7
  */
12
-
13
- /** type: 'none' | 'daily' | 'weekly' | 'interval' */
14
- export type AlarmRepeat = {
15
- type: string;
16
- weekdays?: number[]; // 1 = Sunday ... 7 = Saturday (weekly)
17
- intervalMs?: number; // interval
18
- };
19
-
20
- export type AlarmConfig = {
21
- alarmId?: string;
22
- timestamp: number; // ms since epoch
23
- title: string;
24
- body?: string;
25
- sound?: string; // 'default' or a bundled sound file name
26
- vibrate?: boolean; // Android only (iOS alarms always vibrate per system settings)
27
- snoozeEnabled?: boolean;
28
- snoozeMinutes?: number;
29
- repeat?: AlarmRepeat;
30
- data?: string; // JSON string payload
31
- };
32
-
33
- export type Alarm = {
34
- alarmId: string;
35
- timestamp: number;
36
- title: string;
37
- body: string;
38
- sound: string;
39
- vibrate: boolean;
40
- snoozeEnabled: boolean;
41
- snoozeMinutes: number;
42
- repeat: AlarmRepeat;
43
- data: string;
44
- enabled: boolean;
45
- nextTriggerAt: number; // 0 if none
46
- };
47
-
48
- /** status: 'granted' | 'denied' | 'notDetermined' */
49
- export type AlarmPermission = {
50
- status: string;
51
- notifications: boolean;
52
- canScheduleExact: boolean;
53
- canDrawOverlays: boolean; // Android only; always true on iOS
54
- canUseFullScreenIntent: boolean; // Android only; always true on iOS
55
- backend?: string; // iOS only: 'alarmkit' (iOS 26+) | 'notifications'
56
- };
57
-
58
- export type AlarmEvent = {
59
- alarmId: string;
60
- timestamp: number;
61
- title: string;
62
- body: string;
63
- data: string;
64
- };
65
-
66
8
  export interface Spec extends TurboModule {
67
- // ---------- Permissions ----------
9
+ // ---------------------------------------------------------------------------
10
+ // Permissions
11
+ // ---------------------------------------------------------------------------
12
+
13
+ /**
14
+ * Returns the current permission status.
15
+ */
68
16
  checkPermission(): Promise<AlarmPermission>;
17
+
18
+ /**
19
+ * Requests any required permissions.
20
+ */
69
21
  requestPermission(): Promise<AlarmPermission>;
22
+
23
+ /**
24
+ * Checks whether exact alarms can be scheduled.
25
+ *
26
+ * Android only.
27
+ */
70
28
  canScheduleExactAlarms(): Promise<boolean>;
71
- /** Opens the vendor Autostart screen on Android; resolves false on iOS. */
29
+
30
+ /**
31
+ * Opens the device's Autostart settings screen.
32
+ *
33
+ * Android:
34
+ * Opens the vendor-specific Autostart page when available.
35
+ *
36
+ * iOS:
37
+ * Resolves `false`.
38
+ */
72
39
  openAutoStartSettings(): Promise<boolean>;
73
40
 
74
- // ---------- Scheduling ----------
41
+ // ---------------------------------------------------------------------------
42
+ // Scheduling
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /**
46
+ * Schedules a new alarm.
47
+ *
48
+ * Returns the generated or provided alarm ID.
49
+ */
75
50
  scheduleAlarm(config: AlarmConfig): Promise<string>;
51
+
52
+ /**
53
+ * Updates an existing alarm.
54
+ *
55
+ * Returns `true` if the update succeeds.
56
+ */
76
57
  updateAlarm(alarmId: string, config: AlarmConfig): Promise<boolean>;
58
+
59
+ /**
60
+ * Cancels a specific alarm.
61
+ */
77
62
  cancelAlarm(alarmId: string): Promise<boolean>;
63
+
64
+ /**
65
+ * Cancels every scheduled alarm.
66
+ */
78
67
  cancelAllAlarms(): Promise<void>;
79
68
 
80
- // ---------- Control (while ringing) ----------
69
+ // ---------------------------------------------------------------------------
70
+ // Ringing Controls
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Stops the currently ringing alarm.
75
+ */
81
76
  stopAlarm(alarmId: string): Promise<void>;
77
+
78
+ /**
79
+ * Snoozes the currently ringing alarm.
80
+ *
81
+ * @param alarmId Alarm identifier.
82
+ * @param minutes Snooze duration in minutes.
83
+ */
82
84
  snoozeAlarm(alarmId: string, minutes: number): Promise<void>;
83
- getRingingAlarm(): Promise<Alarm | null>;
84
85
 
85
- // ---------- Queries ----------
86
- getAlarm(alarmId: string): Promise<Alarm | null>;
87
- getAllAlarms(): Promise<Alarm[]>;
86
+ /**
87
+ * Returns the currently ringing alarm, if one exists.
88
+ */
89
+ getRingingAlarm(): Promise<AlarmProp | null>;
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Queries
93
+ // ---------------------------------------------------------------------------
94
+
95
+ /**
96
+ * Returns a scheduled alarm by ID.
97
+ */
98
+ getAlarm(alarmId: string): Promise<AlarmProp | null>;
99
+
100
+ /**
101
+ * Returns every scheduled alarm.
102
+ */
103
+ getAllAlarms(): Promise<AlarmProp[]>;
104
+
105
+ /**
106
+ * Checks whether an alarm is scheduled.
107
+ */
88
108
  isScheduled(alarmId: string): Promise<boolean>;
89
109
 
90
- // ---------- Events (used by NativeEventEmitter) ----------
110
+ // ---------------------------------------------------------------------------
111
+ // NativeEventEmitter Support
112
+ // ---------------------------------------------------------------------------
113
+
114
+ /**
115
+ * Internal listener registration.
116
+ */
91
117
  addListener(eventName: string): void;
118
+
119
+ /**
120
+ * Internal listener cleanup.
121
+ */
92
122
  removeListeners(count: number): void;
93
123
  }
94
124
 
125
+ /**
126
+ * Error shown when the native module is unavailable.
127
+ */
95
128
  const LINKING_ERROR =
96
129
  "react-native-alarms: the native 'Alarm' module is not linked. " +
97
130
  Platform.select({
@@ -99,8 +132,24 @@ const LINKING_ERROR =
99
132
  default: 'Rebuild the app after installing the package.',
100
133
  });
101
134
 
102
- // Both platforms register a legacy module, reachable through either registry
103
- // depending on the architecture. Throw on first use, not on import.
135
+ /**
136
+ * Cross-platform native Alarm module.
137
+ *
138
+ * Supports both the legacy Native Module system and the New Architecture
139
+ * (TurboModules). The module is resolved lazily so importing it does not
140
+ * immediately throw if native linking is missing.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * import Alarm from 'react-native-alarms';
145
+ *
146
+ * const id = await Alarm.scheduleAlarm({
147
+ * timestamp: Date.now() + 60000,
148
+ * title: 'Meeting',
149
+ * body: 'Standup starts now',
150
+ * });
151
+ * ```
152
+ */
104
153
  const Alarm: Spec =
105
154
  TurboModuleRegistry.get<Spec>('Alarm') ??
106
155
  NativeModules.Alarm ??