fix: make conversation restore reliable in Safari
This commit is contained in:
@@ -21,7 +21,7 @@ export type ConversationSnapshot = {
|
||||
};
|
||||
|
||||
type PersistedConversation = {
|
||||
version: 1;
|
||||
version: 3;
|
||||
cursor: number;
|
||||
items: StoredConversationItem[];
|
||||
outbox: OutboxEntry[];
|
||||
@@ -38,7 +38,7 @@ const STORE_PREFIX = "lineup.conversation.v1";
|
||||
*/
|
||||
export class ConversationStore {
|
||||
private activeConversationID: string | undefined;
|
||||
private state: PersistedConversation = { version: 1, cursor: 0, items: [], outbox: [] };
|
||||
private state: PersistedConversation = { version: 3, cursor: 0, items: [], outbox: [] };
|
||||
|
||||
public constructor(private readonly storage: Storage) {}
|
||||
|
||||
@@ -50,7 +50,7 @@ export class ConversationStore {
|
||||
|
||||
public close(): void {
|
||||
this.activeConversationID = undefined;
|
||||
this.state = { version: 1, cursor: 0, items: [], outbox: [] };
|
||||
this.state = { version: 3, cursor: 0, items: [], outbox: [] };
|
||||
}
|
||||
|
||||
public snapshot(): ConversationSnapshot {
|
||||
@@ -85,7 +85,11 @@ export class ConversationStore {
|
||||
delivery: { status: "submitted", updated_at: updatedAt, transport_id: String(messageSeq) },
|
||||
};
|
||||
this.state.outbox = this.state.outbox.filter(entry => entry.local_id !== localID);
|
||||
this.state.cursor = Math.max(this.state.cursor, messageSeq);
|
||||
// A send acknowledgement is not a sync acknowledgement. The legacy
|
||||
// `/messages/sync` cursor is inclusive, so advancing it here would make a
|
||||
// refresh start at `messageSeq + 1` and permanently skip this user's echo.
|
||||
// Only a message that has actually passed through the sync path advances
|
||||
// the cursor (recordSyncedUserText / recordIncoming / advanceCursor).
|
||||
this.persist();
|
||||
}
|
||||
|
||||
@@ -153,14 +157,23 @@ export class ConversationStore {
|
||||
private load(conversationID: string): PersistedConversation {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(this.storage.getItem(this.key(conversationID)) ?? "null");
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { version: 1, cursor: 0, items: [], outbox: [] };
|
||||
const candidate = parsed as Partial<PersistedConversation>;
|
||||
if (candidate.version !== 1 || !Array.isArray(candidate.items) || !Array.isArray(candidate.outbox) || typeof candidate.cursor !== "number") {
|
||||
return { version: 1, cursor: 0, items: [], outbox: [] };
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { version: 3, cursor: 0, items: [], outbox: [] };
|
||||
const candidate = parsed as { version?: unknown; cursor?: unknown; items?: unknown; outbox?: unknown };
|
||||
if ((candidate.version !== 1 && candidate.version !== 2 && candidate.version !== 3) || !Array.isArray(candidate.items) || !Array.isArray(candidate.outbox) || typeof candidate.cursor !== "number") {
|
||||
return { version: 3, cursor: 0, items: [], outbox: [] };
|
||||
}
|
||||
return { version: 1, cursor: Math.max(0, candidate.cursor), items: candidate.items, outbox: candidate.outbox };
|
||||
|
||||
if (candidate.version !== 3) {
|
||||
// v1 advanced its cursor from a send acknowledgement. Some browsers
|
||||
// had already been upgraded to v2 before that correction, retaining a
|
||||
// high cursor with holes for their own echoes. Preserve every valid
|
||||
// local item but replay the server history once to repair those holes;
|
||||
// the next persist upgrades this record to v3.
|
||||
return { version: 3, cursor: 0, items: candidate.items as StoredConversationItem[], outbox: candidate.outbox as OutboxEntry[] };
|
||||
}
|
||||
return { version: 3, cursor: Math.max(0, candidate.cursor), items: candidate.items as StoredConversationItem[], outbox: candidate.outbox as OutboxEntry[] };
|
||||
} catch {
|
||||
return { version: 1, cursor: 0, items: [], outbox: [] };
|
||||
return { version: 3, cursor: 0, items: [], outbox: [] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user