@pmate/chat-sdk 0.12.0-next.35 → 0.12.0-next.37
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.
|
@@ -297,7 +297,10 @@ export class ChatSession {
|
|
|
297
297
|
}
|
|
298
298
|
if (restart)
|
|
299
299
|
continue;
|
|
300
|
-
|
|
300
|
+
const result = { messages: pages.reverse().flat(), hasEarlier: false, complete: true, resetVersion: version };
|
|
301
|
+
await this.messages.expandWindow(threadId, result);
|
|
302
|
+
this.rememberHistoryEdge(threadId, result);
|
|
303
|
+
return result;
|
|
301
304
|
}
|
|
302
305
|
catch (error) {
|
|
303
306
|
this.assertOpen();
|
|
@@ -453,9 +456,9 @@ export class ChatSession {
|
|
|
453
456
|
const messages = previous && previous.messages.length === raw.length && previous.messages.every((message, index) => message === raw[index]) ? previous.messages : raw;
|
|
454
457
|
const outgoing = this.sender.snapshot(threadId);
|
|
455
458
|
const sameSource = previous && previous.messages === messages && previous.outgoing.length === outgoing.length && previous.outgoing.every((record, index) => record === outgoing[index]);
|
|
456
|
-
const semanticReading = previous?.messageReading ?? {};
|
|
457
|
-
const semantics = sameSource ? previous.timeline : projectMessageTimeline({ appId: this.options.messages.scope.appId, profileId: this.options.messages.scope.profileId, threadId, messages, outgoing, decode: this.decodeTimeline, reading: semanticReading, previous: previous?.timeline });
|
|
458
459
|
const candidates = new Map(messages.flatMap(message => { const candidate = this.options.readingCandidate?.(message); return candidate ? [[candidate.id, candidate]] : []; }));
|
|
460
|
+
const semanticReading = previous?.messageReading ?? {};
|
|
461
|
+
const semantics = sameSource ? previous.timeline : projectMessageTimeline({ appId: this.options.messages.scope.appId, profileId: this.options.messages.scope.profileId, threadId, messages, outgoing, candidates, decode: this.decodeTimeline, reading: semanticReading, previous: previous?.timeline });
|
|
459
462
|
for (const item of semantics)
|
|
460
463
|
if (item.recalled)
|
|
461
464
|
candidates.set(item.id, { ...candidates.get(item.id), id: item.id, threadId, from: item.from, mine: item.mine, recalled: true });
|
|
@@ -464,7 +467,7 @@ export class ChatSession {
|
|
|
464
467
|
const view = projectConversationReading(state, { id: threadId, kind }, [...candidates.values()]);
|
|
465
468
|
const unread = previous && JSON.stringify(previous.unread) === JSON.stringify(view.unread) ? previous.unread : view.unread;
|
|
466
469
|
const messageReading = previous && JSON.stringify(previous.messageReading) === JSON.stringify(view.messageReading) ? previous.messageReading : view.messageReading;
|
|
467
|
-
const timeline = messageReading === semanticReading ? semantics : projectMessageTimeline({ appId: this.options.messages.scope.appId, profileId: this.options.messages.scope.profileId, threadId, messages, outgoing, decode: this.decodeTimeline, reading: messageReading, previous: previous?.timeline });
|
|
470
|
+
const timeline = messageReading === semanticReading ? semantics : projectMessageTimeline({ appId: this.options.messages.scope.appId, profileId: this.options.messages.scope.profileId, threadId, messages, outgoing, candidates, decode: this.decodeTimeline, reading: messageReading, previous: previous?.timeline });
|
|
468
471
|
return { timeline, messages, hasEarlier: source?.hasEarlier ?? false, resetVersion: source?.resetVersion ?? 0, complete: source?.complete ?? false, outgoing, readCounts: this.countSnapshots.get(threadId), unread, messageReading };
|
|
469
472
|
}
|
|
470
473
|
subscribe(target, listener, fields) {
|
|
@@ -91,6 +91,8 @@ export declare class MessageClient<T extends StoredMessage> {
|
|
|
91
91
|
private flushWrites;
|
|
92
92
|
private runWrites;
|
|
93
93
|
synchronize(thread: string): Promise<void>;
|
|
94
|
+
/** Keep an explicitly requested history range in the shared subscribed view. */
|
|
95
|
+
expandWindow(thread: string, page: MessageSnapshot<T>): Promise<void>;
|
|
94
96
|
earlier(thread: string, before: {
|
|
95
97
|
timestamp: number;
|
|
96
98
|
id: string;
|
|
@@ -485,6 +485,18 @@ export class MessageClient {
|
|
|
485
485
|
this.syncing.set(thread, task);
|
|
486
486
|
return task;
|
|
487
487
|
}
|
|
488
|
+
/** Keep an explicitly requested history range in the shared subscribed view. */
|
|
489
|
+
async expandWindow(thread, page) {
|
|
490
|
+
const first = page.messages[0];
|
|
491
|
+
if (!first || !page.complete)
|
|
492
|
+
return;
|
|
493
|
+
const old = this.windowEdges.get(thread);
|
|
494
|
+
if (!old || old.version !== page.resetVersion || first.timestamp < old.timestamp || first.timestamp === old.timestamp && first.id < old.id)
|
|
495
|
+
this.windowEdges.set(thread, { timestamp: first.timestamp, id: first.id, version: page.resetVersion });
|
|
496
|
+
const snapshot = await this.reload(thread);
|
|
497
|
+
if (snapshot.resetVersion !== page.resetVersion)
|
|
498
|
+
throw new Error("MESSAGE_PROJECTION_CHANGED");
|
|
499
|
+
}
|
|
488
500
|
async earlier(thread, before, limit = 100, expand = false) {
|
|
489
501
|
await this.ensureScope();
|
|
490
502
|
if (this.scopeDenied)
|
|
@@ -500,12 +512,8 @@ export class MessageClient {
|
|
|
500
512
|
await this.synchronize(thread);
|
|
501
513
|
}
|
|
502
514
|
const page = await this.fillWindow(thread, limit, before);
|
|
503
|
-
if (expand
|
|
504
|
-
|
|
505
|
-
if (!old || old.version !== page.resetVersion || first.timestamp < old.timestamp || first.timestamp === old.timestamp && first.id < old.id)
|
|
506
|
-
this.windowEdges.set(thread, { timestamp: first.timestamp, id: first.id, version: page.resetVersion });
|
|
507
|
-
await this.reload(thread);
|
|
508
|
-
}
|
|
515
|
+
if (expand)
|
|
516
|
+
await this.expandWindow(thread, page);
|
|
509
517
|
return page;
|
|
510
518
|
}
|
|
511
519
|
catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StoredMessage } from "./messageStore.js";
|
|
2
2
|
import type { OutgoingMessage } from "./durableSender.js";
|
|
3
3
|
import type { ChatRuntimeMessage } from "./protocol.js";
|
|
4
|
-
import type { MessageReadingState } from "./conversationReading.js";
|
|
4
|
+
import type { MessageReadingState, ReadingCandidate } from "./conversationReading.js";
|
|
5
5
|
export type TimelineReactionEvent = Reaction & {
|
|
6
6
|
threadId: string;
|
|
7
7
|
};
|
|
@@ -18,6 +18,8 @@ export type TimelineItem<T> = {
|
|
|
18
18
|
mine: boolean;
|
|
19
19
|
status: "sending" | "sent" | "failed" | "read";
|
|
20
20
|
recalled: boolean;
|
|
21
|
+
unread?: boolean;
|
|
22
|
+
attentionEligible?: boolean;
|
|
21
23
|
message: T | undefined;
|
|
22
24
|
reading?: MessageReadingState;
|
|
23
25
|
reactions: readonly TimelineReaction[];
|
|
@@ -42,6 +44,7 @@ export declare function projectMessageTimeline<T extends StoredMessage>(input: {
|
|
|
42
44
|
outgoing: readonly OutgoingMessage[];
|
|
43
45
|
decode: (message: ChatRuntimeMessage) => T | undefined;
|
|
44
46
|
reading: Readonly<Record<string, MessageReadingState>>;
|
|
47
|
+
candidates?: ReadonlyMap<string, ReadingCandidate>;
|
|
45
48
|
previous?: readonly TimelineItem<T>[];
|
|
46
49
|
}): readonly TimelineItem<T>[];
|
|
47
50
|
export {};
|
|
@@ -106,13 +106,14 @@ export function projectMessageTimeline(input) {
|
|
|
106
106
|
continue;
|
|
107
107
|
const mine = envelope.from === input.profileId, reading = input.reading[message.id];
|
|
108
108
|
const withdrawn = Boolean(tombstones.has(message.id) || message.recalled || envelope.legacyDisplay?.recalled || envelope.from && recalled.get(message.id)?.has(envelope.from));
|
|
109
|
-
const
|
|
109
|
+
const candidate = input.candidates?.get(message.id);
|
|
110
|
+
const item = { id: message.id, threadId: message.threadId, timestamp: message.timestamp, from: envelope.from, mine, status: !mine && reading ? (reading.read ? "read" : status === "read" ? "sent" : status) : status, recalled: withdrawn, ...(candidate ? { unread: !mine && !withdrawn && (reading ? !reading.read : candidate.unread), attentionEligible: candidate.attentionEligible } : {}),
|
|
110
111
|
message: withdrawn ? undefined : message, reading, reactionEvents: withdrawn ? [] : groupedEvents.get(message.id) ?? [], reactions: withdrawn ? [] : [...(grouped.get(message.id) ?? [])].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([emojiId, users]) => { const userIds = [...users].sort(); return { emojiId, userIds, mine: userIds.includes(input.profileId) }; }) };
|
|
111
112
|
const old = previous.get(item.id);
|
|
112
113
|
const sameReading = old?.reading === item.reading || old?.reading?.seq === item.reading?.seq && old?.reading?.read === item.reading?.read && old?.reading?.readPolicy === item.reading?.readPolicy;
|
|
113
114
|
const sameReactions = old?.reactions.length === item.reactions.length && old.reactions.every((reaction, index) => { const next = item.reactions[index]; return reaction.emojiId === next.emojiId && reaction.mine === next.mine && reaction.userIds.length === next.userIds.length && reaction.userIds.every((user, i) => user === next.userIds[i]); });
|
|
114
115
|
const sameEvents = old?.reactionEvents.length === item.reactionEvents.length && old.reactionEvents.every((event, index) => { const next = item.reactionEvents[index]; return event.id === next.id && event.from === next.from && event.emojiId === next.emojiId && event.active === next.active && event.revision === next.revision && event.timestamp === next.timestamp && event.threadId === next.threadId && event.messageId === next.messageId; });
|
|
115
|
-
result.push(old && sameEvents && old.message === item.message && old.timestamp === item.timestamp && old.from === item.from && old.mine === item.mine && old.status === item.status && old.recalled === item.recalled && sameReading && sameReactions ? old : item);
|
|
116
|
+
result.push(old && sameEvents && old.message === item.message && old.timestamp === item.timestamp && old.from === item.from && old.mine === item.mine && old.status === item.status && old.recalled === item.recalled && old.unread === item.unread && old.attentionEligible === item.attentionEligible && sameReading && sameReactions ? old : item);
|
|
116
117
|
}
|
|
117
118
|
result.sort((a, b) => a.timestamp - b.timestamp || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
118
119
|
return input.previous?.length === result.length && result.every((item, index) => item === input.previous[index]) ? input.previous : result;
|
package/package.json
CHANGED