@actuate-media/cms-core 0.23.0 → 0.24.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.
Files changed (34) hide show
  1. package/dist/__tests__/api/notifications-routes.test.js +45 -0
  2. package/dist/__tests__/api/notifications-routes.test.js.map +1 -1
  3. package/dist/__tests__/realtime/notification-bus.test.d.ts +2 -0
  4. package/dist/__tests__/realtime/notification-bus.test.d.ts.map +1 -0
  5. package/dist/__tests__/realtime/notification-bus.test.js +79 -0
  6. package/dist/__tests__/realtime/notification-bus.test.js.map +1 -0
  7. package/dist/__tests__/realtime/notification-stream.test.d.ts +2 -0
  8. package/dist/__tests__/realtime/notification-stream.test.d.ts.map +1 -0
  9. package/dist/__tests__/realtime/notification-stream.test.js +157 -0
  10. package/dist/__tests__/realtime/notification-stream.test.js.map +1 -0
  11. package/dist/api/handlers.d.ts.map +1 -1
  12. package/dist/api/handlers.js +41 -3
  13. package/dist/api/handlers.js.map +1 -1
  14. package/dist/preview/sse-stream.d.ts +2 -5
  15. package/dist/preview/sse-stream.d.ts.map +1 -1
  16. package/dist/preview/sse-stream.js +7 -16
  17. package/dist/preview/sse-stream.js.map +1 -1
  18. package/dist/realtime/index.d.ts +3 -0
  19. package/dist/realtime/index.d.ts.map +1 -1
  20. package/dist/realtime/index.js +9 -0
  21. package/dist/realtime/index.js.map +1 -1
  22. package/dist/realtime/notification-bus.d.ts +60 -0
  23. package/dist/realtime/notification-bus.d.ts.map +1 -0
  24. package/dist/realtime/notification-bus.js +77 -0
  25. package/dist/realtime/notification-bus.js.map +1 -0
  26. package/dist/realtime/notification-stream.d.ts +50 -0
  27. package/dist/realtime/notification-stream.d.ts.map +1 -0
  28. package/dist/realtime/notification-stream.js +148 -0
  29. package/dist/realtime/notification-stream.js.map +1 -0
  30. package/dist/realtime/sse.d.ts +20 -0
  31. package/dist/realtime/sse.d.ts.map +1 -0
  32. package/dist/realtime/sse.js +31 -0
  33. package/dist/realtime/sse.js.map +1 -0
  34. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { handleActuateAPI } from '../../api/index.js';
3
3
  import { createSession } from '../../auth/session.js';
4
4
  import { initDB } from '../../db.js';
5
5
  import { generateToken as generateCsrfToken } from '../../security/csrf.js';
