@memberjunction/ng-notifications 5.27.1 → 5.28.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.
@@ -11,8 +11,19 @@ export declare class MJNotificationService {
11
11
  private static isLoading$;
12
12
  private tabChange;
13
13
  tabChange$: Observable<unknown>;
14
- private static _notifications$;
15
- private static _unreadCount$;
14
+ /**
15
+ * Observable stream of the current user's notifications, derived from
16
+ * UserInfoEngine.UserNotifications$. Emits immediately on subscribe (shareReplay
17
+ * buffers the latest value) and re-emits whenever the engine's notifications
18
+ * cache is mutated — by save, delete, remote-invalidate, or refresh. Each
19
+ * emission re-runs the engine's per-user filter+sort.
20
+ */
21
+ static readonly Notifications$: Observable<MJUserNotificationEntity[]>;
22
+ /**
23
+ * Observable stream of the current user's unread notification count, derived
24
+ * from Notifications$. Same emission lifecycle as Notifications$.
25
+ */
26
+ static readonly UnreadCount$: Observable<number>;
16
27
  /**
17
28
  * Optional callback that consuming apps can set to suppress toast notifications
18
29
  * for specific events (e.g., when the user is actively viewing the conversation
@@ -23,18 +34,19 @@ export declare class MJNotificationService {
23
34
  constructor();
24
35
  PushStatusUpdates(): Observable<string>;
25
36
  static get Instance(): MJNotificationService;
26
- private static _userNotifications;
37
+ /**
38
+ * The current user's notifications, delegated to UserInfoEngine which owns the
39
+ * cache. Applies the per-user filter and newest-first sort on each access.
40
+ */
27
41
  static get UserNotifications(): MJUserNotificationEntity[];
28
- static get UnreadUserNotifications(): MJUserNotificationEntity[];
29
- static get UnreadUserNotificationCount(): number;
30
42
  /**
31
- * Observable that emits the full list of user notifications whenever they change
43
+ * The current user's unread notifications, delegated to UserInfoEngine.
32
44
  */
33
- static get Notifications$(): Observable<MJUserNotificationEntity[]>;
45
+ static get UnreadUserNotifications(): MJUserNotificationEntity[];
34
46
  /**
35
- * Observable that emits the unread notification count whenever it changes
47
+ * The current user's unread notification count, delegated to UserInfoEngine.
36
48
  */
37
- static get UnreadCount$(): Observable<number>;
49
+ static get UnreadUserNotificationCount(): number;
38
50
  /**
39
51
  * Instance method to access Notifications$ observable
40
52
  */
@@ -54,20 +66,22 @@ export declare class MJNotificationService {
54
66
  */
55
67
  CreateNotification(title: string, message: string, resourceTypeId: string | null, resourceRecordId: string | null, resourceConfiguration: any | null, displayToUser?: boolean): Promise<MJUserNotificationEntity>;
56
68
  /**
57
- * Refresh user notifications and re-emit to observables.
69
+ * Refresh user notifications from the server. Delegates to UserInfoEngine.RefreshItem,
70
+ * whose successful reload mutates the engine's notifications cache and triggers an
71
+ * emission on Notifications$/UnreadCount$ via the BaseEngine observable plumbing —
72
+ * subscribers are updated automatically with no manual re-emit required here.
58
73
  *
59
- * Uses RefreshItem() to guarantee fresh data from the server. The global
60
- * CACHE_INVALIDATION listener keeps the engine cache updated for background
61
- * changes, but when this method is called in response to a PushStatusUpdates
62
- * message, the cache invalidation event may not have arrived yet (separate
63
- * WebSocket message, potential race). RefreshItem() eliminates that race by
64
- * doing a targeted RunView for just notifications.
74
+ * Uses RefreshItem() rather than relying solely on the global CACHE_INVALIDATION
75
+ * listener: when this method is called in response to a PushStatusUpdates message,
76
+ * the cache-invalidation event may not have arrived yet (separate WebSocket message,
77
+ * potential race). RefreshItem() eliminates that race by doing a targeted RunView
78
+ * for just notifications.
65
79
  */
66
80
  static RefreshUserNotifications(): Promise<void>;
67
81
  /**
68
- * Update notification observables from existing in-memory data without doing a database query.
69
- * This is efficient for cases where notifications have been modified locally (e.g., marking as read)
70
- * and we just need to notify subscribers of the change.
82
+ * @deprecated Kept as a no-op for backward compatibility. Observable emissions are now
83
+ * driven by UserInfoEngine.UserNotifications$, which auto-emits on save/delete/
84
+ * remote-invalidate/refresh. External callers no longer need to manually push updates.
71
85
  */
72
86
  static UpdateNotificationObservables(): void;
73
87
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.service.d.ts","sourceRoot":"","sources":["../../src/lib/notifications.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAGzF,OAAO,EAAmB,UAAU,EAAW,MAAM,MAAM,CAAC;;AAE5D;;;GAGG;AACH,qBAGa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAyC;IAChF,OAAO,CAAC,MAAM,CAAC,OAAO,CAAkB;IAExC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAuC;IAChE,OAAO,CAAC,SAAS,CAAiB;IAClC,UAAU,sBAAiC;IAG3C,OAAO,CAAC,MAAM,CAAC,eAAe,CAAuD;IACrF,OAAO,CAAC,MAAM,CAAC,aAAa,CAAkC;IAE9D;;;;;OAKG;IACI,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;;IA0FtE,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC;IAK9C,WAAkB,QAAQ,IAAI,qBAAqB,CAElD;IAED,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAkC;IACnE,WAAkB,iBAAiB,IAAI,wBAAwB,EAAE,CAEhE;IACD,WAAkB,uBAAuB,IAAI,wBAAwB,EAAE,CAEtE;IACD,WAAkB,2BAA2B,IAAI,MAAM,CAEtD;IAED;;OAEG;IACH,WAAkB,cAAc,IAAI,UAAU,CAAC,wBAAwB,EAAE,CAAC,CAEzE;IAED;;OAEG;IACH,WAAkB,YAAY,IAAI,UAAU,CAAC,MAAM,CAAC,CAEnD;IAED;;OAEG;IACH,IAAW,cAAc,IAAI,UAAU,CAAC,wBAAwB,EAAE,CAAC,CAElE;IAED;;OAEG;IACH,IAAW,YAAY,IAAI,UAAU,CAAC,MAAM,CAAC,CAE5C;IAGD;;;;;;;;OAQG;IACU,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,EAAE,qBAAqB,EAAE,GAAG,GAAG,IAAI,EAAE,aAAa,GAAE,OAAc,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAyBpO;;;;;;;;;OASG;WACiB,wBAAwB;IAc5C;;;;OAIG;WACW,6BAA6B;IAK3C;;;;;;OAMG;IACI,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAkB,EAAE,SAAS,CAAC,EAAE,MAAM;IAIzI;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAe5B;;OAEG;IACH,OAAO,CAAC,SAAS;IA+DjB;;OAEG;IACH,OAAO,CAAC,UAAU;yCAnUP,qBAAqB;6CAArB,qBAAqB;CAwUjC"}
1
+ {"version":3,"file":"notifications.service.d.ts","sourceRoot":"","sources":["../../src/lib/notifications.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAGzF,OAAO,EAAmB,UAAU,EAAW,MAAM,MAAM,CAAC;;AAG5D;;;GAGG;AACH,qBAGa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAyC;IAChF,OAAO,CAAC,MAAM,CAAC,OAAO,CAAkB;IAExC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAuC;IAChE,OAAO,CAAC,SAAS,CAAiB;IAClC,UAAU,sBAAiC;IAE3C;;;;;;OAMG;IACH,gBAAuB,cAAc,EAAE,UAAU,CAAC,wBAAwB,EAAE,CAAC,CAIzE;IAEJ;;;OAGG;IACH,gBAAuB,YAAY,EAAE,UAAU,CAAC,MAAM,CAAC,CAInD;IAEJ;;;;;OAKG;IACI,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;;IAmFtE,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC;IAK9C,WAAkB,QAAQ,IAAI,qBAAqB,CAElD;IAED;;;OAGG;IACH,WAAkB,iBAAiB,IAAI,wBAAwB,EAAE,CAEhE;IAED;;OAEG;IACH,WAAkB,uBAAuB,IAAI,wBAAwB,EAAE,CAEtE;IAED;;OAEG;IACH,WAAkB,2BAA2B,IAAI,MAAM,CAEtD;IAED;;OAEG;IACH,IAAW,cAAc,IAAI,UAAU,CAAC,wBAAwB,EAAE,CAAC,CAElE;IAED;;OAEG;IACH,IAAW,YAAY,IAAI,UAAU,CAAC,MAAM,CAAC,CAE5C;IAGD;;;;;;;;OAQG;IACU,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,EAAE,qBAAqB,EAAE,GAAG,GAAG,IAAI,EAAE,aAAa,GAAE,OAAc,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAyBpO;;;;;;;;;;;OAWG;WACiB,wBAAwB;IAU5C;;;;OAIG;WACW,6BAA6B;IAI3C;;;;;;OAMG;IACI,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAkB,EAAE,SAAS,CAAC,EAAE,MAAM;IAIzI;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAe5B;;OAEG;IACH,OAAO,CAAC,SAAS;IA+DjB;;OAEG;IACH,OAAO,CAAC,UAAU;yCAzUP,qBAAqB;6CAArB,qBAAqB;CA8UjC"}
@@ -3,6 +3,7 @@ import { LogError, Metadata, StartupManager } from '@memberjunction/core';
3
3
  import { UserInfoEngine } from '@memberjunction/core-entities';
4
4
  import { MJEventType, MJGlobal, GetGlobalObjectStore } from '@memberjunction/global';
5
5
  import { BehaviorSubject, Subject } from 'rxjs';
6
+ import { map, shareReplay } from 'rxjs/operators';
6
7
  import * as i0 from "@angular/core";
7
8
  /**
8
9
  * This injectable service is also available as a singleton MJNotificationService.Instance globally within an Angular application/library process space. It is responsible for displaying notifications to the user and also is able to manage the User Notifications entity
@@ -14,9 +15,19 @@ export class MJNotificationService {
14
15
  static isLoading$ = new BehaviorSubject(false);
15
16
  tabChange = new Subject();
16
17
  tabChange$ = this.tabChange.asObservable();
17
- // Observable for notification changes
18
- static _notifications$ = new BehaviorSubject([]);
19
- static _unreadCount$ = new BehaviorSubject(0);
18
+ /**
19
+ * Observable stream of the current user's notifications, derived from
20
+ * UserInfoEngine.UserNotifications$. Emits immediately on subscribe (shareReplay
21
+ * buffers the latest value) and re-emits whenever the engine's notifications
22
+ * cache is mutated — by save, delete, remote-invalidate, or refresh. Each
23
+ * emission re-runs the engine's per-user filter+sort.
24
+ */
25
+ static Notifications$ = UserInfoEngine.Instance.UserNotifications$.pipe(map(() => UserInfoEngine.Instance.UserNotifications), shareReplay({ bufferSize: 1, refCount: false }));
26
+ /**
27
+ * Observable stream of the current user's unread notification count, derived
28
+ * from Notifications$. Same emission lifecycle as Notifications$.
29
+ */
30
+ static UnreadCount$ = MJNotificationService.Notifications$.pipe(map((arr) => arr.filter((n) => n.Unread).length), shareReplay({ bufferSize: 1, refCount: false }));
20
31
  /**
21
32
  * Optional callback that consuming apps can set to suppress toast notifications
22
33
  * for specific events (e.g., when the user is actively viewing the conversation
@@ -54,16 +65,9 @@ export class MJNotificationService {
54
65
  MJNotificationService.RefreshUserNotifications();
55
66
  });
56
67
  }
57
- // Subscribe to UserInfoEngine's DataChange$ so that when CACHE_INVALIDATION
58
- // updates the _UserNotifications array, we immediately sync our BehaviorSubjects.
59
- // This is the primary mechanism for real-time notification updates — it fires
60
- // whenever BaseEngine processes a remote-invalidate event for User Notifications.
61
- UserInfoEngine.Instance.DataChange$.subscribe(event => {
62
- if (event.config.PropertyName === '_UserNotifications') {
63
- MJNotificationService._userNotifications = UserInfoEngine.Instance.UserNotifications;
64
- MJNotificationService.UpdateNotificationObservables();
65
- }
66
- });
68
+ // Notifications$ / UnreadCount$ are derived from UserInfoEngine.UserNotifications$
69
+ // at static-init time, so they auto-update whenever the engine's cache mutates.
70
+ // No per-login subscription wiring is needed.
67
71
  // got the login, now subscribe to push status updates here so we can then raise them as events in MJ Global locally
68
72
  this.PushStatusUpdates().subscribe((message) => {
69
73
  // Handle undefined/null messages gracefully
@@ -112,27 +116,24 @@ export class MJNotificationService {
112
116
  static get Instance() {
113
117
  return GetGlobalObjectStore()[MJNotificationService._globalStoreKey];
114
118
  }
115
- static _userNotifications = [];
119
+ /**
120
+ * The current user's notifications, delegated to UserInfoEngine which owns the
121
+ * cache. Applies the per-user filter and newest-first sort on each access.
122
+ */
116
123
  static get UserNotifications() {
117
- return MJNotificationService._userNotifications;
118
- }
119
- static get UnreadUserNotifications() {
120
- return MJNotificationService._userNotifications.filter(n => n.Unread);
121
- }
122
- static get UnreadUserNotificationCount() {
123
- return MJNotificationService.UnreadUserNotifications.length;
124
+ return UserInfoEngine.Instance.UserNotifications;
124
125
  }
125
126
  /**
126
- * Observable that emits the full list of user notifications whenever they change
127
+ * The current user's unread notifications, delegated to UserInfoEngine.
127
128
  */
128
- static get Notifications$() {
129
- return MJNotificationService._notifications$.asObservable();
129
+ static get UnreadUserNotifications() {
130
+ return UserInfoEngine.Instance.UnreadNotifications;
130
131
  }
131
132
  /**
132
- * Observable that emits the unread notification count whenever it changes
133
+ * The current user's unread notification count, delegated to UserInfoEngine.
133
134
  */
134
- static get UnreadCount$() {
135
- return MJNotificationService._unreadCount$.asObservable();
135
+ static get UnreadUserNotificationCount() {
136
+ return UserInfoEngine.Instance.UnreadNotificationCount;
136
137
  }
137
138
  /**
138
139
  * Instance method to access Notifications$ observable
@@ -177,22 +178,20 @@ export class MJNotificationService {
177
178
  return notification;
178
179
  }
179
180
  /**
180
- * Refresh user notifications and re-emit to observables.
181
+ * Refresh user notifications from the server. Delegates to UserInfoEngine.RefreshItem,
182
+ * whose successful reload mutates the engine's notifications cache and triggers an
183
+ * emission on Notifications$/UnreadCount$ via the BaseEngine observable plumbing —
184
+ * subscribers are updated automatically with no manual re-emit required here.
181
185
  *
182
- * Uses RefreshItem() to guarantee fresh data from the server. The global
183
- * CACHE_INVALIDATION listener keeps the engine cache updated for background
184
- * changes, but when this method is called in response to a PushStatusUpdates
185
- * message, the cache invalidation event may not have arrived yet (separate
186
- * WebSocket message, potential race). RefreshItem() eliminates that race by
187
- * doing a targeted RunView for just notifications.
186
+ * Uses RefreshItem() rather than relying solely on the global CACHE_INVALIDATION
187
+ * listener: when this method is called in response to a PushStatusUpdates message,
188
+ * the cache-invalidation event may not have arrived yet (separate WebSocket message,
189
+ * potential race). RefreshItem() eliminates that race by doing a targeted RunView
190
+ * for just notifications.
188
191
  */
189
192
  static async RefreshUserNotifications() {
190
193
  try {
191
- const engine = UserInfoEngine.Instance;
192
- await engine.RefreshItem('_UserNotifications');
193
- MJNotificationService._userNotifications = engine.UserNotifications;
194
- MJNotificationService._notifications$.next(engine.UserNotifications);
195
- MJNotificationService._unreadCount$.next(MJNotificationService.UnreadUserNotificationCount);
194
+ await UserInfoEngine.Instance.RefreshItem('_UserNotifications');
196
195
  MJNotificationService._loaded = true;
197
196
  }
198
197
  catch (e) {
@@ -200,13 +199,12 @@ export class MJNotificationService {
200
199
  }
201
200
  }
202
201
  /**
203
- * Update notification observables from existing in-memory data without doing a database query.
204
- * This is efficient for cases where notifications have been modified locally (e.g., marking as read)
205
- * and we just need to notify subscribers of the change.
202
+ * @deprecated Kept as a no-op for backward compatibility. Observable emissions are now
203
+ * driven by UserInfoEngine.UserNotifications$, which auto-emits on save/delete/
204
+ * remote-invalidate/refresh. External callers no longer need to manually push updates.
206
205
  */
207
206
  static UpdateNotificationObservables() {
208
- MJNotificationService._notifications$.next(MJNotificationService._userNotifications);
209
- MJNotificationService._unreadCount$.next(MJNotificationService.UnreadUserNotificationCount);
207
+ // No-op — see JSDoc above.
210
208
  }
211
209
  /**
212
210
  * Creates a message that is not saved to the User Notifications table, but is displayed to the user.
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.service.js","sourceRoot":"","sources":["../../src/lib/notifications.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { LogError, Metadata, StartupManager } from '@memberjunction/core';\nimport { UserInfoEngine, MJUserNotificationEntity } from '@memberjunction/core-entities';\nimport { DisplaySimpleNotificationRequestData, MJEventType, MJGlobal, GetGlobalObjectStore } from '@memberjunction/global';\nimport { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';\nimport { BehaviorSubject, Observable, Subject } from 'rxjs';\n\n/**\n * This injectable service is also available as a singleton MJNotificationService.Instance globally within an Angular application/library process space. It is responsible for displaying notifications to the user and also is able to manage the User Notifications entity\n * in the database.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class MJNotificationService {\n private static readonly _globalStoreKey = '___SINGLETON__MJNotificationService';\n private static _loaded: boolean = false;\n\n private static isLoading$ = new BehaviorSubject<boolean>(false);\n private tabChange = new Subject();\n tabChange$ = this.tabChange.asObservable();\n\n // Observable for notification changes\n private static _notifications$ = new BehaviorSubject<MJUserNotificationEntity[]>([]);\n private static _unreadCount$ = new BehaviorSubject<number>(0);\n\n /**\n * Optional callback that consuming apps can set to suppress toast notifications\n * for specific events (e.g., when the user is actively viewing the conversation\n * that triggered the notification). Return true to suppress the toast.\n * The notification DB record is still created and the badge count still updates.\n */\n public ShouldSuppressToast?: (statusObj: Record<string, unknown>) => boolean;\n\n constructor() {\n const g = GetGlobalObjectStore()!;\n if (g[MJNotificationService._globalStoreKey]) {\n return g[MJNotificationService._globalStoreKey] as MJNotificationService;\n }\n g[MJNotificationService._globalStoreKey] = this;\n\n MJGlobal.Instance.GetEventListener(true).subscribe( (event) => {\n switch (event.event) {\n case MJEventType.DisplaySimpleNotificationRequest: \n // received the message to display a notification to the user, so do that...\n const messageData: DisplaySimpleNotificationRequestData = <DisplaySimpleNotificationRequestData>event.args;\n this.CreateSimpleNotification(messageData.message,messageData.style, messageData.DisplayDuration);\n break;\n case MJEventType.ComponentEvent:\n if (event.eventCode === \"UserNotificationsUpdated\") {\n // refresh the user notifications\n MJNotificationService.RefreshUserNotifications();\n }\n break;\n case MJEventType.LoggedIn:\n if (MJNotificationService._loaded === false) {\n // Wait for StartupManager to complete before refreshing notifications.\n // UserInfoEngine (which backs RefreshUserNotifications) is @RegisterForStartup\n // and its _metadataConfigs are only populated during Config() which runs\n // as part of StartupManager.Startup(). Calling RefreshItem() before that\n // completes would silently find no config and return nothing.\n StartupManager.Instance.Startup().then(() => {\n MJNotificationService.RefreshUserNotifications();\n });\n }\n\n // Subscribe to UserInfoEngine's DataChange$ so that when CACHE_INVALIDATION\n // updates the _UserNotifications array, we immediately sync our BehaviorSubjects.\n // This is the primary mechanism for real-time notification updates — it fires\n // whenever BaseEngine processes a remote-invalidate event for User Notifications.\n UserInfoEngine.Instance.DataChange$.subscribe(event => {\n if (event.config.PropertyName === '_UserNotifications') {\n MJNotificationService._userNotifications = UserInfoEngine.Instance.UserNotifications;\n MJNotificationService.UpdateNotificationObservables();\n }\n });\n\n // got the login, now subscribe to push status updates here so we can then raise them as events in MJ Global locally\n this.PushStatusUpdates().subscribe( (message: string) => {\n // Handle undefined/null messages gracefully\n if (!message) {\n return;\n }\n\n const statusObj = JSON.parse(message);\n\n // pass along as an event so anyone else who wants to know about the push status update can do stuff\n MJGlobal.Instance.RaiseEvent({\n event: MJEventType.ComponentEvent,\n eventCode: \"PushStatusUpdates\",\n args: statusObj,\n component: this\n })\n\n const type = statusObj.type?.trim().toLowerCase();\n if (type === 'notification' || type === 'usernotifications') {\n // Server sends type:'notification', legacy used 'usernotifications' — support both\n const action = statusObj.action?.trim().toLowerCase()\n || statusObj.details?.action?.trim().toLowerCase();\n if (action === 'create') {\n // Check if the consuming app wants to suppress this toast\n // (e.g., user is actively viewing the conversation that triggered it)\n const suppress = this.ShouldSuppressToast?.(statusObj) ?? false;\n if (!suppress) {\n this.CreateSimpleNotification(statusObj.title || 'New Notification Available', \"success\", 3000);\n }\n // Always refresh the notification list (badge count, unread state)\n MJNotificationService.RefreshUserNotifications();\n }\n }\n else {\n // otherwise just post it as a simple notification, except Skip messages, we will let Skip handle those\n if (type !== 'askskip' && type !== 'entityobjectstatusmessage' && typeof statusObj.message === 'string') {\n this.CreateSimpleNotification(statusObj.message, \"success\", 2500);\n }\n }\n });\n break;\n } \n });\n }\n\n public PushStatusUpdates(): Observable<string> {\n const gp: GraphQLDataProvider = <GraphQLDataProvider>Metadata.Provider;\n return gp.PushStatusUpdates();\n }\n\n public static get Instance(): MJNotificationService {\n return GetGlobalObjectStore()![MJNotificationService._globalStoreKey] as MJNotificationService;\n }\n \n private static _userNotifications: MJUserNotificationEntity[] = [];\n public static get UserNotifications(): MJUserNotificationEntity[] {\n return MJNotificationService._userNotifications;\n }\n public static get UnreadUserNotifications(): MJUserNotificationEntity[] {\n return MJNotificationService._userNotifications.filter(n => n.Unread);\n }\n public static get UnreadUserNotificationCount(): number {\n return MJNotificationService.UnreadUserNotifications.length;\n }\n\n /**\n * Observable that emits the full list of user notifications whenever they change\n */\n public static get Notifications$(): Observable<MJUserNotificationEntity[]> {\n return MJNotificationService._notifications$.asObservable();\n }\n\n /**\n * Observable that emits the unread notification count whenever it changes\n */\n public static get UnreadCount$(): Observable<number> {\n return MJNotificationService._unreadCount$.asObservable();\n }\n\n /**\n * Instance method to access Notifications$ observable\n */\n public get notifications$(): Observable<MJUserNotificationEntity[]> {\n return MJNotificationService.Notifications$;\n }\n\n /**\n * Instance method to access UnreadCount$ observable\n */\n public get unreadCount$(): Observable<number> {\n return MJNotificationService.UnreadCount$;\n }\n\n\n /**\n * Creates a user notification in the database and refreshes the UI. Returns the notification object.\n * @param title \n * @param message \n * @param resourceTypeId \n * @param resourceRecordId \n * @param resourceConfiguration Any object, it is converted to a string by JSON.stringify and stored in the database\n * @returns \n */\n public async CreateNotification(title: string, message: string, resourceTypeId: string | null, resourceRecordId: string | null, resourceConfiguration: any | null, displayToUser: boolean = true): Promise<MJUserNotificationEntity> {\n const md = new Metadata();\n const notification = <MJUserNotificationEntity>await md.GetEntityObject('MJ: User Notifications');\n notification.Title = title;\n notification.Message = message;\n if (resourceTypeId)\n notification.ResourceTypeID = resourceTypeId;\n if (resourceRecordId)\n notification.ResourceRecordID = resourceRecordId;\n if (resourceConfiguration)\n notification.ResourceConfiguration = JSON.stringify(resourceConfiguration);\n \n notification.UserID = md.CurrentUser.ID;\n notification.Unread = true;\n const result = await notification.Save();\n if (result) {\n MJNotificationService.RefreshUserNotifications();\n }\n\n if (displayToUser)\n this.CreateSimpleNotification(notification.Message, \"success\", 2500);\n\n return notification;\n }\n\n /**\n * Refresh user notifications and re-emit to observables.\n *\n * Uses RefreshItem() to guarantee fresh data from the server. The global\n * CACHE_INVALIDATION listener keeps the engine cache updated for background\n * changes, but when this method is called in response to a PushStatusUpdates\n * message, the cache invalidation event may not have arrived yet (separate\n * WebSocket message, potential race). RefreshItem() eliminates that race by\n * doing a targeted RunView for just notifications.\n */\n public static async RefreshUserNotifications() {\n try {\n const engine = UserInfoEngine.Instance;\n await engine.RefreshItem('_UserNotifications');\n MJNotificationService._userNotifications = engine.UserNotifications;\n MJNotificationService._notifications$.next(engine.UserNotifications);\n MJNotificationService._unreadCount$.next(MJNotificationService.UnreadUserNotificationCount);\n MJNotificationService._loaded = true;\n }\n catch (e) {\n LogError(e);\n }\n }\n\n /**\n * Update notification observables from existing in-memory data without doing a database query.\n * This is efficient for cases where notifications have been modified locally (e.g., marking as read)\n * and we just need to notify subscribers of the change.\n */\n public static UpdateNotificationObservables() {\n MJNotificationService._notifications$.next(MJNotificationService._userNotifications);\n MJNotificationService._unreadCount$.next(MJNotificationService.UnreadUserNotificationCount);\n }\n\n /**\n * Creates a message that is not saved to the User Notifications table, but is displayed to the user.\n * Uses a lightweight DOM-based toast notification.\n * @param message - text to display\n * @param style - display styling\n * @param hideAfter - option to auto hide after the specified delay in milliseconds\n */\n public CreateSimpleNotification(message: string, style: \"none\" | \"success\" | \"error\" | \"warning\" | \"info\" = \"success\", hideAfter?: number) {\n this.showToast(message, style, hideAfter);\n }\n\n /**\n * Ensures the toast container element exists in the DOM.\n */\n private ensureToastContainer(): HTMLElement {\n let container = document.getElementById('mj-toast-container');\n if (!container) {\n container = document.createElement('div');\n container.id = 'mj-toast-container';\n container.style.cssText = `\n position: fixed; top: 16px; left: 50%; transform: translateX(-50%);\n z-index: 100000; display: flex; flex-direction: column; align-items: center; gap: 8px;\n pointer-events: none;\n `;\n document.body.appendChild(container);\n }\n return container;\n }\n\n /**\n * Shows a lightweight toast notification using DOM elements.\n */\n private showToast(message: string, style: string, hideAfter?: number): void {\n const container = this.ensureToastContainer();\n\n const toast = document.createElement('div');\n toast.style.cssText = `\n pointer-events: auto; padding: 12px 20px; border-radius: 6px; font-size: 14px;\n font-family: inherit; max-width: 500px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n display: flex; align-items: center; gap: 8px; animation: mj-toast-slide-in 0.3s ease-out;\n color: var(--mj-text-inverse);\n `;\n\n const bgColors: Record<string, string> = {\n success: 'var(--mj-status-success)',\n error: 'var(--mj-status-error)',\n warning: 'var(--mj-status-warning)',\n info: 'var(--mj-status-info)',\n none: 'var(--mj-text-secondary)'\n };\n toast.style.backgroundColor = bgColors[style] || bgColors['none'];\n\n const iconMap: Record<string, string> = {\n success: '\\u2713',\n error: '\\u2717',\n warning: '\\u26A0',\n info: '\\u2139',\n none: ''\n };\n const icon = iconMap[style] || '';\n\n const removeToast = () => {\n toast.style.animation = 'mj-toast-slide-out 0.3s ease-in forwards';\n toast.addEventListener('animationend', () => toast.remove());\n };\n\n toast.innerHTML = `\n ${icon ? `<span style=\"font-size:16px;font-weight:bold;\">${icon}</span>` : ''}\n <span style=\"flex:1;\">${this.escapeHtml(message)}</span>\n ${!hideAfter ? `<button style=\"background:none;border:none;color:var(--mj-text-inverse);cursor:pointer;font-size:18px;padding:0 0 0 8px;line-height:1;\" aria-label=\"Close\">&times;</button>` : ''}\n `;\n\n if (!hideAfter) {\n const closeBtn = toast.querySelector('button');\n closeBtn?.addEventListener('click', removeToast);\n }\n\n container.appendChild(toast);\n\n // Inject keyframes if not already present\n if (!document.getElementById('mj-toast-keyframes')) {\n const styleEl = document.createElement('style');\n styleEl.id = 'mj-toast-keyframes';\n styleEl.textContent = `\n @keyframes mj-toast-slide-in { from { opacity:0; transform:translateY(-20px); } to { opacity:1; transform:translateY(0); } }\n @keyframes mj-toast-slide-out { from { opacity:1; transform:translateY(0); } to { opacity:0; transform:translateY(-20px); } }\n `;\n document.head.appendChild(styleEl);\n }\n\n if (hideAfter) {\n setTimeout(removeToast, hideAfter);\n }\n }\n\n /**\n * Escapes HTML entities to prevent XSS in toast messages.\n */\n private escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n}"]}
1
+ {"version":3,"file":"notifications.service.js","sourceRoot":"","sources":["../../src/lib/notifications.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { LogError, Metadata, StartupManager } from '@memberjunction/core';\nimport { UserInfoEngine, MJUserNotificationEntity } from '@memberjunction/core-entities';\nimport { DisplaySimpleNotificationRequestData, MJEventType, MJGlobal, GetGlobalObjectStore } from '@memberjunction/global';\nimport { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';\nimport { BehaviorSubject, Observable, Subject } from 'rxjs';\nimport { map, shareReplay } from 'rxjs/operators';\n\n/**\n * This injectable service is also available as a singleton MJNotificationService.Instance globally within an Angular application/library process space. It is responsible for displaying notifications to the user and also is able to manage the User Notifications entity\n * in the database.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class MJNotificationService {\n private static readonly _globalStoreKey = '___SINGLETON__MJNotificationService';\n private static _loaded: boolean = false;\n\n private static isLoading$ = new BehaviorSubject<boolean>(false);\n private tabChange = new Subject();\n tabChange$ = this.tabChange.asObservable();\n\n /**\n * Observable stream of the current user's notifications, derived from\n * UserInfoEngine.UserNotifications$. Emits immediately on subscribe (shareReplay\n * buffers the latest value) and re-emits whenever the engine's notifications\n * cache is mutated — by save, delete, remote-invalidate, or refresh. Each\n * emission re-runs the engine's per-user filter+sort.\n */\n public static readonly Notifications$: Observable<MJUserNotificationEntity[]> =\n UserInfoEngine.Instance.UserNotifications$.pipe(\n map(() => UserInfoEngine.Instance.UserNotifications),\n shareReplay({ bufferSize: 1, refCount: false })\n );\n\n /**\n * Observable stream of the current user's unread notification count, derived\n * from Notifications$. Same emission lifecycle as Notifications$.\n */\n public static readonly UnreadCount$: Observable<number> =\n MJNotificationService.Notifications$.pipe(\n map((arr) => arr.filter((n) => n.Unread).length),\n shareReplay({ bufferSize: 1, refCount: false })\n );\n\n /**\n * Optional callback that consuming apps can set to suppress toast notifications\n * for specific events (e.g., when the user is actively viewing the conversation\n * that triggered the notification). Return true to suppress the toast.\n * The notification DB record is still created and the badge count still updates.\n */\n public ShouldSuppressToast?: (statusObj: Record<string, unknown>) => boolean;\n\n constructor() {\n const g = GetGlobalObjectStore()!;\n if (g[MJNotificationService._globalStoreKey]) {\n return g[MJNotificationService._globalStoreKey] as MJNotificationService;\n }\n g[MJNotificationService._globalStoreKey] = this;\n\n MJGlobal.Instance.GetEventListener(true).subscribe( (event) => {\n switch (event.event) {\n case MJEventType.DisplaySimpleNotificationRequest: \n // received the message to display a notification to the user, so do that...\n const messageData: DisplaySimpleNotificationRequestData = <DisplaySimpleNotificationRequestData>event.args;\n this.CreateSimpleNotification(messageData.message,messageData.style, messageData.DisplayDuration);\n break;\n case MJEventType.ComponentEvent:\n if (event.eventCode === \"UserNotificationsUpdated\") {\n // refresh the user notifications\n MJNotificationService.RefreshUserNotifications();\n }\n break;\n case MJEventType.LoggedIn:\n if (MJNotificationService._loaded === false) {\n // Wait for StartupManager to complete before refreshing notifications.\n // UserInfoEngine (which backs RefreshUserNotifications) is @RegisterForStartup\n // and its _metadataConfigs are only populated during Config() which runs\n // as part of StartupManager.Startup(). Calling RefreshItem() before that\n // completes would silently find no config and return nothing.\n StartupManager.Instance.Startup().then(() => {\n MJNotificationService.RefreshUserNotifications();\n });\n }\n\n // Notifications$ / UnreadCount$ are derived from UserInfoEngine.UserNotifications$\n // at static-init time, so they auto-update whenever the engine's cache mutates.\n // No per-login subscription wiring is needed.\n\n // got the login, now subscribe to push status updates here so we can then raise them as events in MJ Global locally\n this.PushStatusUpdates().subscribe( (message: string) => {\n // Handle undefined/null messages gracefully\n if (!message) {\n return;\n }\n\n const statusObj = JSON.parse(message);\n\n // pass along as an event so anyone else who wants to know about the push status update can do stuff\n MJGlobal.Instance.RaiseEvent({\n event: MJEventType.ComponentEvent,\n eventCode: \"PushStatusUpdates\",\n args: statusObj,\n component: this\n })\n\n const type = statusObj.type?.trim().toLowerCase();\n if (type === 'notification' || type === 'usernotifications') {\n // Server sends type:'notification', legacy used 'usernotifications' — support both\n const action = statusObj.action?.trim().toLowerCase()\n || statusObj.details?.action?.trim().toLowerCase();\n if (action === 'create') {\n // Check if the consuming app wants to suppress this toast\n // (e.g., user is actively viewing the conversation that triggered it)\n const suppress = this.ShouldSuppressToast?.(statusObj) ?? false;\n if (!suppress) {\n this.CreateSimpleNotification(statusObj.title || 'New Notification Available', \"success\", 3000);\n }\n // Always refresh the notification list (badge count, unread state)\n MJNotificationService.RefreshUserNotifications();\n }\n }\n else {\n // otherwise just post it as a simple notification, except Skip messages, we will let Skip handle those\n if (type !== 'askskip' && type !== 'entityobjectstatusmessage' && typeof statusObj.message === 'string') {\n this.CreateSimpleNotification(statusObj.message, \"success\", 2500);\n }\n }\n });\n break;\n } \n });\n }\n\n public PushStatusUpdates(): Observable<string> {\n const gp: GraphQLDataProvider = <GraphQLDataProvider>Metadata.Provider;\n return gp.PushStatusUpdates();\n }\n\n public static get Instance(): MJNotificationService {\n return GetGlobalObjectStore()![MJNotificationService._globalStoreKey] as MJNotificationService;\n }\n \n /**\n * The current user's notifications, delegated to UserInfoEngine which owns the\n * cache. Applies the per-user filter and newest-first sort on each access.\n */\n public static get UserNotifications(): MJUserNotificationEntity[] {\n return UserInfoEngine.Instance.UserNotifications;\n }\n\n /**\n * The current user's unread notifications, delegated to UserInfoEngine.\n */\n public static get UnreadUserNotifications(): MJUserNotificationEntity[] {\n return UserInfoEngine.Instance.UnreadNotifications;\n }\n\n /**\n * The current user's unread notification count, delegated to UserInfoEngine.\n */\n public static get UnreadUserNotificationCount(): number {\n return UserInfoEngine.Instance.UnreadNotificationCount;\n }\n\n /**\n * Instance method to access Notifications$ observable\n */\n public get notifications$(): Observable<MJUserNotificationEntity[]> {\n return MJNotificationService.Notifications$;\n }\n\n /**\n * Instance method to access UnreadCount$ observable\n */\n public get unreadCount$(): Observable<number> {\n return MJNotificationService.UnreadCount$;\n }\n\n\n /**\n * Creates a user notification in the database and refreshes the UI. Returns the notification object.\n * @param title \n * @param message \n * @param resourceTypeId \n * @param resourceRecordId \n * @param resourceConfiguration Any object, it is converted to a string by JSON.stringify and stored in the database\n * @returns \n */\n public async CreateNotification(title: string, message: string, resourceTypeId: string | null, resourceRecordId: string | null, resourceConfiguration: any | null, displayToUser: boolean = true): Promise<MJUserNotificationEntity> {\n const md = new Metadata();\n const notification = <MJUserNotificationEntity>await md.GetEntityObject('MJ: User Notifications');\n notification.Title = title;\n notification.Message = message;\n if (resourceTypeId)\n notification.ResourceTypeID = resourceTypeId;\n if (resourceRecordId)\n notification.ResourceRecordID = resourceRecordId;\n if (resourceConfiguration)\n notification.ResourceConfiguration = JSON.stringify(resourceConfiguration);\n \n notification.UserID = md.CurrentUser.ID;\n notification.Unread = true;\n const result = await notification.Save();\n if (result) {\n MJNotificationService.RefreshUserNotifications();\n }\n\n if (displayToUser)\n this.CreateSimpleNotification(notification.Message, \"success\", 2500);\n\n return notification;\n }\n\n /**\n * Refresh user notifications from the server. Delegates to UserInfoEngine.RefreshItem,\n * whose successful reload mutates the engine's notifications cache and triggers an\n * emission on Notifications$/UnreadCount$ via the BaseEngine observable plumbing —\n * subscribers are updated automatically with no manual re-emit required here.\n *\n * Uses RefreshItem() rather than relying solely on the global CACHE_INVALIDATION\n * listener: when this method is called in response to a PushStatusUpdates message,\n * the cache-invalidation event may not have arrived yet (separate WebSocket message,\n * potential race). RefreshItem() eliminates that race by doing a targeted RunView\n * for just notifications.\n */\n public static async RefreshUserNotifications() {\n try {\n await UserInfoEngine.Instance.RefreshItem('_UserNotifications');\n MJNotificationService._loaded = true;\n }\n catch (e) {\n LogError(e);\n }\n }\n\n /**\n * @deprecated Kept as a no-op for backward compatibility. Observable emissions are now\n * driven by UserInfoEngine.UserNotifications$, which auto-emits on save/delete/\n * remote-invalidate/refresh. External callers no longer need to manually push updates.\n */\n public static UpdateNotificationObservables() {\n // No-op — see JSDoc above.\n }\n\n /**\n * Creates a message that is not saved to the User Notifications table, but is displayed to the user.\n * Uses a lightweight DOM-based toast notification.\n * @param message - text to display\n * @param style - display styling\n * @param hideAfter - option to auto hide after the specified delay in milliseconds\n */\n public CreateSimpleNotification(message: string, style: \"none\" | \"success\" | \"error\" | \"warning\" | \"info\" = \"success\", hideAfter?: number) {\n this.showToast(message, style, hideAfter);\n }\n\n /**\n * Ensures the toast container element exists in the DOM.\n */\n private ensureToastContainer(): HTMLElement {\n let container = document.getElementById('mj-toast-container');\n if (!container) {\n container = document.createElement('div');\n container.id = 'mj-toast-container';\n container.style.cssText = `\n position: fixed; top: 16px; left: 50%; transform: translateX(-50%);\n z-index: 100000; display: flex; flex-direction: column; align-items: center; gap: 8px;\n pointer-events: none;\n `;\n document.body.appendChild(container);\n }\n return container;\n }\n\n /**\n * Shows a lightweight toast notification using DOM elements.\n */\n private showToast(message: string, style: string, hideAfter?: number): void {\n const container = this.ensureToastContainer();\n\n const toast = document.createElement('div');\n toast.style.cssText = `\n pointer-events: auto; padding: 12px 20px; border-radius: 6px; font-size: 14px;\n font-family: inherit; max-width: 500px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n display: flex; align-items: center; gap: 8px; animation: mj-toast-slide-in 0.3s ease-out;\n color: var(--mj-text-inverse);\n `;\n\n const bgColors: Record<string, string> = {\n success: 'var(--mj-status-success)',\n error: 'var(--mj-status-error)',\n warning: 'var(--mj-status-warning)',\n info: 'var(--mj-status-info)',\n none: 'var(--mj-text-secondary)'\n };\n toast.style.backgroundColor = bgColors[style] || bgColors['none'];\n\n const iconMap: Record<string, string> = {\n success: '\\u2713',\n error: '\\u2717',\n warning: '\\u26A0',\n info: '\\u2139',\n none: ''\n };\n const icon = iconMap[style] || '';\n\n const removeToast = () => {\n toast.style.animation = 'mj-toast-slide-out 0.3s ease-in forwards';\n toast.addEventListener('animationend', () => toast.remove());\n };\n\n toast.innerHTML = `\n ${icon ? `<span style=\"font-size:16px;font-weight:bold;\">${icon}</span>` : ''}\n <span style=\"flex:1;\">${this.escapeHtml(message)}</span>\n ${!hideAfter ? `<button style=\"background:none;border:none;color:var(--mj-text-inverse);cursor:pointer;font-size:18px;padding:0 0 0 8px;line-height:1;\" aria-label=\"Close\">&times;</button>` : ''}\n `;\n\n if (!hideAfter) {\n const closeBtn = toast.querySelector('button');\n closeBtn?.addEventListener('click', removeToast);\n }\n\n container.appendChild(toast);\n\n // Inject keyframes if not already present\n if (!document.getElementById('mj-toast-keyframes')) {\n const styleEl = document.createElement('style');\n styleEl.id = 'mj-toast-keyframes';\n styleEl.textContent = `\n @keyframes mj-toast-slide-in { from { opacity:0; transform:translateY(-20px); } to { opacity:1; transform:translateY(0); } }\n @keyframes mj-toast-slide-out { from { opacity:1; transform:translateY(0); } to { opacity:0; transform:translateY(-20px); } }\n `;\n document.head.appendChild(styleEl);\n }\n\n if (hideAfter) {\n setTimeout(removeToast, hideAfter);\n }\n }\n\n /**\n * Escapes HTML entities to prevent XSS in toast messages.\n */\n private escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n}"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-notifications",
3
- "version": "5.27.1",
3
+ "version": "5.28.0",
4
4
  "description": "MemberJunction: Simple Angular library for displaying user notifications",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -25,10 +25,10 @@
25
25
  "@angular/core": "21.1.3"
26
26
  },
27
27
  "dependencies": {
28
- "@memberjunction/core": "5.27.1",
29
- "@memberjunction/core-entities": "5.27.1",
30
- "@memberjunction/global": "5.27.1",
31
- "@memberjunction/graphql-dataprovider": "5.27.1",
28
+ "@memberjunction/core": "5.28.0",
29
+ "@memberjunction/core-entities": "5.28.0",
30
+ "@memberjunction/global": "5.28.0",
31
+ "@memberjunction/graphql-dataprovider": "5.28.0",
32
32
  "tslib": "^2.8.1",
33
33
  "rxjs": "^7.8.2"
34
34
  },