@rajeev02/notify 0.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/lib/inbox/index.d.ts +75 -0
- package/lib/inbox/index.d.ts.map +1 -0
- package/lib/inbox/index.js +160 -0
- package/lib/inbox/index.js.map +1 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +17 -0
- package/lib/index.js.map +1 -0
- package/lib/scheduler/index.d.ts +132 -0
- package/lib/scheduler/index.d.ts.map +1 -0
- package/lib/scheduler/index.js +143 -0
- package/lib/scheduler/index.js.map +1 -0
- package/package.json +51 -0
- package/src/inbox/index.ts +179 -0
- package/src/index.ts +21 -0
- package/src/scheduler/index.ts +236 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rajeev02/notify - In-App Inbox
|
|
3
|
+
*
|
|
4
|
+
* Persistent in-app notification inbox with read/unread tracking,
|
|
5
|
+
* categories, and mark-all-read support.
|
|
6
|
+
*/
|
|
7
|
+
export interface InboxMessage {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
body: string;
|
|
11
|
+
category?: string;
|
|
12
|
+
imageUrl?: string;
|
|
13
|
+
deepLink?: string;
|
|
14
|
+
data?: Record<string, unknown>;
|
|
15
|
+
read: boolean;
|
|
16
|
+
archived: boolean;
|
|
17
|
+
receivedAt: number;
|
|
18
|
+
expiresAt?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* In-App Notification Inbox
|
|
22
|
+
*/
|
|
23
|
+
export declare class NotificationInbox {
|
|
24
|
+
private messages;
|
|
25
|
+
private maxMessages;
|
|
26
|
+
private onChangeCallbacks;
|
|
27
|
+
constructor(maxMessages?: number);
|
|
28
|
+
/**
|
|
29
|
+
* Add a message to the inbox
|
|
30
|
+
*/
|
|
31
|
+
add(message: Omit<InboxMessage, 'read' | 'archived' | 'receivedAt'>): InboxMessage;
|
|
32
|
+
/**
|
|
33
|
+
* Mark a message as read
|
|
34
|
+
*/
|
|
35
|
+
markRead(id: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Mark all messages as read
|
|
38
|
+
*/
|
|
39
|
+
markAllRead(): number;
|
|
40
|
+
/**
|
|
41
|
+
* Archive a message (soft delete)
|
|
42
|
+
*/
|
|
43
|
+
archive(id: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Delete a message permanently
|
|
46
|
+
*/
|
|
47
|
+
delete(id: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Get all non-archived messages, newest first
|
|
50
|
+
*/
|
|
51
|
+
getAll(category?: string): InboxMessage[];
|
|
52
|
+
/**
|
|
53
|
+
* Get unread count
|
|
54
|
+
*/
|
|
55
|
+
getUnreadCount(category?: string): number;
|
|
56
|
+
/**
|
|
57
|
+
* Get all unique categories
|
|
58
|
+
*/
|
|
59
|
+
getCategories(): string[];
|
|
60
|
+
/**
|
|
61
|
+
* Subscribe to inbox changes
|
|
62
|
+
*/
|
|
63
|
+
onChange(callback: () => void): () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Clear all messages
|
|
66
|
+
*/
|
|
67
|
+
clear(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Remove expired messages
|
|
70
|
+
*/
|
|
71
|
+
cleanup(): number;
|
|
72
|
+
private enforceLimit;
|
|
73
|
+
private notifyChange;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/inbox/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAA8B;gBAE3C,WAAW,GAAE,MAAY;IAIrC;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC,GAAG,YAAY;IAclF;;OAEG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAU7B;;OAEG;IACH,WAAW,IAAI,MAAM;IAYrB;;OAEG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAU5B;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAM3B;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE;IASzC;;OAEG;IACH,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAIzC;;OAEG;IACH,aAAa,IAAI,MAAM,EAAE;IAQzB;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAK1C;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,OAAO,IAAI,MAAM;IAajB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,YAAY;CAKrB"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @rajeev02/notify - In-App Inbox
|
|
4
|
+
*
|
|
5
|
+
* Persistent in-app notification inbox with read/unread tracking,
|
|
6
|
+
* categories, and mark-all-read support.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.NotificationInbox = void 0;
|
|
10
|
+
/**
|
|
11
|
+
* In-App Notification Inbox
|
|
12
|
+
*/
|
|
13
|
+
class NotificationInbox {
|
|
14
|
+
constructor(maxMessages = 100) {
|
|
15
|
+
this.messages = new Map();
|
|
16
|
+
this.onChangeCallbacks = new Set();
|
|
17
|
+
this.maxMessages = maxMessages;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Add a message to the inbox
|
|
21
|
+
*/
|
|
22
|
+
add(message) {
|
|
23
|
+
const full = {
|
|
24
|
+
...message,
|
|
25
|
+
read: false,
|
|
26
|
+
archived: false,
|
|
27
|
+
receivedAt: Date.now(),
|
|
28
|
+
};
|
|
29
|
+
this.messages.set(message.id, full);
|
|
30
|
+
this.enforceLimit();
|
|
31
|
+
this.notifyChange();
|
|
32
|
+
return full;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Mark a message as read
|
|
36
|
+
*/
|
|
37
|
+
markRead(id) {
|
|
38
|
+
const msg = this.messages.get(id);
|
|
39
|
+
if (msg) {
|
|
40
|
+
msg.read = true;
|
|
41
|
+
this.notifyChange();
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Mark all messages as read
|
|
48
|
+
*/
|
|
49
|
+
markAllRead() {
|
|
50
|
+
let count = 0;
|
|
51
|
+
for (const msg of this.messages.values()) {
|
|
52
|
+
if (!msg.read) {
|
|
53
|
+
msg.read = true;
|
|
54
|
+
count++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (count > 0)
|
|
58
|
+
this.notifyChange();
|
|
59
|
+
return count;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Archive a message (soft delete)
|
|
63
|
+
*/
|
|
64
|
+
archive(id) {
|
|
65
|
+
const msg = this.messages.get(id);
|
|
66
|
+
if (msg) {
|
|
67
|
+
msg.archived = true;
|
|
68
|
+
this.notifyChange();
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Delete a message permanently
|
|
75
|
+
*/
|
|
76
|
+
delete(id) {
|
|
77
|
+
const result = this.messages.delete(id);
|
|
78
|
+
if (result)
|
|
79
|
+
this.notifyChange();
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get all non-archived messages, newest first
|
|
84
|
+
*/
|
|
85
|
+
getAll(category) {
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
return Array.from(this.messages.values())
|
|
88
|
+
.filter(m => !m.archived)
|
|
89
|
+
.filter(m => !m.expiresAt || m.expiresAt > now)
|
|
90
|
+
.filter(m => !category || m.category === category)
|
|
91
|
+
.sort((a, b) => b.receivedAt - a.receivedAt);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get unread count
|
|
95
|
+
*/
|
|
96
|
+
getUnreadCount(category) {
|
|
97
|
+
return this.getAll(category).filter(m => !m.read).length;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get all unique categories
|
|
101
|
+
*/
|
|
102
|
+
getCategories() {
|
|
103
|
+
const cats = new Set();
|
|
104
|
+
for (const msg of this.messages.values()) {
|
|
105
|
+
if (msg.category)
|
|
106
|
+
cats.add(msg.category);
|
|
107
|
+
}
|
|
108
|
+
return Array.from(cats);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Subscribe to inbox changes
|
|
112
|
+
*/
|
|
113
|
+
onChange(callback) {
|
|
114
|
+
this.onChangeCallbacks.add(callback);
|
|
115
|
+
return () => this.onChangeCallbacks.delete(callback);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Clear all messages
|
|
119
|
+
*/
|
|
120
|
+
clear() {
|
|
121
|
+
this.messages.clear();
|
|
122
|
+
this.notifyChange();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Remove expired messages
|
|
126
|
+
*/
|
|
127
|
+
cleanup() {
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
let removed = 0;
|
|
130
|
+
for (const [id, msg] of this.messages) {
|
|
131
|
+
if (msg.expiresAt && msg.expiresAt <= now) {
|
|
132
|
+
this.messages.delete(id);
|
|
133
|
+
removed++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (removed > 0)
|
|
137
|
+
this.notifyChange();
|
|
138
|
+
return removed;
|
|
139
|
+
}
|
|
140
|
+
enforceLimit() {
|
|
141
|
+
if (this.messages.size > this.maxMessages) {
|
|
142
|
+
const sorted = Array.from(this.messages.entries())
|
|
143
|
+
.sort(([, a], [, b]) => a.receivedAt - b.receivedAt);
|
|
144
|
+
const toRemove = sorted.slice(0, this.messages.size - this.maxMessages);
|
|
145
|
+
for (const [id] of toRemove) {
|
|
146
|
+
this.messages.delete(id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
notifyChange() {
|
|
151
|
+
for (const cb of this.onChangeCallbacks) {
|
|
152
|
+
try {
|
|
153
|
+
cb();
|
|
154
|
+
}
|
|
155
|
+
catch (_) { /* ignore */ }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.NotificationInbox = NotificationInbox;
|
|
160
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/inbox/index.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rajeev02/notify
|
|
3
|
+
*
|
|
4
|
+
* Unified Notification & Messaging Layer
|
|
5
|
+
* Cross-platform: Android, iOS, Web, Watch, Auto
|
|
6
|
+
*
|
|
7
|
+
* @author Rajeev Kumar Joshi (https://github.com/Rajeev02)
|
|
8
|
+
* @license MIT
|
|
9
|
+
*/
|
|
10
|
+
export { NotificationScheduler } from './scheduler';
|
|
11
|
+
export type { NotificationConfig, NotificationAction, ScheduleConfig, PlatformOverrides, NotificationGroup, } from './scheduler';
|
|
12
|
+
export { NotificationInbox } from './inbox';
|
|
13
|
+
export type { InboxMessage } from './inbox';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @rajeev02/notify
|
|
4
|
+
*
|
|
5
|
+
* Unified Notification & Messaging Layer
|
|
6
|
+
* Cross-platform: Android, iOS, Web, Watch, Auto
|
|
7
|
+
*
|
|
8
|
+
* @author Rajeev Kumar Joshi (https://github.com/Rajeev02)
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.NotificationInbox = exports.NotificationScheduler = void 0;
|
|
13
|
+
var scheduler_1 = require("./scheduler");
|
|
14
|
+
Object.defineProperty(exports, "NotificationScheduler", { enumerable: true, get: function () { return scheduler_1.NotificationScheduler; } });
|
|
15
|
+
var inbox_1 = require("./inbox");
|
|
16
|
+
Object.defineProperty(exports, "NotificationInbox", { enumerable: true, get: function () { return inbox_1.NotificationInbox; } });
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,yCAAoD;AAA3C,kHAAA,qBAAqB,OAAA;AAS9B,iCAA4C;AAAnC,0GAAA,iBAAiB,OAAA"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rajeev02/notify - Notification Scheduler
|
|
3
|
+
*
|
|
4
|
+
* Schedule, group, deduplicate, and manage notifications across platforms.
|
|
5
|
+
*/
|
|
6
|
+
export interface NotificationConfig {
|
|
7
|
+
/** Unique notification ID */
|
|
8
|
+
id: string;
|
|
9
|
+
/** Notification title */
|
|
10
|
+
title: string;
|
|
11
|
+
/** Notification body text */
|
|
12
|
+
body: string;
|
|
13
|
+
/** Optional subtitle (iOS) */
|
|
14
|
+
subtitle?: string;
|
|
15
|
+
/** Group/channel ID for grouping */
|
|
16
|
+
groupId?: string;
|
|
17
|
+
/** Priority: 'low' | 'normal' | 'high' | 'critical' */
|
|
18
|
+
priority?: 'low' | 'normal' | 'high' | 'critical';
|
|
19
|
+
/** Optional data payload (JSON) */
|
|
20
|
+
data?: Record<string, unknown>;
|
|
21
|
+
/** Action buttons */
|
|
22
|
+
actions?: NotificationAction[];
|
|
23
|
+
/** Schedule for future delivery */
|
|
24
|
+
schedule?: ScheduleConfig;
|
|
25
|
+
/** Platform-specific overrides */
|
|
26
|
+
platform?: PlatformOverrides;
|
|
27
|
+
}
|
|
28
|
+
export interface NotificationAction {
|
|
29
|
+
id: string;
|
|
30
|
+
title: string;
|
|
31
|
+
/** Whether tapping opens the app */
|
|
32
|
+
opensApp?: boolean;
|
|
33
|
+
/** Whether this is a destructive action (shown in red) */
|
|
34
|
+
destructive?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface ScheduleConfig {
|
|
37
|
+
/** Deliver at specific timestamp (ISO string) */
|
|
38
|
+
at?: string;
|
|
39
|
+
/** Deliver after delay in milliseconds */
|
|
40
|
+
afterMs?: number;
|
|
41
|
+
/** Repeat interval: 'minute' | 'hour' | 'day' | 'week' */
|
|
42
|
+
repeat?: 'minute' | 'hour' | 'day' | 'week';
|
|
43
|
+
/** Allow delivery during quiet hours (default: false) */
|
|
44
|
+
ignoreQuietHours?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface PlatformOverrides {
|
|
47
|
+
android?: {
|
|
48
|
+
channelId?: string;
|
|
49
|
+
smallIcon?: string;
|
|
50
|
+
color?: string;
|
|
51
|
+
ongoing?: boolean;
|
|
52
|
+
autoCancel?: boolean;
|
|
53
|
+
};
|
|
54
|
+
ios?: {
|
|
55
|
+
sound?: string;
|
|
56
|
+
badge?: number;
|
|
57
|
+
categoryId?: string;
|
|
58
|
+
threadId?: string;
|
|
59
|
+
};
|
|
60
|
+
watch?: {
|
|
61
|
+
/** Compact format for watch display */
|
|
62
|
+
shortTitle?: string;
|
|
63
|
+
shortBody?: string;
|
|
64
|
+
};
|
|
65
|
+
auto?: {
|
|
66
|
+
/** TTS-friendly text for Android Auto */
|
|
67
|
+
ttsText?: string;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export interface NotificationGroup {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
/** Default importance: 'min' | 'low' | 'default' | 'high' | 'max' */
|
|
75
|
+
importance?: string;
|
|
76
|
+
/** Whether notifications in this group should be silent */
|
|
77
|
+
silent?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Notification Scheduler
|
|
81
|
+
*
|
|
82
|
+
* Manages local notification scheduling with deduplication,
|
|
83
|
+
* grouping, and quiet hours support.
|
|
84
|
+
*/
|
|
85
|
+
export declare class NotificationScheduler {
|
|
86
|
+
private scheduled;
|
|
87
|
+
private groups;
|
|
88
|
+
private quietHoursStart;
|
|
89
|
+
private quietHoursEnd;
|
|
90
|
+
private quietHoursEnabled;
|
|
91
|
+
/**
|
|
92
|
+
* Schedule a notification
|
|
93
|
+
*/
|
|
94
|
+
schedule(config: NotificationConfig): string;
|
|
95
|
+
/**
|
|
96
|
+
* Cancel a scheduled notification
|
|
97
|
+
*/
|
|
98
|
+
cancel(id: string): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Cancel all notifications in a group
|
|
101
|
+
*/
|
|
102
|
+
cancelGroup(groupId: string): number;
|
|
103
|
+
/**
|
|
104
|
+
* Cancel all pending notifications
|
|
105
|
+
*/
|
|
106
|
+
cancelAll(): number;
|
|
107
|
+
/**
|
|
108
|
+
* Register a notification group/channel
|
|
109
|
+
*/
|
|
110
|
+
registerGroup(group: NotificationGroup): void;
|
|
111
|
+
/**
|
|
112
|
+
* Configure quiet hours
|
|
113
|
+
*/
|
|
114
|
+
setQuietHours(startHour: number, endHour: number, enabled: boolean): void;
|
|
115
|
+
/**
|
|
116
|
+
* Check if current time is in quiet hours
|
|
117
|
+
*/
|
|
118
|
+
isQuietTime(): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Get all pending (undelivered) notifications
|
|
121
|
+
*/
|
|
122
|
+
getPending(): NotificationConfig[];
|
|
123
|
+
/**
|
|
124
|
+
* Get count of pending notifications
|
|
125
|
+
*/
|
|
126
|
+
getPendingCount(): number;
|
|
127
|
+
/**
|
|
128
|
+
* Get notifications ready to deliver now
|
|
129
|
+
*/
|
|
130
|
+
getReadyToDeliver(): NotificationConfig[];
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scheduler/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @rajeev02/notify - Notification Scheduler
|
|
4
|
+
*
|
|
5
|
+
* Schedule, group, deduplicate, and manage notifications across platforms.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.NotificationScheduler = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* Notification Scheduler
|
|
11
|
+
*
|
|
12
|
+
* Manages local notification scheduling with deduplication,
|
|
13
|
+
* grouping, and quiet hours support.
|
|
14
|
+
*/
|
|
15
|
+
class NotificationScheduler {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.scheduled = new Map();
|
|
18
|
+
this.groups = new Map();
|
|
19
|
+
this.quietHoursStart = 22; // 10 PM
|
|
20
|
+
this.quietHoursEnd = 7; // 7 AM
|
|
21
|
+
this.quietHoursEnabled = false;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Schedule a notification
|
|
25
|
+
*/
|
|
26
|
+
schedule(config) {
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
let deliverAt = now;
|
|
29
|
+
if (config.schedule?.at) {
|
|
30
|
+
deliverAt = new Date(config.schedule.at).getTime();
|
|
31
|
+
}
|
|
32
|
+
else if (config.schedule?.afterMs) {
|
|
33
|
+
deliverAt = now + config.schedule.afterMs;
|
|
34
|
+
}
|
|
35
|
+
// Dedup: cancel existing notification with same ID
|
|
36
|
+
this.cancel(config.id);
|
|
37
|
+
const record = {
|
|
38
|
+
config,
|
|
39
|
+
scheduledAt: now,
|
|
40
|
+
deliverAt,
|
|
41
|
+
delivered: false,
|
|
42
|
+
cancelled: false,
|
|
43
|
+
};
|
|
44
|
+
this.scheduled.set(config.id, record);
|
|
45
|
+
return config.id;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Cancel a scheduled notification
|
|
49
|
+
*/
|
|
50
|
+
cancel(id) {
|
|
51
|
+
const record = this.scheduled.get(id);
|
|
52
|
+
if (record && !record.delivered) {
|
|
53
|
+
record.cancelled = true;
|
|
54
|
+
this.scheduled.delete(id);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Cancel all notifications in a group
|
|
61
|
+
*/
|
|
62
|
+
cancelGroup(groupId) {
|
|
63
|
+
let count = 0;
|
|
64
|
+
for (const [id, record] of this.scheduled) {
|
|
65
|
+
if (record.config.groupId === groupId && !record.delivered) {
|
|
66
|
+
record.cancelled = true;
|
|
67
|
+
this.scheduled.delete(id);
|
|
68
|
+
count++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return count;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Cancel all pending notifications
|
|
75
|
+
*/
|
|
76
|
+
cancelAll() {
|
|
77
|
+
const count = this.scheduled.size;
|
|
78
|
+
this.scheduled.clear();
|
|
79
|
+
return count;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Register a notification group/channel
|
|
83
|
+
*/
|
|
84
|
+
registerGroup(group) {
|
|
85
|
+
this.groups.set(group.id, group);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Configure quiet hours
|
|
89
|
+
*/
|
|
90
|
+
setQuietHours(startHour, endHour, enabled) {
|
|
91
|
+
this.quietHoursStart = startHour;
|
|
92
|
+
this.quietHoursEnd = endHour;
|
|
93
|
+
this.quietHoursEnabled = enabled;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if current time is in quiet hours
|
|
97
|
+
*/
|
|
98
|
+
isQuietTime() {
|
|
99
|
+
if (!this.quietHoursEnabled)
|
|
100
|
+
return false;
|
|
101
|
+
const hour = new Date().getHours();
|
|
102
|
+
if (this.quietHoursStart > this.quietHoursEnd) {
|
|
103
|
+
// Wraps midnight (e.g., 22-07)
|
|
104
|
+
return hour >= this.quietHoursStart || hour < this.quietHoursEnd;
|
|
105
|
+
}
|
|
106
|
+
return hour >= this.quietHoursStart && hour < this.quietHoursEnd;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get all pending (undelivered) notifications
|
|
110
|
+
*/
|
|
111
|
+
getPending() {
|
|
112
|
+
return Array.from(this.scheduled.values())
|
|
113
|
+
.filter(r => !r.delivered && !r.cancelled)
|
|
114
|
+
.sort((a, b) => a.deliverAt - b.deliverAt)
|
|
115
|
+
.map(r => r.config);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get count of pending notifications
|
|
119
|
+
*/
|
|
120
|
+
getPendingCount() {
|
|
121
|
+
return this.getPending().length;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get notifications ready to deliver now
|
|
125
|
+
*/
|
|
126
|
+
getReadyToDeliver() {
|
|
127
|
+
const now = Date.now();
|
|
128
|
+
return Array.from(this.scheduled.values())
|
|
129
|
+
.filter(r => !r.delivered && !r.cancelled && r.deliverAt <= now)
|
|
130
|
+
.filter(r => {
|
|
131
|
+
if (this.isQuietTime() && !r.config.schedule?.ignoreQuietHours) {
|
|
132
|
+
return r.config.priority === 'critical';
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
})
|
|
136
|
+
.map(r => {
|
|
137
|
+
r.delivered = true;
|
|
138
|
+
return r.config;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
exports.NotificationScheduler = NotificationScheduler;
|
|
143
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scheduler/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rajeev02/notify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unified notification & messaging layer for React Native — local, push, in-app inbox, cross-device sync",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"author": "Rajeev Kumar Joshi <rajeevjoshi91@gmail.com> (https://rajeev02.github.io)",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"clean": "rm -rf lib",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"react-native",
|
|
16
|
+
"notifications",
|
|
17
|
+
"push",
|
|
18
|
+
"inbox",
|
|
19
|
+
"scheduler"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/Rajeev02/rajeev-sdk",
|
|
24
|
+
"directory": "packages/notify"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/Rajeev02/rajeev-sdk#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/Rajeev02/rajeev-sdk/issues"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"lib/",
|
|
32
|
+
"src/",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": ">=18.3.0",
|
|
40
|
+
"react-native": ">=0.84.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"react-native": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/react": "^19.0.0",
|
|
49
|
+
"typescript": "^5.4.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rajeev02/notify - In-App Inbox
|
|
3
|
+
*
|
|
4
|
+
* Persistent in-app notification inbox with read/unread tracking,
|
|
5
|
+
* categories, and mark-all-read support.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface InboxMessage {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
body: string;
|
|
12
|
+
category?: string;
|
|
13
|
+
imageUrl?: string;
|
|
14
|
+
deepLink?: string;
|
|
15
|
+
data?: Record<string, unknown>;
|
|
16
|
+
read: boolean;
|
|
17
|
+
archived: boolean;
|
|
18
|
+
receivedAt: number;
|
|
19
|
+
expiresAt?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* In-App Notification Inbox
|
|
24
|
+
*/
|
|
25
|
+
export class NotificationInbox {
|
|
26
|
+
private messages: Map<string, InboxMessage> = new Map();
|
|
27
|
+
private maxMessages: number;
|
|
28
|
+
private onChangeCallbacks: Set<() => void> = new Set();
|
|
29
|
+
|
|
30
|
+
constructor(maxMessages: number = 100) {
|
|
31
|
+
this.maxMessages = maxMessages;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Add a message to the inbox
|
|
36
|
+
*/
|
|
37
|
+
add(message: Omit<InboxMessage, 'read' | 'archived' | 'receivedAt'>): InboxMessage {
|
|
38
|
+
const full: InboxMessage = {
|
|
39
|
+
...message,
|
|
40
|
+
read: false,
|
|
41
|
+
archived: false,
|
|
42
|
+
receivedAt: Date.now(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this.messages.set(message.id, full);
|
|
46
|
+
this.enforceLimit();
|
|
47
|
+
this.notifyChange();
|
|
48
|
+
return full;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Mark a message as read
|
|
53
|
+
*/
|
|
54
|
+
markRead(id: string): boolean {
|
|
55
|
+
const msg = this.messages.get(id);
|
|
56
|
+
if (msg) {
|
|
57
|
+
msg.read = true;
|
|
58
|
+
this.notifyChange();
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Mark all messages as read
|
|
66
|
+
*/
|
|
67
|
+
markAllRead(): number {
|
|
68
|
+
let count = 0;
|
|
69
|
+
for (const msg of this.messages.values()) {
|
|
70
|
+
if (!msg.read) {
|
|
71
|
+
msg.read = true;
|
|
72
|
+
count++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (count > 0) this.notifyChange();
|
|
76
|
+
return count;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Archive a message (soft delete)
|
|
81
|
+
*/
|
|
82
|
+
archive(id: string): boolean {
|
|
83
|
+
const msg = this.messages.get(id);
|
|
84
|
+
if (msg) {
|
|
85
|
+
msg.archived = true;
|
|
86
|
+
this.notifyChange();
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Delete a message permanently
|
|
94
|
+
*/
|
|
95
|
+
delete(id: string): boolean {
|
|
96
|
+
const result = this.messages.delete(id);
|
|
97
|
+
if (result) this.notifyChange();
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get all non-archived messages, newest first
|
|
103
|
+
*/
|
|
104
|
+
getAll(category?: string): InboxMessage[] {
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
return Array.from(this.messages.values())
|
|
107
|
+
.filter(m => !m.archived)
|
|
108
|
+
.filter(m => !m.expiresAt || m.expiresAt > now)
|
|
109
|
+
.filter(m => !category || m.category === category)
|
|
110
|
+
.sort((a, b) => b.receivedAt - a.receivedAt);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get unread count
|
|
115
|
+
*/
|
|
116
|
+
getUnreadCount(category?: string): number {
|
|
117
|
+
return this.getAll(category).filter(m => !m.read).length;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get all unique categories
|
|
122
|
+
*/
|
|
123
|
+
getCategories(): string[] {
|
|
124
|
+
const cats = new Set<string>();
|
|
125
|
+
for (const msg of this.messages.values()) {
|
|
126
|
+
if (msg.category) cats.add(msg.category);
|
|
127
|
+
}
|
|
128
|
+
return Array.from(cats);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Subscribe to inbox changes
|
|
133
|
+
*/
|
|
134
|
+
onChange(callback: () => void): () => void {
|
|
135
|
+
this.onChangeCallbacks.add(callback);
|
|
136
|
+
return () => this.onChangeCallbacks.delete(callback);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Clear all messages
|
|
141
|
+
*/
|
|
142
|
+
clear(): void {
|
|
143
|
+
this.messages.clear();
|
|
144
|
+
this.notifyChange();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Remove expired messages
|
|
149
|
+
*/
|
|
150
|
+
cleanup(): number {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
let removed = 0;
|
|
153
|
+
for (const [id, msg] of this.messages) {
|
|
154
|
+
if (msg.expiresAt && msg.expiresAt <= now) {
|
|
155
|
+
this.messages.delete(id);
|
|
156
|
+
removed++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (removed > 0) this.notifyChange();
|
|
160
|
+
return removed;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private enforceLimit(): void {
|
|
164
|
+
if (this.messages.size > this.maxMessages) {
|
|
165
|
+
const sorted = Array.from(this.messages.entries())
|
|
166
|
+
.sort(([, a], [, b]) => a.receivedAt - b.receivedAt);
|
|
167
|
+
const toRemove = sorted.slice(0, this.messages.size - this.maxMessages);
|
|
168
|
+
for (const [id] of toRemove) {
|
|
169
|
+
this.messages.delete(id);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private notifyChange(): void {
|
|
175
|
+
for (const cb of this.onChangeCallbacks) {
|
|
176
|
+
try { cb(); } catch (_) { /* ignore */ }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rajeev02/notify
|
|
3
|
+
*
|
|
4
|
+
* Unified Notification & Messaging Layer
|
|
5
|
+
* Cross-platform: Android, iOS, Web, Watch, Auto
|
|
6
|
+
*
|
|
7
|
+
* @author Rajeev Kumar Joshi (https://github.com/Rajeev02)
|
|
8
|
+
* @license MIT
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { NotificationScheduler } from './scheduler';
|
|
12
|
+
export type {
|
|
13
|
+
NotificationConfig,
|
|
14
|
+
NotificationAction,
|
|
15
|
+
ScheduleConfig,
|
|
16
|
+
PlatformOverrides,
|
|
17
|
+
NotificationGroup,
|
|
18
|
+
} from './scheduler';
|
|
19
|
+
|
|
20
|
+
export { NotificationInbox } from './inbox';
|
|
21
|
+
export type { InboxMessage } from './inbox';
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rajeev02/notify - Notification Scheduler
|
|
3
|
+
*
|
|
4
|
+
* Schedule, group, deduplicate, and manage notifications across platforms.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface NotificationConfig {
|
|
8
|
+
/** Unique notification ID */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Notification title */
|
|
11
|
+
title: string;
|
|
12
|
+
/** Notification body text */
|
|
13
|
+
body: string;
|
|
14
|
+
/** Optional subtitle (iOS) */
|
|
15
|
+
subtitle?: string;
|
|
16
|
+
/** Group/channel ID for grouping */
|
|
17
|
+
groupId?: string;
|
|
18
|
+
/** Priority: 'low' | 'normal' | 'high' | 'critical' */
|
|
19
|
+
priority?: 'low' | 'normal' | 'high' | 'critical';
|
|
20
|
+
/** Optional data payload (JSON) */
|
|
21
|
+
data?: Record<string, unknown>;
|
|
22
|
+
/** Action buttons */
|
|
23
|
+
actions?: NotificationAction[];
|
|
24
|
+
/** Schedule for future delivery */
|
|
25
|
+
schedule?: ScheduleConfig;
|
|
26
|
+
/** Platform-specific overrides */
|
|
27
|
+
platform?: PlatformOverrides;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface NotificationAction {
|
|
31
|
+
id: string;
|
|
32
|
+
title: string;
|
|
33
|
+
/** Whether tapping opens the app */
|
|
34
|
+
opensApp?: boolean;
|
|
35
|
+
/** Whether this is a destructive action (shown in red) */
|
|
36
|
+
destructive?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ScheduleConfig {
|
|
40
|
+
/** Deliver at specific timestamp (ISO string) */
|
|
41
|
+
at?: string;
|
|
42
|
+
/** Deliver after delay in milliseconds */
|
|
43
|
+
afterMs?: number;
|
|
44
|
+
/** Repeat interval: 'minute' | 'hour' | 'day' | 'week' */
|
|
45
|
+
repeat?: 'minute' | 'hour' | 'day' | 'week';
|
|
46
|
+
/** Allow delivery during quiet hours (default: false) */
|
|
47
|
+
ignoreQuietHours?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PlatformOverrides {
|
|
51
|
+
android?: {
|
|
52
|
+
channelId?: string;
|
|
53
|
+
smallIcon?: string;
|
|
54
|
+
color?: string;
|
|
55
|
+
ongoing?: boolean;
|
|
56
|
+
autoCancel?: boolean;
|
|
57
|
+
};
|
|
58
|
+
ios?: {
|
|
59
|
+
sound?: string;
|
|
60
|
+
badge?: number;
|
|
61
|
+
categoryId?: string;
|
|
62
|
+
threadId?: string;
|
|
63
|
+
};
|
|
64
|
+
watch?: {
|
|
65
|
+
/** Compact format for watch display */
|
|
66
|
+
shortTitle?: string;
|
|
67
|
+
shortBody?: string;
|
|
68
|
+
};
|
|
69
|
+
auto?: {
|
|
70
|
+
/** TTS-friendly text for Android Auto */
|
|
71
|
+
ttsText?: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface NotificationGroup {
|
|
76
|
+
id: string;
|
|
77
|
+
name: string;
|
|
78
|
+
description?: string;
|
|
79
|
+
/** Default importance: 'min' | 'low' | 'default' | 'high' | 'max' */
|
|
80
|
+
importance?: string;
|
|
81
|
+
/** Whether notifications in this group should be silent */
|
|
82
|
+
silent?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Scheduled notification record */
|
|
86
|
+
interface ScheduledNotification {
|
|
87
|
+
config: NotificationConfig;
|
|
88
|
+
scheduledAt: number;
|
|
89
|
+
deliverAt: number;
|
|
90
|
+
delivered: boolean;
|
|
91
|
+
cancelled: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Notification Scheduler
|
|
96
|
+
*
|
|
97
|
+
* Manages local notification scheduling with deduplication,
|
|
98
|
+
* grouping, and quiet hours support.
|
|
99
|
+
*/
|
|
100
|
+
export class NotificationScheduler {
|
|
101
|
+
private scheduled: Map<string, ScheduledNotification> = new Map();
|
|
102
|
+
private groups: Map<string, NotificationGroup> = new Map();
|
|
103
|
+
private quietHoursStart: number = 22; // 10 PM
|
|
104
|
+
private quietHoursEnd: number = 7; // 7 AM
|
|
105
|
+
private quietHoursEnabled: boolean = false;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Schedule a notification
|
|
109
|
+
*/
|
|
110
|
+
schedule(config: NotificationConfig): string {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
let deliverAt = now;
|
|
113
|
+
|
|
114
|
+
if (config.schedule?.at) {
|
|
115
|
+
deliverAt = new Date(config.schedule.at).getTime();
|
|
116
|
+
} else if (config.schedule?.afterMs) {
|
|
117
|
+
deliverAt = now + config.schedule.afterMs;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Dedup: cancel existing notification with same ID
|
|
121
|
+
this.cancel(config.id);
|
|
122
|
+
|
|
123
|
+
const record: ScheduledNotification = {
|
|
124
|
+
config,
|
|
125
|
+
scheduledAt: now,
|
|
126
|
+
deliverAt,
|
|
127
|
+
delivered: false,
|
|
128
|
+
cancelled: false,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
this.scheduled.set(config.id, record);
|
|
132
|
+
return config.id;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Cancel a scheduled notification
|
|
137
|
+
*/
|
|
138
|
+
cancel(id: string): boolean {
|
|
139
|
+
const record = this.scheduled.get(id);
|
|
140
|
+
if (record && !record.delivered) {
|
|
141
|
+
record.cancelled = true;
|
|
142
|
+
this.scheduled.delete(id);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Cancel all notifications in a group
|
|
150
|
+
*/
|
|
151
|
+
cancelGroup(groupId: string): number {
|
|
152
|
+
let count = 0;
|
|
153
|
+
for (const [id, record] of this.scheduled) {
|
|
154
|
+
if (record.config.groupId === groupId && !record.delivered) {
|
|
155
|
+
record.cancelled = true;
|
|
156
|
+
this.scheduled.delete(id);
|
|
157
|
+
count++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return count;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Cancel all pending notifications
|
|
165
|
+
*/
|
|
166
|
+
cancelAll(): number {
|
|
167
|
+
const count = this.scheduled.size;
|
|
168
|
+
this.scheduled.clear();
|
|
169
|
+
return count;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Register a notification group/channel
|
|
174
|
+
*/
|
|
175
|
+
registerGroup(group: NotificationGroup): void {
|
|
176
|
+
this.groups.set(group.id, group);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Configure quiet hours
|
|
181
|
+
*/
|
|
182
|
+
setQuietHours(startHour: number, endHour: number, enabled: boolean): void {
|
|
183
|
+
this.quietHoursStart = startHour;
|
|
184
|
+
this.quietHoursEnd = endHour;
|
|
185
|
+
this.quietHoursEnabled = enabled;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if current time is in quiet hours
|
|
190
|
+
*/
|
|
191
|
+
isQuietTime(): boolean {
|
|
192
|
+
if (!this.quietHoursEnabled) return false;
|
|
193
|
+
const hour = new Date().getHours();
|
|
194
|
+
if (this.quietHoursStart > this.quietHoursEnd) {
|
|
195
|
+
// Wraps midnight (e.g., 22-07)
|
|
196
|
+
return hour >= this.quietHoursStart || hour < this.quietHoursEnd;
|
|
197
|
+
}
|
|
198
|
+
return hour >= this.quietHoursStart && hour < this.quietHoursEnd;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get all pending (undelivered) notifications
|
|
203
|
+
*/
|
|
204
|
+
getPending(): NotificationConfig[] {
|
|
205
|
+
return Array.from(this.scheduled.values())
|
|
206
|
+
.filter(r => !r.delivered && !r.cancelled)
|
|
207
|
+
.sort((a, b) => a.deliverAt - b.deliverAt)
|
|
208
|
+
.map(r => r.config);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get count of pending notifications
|
|
213
|
+
*/
|
|
214
|
+
getPendingCount(): number {
|
|
215
|
+
return this.getPending().length;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get notifications ready to deliver now
|
|
220
|
+
*/
|
|
221
|
+
getReadyToDeliver(): NotificationConfig[] {
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
return Array.from(this.scheduled.values())
|
|
224
|
+
.filter(r => !r.delivered && !r.cancelled && r.deliverAt <= now)
|
|
225
|
+
.filter(r => {
|
|
226
|
+
if (this.isQuietTime() && !r.config.schedule?.ignoreQuietHours) {
|
|
227
|
+
return r.config.priority === 'critical';
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
})
|
|
231
|
+
.map(r => {
|
|
232
|
+
r.delivered = true;
|
|
233
|
+
return r.config;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|