@theshelf/notification 0.0.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,37 +11,40 @@ This package is based on a push notification model.
11
11
  npm install @theshelf/notification
12
12
  ```
13
13
 
14
- ## Implementations
14
+ ## Drivers
15
15
 
16
- Currently, there are two implementations:
16
+ Currently, there are two drivers available:
17
17
 
18
18
  * **Memory** - non-persistent in memory notifications (suited for testing).
19
19
  * **WebPush** - web browser based push notifications.
20
20
 
21
- ## Configuration
21
+ ## How to use
22
22
 
23
- The used implementation needs to be configured in the `.env` file with the debug enabled setting.
23
+ The basic set up looks like this.
24
24
 
25
- ```env
26
- NOTIFICATION_IMPLEMENTATION="webpush" # (memory | webpush)
27
- ```
25
+ ```ts
26
+ import NotificationService, { MemoryDriver | WebPushDriver as SelectedDriver } from '@theshelf/notification';
28
27
 
29
- In case of WebPush, additional configuration is required.
28
+ const driver = new SelectedDriver(/* configuration */);
29
+ const notificationService = new NotificationService(driver);
30
30
 
31
- ```env
32
- WEBPUSH_VAPID_SUBJECT="..."
33
- WEBPUSH_VAPID_PUBLIC_KEY="..."
34
- WEBPUSH_VAPID_PRIVATE_KEY="..."
31
+ // Perform operations with the notificationService instance
35
32
  ```
36
33
 
37
- ## How to use
34
+ ### Configuration
38
35
 
39
- An instance of the configured notification service implementation can be imported for performing notification operations.
36
+ #### Memory driver
40
37
 
