@theshelf/notification 0.0.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/README.md +68 -0
- package/dist/definitions/interfaces.d.ts +9 -0
- package/dist/definitions/interfaces.js +1 -0
- package/dist/errors/NotConnected.d.ts +4 -0
- package/dist/errors/NotConnected.js +6 -0
- package/dist/errors/NotificationError.d.ts +2 -0
- package/dist/errors/NotificationError.js +2 -0
- package/dist/errors/SubscriptionNotFound.d.ts +4 -0
- package/dist/errors/SubscriptionNotFound.js +6 -0
- package/dist/errors/UnknownImplementation.d.ts +4 -0
- package/dist/errors/UnknownImplementation.js +6 -0
- package/dist/implementation.d.ts +3 -0
- package/dist/implementation.js +14 -0
- package/dist/implementations/memory/Memory.d.ts +16 -0
- package/dist/implementations/memory/Memory.js +46 -0
- package/dist/implementations/memory/create.d.ts +2 -0
- package/dist/implementations/memory/create.js +4 -0
- package/dist/implementations/webpush/WebPush.d.ts +19 -0
- package/dist/implementations/webpush/WebPush.js +52 -0
- package/dist/implementations/webpush/create.d.ts +2 -0
- package/dist/implementations/webpush/create.js +7 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
|
|
2
|
+
# Notification | The Shelf
|
|
3
|
+
|
|
4
|
+
The notification package provides a universal interaction layer with an actual notification solution.
|
|
5
|
+
|
|
6
|
+
This package is based on a push notification model.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @theshelf/notification
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Implementations
|
|
15
|
+
|
|
16
|
+
Currently, there are two implementations:
|
|
17
|
+
|
|
18
|
+
* **Memory** - non-persistent in memory notifications (suited for testing).
|
|
19
|
+
* **WebPush** - web browser based push notifications.
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
The used implementation needs to be configured in the `.env` file with the debug enabled setting.
|
|
24
|
+
|
|
25
|
+
```env
|
|
26
|
+
NOTIFICATION_IMPLEMENTATION="webpush" # (memory | webpush)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
In case of WebPush, additional configuration is required.
|
|
30
|
+
|
|
31
|
+
```env
|
|
32
|
+
WEBPUSH_VAPID_SUBJECT="..."
|
|
33
|
+
WEBPUSH_VAPID_PUBLIC_KEY="..."
|
|
34
|
+
WEBPUSH_VAPID_PRIVATE_KEY="..."
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## How to use
|
|
38
|
+
|
|
39
|
+
An instance of the configured notification service implementation can be imported for performing notification operations.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import notificationService from '@theshelf/notification';
|
|
43
|
+
|
|
44
|
+
// Perform operations with the notificationService instance
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Operations
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import notificationService from '@theshelf/notification';
|
|
51
|
+
|
|
52
|
+
// Open connection
|
|
53
|
+
await notificationService.connect();
|
|
54
|
+
|
|
55
|
+
// Close connection
|
|
56
|
+
await notificationService.disconnect();
|
|
57
|
+
|
|
58
|
+
// Subscribe to receive notifications
|
|
59
|
+
await notificationService.subscribe(recipientId);
|
|
60
|
+
|
|
61
|
+
// Unsubscribe from receiving notifications
|
|
62
|
+
// Throws SubscriptionNotFound if subscription not found.
|
|
63
|
+
await notificationService.unsubscribe(recipientId);
|
|
64
|
+
|
|
65
|
+
// Send a notification to a recipient
|
|
66
|
+
// Throws SubscriptionNotFound if subscription not found.
|
|
67
|
+
await notificationService.sendNotification(recipientId, title, body);
|
|
68
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface NotificationService {
|
|
2
|
+
get connected(): boolean;
|
|
3
|
+
get subscriptions(): Map<string, unknown>;
|
|
4
|
+
connect(): Promise<void>;
|
|
5
|
+
disconnect(): Promise<void>;
|
|
6
|
+
subscribe(recipientId: string, subscription: unknown): Promise<void>;
|
|
7
|
+
unsubscribe(recipientId: string): Promise<void>;
|
|
8
|
+
sendNotification(recipientId: string, title: string, message: string): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import UnknownImplementation from './errors/UnknownImplementation';
|
|
2
|
+
import createMemory from './implementations/memory/create';
|
|
3
|
+
import createWebPush from './implementations/webpush/create';
|
|
4
|
+
const implementations = new Map([
|
|
5
|
+
['memory', createMemory],
|
|
6
|
+
['webpush', createWebPush],
|
|
7
|
+
]);
|
|
8
|
+
const DEFAULT_NOTIFICATION_IMPLEMENTATION = 'memory';
|
|
9
|
+
const implementationName = process.env.NOTIFICATION_IMPLEMENTATION ?? DEFAULT_NOTIFICATION_IMPLEMENTATION;
|
|
10
|
+
const creator = implementations.get(implementationName.toLowerCase());
|
|
11
|
+
if (creator === undefined) {
|
|
12
|
+
throw new UnknownImplementation(implementationName);
|
|
13
|
+
}
|
|
14
|
+
export default creator();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NotificationService } from '../../definitions/interfaces';
|
|
2
|
+
type Notification = {
|
|
3
|
+
title: string;
|
|
4
|
+
body: string;
|
|
5
|
+
};
|
|
6
|
+
export default class Memory implements NotificationService {
|
|
7
|
+
#private;
|
|
8
|
+
get connected(): boolean;
|
|
9
|
+
get subscriptions(): Map<string, Notification[]>;
|
|
10
|
+
connect(): Promise<void>;
|
|
11
|
+
disconnect(): Promise<void>;
|
|
12
|
+
subscribe(recipientId: string): Promise<void>;
|
|
13
|
+
unsubscribe(recipientId: string): Promise<void>;
|
|
14
|
+
sendNotification(recipientId: string, title: string, body: string): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import NotConnected from '../../errors/NotConnected';
|
|
2
|
+
import SubscriptionNotFound from '../../errors/SubscriptionNotFound';
|
|
3
|
+
export default class Memory {
|
|
4
|
+
#subscriptions;
|
|
5
|
+
get connected() {
|
|
6
|
+
return this.#subscriptions !== undefined;
|
|
7
|
+
}
|
|
8
|
+
get subscriptions() {
|
|
9
|
+
return this.#getSubscriptions();
|
|
10
|
+
}
|
|
11
|
+
async connect() {
|
|
12
|
+
this.#subscriptions = new Map();
|
|
13
|
+
}
|
|
14
|
+
async disconnect() {
|
|
15
|
+
this.#subscriptions = undefined;
|
|
16
|
+
}
|
|
17
|
+
async subscribe(recipientId) {
|
|
18
|
+
const subscriptions = this.#getSubscriptions();
|
|
19
|
+
subscriptions.set(recipientId, []);
|
|
20
|
+
}
|
|
21
|
+
async unsubscribe(recipientId) {
|
|
22
|
+
const subscriptions = this.#getSubscriptions();
|
|
23
|
+
if (subscriptions.has(recipientId) === false) {
|
|
24
|
+
throw new SubscriptionNotFound(recipientId);
|
|
25
|
+
}
|
|
26
|
+
subscriptions.delete(recipientId);
|
|
27
|
+
}
|
|
28
|
+
async sendNotification(recipientId, title, body) {
|
|
29
|
+
const subscription = this.#getSubscription(recipientId);
|
|
30
|
+
subscription.push({ title, body });
|
|
31
|
+
}
|
|
32
|
+
#getSubscriptions() {
|
|
33
|
+
if (this.#subscriptions === undefined) {
|
|
34
|
+
throw new NotConnected();
|
|
35
|
+
}
|
|
36
|
+
return this.#subscriptions;
|
|
37
|
+
}
|
|
38
|
+
#getSubscription(recipientId) {
|
|
39
|
+
const subscriptions = this.#getSubscriptions();
|
|
40
|
+
const subscription = subscriptions.get(recipientId);
|
|
41
|
+
if (subscription === undefined) {
|
|
42
|
+
throw new SubscriptionNotFound(recipientId);
|
|
43
|
+
}
|
|
44
|
+
return subscription;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PushSubscription } from 'web-push';
|
|
2
|
+
import type { NotificationService } from '../../definitions/interfaces';
|
|
3
|
+
type VapidDetails = {
|
|
4
|
+
subject: string;
|
|
5
|
+
publicKey: string;
|
|
6
|
+
privateKey: string;
|
|
7
|
+
};
|
|
8
|
+
export default class WebPush implements NotificationService {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(configuration: VapidDetails);
|
|
11
|
+
get connected(): boolean;
|
|
12
|
+
get subscriptions(): Map<string, PushSubscription>;
|
|
13
|
+
connect(): Promise<void>;
|
|
14
|
+
disconnect(): Promise<void>;
|
|
15
|
+
subscribe(recipientId: string, subscription: PushSubscription): Promise<void>;
|
|
16
|
+
unsubscribe(recipientId: string): Promise<void>;
|
|
17
|
+
sendNotification(recipientId: string, title: string, body: string): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import webpush from 'web-push';
|
|
2
|
+
import NotConnected from '../../errors/NotConnected';
|
|
3
|
+
import SubscriptionNotFound from '../../errors/SubscriptionNotFound';
|
|
4
|
+
export default class WebPush {
|
|
5
|
+
#configuration;
|
|
6
|
+
#subscriptions;
|
|
7
|
+
constructor(configuration) {
|
|
8
|
+
this.#configuration = configuration;
|
|
9
|
+
}
|
|
10
|
+
get connected() {
|
|
11
|
+
return this.#subscriptions !== undefined;
|
|
12
|
+
}
|
|
13
|
+
get subscriptions() {
|
|
14
|
+
return this.#getSubscriptions();
|
|
15
|
+
}
|
|
16
|
+
async connect() {
|
|
17
|
+
this.#subscriptions = new Map();
|
|
18
|
+
webpush.setVapidDetails(this.#configuration.subject, this.#configuration.publicKey, this.#configuration.privateKey);
|
|
19
|
+
}
|
|
20
|
+
async disconnect() {
|
|
21
|
+
this.#subscriptions = undefined;
|
|
22
|
+
}
|
|
23
|
+
async subscribe(recipientId, subscription) {
|
|
24
|
+
const subscriptions = this.#getSubscriptions();
|
|
25
|
+
subscriptions.set(recipientId, subscription);
|
|
26
|
+
}
|
|
27
|
+
async unsubscribe(recipientId) {
|
|
28
|
+
const subscriptions = this.#getSubscriptions();
|
|
29
|
+
if (subscriptions.has(recipientId) === false) {
|
|
30
|
+
throw new SubscriptionNotFound(recipientId);
|
|
31
|
+
}
|
|
32
|
+
subscriptions.delete(recipientId);
|
|
33
|
+
}
|
|
34
|
+
async sendNotification(recipientId, title, body) {
|
|
35
|
+
const subscription = this.#getSubscription(recipientId);
|
|
36
|
+
await webpush.sendNotification(subscription, JSON.stringify({ title, body }));
|
|
37
|
+
}
|
|
38
|
+
#getSubscriptions() {
|
|
39
|
+
if (this.#subscriptions === undefined) {
|
|
40
|
+
throw new NotConnected();
|
|
41
|
+
}
|
|
42
|
+
return this.#subscriptions;
|
|
43
|
+
}
|
|
44
|
+
#getSubscription(recipientId) {
|
|
45
|
+
const subscriptions = this.#getSubscriptions();
|
|
46
|
+
const subscription = subscriptions.get(recipientId);
|
|
47
|
+
if (subscription === undefined) {
|
|
48
|
+
throw new SubscriptionNotFound(recipientId);
|
|
49
|
+
}
|
|
50
|
+
return subscription;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import WebPush from './WebPush';
|
|
2
|
+
export default function create() {
|
|
3
|
+
const subject = process.env.WEBPUSH_SUBJECT ?? 'undefined';
|
|
4
|
+
const publicKey = process.env.WEBPUSH_PUBLIC_KEY ?? 'undefined';
|
|
5
|
+
const privateKey = process.env.WEBPUSH_PRIVATE_KEY ?? 'undefined';
|
|
6
|
+
return new WebPush({ subject, publicKey, privateKey });
|
|
7
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { NotificationService } from './definitions/interfaces';
|
|
2
|
+
export { default as NotConnected } from './errors/NotConnected';
|
|
3
|
+
export { default as NotificationError } from './errors/NotificationError';
|
|
4
|
+
export { default as SubscriptionNotFound } from './errors/SubscriptionNotFound';
|
|
5
|
+
export { default as UnknownImplementation } from './errors/UnknownImplementation';
|
|
6
|
+
export { default } from './implementation';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as NotConnected } from './errors/NotConnected';
|
|
2
|
+
export { default as NotificationError } from './errors/NotificationError';
|
|
3
|
+
export { default as SubscriptionNotFound } from './errors/SubscriptionNotFound';
|
|
4
|
+
export { default as UnknownImplementation } from './errors/UnknownImplementation';
|
|
5
|
+
export { default } from './implementation';
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theshelf/notification",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"clean": "rimraf dist",
|
|
9
|
+
"test": "vitest run",
|
|
10
|
+
"test-coverage": "vitest run --coverage",
|
|
11
|
+
"lint": "eslint",
|
|
12
|
+
"review": "npm run build && npm run lint && npm run test",
|
|
13
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"README.md",
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"exports": "./dist/index.js",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"web-push": "3.6.7"
|
|
23
|
+
}
|
|
24
|
+
}
|