@streamlayer/sdk-web-notifications 0.8.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 ADDED
@@ -0,0 +1,6 @@
1
+ # sdk-web-notifications
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+ Run `nx build sdk-web-notifications` to build the library.
package/lib/index.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { StreamLayerContext } from '@streamlayer/sdk-web-interfaces';
2
+ import { Notifications } from './notifications';
3
+ export { type Notification, type NotificationData, NotificationType, Notifications } from './notifications';
4
+ export { type NotificationsList } from './queue';
5
+ export type NotificationsStore = ReturnType<Notifications['getQueueStore']>;
6
+ declare module '@streamlayer/sdk-web-interfaces' {
7
+ interface StreamLayerContext {
8
+ notifications: Notifications;
9
+ addNotification: Notifications['add'];
10
+ }
11
+ interface StreamLayerSDK {
12
+ getNotificationsStore: Notifications['getQueueStore'];
13
+ }
14
+ }
15
+ /**
16
+ * notifications plugin, connect notifications to sdk
17
+ */
18
+ export declare const notifications: (instance: StreamLayerContext, opts: unknown, done: () => void) => void;
package/lib/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import { Notifications } from './notifications';
2
+ export { NotificationType, Notifications } from './notifications';
3
+ /**
4
+ * notifications plugin, connect notifications to sdk
5
+ */
6
+ export const notifications = (instance, opts, done) => {
7
+ instance.notifications = new Notifications();
8
+ instance.addNotification = instance.notifications.add;
9
+ instance.sdk.getNotificationsStore = () => instance.notifications.getQueueStore();
10
+ done();
11
+ };
@@ -0,0 +1,42 @@
1
+ import { QuestionImages, ImagePosition } from '@streamlayer/sdk-web-types';
2
+ import { NotificationsQueue, NotificationsQueueOptions } from './queue';
3
+ export type NotificationData = {
4
+ color?: string;
5
+ icon?: string;
6
+ sponsorLogo?: string;
7
+ graphicBg?: string;
8
+ title?: string;
9
+ header?: string;
10
+ subtitle?: string;
11
+ primaryColor?: string;
12
+ imageMode?: QuestionImages;
13
+ imagePosition?: ImagePosition;
14
+ };
15
+ export declare enum NotificationType {
16
+ ONBOARDING = 1,
17
+ QUESTION = 2
18
+ }
19
+ export type Notification<T extends Record<string, unknown> = any, M extends Record<string, Function> = never> = {
20
+ autoHideDuration: number;
21
+ delay?: number;
22
+ hiding?: boolean;
23
+ type: NotificationType;
24
+ action?: (...args: unknown[]) => void;
25
+ close?: (...args: unknown[]) => void;
26
+ methods?: M;
27
+ data: NotificationData;
28
+ id: string;
29
+ meta?: T;
30
+ };
31
+ /**
32
+ * @description app notifications (inapp)
33
+ */
34
+ export declare class Notifications {
35
+ queue: NotificationsQueue;
36
+ private storage;
37
+ constructor(options?: Partial<NotificationsQueueOptions>);
38
+ add: (notification: Notification) => void;
39
+ close: (notificationId: string) => void;
40
+ getQueueStore: () => import("nanostores").ReadableAtom<Notification[]>;
41
+ markAsViewed: (notificationId: string) => void;
42
+ }
@@ -0,0 +1,34 @@
1
+ import { NotificationsQueue } from './queue';
2
+ import { NotificationStorage } from './storage';
3
+ export var NotificationType;
4
+ (function (NotificationType) {
5
+ NotificationType[NotificationType["ONBOARDING"] = 1] = "ONBOARDING";
6
+ NotificationType[NotificationType["QUESTION"] = 2] = "QUESTION";
7
+ })(NotificationType || (NotificationType = {}));
8
+ /**
9
+ * @description app notifications (inapp)
10
+ */
11
+ export class Notifications {
12
+ queue;
13
+ storage;
14
+ constructor(options = {}) {
15
+ this.storage = new NotificationStorage();
16
+ this.queue = new NotificationsQueue({ concurrency: 1, animationDelay: 300, ...options });
17
+ }
18
+ add = (notification) => {
19
+ const isViewed = this.storage.isOpened(notification.id);
20
+ if (!isViewed) {
21
+ this.queue.addToQueue(notification);
22
+ }
23
+ };
24
+ close = (notificationId) => {
25
+ this.queue.closeNotification(notificationId);
26
+ this.markAsViewed(notificationId);
27
+ };
28
+ getQueueStore = () => {
29
+ return this.queue.notificationsList;
30
+ };
31
+ markAsViewed = (notificationId) => {
32
+ this.storage.setOpened(notificationId);
33
+ };
34
+ }
@@ -0,0 +1,22 @@
1
+ import { createComputedStore } from '@streamlayer/sdk-web-interfaces';
2
+ import { Notification } from '..';
3
+ export type NotificationsQueueOptions = {
4
+ concurrency: number;
5
+ animationDelay: number;
6
+ };
7
+ export type NotificationsList = ReturnType<typeof createComputedStore<Notification[]>>;
8
+ export declare class NotificationsQueue {
9
+ notificationsList: NotificationsList;
10
+ private notifications;
11
+ private store;
12
+ private timeouts;
13
+ private waitingQueue;
14
+ private activeQueue;
15
+ private options;
16
+ private logger;
17
+ constructor(options: NotificationsQueueOptions);
18
+ addToQueue: (notification: Notification) => void;
19
+ tickWaitingQueue: () => void;
20
+ tickActiveQueue: (notificationId: string) => void;
21
+ closeNotification: (notificationId: string) => void;
22
+ }
@@ -0,0 +1,113 @@
1
+ import { SingleStore, createSingleStore, createComputedStore } from '@streamlayer/sdk-web-interfaces';
2
+ import { createLogger } from '@streamlayer/sdk-web-logger';
3
+ export class NotificationsQueue {
4
+ notificationsList;
5
+ notifications;
6
+ store;
7
+ timeouts;
8
+ waitingQueue;
9
+ activeQueue;
10
+ options;
11
+ logger;
12
+ constructor(options) {
13
+ this.options = options;
14
+ this.logger = createLogger('notifications');
15
+ this.store = new Map();
16
+ this.timeouts = new Map();
17
+ this.waitingQueue = new Set();
18
+ this.activeQueue = new Set();
19
+ this.notifications = new SingleStore(createSingleStore(new Map()), 'notifications-queue');
20
+ this.notificationsList = createComputedStore(this.notifications.getStore(), (notifications) => notifications ? [...notifications.values()] : []);
21
+ }
22
+ addToQueue = (notification) => {
23
+ if (this.store.has(notification.id)) {
24
+ this.logger.debug({ notification }, 'skip existed notification: %o');
25
+ return;
26
+ }
27
+ const close = notification.close;
28
+ const action = notification.action;
29
+ this.store.set(notification.id, {
30
+ ...notification,
31
+ close: (...args) => {
32
+ if (close) {
33
+ close(...args);
34
+ }
35
+ this.closeNotification(notification.id);
36
+ },
37
+ action: (...args) => {
38
+ if (action) {
39
+ action(...args);
40
+ }
41
+ this.closeNotification(notification.id);
42
+ },
43
+ });
44
+ this.waitingQueue.add(notification.id);
45
+ this.logger.debug({ notification }, 'notification added to waiting queue');
46
+ this.tickWaitingQueue();
47
+ };
48
+ tickWaitingQueue = () => {
49
+ if (this.activeQueue.size < this.options.concurrency) {
50
+ const [job] = this.waitingQueue;
51
+ if (!job) {
52
+ return;
53
+ }
54
+ this.activeQueue.add(job);
55
+ this.waitingQueue.delete(job);
56
+ this.logger.debug({ job }, 'waiting queue tick');
57
+ this.tickActiveQueue(job);
58
+ }
59
+ else {
60
+ this.logger.debug({ queueSize: this.activeQueue.size, concurrency: this.options.concurrency }, 'waiting queue tick skipped');
61
+ }
62
+ };
63
+ tickActiveQueue = (notificationId) => {
64
+ if (!notificationId) {
65
+ return;
66
+ }
67
+ const notification = this.store.get(notificationId);
68
+ if (!notification) {
69
+ this.logger.debug({ notificationId }, 'active queue tick skipped, notification not exist');
70
+ return;
71
+ }
72
+ const timeout = setTimeout(() => {
73
+ const closureId = notificationId;
74
+ const prevQueue = new Map(this.notifications.getValue());
75
+ prevQueue.set(notification.id, notification);
76
+ this.notifications.setValue(prevQueue);
77
+ const timeout = setTimeout(() => {
78
+ this.logger.debug({ notificationId: closureId }, 'notification hiding by autoHideDuration');
79
+ this.closeNotification(closureId);
80
+ }, notification.autoHideDuration || 5000);
81
+ this.timeouts.set(closureId, timeout);
82
+ this.logger.debug({ notificationId: closureId, queue: [...prevQueue.values()] }, 'notification displayed');
83
+ }, notification.delay || 0);
84
+ this.timeouts.set(notificationId, timeout);
85
+ this.logger.debug({ notificationId }, 'active queue tick completed');
86
+ this.tickWaitingQueue();
87
+ };
88
+ closeNotification = (notificationId) => {
89
+ const prevQueue = new Map(this.notifications.getValue());
90
+ const notification = prevQueue.get(notificationId);
91
+ if (notification) {
92
+ notification.hiding = true;
93
+ this.notifications.setValue(prevQueue);
94
+ const timeout = setTimeout(() => {
95
+ const prevQueue = new Map(this.notifications.getValue());
96
+ prevQueue.delete(notificationId);
97
+ this.notifications.setValue(prevQueue);
98
+ const timeout = this.timeouts.get(notificationId);
99
+ if (timeout !== undefined) {
100
+ clearTimeout(timeout);
101
+ this.timeouts.delete(notificationId);
102
+ }
103
+ this.logger.debug({ notificationId }, 'notification hidden');
104
+ }, this.options.animationDelay || 0);
105
+ this.timeouts.set(notificationId, timeout);
106
+ }
107
+ this.store.delete(notificationId);
108
+ this.activeQueue.delete(notificationId);
109
+ this.waitingQueue.delete(notificationId);
110
+ this.tickWaitingQueue();
111
+ this.logger.debug({ notificationId }, 'notification hiding');
112
+ };
113
+ }
@@ -0,0 +1,7 @@
1
+ import { Storage } from '@streamlayer/sdk-web-storage';
2
+ export declare class NotificationStorage extends Storage {
3
+ constructor();
4
+ setOpened: (notificationId: string) => void;
5
+ isOpened: (notificationId: string) => string | undefined;
6
+ clearNotification: () => void;
7
+ }
package/lib/storage.js ADDED
@@ -0,0 +1,19 @@
1
+ import { Storage } from '@streamlayer/sdk-web-storage';
2
+ var KEY_PREFIX;
3
+ (function (KEY_PREFIX) {
4
+ KEY_PREFIX["OPENED"] = "opened";
5
+ })(KEY_PREFIX || (KEY_PREFIX = {}));
6
+ export class NotificationStorage extends Storage {
7
+ constructor() {
8
+ super('notification');
9
+ }
10
+ setOpened = (notificationId) => {
11
+ this.write(KEY_PREFIX.OPENED, notificationId, 'true');
12
+ };
13
+ isOpened = (notificationId) => {
14
+ return this.read(KEY_PREFIX.OPENED, notificationId);
15
+ };
16
+ clearNotification = () => {
17
+ this.clear();
18
+ };
19
+ }
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@streamlayer/sdk-web-notifications",
3
+ "version": "0.8.0",
4
+ "type": "module",
5
+ "main": "./lib/index.js",
6
+ "typings": "./lib/index.d.ts",
7
+ "files": [
8
+ "lib/"
9
+ ],
10
+ "peerDependencies": {
11
+ "@streamlayer/sdk-web-interfaces": "^0.1.0",
12
+ "@streamlayer/sdk-web-types": "^0.1.0",
13
+ "@streamlayer/sdk-web-logger": "^0.4.0",
14
+ "@streamlayer/sdk-web-storage": "^0.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "tslib": "^2.6.2"
18
+ }
19
+ }