41
- ```ts
42
- import notificationService from '@theshelf/notification';
38
+ No configuration options.
43
39
 
44
- // Perform operations with the notificationService instance
40
+ #### WebPush driver
41
+
42
+ ```ts
43
+ type WebPushConfiguration = { // Vapid details
44
+ subject: string;
45
+ publicKey: string;
46
+ privateKey: string;
47
+ };
45
48
  ```
46
49
 
47
50
  ### Operations
@@ -0,0 +1,9 @@
1
+ import type { ConnectionState } from './definitions/constants.js';
2
+ import type { Driver } from './definitions/interfaces.js';
3
+ export default class ConnectionManager {
4
+ #private;
5
+ constructor(driver: Driver);
6
+ get state(): ConnectionState;
7
+ connect(): Promise<void>;
8
+ disconnect(): Promise<void>;
9
+ }
@@ -0,0 +1,53 @@
1
+ import { ConnectionStates } from './definitions/constants.js';
2
+ export default class ConnectionManager {
3
+ #driver;
4
+ #state = ConnectionStates.DISCONNECTED;
5
+ #connectPromise;
6
+ #disconnectPromise;
7
+ constructor(driver) {
8
+ this.#driver = driver;
9
+ }
10
+ get state() { return this.#state; }
11
+ async connect() {
12
+ if (this.#connectPromise !== undefined) {
13
+ return this.#connectPromise;
14
+ }
15
+ if (this.#state !== ConnectionStates.DISCONNECTED) {
16
+ return;
17
+ }
18
+ this.#state = ConnectionStates.CONNECTING;
19
+ try {
20
+ this.#connectPromise = this.#driver.connect();
21
+ await this.#connectPromise;
22
+ this.#state = ConnectionStates.CONNECTED;
23
+ }
24
+ catch (error) {
25
+ this.#state = ConnectionStates.DISCONNECTED;
26
+ throw error;
27
+ }
28
+ finally {
29
+ this.#connectPromise = undefined;
30
+ }
31
+ }
32
+ async disconnect() {
33
+ if (this.#disconnectPromise !== undefined) {
34
+ return this.#disconnectPromise;
35
+ }
36
+ if (this.#state !== ConnectionStates.CONNECTED) {
37
+ return;
38
+ }
39
+ this.#state = ConnectionStates.DISCONNECTING;
40
+ try {
41
+ this.#disconnectPromise = this.#driver.disconnect();
42
+ await this.#disconnectPromise;
43
+ this.#state = ConnectionStates.DISCONNECTED;
44
+ }
45
+ catch (error) {
46
+ this.#state = ConnectionStates.CONNECTED;
47
+ throw error;
48
+ }
49
+ finally {
50
+ this.#disconnectPromise = undefined;
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,14 @@
1
+ import type { ConnectionState } from './definitions/constants.js';
2
+ import type { Driver } from './definitions/interfaces.js';
3
+ export default class NotificationService implements Driver {
4
+ #private;
5
+ constructor(driver: Driver);
6
+ get connectionState(): ConnectionState;
7
+ get connected(): boolean;
8
+ get subscriptions(): Map<string, unknown>;
9
+ connect(): Promise<void>;
10
+ disconnect(): Promise<void>;
11
+ subscribe(recipientId: string, subscription: unknown): Promise<void>;
12
+ unsubscribe(recipientId: string): Promise<void>;
13
+ sendNotification(recipientId: string, title: string, message: string): Promise<void>;
14
+ }
@@ -0,0 +1,34 @@
1
+ import { ConnectionStates } from './definitions/constants.js';
2
+ import ConnectionManager from './ConnectionManager.js';
3
+ export default class NotificationService {
4
+ #driver;
5
+ #connectionManager;
6
+ constructor(driver) {
7
+ this.#driver = driver;
8
+ this.#connectionManager = new ConnectionManager(driver);
9
+ }
10
+ get connectionState() {
11
+ return this.#connectionManager.state;
12
+ }
13
+ get connected() {
14
+ return this.connectionState === ConnectionStates.CONNECTED;
15
+ }
16
+ get subscriptions() {
17
+ return new Map(this.#driver.subscriptions);
18
+ }
19
+ connect() {
20
+ return this.#connectionManager.connect();
21
+ }
22
+ disconnect() {
23
+ return this.#connectionManager.disconnect();
24
+ }
25
+ subscribe(recipientId, subscription) {
26
+ return this.#driver.subscribe(recipientId, subscription);
27
+ }
28
+ unsubscribe(recipientId) {
29
+ return this.#driver.unsubscribe(recipientId);
30
+ }
31
+ sendNotification(recipientId, title, message) {
32
+ return this.#driver.sendNotification(recipientId, title, message);
33
+ }
34
+ }
@@ -0,0 +1,7 @@
1
+ export declare const ConnectionStates: {
2
+ readonly DISCONNECTED: "DISCONNECTED";
3
+ readonly DISCONNECTING: "DISCONNECTING";
4
+ readonly CONNECTING: "CONNECTING";
5
+ readonly CONNECTED: "CONNECTED";
6
+ };
7
+ export type ConnectionState = typeof ConnectionStates[keyof typeof ConnectionStates];
@@ -0,0 +1,6 @@
1
+ export const ConnectionStates = {
2
+ DISCONNECTED: 'DISCONNECTED',
3
+ DISCONNECTING: 'DISCONNECTING',
4
+ CONNECTING: 'CONNECTING',
5
+ CONNECTED: 'CONNECTED'
6
+ };
@@ -1,4 +1,4 @@
1
- export interface NotificationService {
1
+ export interface Driver {
2
2
  get connected(): boolean;
3
3
  get subscriptions(): Map<string, unknown>;
4
4
  connect(): Promise<void>;
@@ -1,9 +1,9 @@
1
- import type { NotificationService } from '../../definitions/interfaces.js';
1
+ import type { Driver } from '../definitions/interfaces.js';
2
2
  type Notification = {
3
3
  title: string;
4
4
  body: string;
5
5
  };
6
- export default class Memory implements NotificationService {
6
+ export default class Memory implements Driver {
7
7
  #private;
8
8
  get connected(): boolean;
9
9
  get subscriptions(): Map<string, Notification[]>;
@@ -12,5 +12,6 @@ export default class Memory implements NotificationService {
12
12
  subscribe(recipientId: string): Promise<void>;
13
13
  unsubscribe(recipientId: string): Promise<void>;
14
14
  sendNotification(recipientId: string, title: string, body: string): Promise<void>;
15
+ clear(): void;
15
16
  }
16
17
  export {};
@@ -0,0 +1,43 @@
1
+ import NotConnected from '../errors/NotConnected.js';
2
+ import SubscriptionNotFound from '../errors/SubscriptionNotFound.js';
3
+ export default class Memory {
4
+ #subscriptions = new Map();
5
+ #connected = false;
6
+ get connected() { return this.#connected; }
7
+ get subscriptions() {
8
+ if (this.#connected === false) {
9
+ throw new NotConnected();
10
+ }
11
+ return this.#subscriptions;
12
+ }
13
+ async connect() {
14
+ this.#connected = true;
15
+ }
16
+ async disconnect() {
17
+ this.#connected = false;
18
+ this.#subscriptions.clear();
19
+ }
20
+ async subscribe(recipientId) {
21
+ this.subscriptions.set(recipientId, []);
22
+ }
23
+ async unsubscribe(recipientId) {
24
+ if (this.subscriptions.has(recipientId) === false) {
25
+ throw new SubscriptionNotFound(recipientId);
26
+ }
27
+ this.subscriptions.delete(recipientId);
28
+ }
29
+ async sendNotification(recipientId, title, body) {
30
+ const subscription = this.#getSubscription(recipientId);
31
+ subscription.push({ title, body });
32
+ }
33
+ clear() {
34
+ this.subscriptions.clear();
35
+ }
36
+ #getSubscription(recipientId) {
37
+ const subscription = this.subscriptions.get(recipientId);
38
+ if (subscription === undefined) {
39
+ throw new SubscriptionNotFound(recipientId);
40
+ }
41
+ return subscription;
42
+ }
43
+ }
@@ -1,13 +1,13 @@
1
1
  import type { PushSubscription } from 'web-push';
2
- import type { NotificationService } from '../../definitions/interfaces.js';
3
- type VapidDetails = {
2
+ import type { Driver } from '../definitions/interfaces.js';
3
+ type WebPushConfiguration = {
4
4
  subject: string;
5
5
  publicKey: string;
6
6
  privateKey: string;
7
7
  };
8
- export default class WebPush implements NotificationService {
8
+ export default class WebPush implements Driver {
9
9
  #private;
10
- constructor(configuration: VapidDetails);
10
+ constructor(configuration: WebPushConfiguration);
11
11
  get connected(): boolean;
12
12
  get subscriptions(): Map<string, PushSubscription>;
13
13
  connect(): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import webpush from 'web-push';
2
- import NotConnected from '../../errors/NotConnected.js';
3
- import SubscriptionNotFound from '../../errors/SubscriptionNotFound.js';
2
+ import NotConnected from '../errors/NotConnected.js';
3
+ import SubscriptionNotFound from '../errors/SubscriptionNotFound.js';
4
4
  export default class WebPush {
5
5
  #configuration;
6
6
  #subscriptions;
@@ -1,2 +1,3 @@
1
1
  export default class NotificationError extends Error {
2
+ constructor(message?: string);
2
3
  }
@@ -1,2 +1,9 @@
1
1
  export default class NotificationError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = this.constructor.name;
5
+ if (Error.captureStackTrace) {
6
+ Error.captureStackTrace(this, this.constructor);
7
+ }
8
+ }
2
9
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export type { NotificationService } from './definitions/interfaces.js';
1
+ export type * from './definitions/interfaces.js';
2
2
  export { default as NotConnected } from './errors/NotConnected.js';
3
3
  export { default as NotificationError } from './errors/NotificationError.js';
4
4
  export { default as SubscriptionNotFound } from './errors/SubscriptionNotFound.js';
5
- export { default as UnknownImplementation } from './errors/UnknownImplementation.js';
6
- export { default } from './implementation.js';
5
+ export { default as MemoryDriver } from './drivers/Memory.js';
6
+ export { default } from './NotificationService.js';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { default as NotConnected } from './errors/NotConnected.js';
2
2
  export { default as NotificationError } from './errors/NotificationError.js';
3
3
  export { default as SubscriptionNotFound } from './errors/SubscriptionNotFound.js';
4
- export { default as UnknownImplementation } from './errors/UnknownImplementation.js';
5
- export { default } from './implementation.js';
4
+ export { default as MemoryDriver } from './drivers/Memory.js';
5
+ export { default } from './NotificationService.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@theshelf/notification",
3
3
  "private": false,
4
- "version": "0.0.4",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "url": "https://github.com/MaskingTechnology/theshelf"
@@ -1,4 +0,0 @@
1
- import NotificationError from './NotificationError.js';
2
- export default class UnknownImplementation extends NotificationError {
3
- constructor(name: string);
4
- }
@@ -1,6 +0,0 @@
1
- import NotificationError from './NotificationError.js';
2
- export default class UnknownImplementation extends NotificationError {
3
- constructor(name) {
4
- super(`Unknown notification implementation: ${name}`);
5
- }
6
- }
@@ -1,3 +0,0 @@
1
- import type { NotificationService } from './definitions/interfaces.js';
2
- declare const _default: NotificationService;
3
- export default _default;
@@ -1,14 +0,0 @@
1
- import UnknownImplementation from './errors/UnknownImplementation.js';
2
- import createMemory from './implementations/memory/create.js';
3
- import createWebPush from './implementations/webpush/create.js';
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();
@@ -1,46 +0,0 @@
1
- import NotConnected from '../../errors/NotConnected.js';
2
- import SubscriptionNotFound from '../../errors/SubscriptionNotFound.js';
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
- }
@@ -1,2 +0,0 @@
1
- import Memory from './Memory.js';
2
- export default function create(): Memory;
@@ -1,4 +0,0 @@
1
- import Memory from './Memory.js';
2
- export default function create() {
3
- return new Memory();
4
- }
@@ -1,2 +0,0 @@
1
- import WebPush from './WebPush.js';
2
- export default function create(): WebPush;
@@ -1,7 +0,0 @@
1
- import WebPush from './WebPush.js';
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
- }