@unisat/notification-service 1.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 UniSat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @unisat/notification-service
2
+
3
+ Cross-platform notification management service for UniSat Wallet. Handles fetching, storing, reading, and deleting in-app notifications with local persistence and automatic cleanup.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @unisat/notification-service
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { NotificationService } from '@unisat/notification-service'
15
+
16
+ const notificationService = new NotificationService()
17
+
18
+ await notificationService.init({
19
+ storage, // ProxyStorageAdapter
20
+ api, // WalletApiService
21
+ logger, // optional Logger
22
+ })
23
+ ```
24
+
25
+ ## API
26
+
27
+ ### `init(config)`
28
+
29
+ Initializes the service and loads persisted notifications from storage.
30
+
31
+ | Field | Type | Required | Description |
32
+ | --------- | ---------------------- | -------- | ------------------------ |
33
+ | `storage` | `ProxyStorageAdapter` | Yes | Storage adapter |
34
+ | `api` | `WalletApiService` | Yes | API client |
35
+ | `logger` | `Logger` | No | Custom logger |
36
+
37
+ ### `getNotifications()`
38
+
39
+ Fetches the latest notifications from the server, merges them with the local store, and returns a sorted list.
40
+
41
+ - Sorted by `priority` desc, then `publishTime` desc
42
+ - Caps the local store at **20 items** (lowest priority/oldest are dropped)
43
+ - Automatically purges read notifications older than **7 days**
44
+ - Falls back to the local store on network error
45
+
46
+ ```ts
47
+ const notifications = await notificationService.getNotifications()
48
+ // => StoredNotification[]
49
+ ```
50
+
51
+ ### `markAsRead(id)`
52
+
53
+ Marks a notification as read on the server and records the read timestamp locally.
54
+
55
+ ```ts
56
+ await notificationService.markAsRead('notification-id')
57
+ ```
58
+
59
+ ### `deleteNotification(id)`
60
+
61
+ Deletes a notification locally. If it has not been read yet, it is first marked as read on the server.
62
+
63
+ ```ts
64
+ await notificationService.deleteNotification('notification-id')
65
+ ```
66
+
67
+ ### `getUnreadCount()`
68
+
69
+ Returns the number of unread notifications in the local store.
70
+
71
+ ```ts
72
+ const count = notificationService.getUnreadCount()
73
+ // => number
74
+ ```
75
+
76
+ ### `resetAllData()`
77
+
78
+ Clears all notifications from both the local store and storage.
79
+
80
+ ```ts
81
+ notificationService.resetAllData()
82
+ ```
83
+
84
+ ## Types
85
+
86
+ ```ts
87
+ interface StoredNotification {
88
+ id: string
89
+ title: string
90
+ content: string
91
+ type: string
92
+ link?: string
93
+ priority: number
94
+ publishTime: number
95
+ readAt?: number // timestamp when marked as read; undefined means unread
96
+ }
97
+
98
+ type NotificationStore = Record<string, StoredNotification>
99
+ ```
@@ -0,0 +1,27 @@
1
+ import { WalletApiService } from '@unisat/wallet-api';
2
+ import { StoredNotification, Logger } from '@unisat/wallet-shared';
3
+ import { ProxyStorageAdapter } from '@unisat/wallet-storage';
4
+ export { NotificationStore } from './types.mjs';
5
+
6
+ interface NotificationServiceConfig {
7
+ storage?: ProxyStorageAdapter;
8
+ logger?: Logger;
9
+ api?: WalletApiService;
10
+ }
11
+ declare class NotificationService {
12
+ private storage;
13
+ private logger;
14
+ private storageKey;
15
+ private store;
16
+ private api;
17
+ constructor();
18
+ init(config: NotificationServiceConfig): Promise<void>;
19
+ resetAllData: () => void;
20
+ getNotifications: () => Promise<StoredNotification[]>;
21
+ markAsRead: (id: string) => Promise<void>;
22
+ readAll: () => Promise<void>;
23
+ deleteNotification: (id: string) => Promise<void>;
24
+ getUnreadCount: () => number;
25
+ }
26
+
27
+ export { NotificationService };
package/lib/index.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { WalletApiService } from '@unisat/wallet-api';
2
+ import { StoredNotification, Logger } from '@unisat/wallet-shared';
3
+ import { ProxyStorageAdapter } from '@unisat/wallet-storage';
4
+ export { NotificationStore } from './types.js';
5
+
6
+ interface NotificationServiceConfig {
7
+ storage?: ProxyStorageAdapter;
8
+ logger?: Logger;
9
+ api?: WalletApiService;
10
+ }
11
+ declare class NotificationService {
12
+ private storage;
13
+ private logger;
14
+ private storageKey;
15
+ private store;
16
+ private api;
17
+ constructor();
18
+ init(config: NotificationServiceConfig): Promise<void>;
19
+ resetAllData: () => void;
20
+ getNotifications: () => Promise<StoredNotification[]>;
21
+ markAsRead: (id: string) => Promise<void>;
22
+ readAll: () => Promise<void>;
23
+ deleteNotification: (id: string) => Promise<void>;
24
+ getUnreadCount: () => number;
25
+ }
26
+
27
+ export { NotificationService };
package/lib/index.js ADDED
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ NotificationService: () => NotificationService
24
+ });
25
+ module.exports = __toCommonJS(src_exports);
26
+
27
+ // src/notification-service.ts
28
+ var MAX_NOTIFICATIONS = 20;
29
+ var READ_EXPIRY_MS = 7 * 24 * 60 * 60 * 1e3;
30
+ var defaultLogger = {
31
+ debug: () => {
32
+ },
33
+ info: () => {
34
+ },
35
+ warn: () => {
36
+ },
37
+ error: () => {
38
+ }
39
+ };
40
+ var NotificationService = class {
41
+ constructor() {
42
+ this.storage = void 0;
43
+ this.logger = defaultLogger;
44
+ this.storageKey = "notifications";
45
+ this.store = {};
46
+ this.api = void 0;
47
+ this.resetAllData = () => {
48
+ this.storage.set(this.storageKey, {});
49
+ this.store = {};
50
+ };
51
+ // Fetch from server, merge into local store.
52
+ // Prune read+expired entries, then cap total at MAX_NOTIFICATIONS (keep highest priority / newest).
53
+ this.getNotifications = async () => {
54
+ try {
55
+ const res = await this.api.notification.getList();
56
+ for (const item of res.list) {
57
+ const existing = this.store[item.id];
58
+ this.store[item.id] = {
59
+ ...item,
60
+ // preserve readAt if already marked locally
61
+ readAt: existing?.readAt
62
+ };
63
+ }
64
+ const now = Date.now();
65
+ for (const id of Object.keys(this.store)) {
66
+ const entry = this.store[id];
67
+ if (entry.readAt !== void 0 && now - entry.readAt > READ_EXPIRY_MS) {
68
+ delete this.store[id];
69
+ }
70
+ }
71
+ const entries = Object.values(this.store);
72
+ if (entries.length > MAX_NOTIFICATIONS) {
73
+ entries.sort((a, b) => b.priority - a.priority || b.publishTime - a.publishTime);
74
+ const keep = entries.slice(0, MAX_NOTIFICATIONS);
75
+ const keepIds = new Set(keep.map((e) => e.id));
76
+ for (const id of Object.keys(this.store)) {
77
+ if (!keepIds.has(id)) {
78
+ delete this.store[id];
79
+ }
80
+ }
81
+ }
82
+ await this.storage.set(this.storageKey, this.store);
83
+ } catch (error) {
84
+ this.logger.error("Failed to fetch notifications from server:", error);
85
+ }
86
+ return Object.values(this.store).sort(
87
+ (a, b) => b.priority - a.priority || b.publishTime - a.publishTime
88
+ );
89
+ };
90
+ this.markAsRead = async (id) => {
91
+ if (!this.store[id])
92
+ return;
93
+ await this.api.notification.read(id);
94
+ this.store[id] = { ...this.store[id], readAt: Date.now() };
95
+ await this.storage.set(this.storageKey, this.store);
96
+ };
97
+ this.readAll = async () => {
98
+ const unreadIds = Object.values(this.store).filter((n) => n.readAt === void 0).map((n) => n.id);
99
+ await this.api.notification.readAll(unreadIds);
100
+ const now = Date.now();
101
+ for (const id of unreadIds) {
102
+ this.store[id] = { ...this.store[id], readAt: now };
103
+ }
104
+ await this.storage.set(this.storageKey, this.store);
105
+ };
106
+ this.deleteNotification = async (id) => {
107
+ if (!this.store[id])
108
+ return;
109
+ if (this.store[id].readAt === void 0) {
110
+ try {
111
+ await this.api.notification.read(id);
112
+ } catch (error) {
113
+ this.logger.warn("Failed to mark notification as read before deletion:", error);
114
+ }
115
+ }
116
+ delete this.store[id];
117
+ await this.storage.set(this.storageKey, this.store);
118
+ };
119
+ this.getUnreadCount = () => {
120
+ return Object.values(this.store).filter((n) => n.readAt === void 0).length;
121
+ };
122
+ }
123
+ async init(config) {
124
+ if (config.storage) {
125
+ this.storage = config.storage;
126
+ }
127
+ if (config.logger) {
128
+ this.logger = config.logger;
129
+ }
130
+ if (config.api) {
131
+ this.api = config.api;
132
+ }
133
+ this.logger.debug("Initializing notification service...");
134
+ try {
135
+ const storedData = await this.storage.get(this.storageKey);
136
+ this.store = storedData || {};
137
+ this.logger.debug("Notification service initialization completed");
138
+ } catch (error) {
139
+ this.logger.error("Notification service initialization failed:", error);
140
+ throw error;
141
+ }
142
+ }
143
+ };
144
+ // Annotate the CommonJS export names for ESM import in node:
145
+ 0 && (module.exports = {
146
+ NotificationService
147
+ });
148
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/notification-service.ts"],"sourcesContent":["export { NotificationService } from './notification-service'\nexport type { NotificationStore } from './types'\n","import { WalletApiService } from '@unisat/wallet-api'\nimport { Logger, StoredNotification } from '@unisat/wallet-shared'\nimport { ProxyStorageAdapter } from '@unisat/wallet-storage'\nimport { NotificationStore } from './types'\n\nconst MAX_NOTIFICATIONS = 20\n// Read notifications are deleted after 7 days\nconst READ_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000\n\n// Default no-op logger\nconst defaultLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n}\n\nexport interface NotificationServiceConfig {\n storage?: ProxyStorageAdapter\n logger?: Logger\n api?: WalletApiService\n}\n\nexport class NotificationService {\n private storage: ProxyStorageAdapter = undefined as any\n private logger: Logger = defaultLogger\n private storageKey: string = 'notifications'\n private store: NotificationStore = {}\n private api: WalletApiService = undefined as any\n\n constructor() {}\n\n async init(config: NotificationServiceConfig): Promise<void> {\n if (config.storage) {\n this.storage = config.storage\n }\n if (config.logger) {\n this.logger = config.logger\n }\n if (config.api) {\n this.api = config.api\n }\n\n this.logger.debug('Initializing notification service...')\n\n try {\n const storedData = await this.storage.get(this.storageKey)\n this.store = storedData || {}\n this.logger.debug('Notification service initialization completed')\n } catch (error) {\n this.logger.error('Notification service initialization failed:', error)\n throw error\n }\n }\n\n resetAllData = () => {\n this.storage.set(this.storageKey, {})\n this.store = {}\n }\n\n // Fetch from server, merge into local store.\n // Prune read+expired entries, then cap total at MAX_NOTIFICATIONS (keep highest priority / newest).\n getNotifications = async (): Promise<StoredNotification[]> => {\n try {\n const res = await this.api.notification.getList()\n\n // Merge server items into local store (server is source of truth for content)\n for (const item of res.list) {\n const existing = this.store[item.id]\n this.store[item.id] = {\n ...item,\n // preserve readAt if already marked locally\n readAt: existing?.readAt,\n }\n }\n\n // Purge read entries that have expired\n const now = Date.now()\n for (const id of Object.keys(this.store)) {\n const entry = this.store[id]\n if (entry.readAt !== undefined && now - entry.readAt > READ_EXPIRY_MS) {\n delete this.store[id]\n }\n }\n\n // Cap at MAX_NOTIFICATIONS: sort by priority desc, then publishTime desc, keep first N\n const entries = Object.values(this.store)\n if (entries.length > MAX_NOTIFICATIONS) {\n entries.sort((a, b) => b.priority - a.priority || b.publishTime - a.publishTime)\n const keep = entries.slice(0, MAX_NOTIFICATIONS)\n const keepIds = new Set(keep.map(e => e.id))\n for (const id of Object.keys(this.store)) {\n if (!keepIds.has(id)) {\n delete this.store[id]\n }\n }\n }\n\n await this.storage.set(this.storageKey, this.store)\n } catch (error) {\n this.logger.error('Failed to fetch notifications from server:', error)\n // Fall back to local store on network error\n }\n\n return Object.values(this.store).sort(\n (a, b) => b.priority - a.priority || b.publishTime - a.publishTime\n )\n }\n\n markAsRead = async (id: string): Promise<void> => {\n if (!this.store[id]) return\n\n await this.api.notification.read(id)\n\n this.store[id] = { ...this.store[id], readAt: Date.now() }\n await this.storage.set(this.storageKey, this.store)\n }\n\n readAll = async (): Promise<void> => {\n const unreadIds = Object.values(this.store)\n .filter(n => n.readAt === undefined)\n .map(n => n.id)\n\n await this.api.notification.readAll(unreadIds)\n const now = Date.now()\n for (const id of unreadIds) {\n this.store[id] = { ...this.store[id], readAt: now }\n }\n await this.storage.set(this.storageKey, this.store)\n }\n\n deleteNotification = async (id: string): Promise<void> => {\n if (!this.store[id]) return\n\n // Mark as read on server before deleting locally if not already read\n if (this.store[id].readAt === undefined) {\n try {\n await this.api.notification.read(id)\n } catch (error) {\n this.logger.warn('Failed to mark notification as read before deletion:', error)\n }\n }\n\n delete this.store[id]\n await this.storage.set(this.storageKey, this.store)\n }\n\n getUnreadCount = (): number => {\n return Object.values(this.store).filter(n => n.readAt === undefined).length\n }\n}\n"],"mappings":"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","names":[]}
package/lib/index.mjs ADDED
@@ -0,0 +1,121 @@
1
+ // src/notification-service.ts
2
+ var MAX_NOTIFICATIONS = 20;
3
+ var READ_EXPIRY_MS = 7 * 24 * 60 * 60 * 1e3;
4
+ var defaultLogger = {
5
+ debug: () => {
6
+ },
7
+ info: () => {
8
+ },
9
+ warn: () => {
10
+ },
11
+ error: () => {
12
+ }
13
+ };
14
+ var NotificationService = class {
15
+ constructor() {
16
+ this.storage = void 0;
17
+ this.logger = defaultLogger;
18
+ this.storageKey = "notifications";
19
+ this.store = {};
20
+ this.api = void 0;
21
+ this.resetAllData = () => {
22
+ this.storage.set(this.storageKey, {});
23
+ this.store = {};
24
+ };
25
+ // Fetch from server, merge into local store.
26
+ // Prune read+expired entries, then cap total at MAX_NOTIFICATIONS (keep highest priority / newest).
27
+ this.getNotifications = async () => {
28
+ try {
29
+ const res = await this.api.notification.getList();
30
+ for (const item of res.list) {
31
+ const existing = this.store[item.id];
32
+ this.store[item.id] = {
33
+ ...item,
34
+ // preserve readAt if already marked locally
35
+ readAt: existing?.readAt
36
+ };
37
+ }
38
+ const now = Date.now();
39
+ for (const id of Object.keys(this.store)) {
40
+ const entry = this.store[id];
41
+ if (entry.readAt !== void 0 && now - entry.readAt > READ_EXPIRY_MS) {
42
+ delete this.store[id];
43
+ }
44
+ }
45
+ const entries = Object.values(this.store);
46
+ if (entries.length > MAX_NOTIFICATIONS) {
47
+ entries.sort((a, b) => b.priority - a.priority || b.publishTime - a.publishTime);
48
+ const keep = entries.slice(0, MAX_NOTIFICATIONS);
49
+ const keepIds = new Set(keep.map((e) => e.id));
50
+ for (const id of Object.keys(this.store)) {
51
+ if (!keepIds.has(id)) {
52
+ delete this.store[id];
53
+ }
54
+ }
55
+ }
56
+ await this.storage.set(this.storageKey, this.store);
57
+ } catch (error) {
58
+ this.logger.error("Failed to fetch notifications from server:", error);
59
+ }
60
+ return Object.values(this.store).sort(
61
+ (a, b) => b.priority - a.priority || b.publishTime - a.publishTime
62
+ );
63
+ };
64
+ this.markAsRead = async (id) => {
65
+ if (!this.store[id])
66
+ return;
67
+ await this.api.notification.read(id);
68
+ this.store[id] = { ...this.store[id], readAt: Date.now() };
69
+ await this.storage.set(this.storageKey, this.store);
70
+ };
71
+ this.readAll = async () => {
72
+ const unreadIds = Object.values(this.store).filter((n) => n.readAt === void 0).map((n) => n.id);
73
+ await this.api.notification.readAll(unreadIds);
74
+ const now = Date.now();
75
+ for (const id of unreadIds) {
76
+ this.store[id] = { ...this.store[id], readAt: now };
77
+ }
78
+ await this.storage.set(this.storageKey, this.store);
79
+ };
80
+ this.deleteNotification = async (id) => {
81
+ if (!this.store[id])
82
+ return;
83
+ if (this.store[id].readAt === void 0) {
84
+ try {
85
+ await this.api.notification.read(id);
86
+ } catch (error) {
87
+ this.logger.warn("Failed to mark notification as read before deletion:", error);
88
+ }
89
+ }
90
+ delete this.store[id];
91
+ await this.storage.set(this.storageKey, this.store);
92
+ };
93
+ this.getUnreadCount = () => {
94
+ return Object.values(this.store).filter((n) => n.readAt === void 0).length;
95
+ };
96
+ }
97
+ async init(config) {
98
+ if (config.storage) {
99
+ this.storage = config.storage;
100
+ }
101
+ if (config.logger) {
102
+ this.logger = config.logger;
103
+ }
104
+ if (config.api) {
105
+ this.api = config.api;
106
+ }
107
+ this.logger.debug("Initializing notification service...");
108
+ try {
109
+ const storedData = await this.storage.get(this.storageKey);
110
+ this.store = storedData || {};
111
+ this.logger.debug("Notification service initialization completed");
112
+ } catch (error) {
113
+ this.logger.error("Notification service initialization failed:", error);
114
+ throw error;
115
+ }
116
+ }
117
+ };
118
+ export {
119
+ NotificationService
120
+ };
121
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/notification-service.ts"],"sourcesContent":["import { WalletApiService } from '@unisat/wallet-api'\nimport { Logger, StoredNotification } from '@unisat/wallet-shared'\nimport { ProxyStorageAdapter } from '@unisat/wallet-storage'\nimport { NotificationStore } from './types'\n\nconst MAX_NOTIFICATIONS = 20\n// Read notifications are deleted after 7 days\nconst READ_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000\n\n// Default no-op logger\nconst defaultLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n}\n\nexport interface NotificationServiceConfig {\n storage?: ProxyStorageAdapter\n logger?: Logger\n api?: WalletApiService\n}\n\nexport class NotificationService {\n private storage: ProxyStorageAdapter = undefined as any\n private logger: Logger = defaultLogger\n private storageKey: string = 'notifications'\n private store: NotificationStore = {}\n private api: WalletApiService = undefined as any\n\n constructor() {}\n\n async init(config: NotificationServiceConfig): Promise<void> {\n if (config.storage) {\n this.storage = config.storage\n }\n if (config.logger) {\n this.logger = config.logger\n }\n if (config.api) {\n this.api = config.api\n }\n\n this.logger.debug('Initializing notification service...')\n\n try {\n const storedData = await this.storage.get(this.storageKey)\n this.store = storedData || {}\n this.logger.debug('Notification service initialization completed')\n } catch (error) {\n this.logger.error('Notification service initialization failed:', error)\n throw error\n }\n }\n\n resetAllData = () => {\n this.storage.set(this.storageKey, {})\n this.store = {}\n }\n\n // Fetch from server, merge into local store.\n // Prune read+expired entries, then cap total at MAX_NOTIFICATIONS (keep highest priority / newest).\n getNotifications = async (): Promise<StoredNotification[]> => {\n try {\n const res = await this.api.notification.getList()\n\n // Merge server items into local store (server is source of truth for content)\n for (const item of res.list) {\n const existing = this.store[item.id]\n this.store[item.id] = {\n ...item,\n // preserve readAt if already marked locally\n readAt: existing?.readAt,\n }\n }\n\n // Purge read entries that have expired\n const now = Date.now()\n for (const id of Object.keys(this.store)) {\n const entry = this.store[id]\n if (entry.readAt !== undefined && now - entry.readAt > READ_EXPIRY_MS) {\n delete this.store[id]\n }\n }\n\n // Cap at MAX_NOTIFICATIONS: sort by priority desc, then publishTime desc, keep first N\n const entries = Object.values(this.store)\n if (entries.length > MAX_NOTIFICATIONS) {\n entries.sort((a, b) => b.priority - a.priority || b.publishTime - a.publishTime)\n const keep = entries.slice(0, MAX_NOTIFICATIONS)\n const keepIds = new Set(keep.map(e => e.id))\n for (const id of Object.keys(this.store)) {\n if (!keepIds.has(id)) {\n delete this.store[id]\n }\n }\n }\n\n await this.storage.set(this.storageKey, this.store)\n } catch (error) {\n this.logger.error('Failed to fetch notifications from server:', error)\n // Fall back to local store on network error\n }\n\n return Object.values(this.store).sort(\n (a, b) => b.priority - a.priority || b.publishTime - a.publishTime\n )\n }\n\n markAsRead = async (id: string): Promise<void> => {\n if (!this.store[id]) return\n\n await this.api.notification.read(id)\n\n this.store[id] = { ...this.store[id], readAt: Date.now() }\n await this.storage.set(this.storageKey, this.store)\n }\n\n readAll = async (): Promise<void> => {\n const unreadIds = Object.values(this.store)\n .filter(n => n.readAt === undefined)\n .map(n => n.id)\n\n await this.api.notification.readAll(unreadIds)\n const now = Date.now()\n for (const id of unreadIds) {\n this.store[id] = { ...this.store[id], readAt: now }\n }\n await this.storage.set(this.storageKey, this.store)\n }\n\n deleteNotification = async (id: string): Promise<void> => {\n if (!this.store[id]) return\n\n // Mark as read on server before deleting locally if not already read\n if (this.store[id].readAt === undefined) {\n try {\n await this.api.notification.read(id)\n } catch (error) {\n this.logger.warn('Failed to mark notification as read before deletion:', error)\n }\n }\n\n delete this.store[id]\n await this.storage.set(this.storageKey, this.store)\n }\n\n getUnreadCount = (): number => {\n return Object.values(this.store).filter(n => n.readAt === undefined).length\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,6 @@
1
+ import { StoredNotification } from '@unisat/wallet-shared';
2
+ export { NotificationListItem } from '@unisat/wallet-shared';
3
+
4
+ type NotificationStore = Record<string, StoredNotification>;
5
+
6
+ export { NotificationStore };
package/lib/types.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { StoredNotification } from '@unisat/wallet-shared';
2
+ export { NotificationListItem } from '@unisat/wallet-shared';
3
+
4
+ type NotificationStore = Record<string, StoredNotification>;
5
+
6
+ export { NotificationStore };
package/lib/types.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/types.ts
17
+ var types_exports = {};
18
+ module.exports = __toCommonJS(types_exports);
19
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import { StoredNotification } from '@unisat/wallet-shared'\n\nexport type { NotificationListItem } from '@unisat/wallet-shared'\n\nexport type NotificationStore = Record<string, StoredNotification>\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
package/lib/types.mjs ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@unisat/notification-service",
3
+ "version": "1.1.1",
4
+ "description": "Cross-platform permission management service for web3 applications",
5
+ "main": "lib/index.js",
6
+ "module": "lib/index.mjs",
7
+ "types": "lib/index.d.ts",
8
+ "files": [
9
+ "lib/**/*",
10
+ "src/**/*",
11
+ "README.md"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./lib/index.d.ts",
16
+ "require": "./lib/index.js",
17
+ "import": "./lib/index.mjs"
18
+ },
19
+ "./types": {
20
+ "types": "./lib/types.d.ts",
21
+ "require": "./lib/types.js",
22
+ "import": "./lib/types.mjs"
23
+ }
24
+ },
25
+ "keywords": [
26
+ "bitcoin",
27
+ "permission",
28
+ "web3",
29
+ "wallet",
30
+ "browser-extension",
31
+ "dapp",
32
+ "connected-sites"
33
+ ],
34
+ "author": "UniSat Team",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "eventemitter3": "^5.0.1",
38
+ "@unisat/wallet-shared": "0.2.0",
39
+ "@unisat/wallet-storage": "0.2.1",
40
+ "@unisat/wallet-api": "1.1.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.0.0",
44
+ "tsup": "^7.0.0",
45
+ "typescript": "^5.0.0",
46
+ "vitest": "^0.34.0"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "dev": "tsup --watch",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest",
56
+ "lint": "eslint src --ext .ts,.tsx",
57
+ "typecheck": "tsc --noEmit",
58
+ "clean": "rimraf dist"
59
+ }
60
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { NotificationService } from './notification-service'
2
+ export type { NotificationStore } from './types'
@@ -0,0 +1,151 @@
1
+ import { WalletApiService } from '@unisat/wallet-api'
2
+ import { Logger, StoredNotification } from '@unisat/wallet-shared'
3
+ import { ProxyStorageAdapter } from '@unisat/wallet-storage'
4
+ import { NotificationStore } from './types'
5
+
6
+ const MAX_NOTIFICATIONS = 20
7
+ // Read notifications are deleted after 7 days
8
+ const READ_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000
9
+
10
+ // Default no-op logger
11
+ const defaultLogger: Logger = {
12
+ debug: () => {},
13
+ info: () => {},
14
+ warn: () => {},
15
+ error: () => {},
16
+ }
17
+
18
+ export interface NotificationServiceConfig {
19
+ storage?: ProxyStorageAdapter
20
+ logger?: Logger
21
+ api?: WalletApiService
22
+ }
23
+
24
+ export class NotificationService {
25
+ private storage: ProxyStorageAdapter = undefined as any
26
+ private logger: Logger = defaultLogger
27
+ private storageKey: string = 'notifications'
28
+ private store: NotificationStore = {}
29
+ private api: WalletApiService = undefined as any
30
+
31
+ constructor() {}
32
+
33
+ async init(config: NotificationServiceConfig): Promise<void> {
34
+ if (config.storage) {
35
+ this.storage = config.storage
36
+ }
37
+ if (config.logger) {
38
+ this.logger = config.logger
39
+ }
40
+ if (config.api) {
41
+ this.api = config.api
42
+ }
43
+
44
+ this.logger.debug('Initializing notification service...')
45
+
46
+ try {
47
+ const storedData = await this.storage.get(this.storageKey)
48
+ this.store = storedData || {}
49
+ this.logger.debug('Notification service initialization completed')
50
+ } catch (error) {
51
+ this.logger.error('Notification service initialization failed:', error)
52
+ throw error
53
+ }
54
+ }
55
+
56
+ resetAllData = () => {
57
+ this.storage.set(this.storageKey, {})
58
+ this.store = {}
59
+ }
60
+
61
+ // Fetch from server, merge into local store.
62
+ // Prune read+expired entries, then cap total at MAX_NOTIFICATIONS (keep highest priority / newest).
63
+ getNotifications = async (): Promise<StoredNotification[]> => {
64
+ try {
65
+ const res = await this.api.notification.getList()
66
+
67
+ // Merge server items into local store (server is source of truth for content)
68
+ for (const item of res.list) {
69
+ const existing = this.store[item.id]
70
+ this.store[item.id] = {
71
+ ...item,
72
+ // preserve readAt if already marked locally
73
+ readAt: existing?.readAt,
74
+ }
75
+ }
76
+
77
+ // Purge read entries that have expired
78
+ const now = Date.now()
79
+ for (const id of Object.keys(this.store)) {
80
+ const entry = this.store[id]
81
+ if (entry.readAt !== undefined && now - entry.readAt > READ_EXPIRY_MS) {
82
+ delete this.store[id]
83
+ }
84
+ }
85
+
86
+ // Cap at MAX_NOTIFICATIONS: sort by priority desc, then publishTime desc, keep first N
87
+ const entries = Object.values(this.store)
88
+ if (entries.length > MAX_NOTIFICATIONS) {
89
+ entries.sort((a, b) => b.priority - a.priority || b.publishTime - a.publishTime)
90
+ const keep = entries.slice(0, MAX_NOTIFICATIONS)
91
+ const keepIds = new Set(keep.map(e => e.id))
92
+ for (const id of Object.keys(this.store)) {
93
+ if (!keepIds.has(id)) {
94
+ delete this.store[id]
95
+ }
96
+ }
97
+ }
98
+
99
+ await this.storage.set(this.storageKey, this.store)
100
+ } catch (error) {
101
+ this.logger.error('Failed to fetch notifications from server:', error)
102
+ // Fall back to local store on network error
103
+ }
104
+
105
+ return Object.values(this.store).sort(
106
+ (a, b) => b.priority - a.priority || b.publishTime - a.publishTime
107
+ )
108
+ }
109
+
110
+ markAsRead = async (id: string): Promise<void> => {
111
+ if (!this.store[id]) return
112
+
113
+ await this.api.notification.read(id)
114
+
115
+ this.store[id] = { ...this.store[id], readAt: Date.now() }
116
+ await this.storage.set(this.storageKey, this.store)
117
+ }
118
+
119
+ readAll = async (): Promise<void> => {
120
+ const unreadIds = Object.values(this.store)
121
+ .filter(n => n.readAt === undefined)
122
+ .map(n => n.id)
123
+
124
+ await this.api.notification.readAll(unreadIds)
125
+ const now = Date.now()
126
+ for (const id of unreadIds) {
127
+ this.store[id] = { ...this.store[id], readAt: now }
128
+ }
129
+ await this.storage.set(this.storageKey, this.store)
130
+ }
131
+
132
+ deleteNotification = async (id: string): Promise<void> => {
133
+ if (!this.store[id]) return
134
+
135
+ // Mark as read on server before deleting locally if not already read
136
+ if (this.store[id].readAt === undefined) {
137
+ try {
138
+ await this.api.notification.read(id)
139
+ } catch (error) {
140
+ this.logger.warn('Failed to mark notification as read before deletion:', error)
141
+ }
142
+ }
143
+
144
+ delete this.store[id]
145
+ await this.storage.set(this.storageKey, this.store)
146
+ }
147
+
148
+ getUnreadCount = (): number => {
149
+ return Object.values(this.store).filter(n => n.readAt === undefined).length
150
+ }
151
+ }
package/src/types.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { StoredNotification } from '@unisat/wallet-shared'
2
+
3
+ export type { NotificationListItem } from '@unisat/wallet-shared'
4
+
5
+ export type NotificationStore = Record<string, StoredNotification>