@manonero/chat-client-sdk 0.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +2453 -0
  2. package/dist/ChatClient.d.ts +181 -0
  3. package/dist/ChatClient.d.ts.map +1 -0
  4. package/dist/ChatClient.js +246 -0
  5. package/dist/ChatClient.js.map +1 -0
  6. package/dist/errors/ChatApiError.d.ts +45 -0
  7. package/dist/errors/ChatApiError.d.ts.map +1 -0
  8. package/dist/errors/ChatApiError.js +69 -0
  9. package/dist/errors/ChatApiError.js.map +1 -0
  10. package/dist/http/AuthApi.d.ts +26 -0
  11. package/dist/http/AuthApi.d.ts.map +1 -0
  12. package/dist/http/AuthApi.js +35 -0
  13. package/dist/http/AuthApi.js.map +1 -0
  14. package/dist/http/BotApi.d.ts +43 -0
  15. package/dist/http/BotApi.d.ts.map +1 -0
  16. package/dist/http/BotApi.js +60 -0
  17. package/dist/http/BotApi.js.map +1 -0
  18. package/dist/http/ConversationApi.d.ts +92 -0
  19. package/dist/http/ConversationApi.d.ts.map +1 -0
  20. package/dist/http/ConversationApi.js +128 -0
  21. package/dist/http/ConversationApi.js.map +1 -0
  22. package/dist/http/FileApi.d.ts +60 -0
  23. package/dist/http/FileApi.d.ts.map +1 -0
  24. package/dist/http/FileApi.js +91 -0
  25. package/dist/http/FileApi.js.map +1 -0
  26. package/dist/http/HealthApi.d.ts +28 -0
  27. package/dist/http/HealthApi.d.ts.map +1 -0
  28. package/dist/http/HealthApi.js +34 -0
  29. package/dist/http/HealthApi.js.map +1 -0
  30. package/dist/http/HttpClient.d.ts +69 -0
  31. package/dist/http/HttpClient.d.ts.map +1 -0
  32. package/dist/http/HttpClient.js +244 -0
  33. package/dist/http/HttpClient.js.map +1 -0
  34. package/dist/http/MessageApi.d.ts +69 -0
  35. package/dist/http/MessageApi.d.ts.map +1 -0
  36. package/dist/http/MessageApi.js +104 -0
  37. package/dist/http/MessageApi.js.map +1 -0
  38. package/dist/http/ParticipantApi.d.ts +28 -0
  39. package/dist/http/ParticipantApi.d.ts.map +1 -0
  40. package/dist/http/ParticipantApi.js +37 -0
  41. package/dist/http/ParticipantApi.js.map +1 -0
  42. package/dist/http/ProxyApi.d.ts +59 -0
  43. package/dist/http/ProxyApi.d.ts.map +1 -0
  44. package/dist/http/ProxyApi.js +86 -0
  45. package/dist/http/ProxyApi.js.map +1 -0
  46. package/dist/index.d.ts +26 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +18 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/realtime/ChatHubClient.d.ts +143 -0
  51. package/dist/realtime/ChatHubClient.d.ts.map +1 -0
  52. package/dist/realtime/ChatHubClient.js +365 -0
  53. package/dist/realtime/ChatHubClient.js.map +1 -0
  54. package/dist/realtime/NotificationHubClient.d.ts +89 -0
  55. package/dist/realtime/NotificationHubClient.d.ts.map +1 -0
  56. package/dist/realtime/NotificationHubClient.js +191 -0
  57. package/dist/realtime/NotificationHubClient.js.map +1 -0
  58. package/dist/realtime/ReconnectionManager.d.ts +65 -0
  59. package/dist/realtime/ReconnectionManager.d.ts.map +1 -0
  60. package/dist/realtime/ReconnectionManager.js +129 -0
  61. package/dist/realtime/ReconnectionManager.js.map +1 -0
  62. package/dist/types/auth.d.ts +30 -0
  63. package/dist/types/auth.d.ts.map +1 -0
  64. package/dist/types/auth.js +3 -0
  65. package/dist/types/auth.js.map +1 -0
  66. package/dist/types/block.d.ts +163 -0
  67. package/dist/types/block.d.ts.map +1 -0
  68. package/dist/types/block.js +77 -0
  69. package/dist/types/block.js.map +1 -0
  70. package/dist/types/bot.d.ts +42 -0
  71. package/dist/types/bot.d.ts.map +1 -0
  72. package/dist/types/bot.js +3 -0
  73. package/dist/types/bot.js.map +1 -0
  74. package/dist/types/chat-events.d.ts +191 -0
  75. package/dist/types/chat-events.d.ts.map +1 -0
  76. package/dist/types/chat-events.js +3 -0
  77. package/dist/types/chat-events.js.map +1 -0
  78. package/dist/types/common.d.ts +64 -0
  79. package/dist/types/common.d.ts.map +1 -0
  80. package/dist/types/common.js +3 -0
  81. package/dist/types/common.js.map +1 -0
  82. package/dist/types/conversation.d.ts +106 -0
  83. package/dist/types/conversation.d.ts.map +1 -0
  84. package/dist/types/conversation.js +3 -0
  85. package/dist/types/conversation.js.map +1 -0
  86. package/dist/types/file.d.ts +31 -0
  87. package/dist/types/file.d.ts.map +1 -0
  88. package/dist/types/file.js +3 -0
  89. package/dist/types/file.js.map +1 -0
  90. package/dist/types/message.d.ts +84 -0
  91. package/dist/types/message.d.ts.map +1 -0
  92. package/dist/types/message.js +3 -0
  93. package/dist/types/message.js.map +1 -0
  94. package/dist/types/notification-events.d.ts +89 -0
  95. package/dist/types/notification-events.d.ts.map +1 -0
  96. package/dist/types/notification-events.js +3 -0
  97. package/dist/types/notification-events.js.map +1 -0
  98. package/dist/types/participant.d.ts +22 -0
  99. package/dist/types/participant.d.ts.map +1 -0
  100. package/dist/types/participant.js +3 -0
  101. package/dist/types/participant.js.map +1 -0
  102. package/dist/types/signalr.d.ts +84 -0
  103. package/dist/types/signalr.d.ts.map +1 -0
  104. package/dist/types/signalr.js +3 -0
  105. package/dist/types/signalr.js.map +1 -0
  106. package/dist/utils/TypedEventEmitter.d.ts +32 -0
  107. package/dist/utils/TypedEventEmitter.d.ts.map +1 -0
  108. package/dist/utils/TypedEventEmitter.js +60 -0
  109. package/dist/utils/TypedEventEmitter.js.map +1 -0
  110. package/package.json +42 -0
