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.
- package/README.md +607 -0
- package/android/.gradle/7.4.2/checksums/checksums.lock +0 -0
- package/android/.gradle/7.4.2/fileChanges/last-build.bin +0 -0
- package/android/.gradle/7.4.2/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/7.4.2/gc.properties +0 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +15 -0
- package/android/src/main/java/com/rnpersistenttimer/RNPersistentTimerModule.java +164 -0
- package/android/src/main/java/com/rnpersistenttimer/RNPersistentTimerPackage.java +27 -0
- package/android/src/main/java/com/rnpersistenttimer/TimerForegroundService.java +280 -0
- package/ios/RNPersistentTimer.h +10 -0
- package/ios/RNPersistentTimer.m +221 -0
- package/lib/commonjs/NativeTimerModule.js +46 -0
- package/lib/commonjs/NativeTimerModule.js.map +1 -0
- package/lib/commonjs/PersistentTimerManager.js +337 -0
- package/lib/commonjs/PersistentTimerManager.js.map +1 -0
- package/lib/commonjs/index.js +76 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/usePersistentTimer.js +159 -0
- package/lib/commonjs/usePersistentTimer.js.map +1 -0
- package/lib/commonjs/utils.js +112 -0
- package/lib/commonjs/utils.js.map +1 -0
- package/lib/module/NativeTimerModule.js +40 -0
- package/lib/module/NativeTimerModule.js.map +1 -0
- package/lib/module/PersistentTimerManager.js +329 -0
- package/lib/module/PersistentTimerManager.js.map +1 -0
- package/lib/module/index.js +17 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/usePersistentTimer.js +153 -0
- package/lib/module/usePersistentTimer.js.map +1 -0
- package/lib/module/utils.js +100 -0
- package/lib/module/utils.js.map +1 -0
- package/lib/typescript/NativeTimerModule.d.ts +31 -0
- package/lib/typescript/NativeTimerModule.d.ts.map +1 -0
- package/lib/typescript/PersistentTimerManager.d.ts +37 -0
- package/lib/typescript/PersistentTimerManager.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +7 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +167 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/usePersistentTimer.d.ts +16 -0
- package/lib/typescript/usePersistentTimer.d.ts.map +1 -0
- package/lib/typescript/utils.d.ts +36 -0
- package/lib/typescript/utils.d.ts.map +1 -0
- package/package.json +98 -0
- package/src/NativeTimerModule.ts +73 -0
- package/src/PersistentTimerManager.ts +410 -0
- package/src/index.ts +41 -0
- package/src/types.ts +198 -0
- package/src/usePersistentTimer.tsx +173 -0
- 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';
|