6
+ import { __resetNotificationBus, notificationSubscriberCount, } from '../../realtime/notification-bus.js';
6
7
  /**
7
8
  * Integration tests for the notifications REST endpoints introduced in
8
9
  * slice 6 of Phase 3. The pure service is covered by
@@ -202,6 +203,50 @@ describe('notifications REST endpoints (Phase 3 slice 6)', () => {
202
203
  }));
203
204
  expect((await after.json()).data.count).toBe(0);
204
205
  });
206
+ describe('GET /notifications/stream (SSE push channel)', () => {
207
+ beforeEach(() => {
208
+ __resetNotificationBus();
209
+ });
210
+ it('rejects unauthenticated stream requests with 401', async () => {
211
+ const res = await handler(new Request('https://example.com/api/cms/notifications/stream'));
212
+ expect(res.status).toBe(401);
213
+ });
214
+ it('returns 200 + text/event-stream + a ready frame for the session user', async () => {
215
+ const headers = await sessionHeaders('EDITOR', 'user-1');
216
+ const res = await handler(new Request('https://example.com/api/cms/notifications/stream', {
217
+ headers: { cookie: headers.cookie },
218
+ }));
219
+ expect(res.status).toBe(200);
220
+ expect(res.headers.get('content-type')).toMatch(/text\/event-stream/);
221
+ expect(res.headers.get('cache-control')).toBe('no-cache, no-transform');
222
+ // SSE buffering guard — without this proxies hold events back.
223
+ expect(res.headers.get('x-accel-buffering')).toBe('no');
224
+ const reader = res.body.getReader();
225
+ try {
226
+ // Subscription is attached synchronously in the stream's
227
+ // start() — by the time we get a reader, the bus should
228
+ // already have the listener registered. That contract matters
229
+ // because the comment-write path publishes immediately after
230
+ // the DB insert; a lazy subscribe would race.
231
+ expect(notificationSubscriberCount('user-1')).toBe(1);
232
+ const decoder = new TextDecoder();
233
+ const first = await reader.read();
234
+ expect(first.done).toBe(false);
235
+ const text = decoder.decode(first.value);
236
+ expect(text).toContain('event: ready');
237
+ expect(text).toContain('"userId":"user-1"');
238
+ }
239
+ finally {
240
+ // Cancel triggers the stream's cleanup hook — verifies the
241
+ // bus subscription is dropped on hang-up.
242
+ await reader.cancel().catch(() => undefined);
243
+ }
244
+ // Yield once so the cancel() callback has a chance to run
245
+ // before we sample the subscriber count again.
246
+ await new Promise((r) => setTimeout(r, 0));
247
+ expect(notificationSubscriberCount('user-1')).toBe(0);
248
+ });
249
+ });
205
250
  it('honours includeRead=true', async () => {
206
251
  await seedNotification(db, 'user-1', {
207
252
  payload: { actorId: 'a', actorName: 'A', excerpt: 'unread' },
@@ -1 +1 @@
1
- {"version":3,"file":"notifications-routes.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/notifications-routes.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"notifications-routes.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/notifications-routes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=notification-bus.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-bus.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/realtime/notification-bus.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { __resetNotificationBus, notificationSubscriberCount, publishNotification, subscribeUserNotifications, } from '../../realtime/notification-bus.js';
3
+ beforeEach(() => {
4
+ __resetNotificationBus();
5
+ });
6
+ function makeDto(overrides = {}) {
7
+ return {
8
+ id: 'n-1',
9
+ userId: 'user-a',
10
+ kind: 'comment_reply',
11
+ documentId: 'doc-1',
12
+ commentId: 'c-1',
13
+ payload: {
14
+ kind: 'comment_reply',
15
+ actorId: 'user-b',
16
+ actorName: 'B',
17
+ excerpt: 'hi',
18
+ },
19
+ readAt: null,
20
+ createdAt: new Date().toISOString(),
21
+ ...overrides,
22
+ };
23
+ }
24
+ describe('notification-bus', () => {
25
+ it('delivers published notifications to subscribed userIds only', () => {
26
+ const a = [];
27
+ const b = [];
28
+ subscribeUserNotifications('user-a', (n) => a.push(n));
29
+ subscribeUserNotifications('user-b', (n) => b.push(n));
30
+ publishNotification(makeDto({ id: 'a-1', userId: 'user-a' }));
31
+ publishNotification(makeDto({ id: 'b-1', userId: 'user-b' }));
32
+ expect(a.map((n) => n.id)).toEqual(['a-1']);
33
+ expect(b.map((n) => n.id)).toEqual(['b-1']);
34
+ });
35
+ it('returns true only when at least one subscriber received the event', () => {
36
+ expect(publishNotification(makeDto({ userId: 'nobody' }))).toBe(false);
37
+ subscribeUserNotifications('user-a', () => undefined);
38
+ expect(publishNotification(makeDto({ userId: 'user-a' }))).toBe(true);
39
+ });
40
+ it('isolates throwing listeners so siblings still receive the event', () => {
41
+ const seen = [];
42
+ subscribeUserNotifications('user-a', () => {
43
+ throw new Error('boom');
44
+ });
45
+ subscribeUserNotifications('user-a', (n) => seen.push(n.id));
46
+ publishNotification(makeDto({ id: 'iso-1', userId: 'user-a' }));
47
+ expect(seen).toEqual(['iso-1']);
48
+ });
49
+ it('unsubscribe drops the listener and clears empty buckets', () => {
50
+ const seen = [];
51
+ const off = subscribeUserNotifications('user-a', (n) => seen.push(n));
52
+ expect(notificationSubscriberCount('user-a')).toBe(1);
53
+ publishNotification(makeDto({ id: 'pre' }));
54
+ off();
55
+ expect(notificationSubscriberCount('user-a')).toBe(0);
56
+ publishNotification(makeDto({ id: 'post' }));
57
+ expect(seen.map((n) => n.id)).toEqual(['pre']);
58
+ });
59
+ it('snapshots the listener set so synchronous unsubscribes from inside a listener do not skip neighbours', () => {
60
+ const seen = [];
61
+ let off2;
62
+ subscribeUserNotifications('user-a', (n) => {
63
+ seen.push(`first:${n.id}`);
64
+ // Synchronous teardown — exactly the shape the SSE stream uses
65
+ // when its own controller throws. Must not poison the rest of
66
+ // the broadcast pass.
67
+ off2?.();
68
+ });
69
+ off2 = subscribeUserNotifications('user-a', (n) => {
70
+ seen.push(`second:${n.id}`);
71
+ });
72
+ publishNotification(makeDto({ id: 'snap' }));
73
+ expect(seen).toEqual(['first:snap', 'second:snap']);
74
+ });
75
+ it('rejects empty userId at subscribe time to prevent silent cross-user leaks', () => {
76
+ expect(() => subscribeUserNotifications('', () => undefined)).toThrow();
77
+ });
78
+ });
79
+ //# sourceMappingURL=notification-bus.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-bus.test.js","sourceRoot":"","sources":["../../../src/__tests__/realtime/notification-bus.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=notification-stream.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-stream.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/realtime/notification-stream.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { createNotificationStream, createNotificationStreamResponse, encodeSseEvent, NOTIFICATION_SSE_HEADERS, } from '../../realtime/notification-stream.js';
3
+ import { __resetNotificationBus, notificationSubscriberCount, publishNotification, } from '../../realtime/notification-bus.js';
4
+ beforeEach(() => {
5
+ __resetNotificationBus();
6
+ });
7
+ const decoder = new TextDecoder();
8
+ function makeDto(overrides = {}) {
9
+ return {
10
+ id: 'n-1',
11
+ userId: 'user-a',
12
+ kind: 'comment_mention',
13
+ documentId: 'doc-1',
14
+ commentId: 'c-1',
15
+ payload: {
16
+ kind: 'comment_mention',
17
+ actorId: 'user-b',
18
+ actorName: 'B',
19
+ excerpt: 'ping',
20
+ },
21
+ readAt: null,
22
+ createdAt: '2026-05-27T00:00:00.000Z',
23
+ ...overrides,
24
+ };
25
+ }
26
+ async function readNext(reader, timeoutMs = 1000) {
27
+ let timer;
28
+ const timeout = new Promise((_, reject) => {
29
+ timer = setTimeout(() => reject(new Error(`SSE read timed out after ${timeoutMs}ms`)), timeoutMs);
30
+ });
31
+ try {
32
+ const result = await Promise.race([reader.read(), timeout]);
33
+ if (result.done)
34
+ throw new Error('Stream closed before a chunk arrived');
35
+ return decoder.decode(result.value);
36
+ }
37
+ finally {
38
+ if (timer)
39
+ clearTimeout(timer);
40
+ }
41
+ }
42
+ describe('createNotificationStream', () => {
43
+ let reader;
44
+ afterEach(async () => {
45
+ if (reader) {
46
+ // Cancel triggers the stream's cancel() — verifies cleanup.
47
+ await reader.cancel().catch(() => undefined);
48
+ reader = undefined;
49
+ }
50
+ });
51
+ it('emits a ready frame immediately', async () => {
52
+ const stream = createNotificationStream('user-a', {
53
+ heartbeatMs: 60_000,
54
+ maxLifetimeMs: 60_000,
55
+ });
56
+ reader = stream.getReader();
57
+ const first = await readNext(reader);
58
+ expect(first).toContain('event: ready');
59
+ expect(first).toContain('"userId":"user-a"');
60
+ });
61
+ it('forwards published notifications as notification events', async () => {
62
+ const stream = createNotificationStream('user-a', {
63
+ heartbeatMs: 60_000,
64
+ maxLifetimeMs: 60_000,
65
+ });
66
+ reader = stream.getReader();
67
+ // ready frame
68
+ await readNext(reader);
69
+ const dto = makeDto({ id: 'sse-1', userId: 'user-a' });
70
+ publishNotification(dto);
71
+ const chunk = await readNext(reader);
72
+ expect(chunk).toContain('event: notification');
73
+ expect(chunk).toContain('"id":"sse-1"');
74
+ expect(chunk).toContain('"userId":"user-a"');
75
+ });
76
+ it('does not deliver events for other users', async () => {
77
+ const stream = createNotificationStream('user-a', {
78
+ heartbeatMs: 60_000,
79
+ maxLifetimeMs: 60_000,
80
+ });
81
+ reader = stream.getReader();
82
+ await readNext(reader); // ready
83
+ publishNotification(makeDto({ id: 'wrong', userId: 'user-b' }));
84
+ publishNotification(makeDto({ id: 'right', userId: 'user-a' }));
85
+ const chunk = await readNext(reader);
86
+ expect(chunk).toContain('"id":"right"');
87
+ expect(chunk).not.toContain('"id":"wrong"');
88
+ });
89
+ it('emits heartbeats on interval', async () => {
90
+ const stream = createNotificationStream('user-a', {
91
+ heartbeatMs: 20,
92
+ maxLifetimeMs: 60_000,
93
+ });
94
+ reader = stream.getReader();
95
+ await readNext(reader); // ready
96
+ const heartbeat = await readNext(reader, 500);
97
+ expect(heartbeat).toBe(': heartbeat\n\n');
98
+ });
99
+ it('closes the stream after maxLifetimeMs and tears down the bus subscription', async () => {
100
+ const stream = createNotificationStream('user-a', {
101
+ heartbeatMs: 60_000,
102
+ maxLifetimeMs: 20,
103
+ });
104
+ reader = stream.getReader();
105
+ await readNext(reader); // ready
106
+ expect(notificationSubscriberCount('user-a')).toBe(1);
107
+ // Wait for the lifetime timer to fire.
108
+ const result = await reader.read();
109
+ expect(result.done).toBe(true);
110
+ // After the stream closes the cancel() handler must run and
111
+ // drop the subscription — otherwise we leak listener refs over
112
+ // the process lifetime.
113
+ expect(notificationSubscriberCount('user-a')).toBe(0);
114
+ reader = undefined;
115
+ });
116
+ it('removes the bus subscription on cancel() (client disconnect)', async () => {
117
+ const stream = createNotificationStream('user-a', {
118
+ heartbeatMs: 60_000,
119
+ maxLifetimeMs: 60_000,
120
+ });
121
+ reader = stream.getReader();
122
+ await readNext(reader); // ready
123
+ expect(notificationSubscriberCount('user-a')).toBe(1);
124
+ await reader.cancel();
125
+ reader = undefined;
126
+ expect(notificationSubscriberCount('user-a')).toBe(0);
127
+ });
128
+ it('rejects an empty userId at stream creation', () => {
129
+ expect(() => createNotificationStream('')).toThrow();
130
+ });
131
+ });
132
+ describe('createNotificationStreamResponse', () => {
133
+ it('wraps the stream with text/event-stream headers + 200', async () => {
134
+ const res = createNotificationStreamResponse('user-a', {
135
+ heartbeatMs: 60_000,
136
+ maxLifetimeMs: 60_000,
137
+ });
138
+ expect(res.status).toBe(200);
139
+ expect(res.headers.get('content-type')).toBe(NOTIFICATION_SSE_HEADERS['Content-Type']);
140
+ expect(res.headers.get('cache-control')).toBe(NOTIFICATION_SSE_HEADERS['Cache-Control']);
141
+ expect(res.headers.get('x-accel-buffering')).toBe(NOTIFICATION_SSE_HEADERS['X-Accel-Buffering']);
142
+ // Drain the body so the underlying stream tears down cleanly.
143
+ await res.body?.cancel();
144
+ });
145
+ });
146
+ describe('encodeSseEvent (notification stream copy)', () => {
147
+ // The notification stream re-exports its own encoder so callers
148
+ // don't take a cross-module dep on the preview stream's copy.
149
+ // Verify the contract stays in lock-step.
150
+ it('serializes JSON payloads with an event tag', () => {
151
+ expect(encodeSseEvent({ id: 'x' }, 'notification')).toBe('event: notification\ndata: {"id":"x"}\n\n');
152
+ });
153
+ it('splits multi-line strings across data lines', () => {
154
+ expect(encodeSseEvent('one\ntwo')).toBe('data: one\ndata: two\n\n');
155
+ });
156
+ });
157
+ //# sourceMappingURL=notification-stream.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-stream.test.js","sourceRoot":"","sources":["../../../src/__tests__/realtime/notification-stream.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAqnB5C,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AA2ND,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAquNzD"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAunB5C,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AA2ND,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAqwNzD"}
@@ -17,6 +17,8 @@ import { createPreviewStreamResponse } from '../preview/sse-stream.js';
17
17
  import { notifyPreviewUpdate } from '../preview/event-bus.js';
18
18
  import { createComment as createCommentService, deleteComment as deleteCommentService, listComments as listCommentsService, reopenComment as reopenCommentService, resolveComment as resolveCommentService, updateComment as updateCommentService, } from '../realtime/comments.js';
19
19
  import { createNotification, excerptForNotification, extractMentionedUserIds, listForUser as listNotificationsForUser, markAllRead as markAllNotificationsRead, markRead as markNotificationRead, unreadCount as notificationsUnreadCount, } from '../realtime/notifications.js';
20
+ import { publishNotification } from '../realtime/notification-bus.js';
21
+ import { createNotificationStreamResponse } from '../realtime/notification-stream.js';
20
22
  import { schedulingCronHandler } from '../scheduling/index.js';
21
23
  import { isAuthorizedCronRequest, processCleanup, processSeoScan } from '../cron/index.js';
22
24
  import { verifyCaptcha, getCaptchaConfig } from '../security/captcha.js';
@@ -4710,7 +4712,7 @@ export function registerCMSRoutes(router) {
4710
4712
  if (opts.parentId) {
4711
4713
  const root = await findCommentRecipient(opts.parentId);
4712
4714
  if (root && root.userId && root.userId !== opts.actorId) {
4713
- await createNotification(ndb, {
4715
+ const result = await createNotification(ndb, {
4714
4716
  userId: root.userId,
4715
4717
  documentId: opts.documentId,
4716
4718
  commentId: opts.commentId,
@@ -4721,12 +4723,21 @@ export function registerCMSRoutes(router) {
4721
4723
  excerpt: excerptForNotification(opts.body),
4722
4724
  },
4723
4725
  });
4726
+ // Push the new DTO to any active SSE subscribers for this user
4727
+ // so the bell badge bumps in real time. We only publish AFTER
4728
+ // the DB insert succeeds — otherwise an SSE client would see a
4729
+ // ghost notification that doesn't exist on a subsequent
4730
+ // refresh. Failure to publish (no subscribers, or bus error)
4731
+ // is non-fatal: the poll fallback in the admin UI still
4732
+ // converges.
4733
+ if (result.ok)
4734
+ publishNotification(result.value);
4724
4735
  }
4725
4736
  }
4726
4737
  for (const userId of extractMentionedUserIds(opts.body)) {
4727
4738
  if (userId === opts.actorId)
4728
4739
  continue;
4729
- await createNotification(ndb, {
4740
+ const result = await createNotification(ndb, {
4730
4741
  userId,
4731
4742
  documentId: opts.documentId,
4732
4743
  commentId: opts.commentId,
@@ -4737,6 +4748,8 @@ export function registerCMSRoutes(router) {
4737
4748
  excerpt: excerptForNotification(opts.body),
4738
4749
  },
4739
4750
  });
4751
+ if (result.ok)
4752
+ publishNotification(result.value);
4740
4753
  }
4741
4754
  }
4742
4755
  /** Notify the comment's author when someone else resolves their thread. */