@@ -0,0 +1,365 @@
1
+ // realtime/ChatHubClient.ts — Manages ChatHub SignalR connection
2
+ import { HubConnection, HubConnectionBuilder, HubConnectionState, LogLevel, } from '@microsoft/signalr';
3
+ import { TypedEventEmitter as EventEmitter } from '../utils/TypedEventEmitter.js';
4
+ import { normalizeBlocksForSend } from '../types/block.js';
5
+ // ---------------------------------------------------------------------------
6
+ // ChatHubClient
7
+ // ---------------------------------------------------------------------------
8
+ /**
9
+ * Typed client for the ChatHub (`/hubs/chat`).
10
+ *
11
+ * Responsibilities:
12
+ * - Manages HubConnection lifecycle (connect / disconnect / auto-reconnect)
13
+ * - Exposes typed Hub methods (Client → Server)
14
+ * - Exposes typed event subscription (Server → Client)
15
+ * - Tracks joined conversations so ReconnectionManager can re-join on reconnect
16
+ */
17
+ export class ChatHubClient {
18
+ constructor(options) {
19
+ this.connection = null;
20
+ /** In-flight connect Promise, used to dedupe concurrent connect() calls. */
21
+ this.connectingPromise = null;
22
+ /**
23
+ * True after a user-initiated disconnect() until the next connect() begins.
24
+ * ReconnectionManager uses this to skip auto-reconnect when the close was
25
+ * intentional rather than caused by a transport failure.
26
+ */
27
+ this._intentionallyClosed = false;
28
+ /** Set of conversationIds that have been joined in the current session */
29
+ this.joinedConversations = new Set();
30
+ /**
31
+ * Tracks join attempts that resolved successfully but may still be revoked by
32
+ * a follow-up server `Error` event (FORBIDDEN/UNAUTHORIZED). Entries auto-expire
33
+ * after PENDING_JOIN_TTL_MS to bound memory.
34
+ */
35
+ this.pendingJoinAttempts = new Map();
36
+ this.options = options;
37
+ this.emitter = new EventEmitter();
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Connection state
41
+ // ---------------------------------------------------------------------------
42
+ get state() {
43
+ return this.connection?.state ?? HubConnectionState.Disconnected;
44
+ }
45
+ /**
46
+ * True after a user-initiated disconnect() and until the next connect() starts.
47
+ * ReconnectionManager honors this to avoid reconnecting after an intentional close.
48
+ */
49
+ get intentionallyClosed() {
50
+ return this._intentionallyClosed;
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // Connection management
54
+ // ---------------------------------------------------------------------------
55
+ /** Build a new HubConnection using the latest token from tokenProvider */
56
+ buildConnection() {
57
+ const { hubUrl, tokenProvider, logLevel = LogLevel.Warning } = this.options;
58
+ return new HubConnectionBuilder()
59
+ .withUrl(hubUrl, {
60
+ // Throw rather than return '' so SignalR fails the request loudly with
61
+ // a useful error instead of sending an empty `Authorization: Bearer ` header.
62
+ accessTokenFactory: () => {
63
+ const t = tokenProvider();
64
+ if (!t) {
65
+ throw new Error('ChatHub: no authentication token available');
66
+ }
67
+ return t;
68
+ },
69
+ })
70
+ .withAutomaticReconnect()
71
+ .configureLogging(logLevel)
72
+ .build();
73
+ }
74
+ /** Attach SignalR server→client event handlers to the connection */
75
+ attachHandlers(conn) {
76
+ // Message events
77
+ conn.on('MessageReceived', (msg) => this.emitter.emit('messageReceived', msg));
78
+ conn.on('MessageUpdated', (dto) => this.emitter.emit('messageUpdated', dto));
79
+ conn.on('MessageDeleted', (dto) => this.emitter.emit('messageDeleted', dto));
80
+ conn.on('MessageRecovered', (msg) => this.emitter.emit('messageRecovered', msg));
81
+ conn.on('MessageThumbnailsReady', (dto) => this.emitter.emit('messageThumbnailsReady', dto));
82
+ // Reaction events
83
+ conn.on('ReactionAdded', (dto) => this.emitter.emit('reactionAdded', dto));
84
+ conn.on('ReactionRemoved', (dto) => this.emitter.emit('reactionRemoved', dto));
85
+ // Typing events
86
+ conn.on('TypingStarted', (dto) => this.emitter.emit('typingStarted', dto));
87
+ conn.on('TypingStopped', (dto) => this.emitter.emit('typingStopped', dto));
88
+ // Streaming events
89
+ conn.on('StreamStarted', (dto) => this.emitter.emit('streamStarted', dto));
90
+ conn.on('StreamStatusUpdated', (dto) => this.emitter.emit('streamStatusUpdated', dto));
91
+ conn.on('StreamChunkReceived', (dto) => this.emitter.emit('streamChunkReceived', dto));
92
+ conn.on('StreamChunkBatchReceived', (dto) => this.emitter.emit('streamChunkBatchReceived', dto));
93
+ conn.on('StreamCompleted', (dto) => this.emitter.emit('streamCompleted', dto));
94
+ conn.on('StreamAborted', (dto) => this.emitter.emit('streamAborted', dto));
95
+ // Hub errors (server→client) — also revert pending joins on FORBIDDEN/UNAUTHORIZED
96
+ conn.on('Error', (error) => {
97
+ this.handlePotentialJoinFailure(error);
98
+ this.emitter.emit('error', error);
99
+ });
100
+ // Connection lifecycle
101
+ conn.onreconnecting((err) => this.emitter.emit('reconnecting', err));
102
+ conn.onreconnected((connectionId) => {
103
+ // Re-join all previously joined conversations after reconnect
104
+ this.rejoinConversations().catch(() => {
105
+ // Best-effort — errors will be surfaced via error events
106
+ });
107
+ this.emitter.emit('reconnected', connectionId);
108
+ });
109
+ conn.onclose((err) => this.emitter.emit('disconnected', err));
110
+ }
111
+ /**
112
+ * Connect to ChatHub. If already connected, resolves immediately.
113
+ * Concurrent calls share a single in-flight Promise — building two
114
+ * connections at once would leak handlers.
115
+ * Builds a fresh connection using the current token.
116
+ *
117
+ * Throws synchronously when no authentication token is available so the
118
+ * caller gets a clear error rather than a malformed Authorization header.
119
+ */
120
+ async connect() {
121
+ if (this.connection && this.connection.state === HubConnectionState.Connected) {
122
+ return;
123
+ }
124
+ if (this.connectingPromise) {
125
+ return this.connectingPromise;
126
+ }
127
+ if (!this.options.tokenProvider()) {
128
+ throw new Error('ChatHubClient.connect(): no authentication token available. ' +
129
+ 'Provide one via ChatClient.setToken() or the tokenProvider option.');
130
+ }
131
+ // The user is explicitly opening a connection — clear any prior
132
+ // intentional-close flag so ReconnectionManager will manage future drops.
133
+ this._intentionallyClosed = false;
134
+ const promise = (async () => {
135
+ try {
136
+ // Disconnect existing stale connection cleanly
137
+ if (this.connection) {
138
+ await this.safeStop();
139
+ }
140
+ const conn = this.buildConnection();
141
+ this.attachHandlers(conn);
142
+ this.connection = conn;
143
+ await conn.start();
144
+ }
145
+ finally {
146
+ this.connectingPromise = null;
147
+ }
148
+ })();
149
+ this.connectingPromise = promise;
150
+ return promise;
151
+ }
152
+ /**
153
+ * Disconnect from ChatHub and clear joined conversation tracking.
154
+ * If a connect is in flight, it is awaited first so we always tear down a
155
+ * fully-formed connection (not a half-built one).
156
+ *
157
+ * Sets `intentionallyClosed` so ReconnectionManager skips its reconnect
158
+ * attempt for the resulting `disconnected` event.
159
+ */
160
+ async disconnect() {
161
+ // Mark intentional BEFORE awaiting the in-flight connect so any
162
+ // disconnected events that fire during teardown are not auto-reconnected.
163
+ this._intentionallyClosed = true;
164
+ if (this.connectingPromise) {
165
+ try {
166
+ await this.connectingPromise;
167
+ }
168
+ catch {
169
+ // The connect failed — there's nothing established to tear down,
170
+ // but still proceed to clear local state below.
171
+ }
172
+ }
173
+ this.joinedConversations.clear();
174
+ this.clearPendingJoinAttempts();
175
+ await this.safeStop();
176
+ this.connection = null;
177
+ }
178
+ /** Detach all server→client method handlers registered via attachHandlers(). */
179
+ detachHandlers(conn) {
180
+ for (const name of ChatHubClient.HUB_EVENT_NAMES) {
181
+ conn.off(name);
182
+ }
183
+ }
184
+ async safeStop() {
185
+ if (!this.connection)
186
+ return;
187
+ // Detach handlers first so any late events on this connection are dropped.
188
+ this.detachHandlers(this.connection);
189
+ try {
190
+ await this.connection.stop();
191
+ }
192
+ catch {
193
+ // Ignore errors during stop
194
+ }
195
+ }
196
+ /**
197
+ * Re-join all tracked conversations. Called internally after reconnect.
198
+ * Called by ReconnectionManager as well.
199
+ *
200
+ * If a rejoin fails with an access-denied style error (e.g. user was kicked
201
+ * between disconnect and reconnect), the conversation is purged from
202
+ * `joinedConversations` so we do not retry it forever on every reconnect.
203
+ * Other errors keep the entry — the next reconnect will try again.
204
+ */
205
+ async rejoinConversations() {
206
+ const ids = Array.from(this.joinedConversations);
207
+ for (const id of ids) {
208
+ try {
209
+ await this.invokeHub('JoinConversation', id);
210
+ }
211
+ catch (err) {
212
+ if (this.isAccessDeniedError(err)) {
213
+ this.joinedConversations.delete(id);
214
+ }
215
+ // Other errors: keep tracking so a later reconnect retries.
216
+ }
217
+ }
218
+ }
219
+ isAccessDeniedError(err) {
220
+ if (!(err instanceof Error))
221
+ return false;
222
+ const msg = err.message.toLowerCase();
223
+ return (msg.includes('forbidden') ||
224
+ msg.includes('not a participant') ||
225
+ msg.includes('unauthorized') ||
226
+ msg.includes('access denied'));
227
+ }
228
+ // ---------------------------------------------------------------------------
229
+ // Hub Methods (Client → Server)
230
+ // ---------------------------------------------------------------------------
231
+ async invokeHub(method, ...args) {
232
+ if (!this.connection || this.connection.state !== HubConnectionState.Connected) {
233
+ throw new Error(`ChatHub is not connected (state: ${this.connection?.state ?? 'null'})`);
234
+ }
235
+ return this.connection.invoke(method, ...args);
236
+ }
237
+ async joinConversation(conversationId) {
238
+ await this.invokeHub('JoinConversation', conversationId);
239
+ this.joinedConversations.add(conversationId);
240
+ // Track as pending so a follow-up FORBIDDEN/UNAUTHORIZED Error event can revert it.
241
+ this.markJoinPending(conversationId);
242
+ }
243
+ async leaveConversation(conversationId) {
244
+ await this.invokeHub('LeaveConversation', conversationId);
245
+ this.joinedConversations.delete(conversationId);
246
+ }
247
+ async startTyping(conversationId) {
248
+ await this.invokeHub('StartTyping', conversationId);
249
+ }
250
+ async stopTyping(conversationId) {
251
+ await this.invokeHub('StopTyping', conversationId);
252
+ }
253
+ async sendMessage(request) {
254
+ // Spec §6: `$type` must be the first property in each block when sent to
255
+ // the server, otherwise the polymorphic deserializer rejects the payload.
256
+ const normalized = {
257
+ ...request,
258
+ blocks: normalizeBlocksForSend(request.blocks),
259
+ };
260
+ return this.invokeHub('SendMessage', normalized);
261
+ }
262
+ async editMessage(request) {
263
+ const normalized = {
264
+ ...request,
265
+ newBlocks: normalizeBlocksForSend(request.newBlocks),
266
+ };
267
+ return this.invokeHub('EditMessage', normalized);
268
+ }
269
+ /** Server expects an object { messageId }, not a bare string */
270
+ async deleteMessage(request) {
271
+ return this.invokeHub('DeleteMessage', request);
272
+ }
273
+ /** Server expects an object { messageId }, not a bare string */
274
+ async recoverMessage(request) {
275
+ return this.invokeHub('RecoverMessage', request);
276
+ }
277
+ async addReaction(request) {
278
+ return this.invokeHub('AddReaction', request);
279
+ }
280
+ async removeReaction(request) {
281
+ return this.invokeHub('RemoveReaction', request);
282
+ }
283
+ // ---------------------------------------------------------------------------
284
+ // Event subscription (Server → Client)
285
+ // ---------------------------------------------------------------------------
286
+ on(event, handler) {
287
+ return this.emitter.on(event, handler);
288
+ }
289
+ off(event, handler) {
290
+ this.emitter.off(event, handler);
291
+ }
292
+ // ---------------------------------------------------------------------------
293
+ // Pending join cleanup — guards against the spec edge case where the server
294
+ // accepts the JoinConversation invoke but later rejects via an `Error` event
295
+ // (e.g. FORBIDDEN — user is not a participant). Without this, the conversation
296
+ // would stay in `joinedConversations` and be wrongly auto-rejoined on reconnect.
297
+ // ---------------------------------------------------------------------------
298
+ markJoinPending(conversationId) {
299
+ const existing = this.pendingJoinAttempts.get(conversationId);
300
+ if (existing)
301
+ clearTimeout(existing);
302
+ const timer = setTimeout(() => this.pendingJoinAttempts.delete(conversationId), ChatHubClient.PENDING_JOIN_TTL_MS);
303
+ this.pendingJoinAttempts.set(conversationId, timer);
304
+ }
305
+ clearPendingJoinAttempts() {
306
+ for (const timer of this.pendingJoinAttempts.values())
307
+ clearTimeout(timer);
308
+ this.pendingJoinAttempts.clear();
309
+ }
310
+ revertJoin(conversationId) {
311
+ this.joinedConversations.delete(conversationId);
312
+ const timer = this.pendingJoinAttempts.get(conversationId);
313
+ if (timer)
314
+ clearTimeout(timer);
315
+ this.pendingJoinAttempts.delete(conversationId);
316
+ }
317
+ /**
318
+ * If a server-emitted Error indicates a join was rejected, remove the
319
+ * conversation from `joinedConversations` to prevent auto-rejoin on reconnect.
320
+ *
321
+ * Strategy:
322
+ * 1. If `error.details.conversationId` is provided AND matches a pending
323
+ * join attempt → revert that one (most reliable signal).
324
+ * 2. Else, if the message matches the spec's documented FORBIDDEN text
325
+ * ("not a participant") AND there is exactly one pending join → revert it.
326
+ * 3. Otherwise do nothing — we cannot safely correlate the error to a join.
327
+ */
328
+ handlePotentialJoinFailure(error) {
329
+ if (error.code !== 'FORBIDDEN' && error.code !== 'UNAUTHORIZED')
330
+ return;
331
+ const detailsConvId = error.details && typeof error.details === 'object'
332
+ ? error.details.conversationId
333
+ : undefined;
334
+ if (typeof detailsConvId === 'string' && this.pendingJoinAttempts.has(detailsConvId)) {
335
+ this.revertJoin(detailsConvId);
336
+ return;
337
+ }
338
+ if (/not a participant/i.test(error.message) && this.pendingJoinAttempts.size === 1) {
339
+ const onlyId = this.pendingJoinAttempts.keys().next().value;
340
+ if (onlyId)
341
+ this.revertJoin(onlyId);
342
+ }
343
+ }
344
+ }
345
+ ChatHubClient.PENDING_JOIN_TTL_MS = 2000;
346
+ /** Server→client method names registered via conn.on() — used for cleanup. */
347
+ ChatHubClient.HUB_EVENT_NAMES = [
348
+ 'MessageReceived',
349
+ 'MessageUpdated',
350
+ 'MessageDeleted',
351
+ 'MessageRecovered',
352
+ 'MessageThumbnailsReady',
353
+ 'ReactionAdded',
354
+ 'ReactionRemoved',
355
+ 'TypingStarted',
356
+ 'TypingStopped',
357
+ 'StreamStarted',
358
+ 'StreamStatusUpdated',
359
+ 'StreamChunkReceived',
360
+ 'StreamChunkBatchReceived',
361
+ 'StreamCompleted',
362
+ 'StreamAborted',
363
+ 'Error',
364
+ ];
365
+ //# sourceMappingURL=ChatHubClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatHubClient.js","sourceRoot":"","sources":["../../src/realtime/ChatHubClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { HubConnectionState, LogLevel } from '@microsoft/signalr';
2
+ import type { Unsubscribe } from '../utils/TypedEventEmitter.js';
3
+ import type { NewMessageNotificationDto, MentionedNotificationDto, ConversationCreatedDto, ConversationUpdatedDto, ParticipantJoinedDto, ParticipantLeftDto, ConversationPinnedDto, ConversationUnpinnedDto } from '../types/notification-events.js';
4
+ export interface NotificationHubEventMap {
5
+ newMessageNotification: NewMessageNotificationDto;
6
+ mentionedNotification: MentionedNotificationDto;
7
+ conversationCreated: ConversationCreatedDto;
8
+ conversationUpdated: ConversationUpdatedDto;
9
+ participantJoined: ParticipantJoinedDto;
10
+ participantLeft: ParticipantLeftDto;
11
+ conversationPinned: ConversationPinnedDto;
12
+ conversationUnpinned: ConversationUnpinnedDto;
13
+ reconnecting: Error | undefined;
14
+ reconnected: string | undefined;
15
+ disconnected: Error | undefined;
16
+ }
17
+ export interface NotificationHubClientOptions {
18
+ /** Full URL of the NotificationHub endpoint, e.g. "https://api.example.com/hubs/notifications" */
19
+ hubUrl: string;
20
+ /** Returns the current JWT token to use as access_token query param */
21
+ tokenProvider: () => string | null;
22
+ /** Minimum log level for SignalR internal logging (default: Warning) */
23
+ logLevel?: LogLevel;
24
+ }
25
+ /**
26
+ * Typed client for the NotificationHub (`/hubs/notifications`).
27
+ *
28
+ * Per spec §4: NotificationHub has **no client-callable hub methods** —
29
+ * after connecting, the server automatically pushes events to the user's
30
+ * personal group (`participant:{userId}`). There is also no `Error` event
31
+ * on this hub: invalid/expired JWT is rejected at the gateway (HTTP 401)
32
+ * and a missing `sub` claim aborts the connection — both silent on the
33
+ * wire. Register the `Error` listener on ChatHub instead.
34
+ *
35
+ * Responsibilities:
36
+ * - Manages HubConnection lifecycle (connect / disconnect / auto-reconnect)
37
+ * - Exposes typed event subscription (Server → Client)
38
+ */
39
+ export declare class NotificationHubClient {
40
+ private readonly options;
41
+ private connection;
42
+ private readonly emitter;
43
+ /** In-flight connect Promise, used to dedupe concurrent connect() calls. */
44
+ private connectingPromise;
45
+ /**
46
+ * True after a user-initiated disconnect() until the next connect() begins.
47
+ * ReconnectionManager uses this to skip auto-reconnect when the close was
48
+ * intentional rather than caused by a transport failure.
49
+ */
50
+ private _intentionallyClosed;
51
+ /** Server→client method names registered via conn.on() — used for cleanup. */
52
+ private static readonly HUB_EVENT_NAMES;
53
+ constructor(options: NotificationHubClientOptions);
54
+ get state(): HubConnectionState;
55
+ /**
56
+ * True after a user-initiated disconnect() and until the next connect() starts.
57
+ * ReconnectionManager honors this to avoid reconnecting after an intentional close.
58
+ */
59
+ get intentionallyClosed(): boolean;
60
+ /** Build a new HubConnection using the latest token from tokenProvider */
61
+ private buildConnection;
62
+ /** Attach SignalR server→client event handlers to the connection */
63
+ private attachHandlers;
64
+ /**
65
+ * Connect to NotificationHub. If already connected, resolves immediately.
66
+ * Concurrent calls share a single in-flight Promise — building two
67
+ * connections at once would leak handlers.
68
+ * Builds a fresh connection using the current token.
69
+ *
70
+ * Throws synchronously when no authentication token is available so the
71
+ * caller gets a clear error rather than a malformed Authorization header.
72
+ */
73
+ connect(): Promise<void>;
74
+ /**
75
+ * Disconnect from NotificationHub.
76
+ * If a connect is in flight, it is awaited first so we always tear down a
77
+ * fully-formed connection (not a half-built one).
78
+ *
79
+ * Sets `intentionallyClosed` so ReconnectionManager skips its reconnect
80
+ * attempt for the resulting `disconnected` event.
81
+ */
82
+ disconnect(): Promise<void>;
83
+ /** Detach all server→client method handlers registered via attachHandlers(). */
84
+ private detachHandlers;
85
+ private safeStop;
86
+ on<K extends keyof NotificationHubEventMap>(event: K, handler: (payload: NotificationHubEventMap[K]) => void): Unsubscribe;
87
+ off<K extends keyof NotificationHubEventMap>(event: K, handler: (payload: NotificationHubEventMap[K]) => void): void;
88
+ }
89
+ //# sourceMappingURL=NotificationHubClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationHubClient.d.ts","sourceRoot":"","sources":["../../src/realtime/NotificationHubClient.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,kBAAkB,EAClB,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAqB,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEpF,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,iCAAiC,CAAC;AAMzC,MAAM,WAAW,uBAAuB;IAEtC,sBAAsB,EAAE,yBAAyB,CAAC;IAClD,qBAAqB,EAAE,wBAAwB,CAAC;IAGhD,mBAAmB,EAAE,sBAAsB,CAAC;IAC5C,mBAAmB,EAAE,sBAAsB,CAAC;IAC5C,iBAAiB,EAAE,oBAAoB,CAAC;IACxC,eAAe,EAAE,kBAAkB,CAAC;IACpC,kBAAkB,EAAE,qBAAqB,CAAC;IAC1C,oBAAoB,EAAE,uBAAuB,CAAC;IAG9C,YAAY,EAAE,KAAK,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,EAAE,KAAK,GAAG,SAAS,CAAC;CACjC;AAMD,MAAM,WAAW,4BAA4B;IAC3C,kGAAkG;IAClG,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,aAAa,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACnC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAMD;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6C;IAErE,4EAA4E;IAC5E,OAAO,CAAC,iBAAiB,CAA8B;IAEvD;;;;OAIG;IACH,OAAO,CAAC,oBAAoB,CAAS;IAErC,8EAA8E;IAC9E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAS5B;gBAEC,OAAO,EAAE,4BAA4B;IASjD,IAAI,KAAK,IAAI,kBAAkB,CAE9B;IAED;;;OAGG;IACH,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAMD,0EAA0E;IAC1E,OAAO,CAAC,eAAe;IAmBvB,oEAAoE;IACpE,OAAO,CAAC,cAAc;IA2BtB;;;;;;;;OAQG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAuC9B;;;;;;;OAOG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBjC,gFAAgF;IAChF,OAAO,CAAC,cAAc;YAMR,QAAQ;IAetB,EAAE,CAAC,CAAC,SAAS,MAAM,uBAAuB,EACxC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,GACrD,WAAW;IAId,GAAG,CAAC,CAAC,SAAS,MAAM,uBAAuB,EACzC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,GACrD,IAAI;CAGR"}
@@ -0,0 +1,191 @@
1
+ // realtime/NotificationHubClient.ts — Manages NotificationHub SignalR connection
2
+ import { HubConnection, HubConnectionBuilder, HubConnectionState, LogLevel, } from '@microsoft/signalr';
3
+ import { TypedEventEmitter as EventEmitter } from '../utils/TypedEventEmitter.js';
4
+ // ---------------------------------------------------------------------------
5
+ // NotificationHubClient
6
+ // ---------------------------------------------------------------------------
7
+ /**
8
+ * Typed client for the NotificationHub (`/hubs/notifications`).
9
+ *
10
+ * Per spec §4: NotificationHub has **no client-callable hub methods** —
11
+ * after connecting, the server automatically pushes events to the user's
12
+ * personal group (`participant:{userId}`). There is also no `Error` event
13
+ * on this hub: invalid/expired JWT is rejected at the gateway (HTTP 401)
14
+ * and a missing `sub` claim aborts the connection — both silent on the
15
+ * wire. Register the `Error` listener on ChatHub instead.
16
+ *
17
+ * Responsibilities:
18
+ * - Manages HubConnection lifecycle (connect / disconnect / auto-reconnect)
19
+ * - Exposes typed event subscription (Server → Client)
20
+ */
21
+ export class NotificationHubClient {
22
+ constructor(options) {
23
+ this.connection = null;
24
+ /** In-flight connect Promise, used to dedupe concurrent connect() calls. */
25
+ this.connectingPromise = null;
26
+ /**
27
+ * True after a user-initiated disconnect() until the next connect() begins.
28
+ * ReconnectionManager uses this to skip auto-reconnect when the close was
29
+ * intentional rather than caused by a transport failure.
30
+ */
31
+ this._intentionallyClosed = false;
32
+ this.options = options;
33
+ this.emitter = new EventEmitter();
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // Connection state
37
+ // ---------------------------------------------------------------------------
38
+ get state() {
39
+ return this.connection?.state ?? HubConnectionState.Disconnected;
40
+ }
41
+ /**
42
+ * True after a user-initiated disconnect() and until the next connect() starts.
43
+ * ReconnectionManager honors this to avoid reconnecting after an intentional close.
44
+ */
45
+ get intentionallyClosed() {
46
+ return this._intentionallyClosed;
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Connection management
50
+ // ---------------------------------------------------------------------------
51
+ /** Build a new HubConnection using the latest token from tokenProvider */
52
+ buildConnection() {
53
+ const { hubUrl, tokenProvider, logLevel = LogLevel.Warning } = this.options;
54
+ return new HubConnectionBuilder()
55
+ .withUrl(hubUrl, {
56
+ // Throw rather than return '' so SignalR fails the request loudly with
57
+ // a useful error instead of sending an empty `Authorization: Bearer ` header.
58
+ accessTokenFactory: () => {
59
+ const t = tokenProvider();
60
+ if (!t) {
61
+ throw new Error('NotificationHub: no authentication token available');
62
+ }
63
+ return t;
64
+ },
65
+ })
66
+ .withAutomaticReconnect()
67
+ .configureLogging(logLevel)
68
+ .build();
69
+ }
70
+ /** Attach SignalR server→client event handlers to the connection */
71
+ attachHandlers(conn) {
72
+ // Message notifications
73
+ conn.on('NewMessageNotification', (dto) => this.emitter.emit('newMessageNotification', dto));
74
+ conn.on('MentionedNotification', (dto) => this.emitter.emit('mentionedNotification', dto));
75
+ // Conversation events
76
+ conn.on('ConversationCreated', (dto) => this.emitter.emit('conversationCreated', dto));
77
+ conn.on('ConversationUpdated', (dto) => this.emitter.emit('conversationUpdated', dto));
78
+ conn.on('ParticipantJoined', (dto) => this.emitter.emit('participantJoined', dto));
79
+ conn.on('ParticipantLeft', (dto) => this.emitter.emit('participantLeft', dto));
80
+ conn.on('ConversationPinned', (dto) => this.emitter.emit('conversationPinned', dto));
81
+ conn.on('ConversationUnpinned', (dto) => this.emitter.emit('conversationUnpinned', dto));
82
+ // Connection lifecycle
83
+ conn.onreconnecting((err) => this.emitter.emit('reconnecting', err));
84
+ conn.onreconnected((connectionId) => this.emitter.emit('reconnected', connectionId));
85
+ conn.onclose((err) => this.emitter.emit('disconnected', err));
86
+ }
87
+ /**
88
+ * Connect to NotificationHub. If already connected, resolves immediately.
89
+ * Concurrent calls share a single in-flight Promise — building two
90
+ * connections at once would leak handlers.
91
+ * Builds a fresh connection using the current token.
92
+ *
93
+ * Throws synchronously when no authentication token is available so the
94
+ * caller gets a clear error rather than a malformed Authorization header.
95
+ */
96
+ async connect() {
97
+ if (this.connection && this.connection.state === HubConnectionState.Connected) {
98
+ return;
99
+ }
100
+ if (this.connectingPromise) {
101
+ return this.connectingPromise;
102
+ }
103
+ if (!this.options.tokenProvider()) {
104
+ throw new Error('NotificationHubClient.connect(): no authentication token available. ' +
105
+ 'Provide one via ChatClient.setToken() or the tokenProvider option.');
106
+ }
107
+ // The user is explicitly opening a connection — clear any prior
108
+ // intentional-close flag so ReconnectionManager will manage future drops.
109
+ this._intentionallyClosed = false;
110
+ const promise = (async () => {
111
+ try {
112
+ // Disconnect existing stale connection cleanly
113
+ if (this.connection) {
114
+ await this.safeStop();
115
+ }
116
+ const conn = this.buildConnection();
117
+ this.attachHandlers(conn);
118
+ this.connection = conn;
119
+ await conn.start();
120
+ }
121
+ finally {
122
+ this.connectingPromise = null;
123
+ }
124
+ })();
125
+ this.connectingPromise = promise;
126
+ return promise;
127
+ }
128
+ /**
129
+ * Disconnect from NotificationHub.
130
+ * If a connect is in flight, it is awaited first so we always tear down a
131
+ * fully-formed connection (not a half-built one).
132
+ *
133
+ * Sets `intentionallyClosed` so ReconnectionManager skips its reconnect
134
+ * attempt for the resulting `disconnected` event.
135
+ */
136
+ async disconnect() {
137
+ // Mark intentional BEFORE awaiting the in-flight connect so any
138
+ // disconnected events that fire during teardown are not auto-reconnected.
139
+ this._intentionallyClosed = true;
140
+ if (this.connectingPromise) {
141
+ try {
142
+ await this.connectingPromise;
143
+ }
144
+ catch {
145
+ // The connect failed — there's nothing established to tear down,
146
+ // but still proceed to clear local state below.
147
+ }
148
+ }
149
+ await this.safeStop();
150
+ this.connection = null;
151
+ }
152
+ /** Detach all server→client method handlers registered via attachHandlers(). */
153
+ detachHandlers(conn) {
154
+ for (const name of NotificationHubClient.HUB_EVENT_NAMES) {
155
+ conn.off(name);
156
+ }
157
+ }
158
+ async safeStop() {
159
+ if (!this.connection)
160
+ return;
161
+ // Detach handlers first so any late events on this connection are dropped.
162
+ this.detachHandlers(this.connection);
163
+ try {
164
+ await this.connection.stop();
165
+ }
166
+ catch {
167
+ // Ignore errors during stop
168
+ }
169
+ }
170
+ // ---------------------------------------------------------------------------
171
+ // Event subscription (Server → Client)
172
+ // ---------------------------------------------------------------------------
173
+ on(event, handler) {
174
+ return this.emitter.on(event, handler);
175
+ }
176
+ off(event, handler) {
177
+ this.emitter.off(event, handler);
178
+ }
179
+ }
180
+ /** Server→client method names registered via conn.on() — used for cleanup. */
181
+ NotificationHubClient.HUB_EVENT_NAMES = [
182
+ 'NewMessageNotification',
183
+ 'MentionedNotification',
184
+ 'ConversationCreated',
185
+ 'ConversationUpdated',
186
+ 'ParticipantJoined',
187
+ 'ParticipantLeft',
188
+ 'ConversationPinned',
189
+ 'ConversationUnpinned',
190
+ ];
191
+ //# sourceMappingURL=NotificationHubClient.js.map