rn-persistent-timer 1.1.0

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.
Files changed (55) hide show
  1. package/README.md +607 -0
  2. package/android/.gradle/7.4.2/checksums/checksums.lock +0 -0
  3. package/android/.gradle/7.4.2/fileChanges/last-build.bin +0 -0
  4. package/android/.gradle/7.4.2/fileHashes/fileHashes.lock +0 -0
  5. package/android/.gradle/7.4.2/gc.properties +0 -0
  6. package/android/.gradle/vcs-1/gc.properties +0 -0
  7. package/android/build.gradle +15 -0
  8. package/android/src/main/java/com/rnpersistenttimer/RNPersistentTimerModule.java +164 -0
  9. package/android/src/main/java/com/rnpersistenttimer/RNPersistentTimerPackage.java +27 -0
  10. package/android/src/main/java/com/rnpersistenttimer/TimerForegroundService.java +280 -0
  11. package/ios/RNPersistentTimer.h +10 -0
  12. package/ios/RNPersistentTimer.m +221 -0
  13. package/lib/commonjs/NativeTimerModule.js +46 -0
  14. package/lib/commonjs/NativeTimerModule.js.map +1 -0
  15. package/lib/commonjs/PersistentTimerManager.js +337 -0
  16. package/lib/commonjs/PersistentTimerManager.js.map +1 -0
  17. package/lib/commonjs/index.js +76 -0
  18. package/lib/commonjs/index.js.map +1 -0
  19. package/lib/commonjs/types.js +2 -0
  20. package/lib/commonjs/types.js.map +1 -0
  21. package/lib/commonjs/usePersistentTimer.js +159 -0
  22. package/lib/commonjs/usePersistentTimer.js.map +1 -0
  23. package/lib/commonjs/utils.js +112 -0
  24. package/lib/commonjs/utils.js.map +1 -0
  25. package/lib/module/NativeTimerModule.js +40 -0
  26. package/lib/module/NativeTimerModule.js.map +1 -0
  27. package/lib/module/PersistentTimerManager.js +329 -0
  28. package/lib/module/PersistentTimerManager.js.map +1 -0
  29. package/lib/module/index.js +17 -0
  30. package/lib/module/index.js.map +1 -0
  31. package/lib/module/types.js +2 -0
  32. package/lib/module/types.js.map +1 -0
  33. package/lib/module/usePersistentTimer.js +153 -0
  34. package/lib/module/usePersistentTimer.js.map +1 -0
  35. package/lib/module/utils.js +100 -0
  36. package/lib/module/utils.js.map +1 -0
  37. package/lib/typescript/NativeTimerModule.d.ts +31 -0
  38. package/lib/typescript/NativeTimerModule.d.ts.map +1 -0
  39. package/lib/typescript/PersistentTimerManager.d.ts +37 -0
  40. package/lib/typescript/PersistentTimerManager.d.ts.map +1 -0
  41. package/lib/typescript/index.d.ts +7 -0
  42. package/lib/typescript/index.d.ts.map +1 -0
  43. package/lib/typescript/types.d.ts +167 -0
  44. package/lib/typescript/types.d.ts.map +1 -0
  45. package/lib/typescript/usePersistentTimer.d.ts +16 -0
  46. package/lib/typescript/usePersistentTimer.d.ts.map +1 -0
  47. package/lib/typescript/utils.d.ts +36 -0
  48. package/lib/typescript/utils.d.ts.map +1 -0
  49. package/package.json +98 -0
  50. package/src/NativeTimerModule.ts +73 -0
  51. package/src/PersistentTimerManager.ts +410 -0
  52. package/src/index.ts +41 -0
  53. package/src/types.ts +198 -0
  54. package/src/usePersistentTimer.tsx +173 -0
  55. package/src/utils.ts +91 -0