@@ -4744,7 +4757,7 @@ export function registerCMSRoutes(router) {
4744
4757
  const comment = await findCommentRecipient(opts.commentId);
4745
4758
  if (!comment || !comment.userId || comment.userId === opts.actorId)
4746
4759
  return;
4747
- await createNotification(notificationsDb(), {
4760
+ const result = await createNotification(notificationsDb(), {
4748
4761
  userId: comment.userId,
4749
4762
  commentId: opts.commentId,
4750
4763
  payload: {
@@ -4754,6 +4767,8 @@ export function registerCMSRoutes(router) {
4754
4767
  rootExcerpt: excerptForNotification(comment.body),
4755
4768
  },
4756
4769
  });
4770
+ if (result.ok)
4771
+ publishNotification(result.value);
4757
4772
  }
4758
4773
  router.post('/documents/:documentId/comments', async (request, params) => {
4759
4774
  try {
@@ -5005,6 +5020,29 @@ export function registerCMSRoutes(router) {
5005
5020
  return internalError(err, 'mark all notifications read');
5006
5021
  }
5007
5022
  });
5023
+ // Per-user notification SSE push channel (Phase 7 carry-over). The
5024
+ // admin's NotificationBell subscribes via EventSource for real-time
5025
+ // delivery of comment_reply / comment_mention / comment_resolved
5026
+ // events. The existing GET /notifications and
5027
+ // /notifications/unread-count REST endpoints are kept as a
5028
+ // poll-fallback path for multi-node deployments where a write on
5029
+ // node A doesn't reach SSE subscribers on node B (the in-process
5030
+ // bus is single-instance by design — see notification-bus.ts).
5031
+ //
5032
+ // Auth: same scope as every other /notifications route — the
5033
+ // session userId determines which channel the stream subscribes to;
5034
+ // there is no admin override.
5035
+ router.get('/notifications/stream', async (request) => {
5036
+ try {
5037
+ const auth = await requireAuth(request);
5038
+ if (auth.error)
5039
+ return auth.error;
5040
+ return createNotificationStreamResponse(auth.session.userId);
5041
+ }
5042
+ catch (err) {
5043
+ return internalError(err, 'notifications stream');
5044
+ }
5045
+ });
5008
5046
  // ---------------------------------------------------------------------------
5009
5047
  // Workflow routes
5010
5048
  // ---------------------------------------------------------------------------