@pmate/chat-sdk 0.12.0-next.14 → 0.12.0-next.16

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.
@@ -101,12 +101,13 @@ export declare class ChatSession<T extends StoredMessage> {
101
101
  /** Bridge transports can delegate social events without maintaining clients. */
102
102
  handleReach(event: OnlineReachEvent): Promise<"handled" | undefined>;
103
103
  private wakeCounts;
104
+ private publishCountSnapshot;
104
105
  private publishCounts;
105
106
  private watchCounts;
106
107
  getSnapshot(): ChatSummarySnapshot<T>;
107
108
  getSnapshot(threadId: string): ChatThreadSnapshot<T>;
108
109
  subscribe(listener: () => void): () => void;
109
- subscribe(threadId: string, listener: () => void): () => void;
110
+ subscribe(threadId: string, listener: () => void, fields?: readonly (keyof ChatThreadSnapshot<T>)[]): () => void;
110
111
  private publishAll;
111
112
  private buildGlobal;
112
113
  private publishGlobal;
@@ -45,7 +45,14 @@ export class ChatSession {
45
45
  throw new Error("CHAT_SESSION_SCOPE_MISMATCH");
46
46
  }
47
47
  this.messages = new MessageClient(options.messages);
48
- this.connection = options.sharedConnection ?? new ChatClient(options.connection);
48
+ this.connection = options.sharedConnection ?? new ChatClient({ ...options.connection, onReach: async (event) => {
49
+ const handled = await this.handleReach(event);
50
+ if (handled)
51
+ return handled;
52
+ if (options.connection.onReach)
53
+ return options.connection.onReach(event);
54
+ throw new Error("REACH_HANDLER_UNAVAILABLE");
55
+ } });
49
56
  this.reading = new ReadingClient({ ...options.reading, storageScope: JSON.stringify([scope.accountId]), migrateLegacy: true });
50
57
  this.readers = new MessageReadersClient({ appId: scope.appId, profileId: scope.profileId, token: options.reading.sessionToken, scope, onError: options.onError, hubHttpUrl: options.reading.hubHttpUrl, fetch: options.reading.fetch });
51
58
  this.sender = new DurableChatSender(this.connection, scope, options.onError);
@@ -65,7 +72,11 @@ export class ChatSession {
65
72
  try {
66
73
  const decoded = options.decode(message);
67
74
  if (decoded)
68
- void this.messages.receive(decoded.threadId, [decoded]).then(() => this.sender.confirm([message.hash])).catch(error => { if (String(error).includes("MESSAGE_PROJECTION_CHANGED"))
75
+ void this.messages.receive(decoded.threadId, [decoded]).then(async () => {
76
+ if (!this.closed && this.readingStarted)
77
+ this.reading.refresh();
78
+ await this.sender.confirm([message.hash]);
79
+ }).catch(error => { if (String(error).includes("MESSAGE_PROJECTION_CHANGED"))
69
80
  this.messages.invalidate(decoded.threadId);
70
81
  else if (!this.closed)
71
82
  options.onError(error); });
@@ -257,6 +268,16 @@ export class ChatSession {
257
268
  if (this.counts && this.countsThread && !this.closed)
258
269
  this.countTasks.schedule("counts", async () => { this.counts?.reconnect(); });
259
270
  }
271
+ publishCountSnapshot(thread) {
272
+ const previous = this.snapshots.get(thread);
273
+ if (!previous) {
274
+ this.publish(thread, false);
275
+ return;
276
+ }
277
+ // Receipt-only updates never serialize or rebuild message bodies.
278
+ this.snapshots.set(thread, { ...previous, readCounts: this.countSnapshots.get(thread) });
279
+ this.listeners.get(thread)?.forEach(listener => listener());
280
+ }
260
281
  publishCounts(value) {
261
282
  const previous = this.countSnapshots.get(value.threadId);
262
283
  if ((previous && previous.revision > value.revision) || JSON.stringify(previous) === JSON.stringify(value))
@@ -266,9 +287,9 @@ export class ChatSession {
266
287
  if (this.countSnapshots.size > 128) {
267
288
  const oldest = this.countSnapshots.keys().next().value;
268
289
  this.countSnapshots.delete(oldest);
269
- this.publish(oldest, false);
290
+ this.publishCountSnapshot(oldest);
270
291
  }
271
- this.publish(value.threadId, false);
292
+ this.publishCountSnapshot(value.threadId);
272
293
  }
273
294
  watchCounts(threadId, ids, receiver = this.connection.identityScope().clientInstanceId) {
274
295
  if (!threadId || !ids.length) {
@@ -293,7 +314,7 @@ export class ChatSession {
293
314
  if (/READ_COUNTS_HTTP_(401|403)/.test(String(error))) {
294
315
  const threads = [...this.countSnapshots.keys()];
295
316
  this.countSnapshots.clear();
296
- threads.forEach(thread => this.publish(thread, false));
317
+ threads.forEach(thread => this.publishCountSnapshot(thread));
297
318
  }
298
319
  this.options.onError(error);
299
320
  } });
@@ -318,14 +339,23 @@ export class ChatSession {
318
339
  }
319
340
  return snapshot;
320
341
  }
321
- subscribe(target, listener) {
342
+ subscribe(target, listener, fields) {
322
343
  this.assertOpen();
323
344
  if (typeof target === "function") {
324
345
  this.globalListeners.add(target);
325
346
  return () => { this.globalListeners.delete(target); };
326
347
  }
327
348
  const threadId = target;
328
- const notify = listener;
349
+ const selected = fields ? [...fields] : undefined;
350
+ let values = selected?.map(field => this.getSnapshot(threadId)[field]);
351
+ const equal = (a, b) => a === b || (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((value, index) => value === b[index]));
352
+ const notify = selected ? () => {
353
+ const next = selected.map(field => this.getSnapshot(threadId)[field]);
354
+ const changed = next.some((value, index) => !equal(value, values[index]));
355
+ values = next;
356
+ if (changed)
357
+ listener();
358
+ } : listener;
329
359
  let listeners = this.listeners.get(threadId);
330
360
  if (!listeners) {
331
361
  listeners = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/chat-sdk",
3
- "version": "0.12.0-next.14",
3
+ "version": "0.12.0-next.16",
4
4
  "description": "Cross-platform PMate chat input components, voice input primitives, and ASR helpers",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",