@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.
@@ -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
@@ -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
+ }