@@ -0,0 +1,31 @@
1
+ import { NativeEventEmitter } from 'react-native';
2
+ export interface NativeStartOptions {
3
+ timerId: string;
4
+ mode: string;
5
+ duration: number;
6
+ elapsed: number;
7
+ startedAt: number;
8
+ showNotification: boolean;
9
+ notification: Record<string, unknown>;
10
+ }
11
+ export interface INativeTimerModule {
12
+ startTimer(options: NativeStartOptions): Promise<void>;
13
+ stopTimer(timerId: string): Promise<void>;
14
+ getElapsed(timerId: string): Promise<number>;
15
+ cancelAll(): Promise<void>;
16
+ getActiveTimers(): Promise<string[]>;
17
+ isKilledStateSupported(): Promise<boolean>;
18
+ }
19
+ export declare const NativeTimerModule: INativeTimerModule | null;
20
+ export declare const NativeTimerEmitter: NativeEventEmitter | null;
21
+ export declare const NATIVE_EVENTS: {
22
+ readonly TICK: "RNPersistentTimer_tick";
23
+ readonly COMPLETE: "RNPersistentTimer_complete";
24
+ readonly ERROR: "RNPersistentTimer_error";
25
+ readonly BACKGROUND: "RNPersistentTimer_background";
26
+ readonly FOREGROUND: "RNPersistentTimer_foreground";
27
+ };
28
+ export type NativeEventName = (typeof NATIVE_EVENTS)[keyof typeof NATIVE_EVENTS];
29
+ export declare const isAndroid: boolean;
30
+ export declare const isIOS: boolean;
31
+ //# sourceMappingURL=NativeTimerModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeTimerModule.d.ts","sourceRoot":"","sources":["../../src/NativeTimerModule.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,kBAAkB,EAAY,MAAM,cAAc,CAAC;AAe3E,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAID,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5C;AAED,eAAO,MAAM,iBAAiB,EAAE,kBAAkB,GAAG,IAU7C,CAAC;AAIT,eAAO,MAAM,kBAAkB,EAAE,kBAAkB,GAAG,IAE9C,CAAC;AAET,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAC;AAEX,MAAM,MAAM,eAAe,GACzB,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAIrD,eAAO,MAAM,SAAS,SAA4B,CAAC;AACnD,eAAO,MAAM,KAAK,SAAwB,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { TimerConfig, TimerSnapshot, TimerCallbacks, RestoredTimerData } from './types';
2
+ type EventName = keyof TimerCallbacks | 'stop' | 'error';
3
+ export declare class PersistentTimerManager {
4
+ private _config;
5
+ private _state;
6
+ private _elapsed;
7
+ private _startedAt;
8
+ private _pausedAt;
9
+ private _tickInterval;
10
+ private _handlers;
11
+ private _appState;
12
+ private _appStateSubscription;
13
+ constructor(config: TimerConfig);
14
+ start(): void;
15
+ pause(): void;
16
+ resume(): void;
17
+ stop(): void;
18
+ reset(): void;
19
+ getSnapshot(): TimerSnapshot;
20
+ destroy(): void;
21
+ on(event: EventName, handler: Function): void;
22
+ off(event: EventName, handler: Function): void;
23
+ private _startJSTick;
24
+ private _stopJSTick;
25
+ private _onTick;
26
+ private _getElapsed;
27
+ private _listenAppState;
28
+ private _startNativeBackground;
29
+ private _stopNativeBackground;
30
+ private _syncFromNative;
31
+ private _persistState;
32
+ private _clearPersistedState;
33
+ static restore(timerId: string): Promise<RestoredTimerData | null>;
34
+ private _emit;
35
+ }
36
+ export {};
37
+ //# sourceMappingURL=PersistentTimerManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PersistentTimerManager.d.ts","sourceRoot":"","sources":["../../src/PersistentTimerManager.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,WAAW,EAGX,aAAa,EACb,cAAc,EAEd,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAIjB,KAAK,SAAS,GAAG,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,CAAC;AAKzD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,aAAa,CAA+C;IACpE,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,qBAAqB,CAEb;gBAEJ,MAAM,EAAE,WAAW;IAiB/B,KAAK,IAAI,IAAI;IA2Bb,KAAK,IAAI,IAAI;IAiBb,MAAM,IAAI,IAAI;IAkBd,IAAI,IAAI,IAAI;IAWZ,KAAK,IAAI,IAAI;IAWb,WAAW,IAAI,aAAa;IAyB5B,OAAO,IAAI,IAAI;IASf,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI;IAO7C,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI;IAS9C,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,OAAO;IAef,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,eAAe;IA+CvB,OAAO,CAAC,sBAAsB;IAmB9B,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,eAAe;YAkBT,aAAa;YAoBb,oBAAoB;WAYrB,OAAO,CAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAwCpC,OAAO,CAAC,KAAK;CAad"}
@@ -0,0 +1,7 @@
1
+ export { usePersistentTimer } from './usePersistentTimer';
2
+ export { PersistentTimerManager } from './PersistentTimerManager';
3
+ export { formatTime, parseTime, isBackgroundTimerSupported, isKilledStateTimerSupported, getActiveTimers, cancelAllTimers, } from './utils';
4
+ export type { TimerMode, TimerState, AppState, TimerConfig, AndroidNotificationConfig, TimerSnapshot, TimerCallbacks, PersistentTimerControls, UsePersistentTimerReturn, PersistedTimerRecord, RestoredTimerData, } from './types';
5
+ export { NativeTimerModule, NativeTimerEmitter, NATIVE_EVENTS, } from './NativeTimerModule';
6
+ export type { INativeTimerModule, NativeStartOptions, NativeEventName } from './NativeTimerModule';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAGlE,OAAO,EACL,UAAU,EACV,SAAS,EACT,0BAA0B,EAC1B,2BAA2B,EAC3B,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC;AAGjB,YAAY,EACV,SAAS,EACT,UAAU,EACV,QAAQ,EACR,WAAW,EACX,yBAAyB,EACzB,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,167 @@
1
+ export type TimerMode = 'countdown' | 'stopwatch';
2
+ export type TimerState = 'idle' | 'running' | 'paused' | 'completed';
3
+ export type AppState = 'foreground' | 'background' | 'killed';
4
+ export interface AndroidNotificationConfig {
5
+ /** Notification title shown in status bar */
6
+ title?: string;
7
+ /** Notification body. Use {time} as a placeholder for the current timer value */
8
+ body?: string;
9
+ /** Android notification channel ID */
10
+ channelId?: string;
11
+ /** Android notification channel name */
12
+ channelName?: string;
13
+ /** Drawable resource name for the notification icon */
14
+ icon?: string;
15
+ /** Accent color (hex, e.g. '#FF5733') */
16
+ color?: string;
17
+ /** Show elapsed / remaining time in the notification */
18
+ showTime?: boolean;
19
+ /** Add pause / resume action buttons to the notification */
20
+ showActions?: boolean;
21
+ }
22
+ export interface TimerConfig {
23
+ /**
24
+ * Unique ID for this timer instance. Required.
25
+ * Used as the key for persistence and native interop.
26
+ */
27
+ timerId: string;
28
+ /**
29
+ * Timer mode.
30
+ * - `'countdown'` — counts down from `duration` to 0.
31
+ * - `'stopwatch'` — counts up from 0.
32
+ * @default 'stopwatch'
33
+ */
34
+ mode?: TimerMode;
35
+ /**
36
+ * Duration in seconds. Required when `mode === 'countdown'`.
37
+ */
38
+ duration?: number;
39
+ /**
40
+ * Keep the timer running when the app goes to the **background**.
41
+ * On Android this starts a Foreground Service; on iOS it uses
42
+ * `UIBackgroundTaskIdentifier` + `BGTaskScheduler`.
43
+ * @default true
44
+ */
45
+ runInBackground?: boolean;
46
+ /**
47
+ * Keep the timer alive even after the app is **killed** from the
48
+ * task manager. State is persisted via AsyncStorage and restored
49
+ * when the app is re-opened.
50
+ * Requires additional native setup — see README.
51
+ * @default false
52
+ */
53
+ runInKilledState?: boolean;
54
+ /**
55
+ * Tick interval in milliseconds.
56
+ * @default 1000
57
+ */
58
+ interval?: number;
59
+ /**
60
+ * Show a persistent notification while the timer is active (Android).
61
+ * Required for reliable background execution on Android 8+.
62
+ * @default true (when runInBackground is true)
63
+ */
64
+ showNotification?: boolean;
65
+ /** Android notification customisation. */
66
+ notification?: AndroidNotificationConfig;
67
+ /**
68
+ * Automatically pause when the app moves to the background.
69
+ * Overrides `runInBackground` when `true`.
70
+ * @default false
71
+ */
72
+ pauseOnBackground?: boolean;
73
+ /**
74
+ * Automatically reset when the app returns to the foreground from a
75
+ * killed state.
76
+ * @default false
77
+ */
78
+ resetOnForeground?: boolean;
79
+ }
80
+ export interface TimerSnapshot {
81
+ /** Timer ID */
82
+ timerId: string;
83
+ /** Elapsed seconds since the timer last started */
84
+ elapsed: number;
85
+ /** Remaining seconds — countdown mode only, otherwise `null` */
86
+ remaining: number | null;
87
+ /** Current timer state */
88
+ state: TimerState;
89
+ /** Current app state at the time the snapshot was taken */
90
+ appState: AppState;
91
+ /** Unix-ms timestamp when the timer was (last) started */
92
+ startedAt: number | null;
93
+ /** Unix-ms timestamp when the timer was paused */
94
+ pausedAt: number | null;
95
+ /** Elapsed time formatted as `HH:MM:SS` */
96
+ formattedElapsed: string;
97
+ /** Remaining time formatted as `HH:MM:SS` — countdown only, otherwise `null` */
98
+ formattedRemaining: string | null;
99
+ /** Progress from 0 to 1 — countdown only, otherwise `null` */
100
+ progress: number | null;
101
+ }
102
+ export interface TimerCallbacks {
103
+ /** Fires every tick (based on `interval`). */
104
+ onTick?: (snapshot: TimerSnapshot) => void;
105
+ /** Fires when the timer starts. */
106
+ onStart?: (snapshot: TimerSnapshot) => void;
107
+ /** Fires when the timer is paused. */
108
+ onPause?: (snapshot: TimerSnapshot) => void;
109
+ /** Fires when the timer is resumed. */
110
+ onResume?: (snapshot: TimerSnapshot) => void;
111
+ /** Fires when the timer is reset. */
112
+ onReset?: (snapshot: TimerSnapshot) => void;
113
+ /** Fires when a countdown timer reaches zero. */
114
+ onComplete?: (snapshot: TimerSnapshot) => void;
115
+ /** Fires when the app moves to the background. */
116
+ onBackground?: (snapshot: TimerSnapshot) => void;
117
+ /** Fires when the app returns to the foreground. */
118
+ onForeground?: (snapshot: TimerSnapshot) => void;
119
+ /** Fires when timer state is restored after an app kill. */
120
+ onRestore?: (snapshot: TimerSnapshot) => void;
121
+ /** Fires on any internal error. */
122
+ onError?: (error: Error) => void;
123
+ }
124
+ export interface PersistentTimerControls {
125
+ /** Start (or restart from 0) the timer. */
126
+ start: () => void;
127
+ /** Pause the timer. */
128
+ pause: () => void;
129
+ /** Resume from a paused state. */
130
+ resume: () => void;
131
+ /** Stop the timer and reset elapsed to 0. */
132
+ reset: () => void;
133
+ /** Stop the timer without resetting elapsed time. */
134
+ stop: () => void;
135
+ /** Get the current snapshot synchronously. */
136
+ getSnapshot: () => TimerSnapshot;
137
+ /** Destroy the timer and remove all listeners. */
138
+ destroy: () => void;
139
+ }
140
+ export interface UsePersistentTimerReturn extends PersistentTimerControls {
141
+ /** Latest reactive timer snapshot. */
142
+ snapshot: TimerSnapshot;
143
+ /** `true` when `state === 'running'`. */
144
+ isRunning: boolean;
145
+ /** `true` when `state === 'paused'`. */
146
+ isPaused: boolean;
147
+ /** `true` when `state === 'completed'` (countdown). */
148
+ isCompleted: boolean;
149
+ /** Current app state. */
150
+ appState: AppState;
151
+ }
152
+ export interface PersistedTimerRecord {
153
+ timerId: string;
154
+ config: Required<TimerConfig>;
155
+ state: TimerState;
156
+ elapsed: number;
157
+ startedAt: number | null;
158
+ pausedAt: number | null;
159
+ savedAt: number;
160
+ }
161
+ export interface RestoredTimerData {
162
+ config: Required<TimerConfig>;
163
+ state: TimerState;
164
+ elapsed: number;
165
+ secondsLost: number;
166
+ }
167
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { TimerConfig, TimerCallbacks, UsePersistentTimerReturn } from './types';
2
+ /**
3
+ * React hook for persistent timers.
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * const { snapshot, start, pause, reset, isRunning } = usePersistentTimer({
8
+ * timerId: 'workout-timer',
9
+ * mode: 'stopwatch',
10
+ * runInBackground: true,
11
+ * onTick: (snap) => console.log(snap.formattedElapsed),
12
+ * });
13
+ * ```
14
+ */
15
+ export declare function usePersistentTimer(config: TimerConfig & TimerCallbacks): UsePersistentTimerReturn;
16
+ //# sourceMappingURL=usePersistentTimer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePersistentTimer.d.ts","sourceRoot":"","sources":["../../src/usePersistentTimer.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EAGd,wBAAwB,EACzB,MAAM,SAAS,CAAC;AAuBjB;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,GAAG,cAAc,GACnC,wBAAwB,CA0H1B"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Format a number of seconds into an `HH:MM:SS` string.
3
+ *
4
+ * @example
5
+ * formatTime(3661); // '01:01:01'
6
+ * formatTime(59); // '00:00:59'
7
+ */
8
+ export declare function formatTime(seconds: number): string;
9
+ /**
10
+ * Parse an `HH:MM:SS` or `MM:SS` string into total seconds.
11
+ *
12
+ * @example
13
+ * parseTime('01:30:00'); // 5400
14
+ * parseTime('05:30'); // 330
15
+ */
16
+ export declare function parseTime(formatted: string): number;
17
+ /**
18
+ * Returns `true` if the native background-timer module is available on this
19
+ * device / build. Always returns `false` in Expo Go.
20
+ */
21
+ export declare function isBackgroundTimerSupported(): Promise<boolean>;
22
+ /**
23
+ * Returns `true` if the killed-state persistence feature is supported.
24
+ * Requires the native module **and** platform capabilities
25
+ * (WorkManager on Android, BGTaskScheduler on iOS 13+).
26
+ */
27
+ export declare function isKilledStateTimerSupported(): Promise<boolean>;
28
+ /**
29
+ * Return the IDs of all timers currently active in the native layer.
30
+ */
31
+ export declare function getActiveTimers(): Promise<string[]>;
32
+ /**
33
+ * Cancel every active timer in both the native layer and JS.
34
+ */
35
+ export declare function cancelAllTimers(): Promise<void>;
36
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CASnD;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC,CAGnE;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,OAAO,CAAC,CAUpE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAUzD;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAUrD"}
package/package.json ADDED
@@ -0,0 +1,98 @@
1
+ {
2
+ "name": "rn-persistent-timer",
3
+ "version": "1.1.0",
4
+ "description": "React Native timers that keep running in the foreground, background, and even after the app is killed — with TypeScript support.",
5
+ "main": "lib/commonjs/index.js",
6
+ "module": "lib/module/index.js",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "react-native": "src/index.ts",
9
+ "source": "src/index.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./lib/module/index.js",
13
+ "require": "./lib/commonjs/index.js",
14
+ "types": "./lib/typescript/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "src/",
19
+ "lib/",
20
+ "android/",
21
+ "ios/",
22
+ "*.podspec",
23
+ "README.md",
24
+ "CHANGELOG.md"
25
+ ],
26
+ "scripts": {
27
+ "test": "jest",
28
+ "typecheck": "tsc --noEmit",
29
+ "lint": "eslint \"src/**/*.{ts,tsx}\" \"example/**/*.{ts,tsx}\"",
30
+ "build": "bob build",
31
+ "prepare": "bob build"
32
+ },
33
+ "keywords": [
34
+ "react-native",
35
+ "timer",
36
+ "background-timer",
37
+ "persistent-timer",
38
+ "foreground-service",
39
+ "background-task",
40
+ "countdown",
41
+ "stopwatch",
42
+ "headless-js",
43
+ "ios-background",
44
+ "android-foreground-service",
45
+ "typescript",
46
+ "react-native-timer",
47
+ "killed-state",
48
+ "bg-timer"
49
+ ],
50
+ "author": "Vipin Jaiswal",
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "https://github.com/vipin208/rn-persistent-timer"
55
+ },
56
+ "bugs": {
57
+ "url": "https://github.com/vipin208/rn-persistent-timer/issues"
58
+ },
59
+ "homepage": "https://github.com/vipin208/rn-persistent-timer#readme",
60
+ "peerDependencies": {
61
+ "@react-native-async-storage/async-storage": ">=1.18.0",
62
+ "react": ">=18.0.0",
63
+ "react-native": ">=0.71.0"
64
+ },
65
+ "devDependencies": {
66
+ "@react-native-async-storage/async-storage": "^1.23.1",
67
+ "@react-native-community/eslint-config": "^3.2.0",
68
+ "@tsconfig/react-native": "^3.0.0",
69
+ "@types/react": "^18.2.0",
70
+ "@types/react-native": "^0.73.0",
71
+ "eslint": "^8.57.0",
72
+ "jest": "^29.7.0",
73
+ "react": "18.3.1",
74
+ "react-native": "0.75.4",
75
+ "react-native-builder-bob": "^0.23.2",
76
+ "typescript": "^5.4.0"
77
+ },
78
+ "react-native-builder-bob": {
79
+ "source": "src",
80
+ "output": "lib",
81
+ "targets": [
82
+ "commonjs",
83
+ "module",
84
+ [
85
+ "typescript",
86
+ {
87
+ "project": "tsconfig.build.json"
88
+ }
89
+ ]
90
+ ]
91
+ },
92
+ "jest": {
93
+ "preset": "react-native",
94
+ "modulePathIgnorePatterns": [
95
+ "<rootDir>/lib/"
96
+ ]
97
+ }
98
+ }
@@ -0,0 +1,73 @@
1
+ // rn-persistent-timer — Native Bridge
2
+ // Bridges TypeScript ↔ Android (ForegroundService) & iOS (BGTaskScheduler)
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
6
+
7
+ const { RNPersistentTimer } = NativeModules;
8
+
9
+ if (__DEV__ && !RNPersistentTimer) {
10
+ console.warn(
11
+ '[rn-persistent-timer] Native module not found.\n' +
12
+ 'iOS → run `npx pod-install` then rebuild.\n' +
13
+ 'Android → rebuild the Android project.\n' +
14
+ 'Foreground (JS-only) timers still work; background/kill-state features are disabled.',
15
+ );
16
+ }
17
+
18
+ // ─── Start options passed to the native layer ────────────────────────────────
19
+
20
+ export interface NativeStartOptions {
21
+ timerId: string;
22
+ mode: string;
23
+ duration: number;
24
+ elapsed: number;
25
+ startedAt: number;
26
+ showNotification: boolean;
27
+ notification: Record<string, unknown>;
28
+ }
29
+
30
+ // ─── Module interface ─────────────────────────────────────────────────────────
31
+
32
+ export interface INativeTimerModule {
33
+ startTimer(options: NativeStartOptions): Promise<void>;
34
+ stopTimer(timerId: string): Promise<void>;
35
+ getElapsed(timerId: string): Promise<number>;
36
+ cancelAll(): Promise<void>;
37
+ getActiveTimers(): Promise<string[]>;
38
+ isKilledStateSupported(): Promise<boolean>;
39
+ }
40
+
41
+ export const NativeTimerModule: INativeTimerModule | null = RNPersistentTimer
42
+ ? {
43
+ startTimer: (options: NativeStartOptions) =>
44
+ RNPersistentTimer.startTimer(options),
45
+ stopTimer: (timerId: string) => RNPersistentTimer.stopTimer(timerId),
46
+ getElapsed: (timerId: string) => RNPersistentTimer.getElapsed(timerId),
47
+ cancelAll: () => RNPersistentTimer.cancelAll(),
48
+ getActiveTimers: () => RNPersistentTimer.getActiveTimers(),
49
+ isKilledStateSupported: () => RNPersistentTimer.isKilledStateSupported(),
50
+ }
51
+ : null;
52
+
53
+ // ─── Native event emitter (optional background tick push) ────────────────────
54
+
55
+ export const NativeTimerEmitter: NativeEventEmitter | null = RNPersistentTimer
56
+ ? new NativeEventEmitter(RNPersistentTimer)
57
+ : null;
58
+
59
+ export const NATIVE_EVENTS = {
60
+ TICK: 'RNPersistentTimer_tick',
61
+ COMPLETE: 'RNPersistentTimer_complete',
62
+ ERROR: 'RNPersistentTimer_error',
63
+ BACKGROUND: 'RNPersistentTimer_background',
64
+ FOREGROUND: 'RNPersistentTimer_foreground',
65
+ } as const;
66
+
67
+ export type NativeEventName =
68
+ (typeof NATIVE_EVENTS)[keyof typeof NATIVE_EVENTS];
69
+
70
+ // ─── Platform helpers ─────────────────────────────────────────────────────────
71
+
72
+ export const isAndroid = Platform.OS === 'android';
73
+ export const isIOS = Platform.OS === 'ios';