Try fixing initialization

This commit is contained in:
2025-12-04 12:34:34 +03:00
Unverified
parent c5d5cdf9d2
commit 5cdaab35f5
8 changed files with 315 additions and 74 deletions
+102 -43
View File
@@ -52,6 +52,10 @@ export function useDM() {
if (!user.authToken) return;
try {
// Wait for session restoration to complete (if in progress)
const { waitForSessionRestore } = await import("@/utils/crypto/sessionRestoreState");
await waitForSessionRestore();
// Get public key
const publicKey = await api.chats.dm.fetchUserPublicKey(dmUser.id, user.authToken);
if (!publicKey) return;
@@ -63,11 +67,22 @@ export function useDM() {
// Find last message
const lastMessage = messages[messages.length - 1];
let lastPlaintext: string | null = null;
const isAuthor = lastMessage.senderId === user.currentUser?.id;
try {
lastPlaintext = (JSON.parse(await decryptDm(lastMessage, lastMessage.senderId)) as DmEncryptedJSON).data.content;
if (isAuthor) {
// For our own messages, fetch plaintexts from server (encrypted at rest)
const { fetchMessagePlaintextsForRecipient } = await import("@/utils/crypto/messagePlaintextSync");
const plaintexts = await fetchMessagePlaintextsForRecipient(dmUser.id);
const cached = plaintexts.get(lastMessage.id);
if (cached) {
lastPlaintext = (JSON.parse(cached) as DmEncryptedJSON).data.content;
}
} else {
// Incoming message - decrypt via Signal
lastPlaintext = (JSON.parse(await decryptDm(lastMessage, lastMessage.senderId)) as DmEncryptedJSON).data.content;
}
} catch (error) {
console.error("Failed to decrypt last message:", error);
console.error("Failed to get last message preview:", error);
}
// Calculate unread count
@@ -102,6 +117,10 @@ export function useDM() {
usersLoadedRef.current = true;
setIsLoadingUsers(true);
try {
// Wait for session restoration to complete (if in progress)
const { waitForSessionRestore } = await import("@/utils/crypto/sessionRestoreState");
await waitForSessionRestore();
const conversations = await api.chats.dm.conversations(user.authToken);
// Process conversations and decrypt last messages
@@ -111,17 +130,25 @@ export function useDM() {
if (conv.lastMessage) {
try {
// Get the public key for the other user
const otherUserId = conv.lastMessage.senderId === user.currentUser?.id
? conv.lastMessage.recipientId
: conv.lastMessage.senderId;
const publicKey = await api.chats.dm.fetchUserPublicKey(otherUserId, user.authToken!);
if (publicKey) {
// Decrypt the last message
const decryptedJson = await decryptDm(conv.lastMessage, conv.lastMessage.senderId);
const decryptedData = JSON.parse(decryptedJson) as DmEncryptedJSON;
lastMessageContent = formatDMMessageContent(decryptedData.data.content, conv.lastMessage.senderId, user.currentUser?.id!);
const isAuthor = conv.lastMessage.senderId === user.currentUser?.id;
const otherUserId = conv.user.id; // the other party in the conversation
if (isAuthor) {
// Fetch plaintext of our own last message from server
const { fetchMessagePlaintextsForRecipient } = await import("@/utils/crypto/messagePlaintextSync");
const plaintexts = await fetchMessagePlaintextsForRecipient(otherUserId);
const cached = plaintexts.get(conv.lastMessage.id);
if (cached) {
const data = JSON.parse(cached) as DmEncryptedJSON;
lastMessageContent = formatDMMessageContent(data.data.content, conv.lastMessage.senderId, user.currentUser!.id);
}
} else {
// Incoming message - decrypt
const publicKey = await api.chats.dm.fetchUserPublicKey(otherUserId, user.authToken!);
if (publicKey) {
const decryptedJson = await decryptDm(conv.lastMessage, conv.lastMessage.senderId);
const decryptedData = JSON.parse(decryptedJson) as DmEncryptedJSON;
lastMessageContent = formatDMMessageContent(decryptedData.data.content, conv.lastMessage.senderId, user.currentUser!.id);
}
}
} catch (error) {
// Silently fail for last message decryption - it's not critical
@@ -160,6 +187,11 @@ export function useDM() {
setIsLoadingHistory(true);
try {
// Wait for session restoration to complete (if in progress)
const { waitForSessionRestore } = await import("@/utils/crypto/sessionRestoreState");
await waitForSessionRestore();
console.log(`[useDM] Session restoration complete, proceeding with message load for user ${userId}`);
const { messages } = await api.chats.dm.fetchMessages(userId, user.authToken, 50);
const decryptedMessages: Message[] = [];
let maxIncomingId = 0;
@@ -276,23 +308,29 @@ export function useDM() {
const conversations = await api.chats.dm.conversations(user.authToken);
const userConversation = conversations.find(conv => conv.user.id === userId);
if (userConversation) {
if (userConversation) {
let lastMessageContent: string | undefined = undefined;
if (userConversation.lastMessage) {
try {
// Get the public key for the other user
const otherUserId = userConversation.lastMessage.senderId === user.currentUser?.id
? userConversation.lastMessage.recipientId
: userConversation.lastMessage.senderId;
const publicKey = await api.chats.dm.fetchUserPublicKey(otherUserId, user.authToken!);
if (publicKey) {
// Decrypt the last message
const decryptedJson = await decryptDm(userConversation.lastMessage, userConversation.lastMessage.senderId);
const decryptedData = JSON.parse(decryptedJson) as DmEncryptedJSON;
lastMessageContent = formatDMMessageContent(decryptedData.data.content, userConversation.lastMessage.senderId, user.currentUser?.id!);
}
const isAuthor = userConversation.lastMessage.senderId === user.currentUser?.id;
const otherUserId = userId;
if (isAuthor) {
const { fetchMessagePlaintextsForRecipient } = await import("@/utils/crypto/messagePlaintextSync");
const plaintexts = await fetchMessagePlaintextsForRecipient(otherUserId);
const cached = plaintexts.get(userConversation.lastMessage.id);
if (cached) {
const data = JSON.parse(cached) as DmEncryptedJSON;
lastMessageContent = formatDMMessageContent(data.data.content, userConversation.lastMessage.senderId, user.currentUser!.id);
}
} else {
const publicKey = await api.chats.dm.fetchUserPublicKey(otherUserId, user.authToken!);
if (publicKey) {
const decryptedJson = await decryptDm(userConversation.lastMessage, userConversation.lastMessage.senderId);
const decryptedData = JSON.parse(decryptedJson) as DmEncryptedJSON;
lastMessageContent = formatDMMessageContent(decryptedData.data.content, userConversation.lastMessage.senderId, user.currentUser!.id);
}
}
} catch (error) {
console.error("Failed to decrypt last message for user", userId, error);
}
@@ -335,23 +373,33 @@ export function useDM() {
return;
}
const otherUserId = senderId === user.currentUser.id ? recipientId : senderId;
// Update unread count and last message preview
try {
const publicKey = await api.chats.dm.fetchUserPublicKey(otherUserId, user.authToken!);
if (publicKey) {
const decryptedJson = await decryptDm(envelope, senderId);
const decryptedData = JSON.parse(decryptedJson) as DmEncryptedJSON;
const messageContent = decryptedData.data.content;
let messageContent: string | null = null;
if (senderId === user.currentUser.id) {
const { fetchMessagePlaintextsForRecipient } = await import("@/utils/crypto/messagePlaintextSync");
const plaintexts = await fetchMessagePlaintextsForRecipient(otherUserId);
const cached = plaintexts.get(envelope.id);
if (cached) {
messageContent = (JSON.parse(cached) as DmEncryptedJSON).data.content;
}
} else {
const publicKey = await api.chats.dm.fetchUserPublicKey(otherUserId, user.authToken!);
if (publicKey) {
const decryptedJson = await decryptDm(envelope, senderId);
const decryptedData = JSON.parse(decryptedJson) as DmEncryptedJSON;
messageContent = decryptedData.data.content;
}
}
if (messageContent !== null) {
const formattedMessage = formatDMMessageContent(messageContent, senderId, user.currentUser.id);
setDmUsersState(prev => prev.map(u =>
u.id === otherUserId
? {
...u,
unreadCount: senderId !== user.currentUser?.id ? u.unreadCount + 1 : u.unreadCount,
lastMessage: formattedMessage,
publicKey
lastMessage: formattedMessage
}
: u
));
@@ -368,18 +416,29 @@ export function useDM() {
}
const otherUserId = senderId === user.currentUser.id ? recipientId : senderId;
try {
const publicKey = await api.chats.dm.fetchUserPublicKey(otherUserId, user.authToken!);
if (publicKey) {
const decryptedJson = await decryptDm(envelope, senderId);
const decryptedData = JSON.parse(decryptedJson) as DmEncryptedJSON;
const messageContent = decryptedData.data.content;
let messageContent: string | null = null;
if (senderId === user.currentUser.id) {
const { fetchMessagePlaintextsForRecipient } = await import("@/utils/crypto/messagePlaintextSync");
const plaintexts = await fetchMessagePlaintextsForRecipient(otherUserId);
const cached = plaintexts.get(id);
if (cached) {
messageContent = (JSON.parse(cached) as DmEncryptedJSON).data.content;
}
} else {
const publicKey = await api.chats.dm.fetchUserPublicKey(otherUserId, user.authToken!);
if (publicKey) {
const decryptedJson = await decryptDm(envelope, senderId);
const decryptedData = JSON.parse(decryptedJson) as DmEncryptedJSON;
messageContent = decryptedData.data.content;
}
}
if (messageContent !== null) {
const formattedMessage = formatDMMessageContent(messageContent, senderId, user.currentUser.id);
setDmUsersState(prev => prev.map(u =>
u.id === otherUserId
? {
...u,
lastMessage: formattedMessage,
publicKey
lastMessage: formattedMessage
}
: u
));
@@ -63,7 +63,7 @@ export class DMPanel extends MessagePanel {
this.failedDecryptionIds.clear();
}
private async parseTextPayload(env: DmEnvelope, decryptedMessages: Message[]) {
private async parseTextPayload(env: DmEnvelope, decryptedMessages: Message[], plaintextOverride?: string) {
// Check if this is a message sent by the current user
const isSentByUs = env.senderId === this.currentUser.currentUser?.id;
@@ -71,8 +71,24 @@ export class DMPanel extends MessagePanel {
if (isSentByUs) {
// Can't decrypt our own sent messages in Signal Protocol
// The plaintext should be passed in from loadMessages (fetched from server)
// For now, throw an error - the caller should handle this by fetching plaintexts first
throw new Error("Cannot decrypt own sent message - plaintext must be fetched from server first");
if (plaintextOverride) {
plaintext = plaintextOverride;
} else {
// Try to fetch from server as fallback
try {
const { fetchMessagePlaintextsForRecipient } = await import("@/utils/crypto/messagePlaintextSync");
const plaintexts = await fetchMessagePlaintextsForRecipient(this.dmData!.userId);
const cached = plaintexts.get(env.id);
if (cached) {
plaintext = cached;
} else {
// Not on server - skip this message
throw new Error("Cannot decrypt own sent message - plaintext not available on server");
}
} catch (error) {
throw new Error("Cannot decrypt own sent message - plaintext must be fetched from server first");
}
}
} else {
// Decrypt incoming messages
plaintext = await decryptDm(env, env.senderId);
@@ -125,6 +141,11 @@ export class DMPanel extends MessagePanel {
this.setLoading(true);
try {
// Wait for session restoration to complete (if in progress)
const { waitForSessionRestore } = await import("@/utils/crypto/sessionRestoreState");
await waitForSessionRestore();
console.log(`[DMPanel] Session restoration complete, proceeding with message load for user ${this.dmData.userId}`);
// Ensure Signal Protocol session is established before fetching messages
if (!this.signalService && this.currentUser.currentUser?.id) {
this.signalService = new SignalProtocolService(this.currentUser.currentUser.id.toString());
@@ -140,6 +161,8 @@ export class DMPanel extends MessagePanel {
console.warn(`Failed to establish Signal Protocol session for user ${this.dmData.userId} during history load:`, error);
// Continue loading history, but decryption will likely fail for new messages
}
} else {
console.log(`[DMPanel] Signal Protocol session exists for user ${this.dmData.userId}`);
}
}
@@ -223,8 +246,13 @@ export class DMPanel extends MessagePanel {
}
}
this.clearMessages();
decryptedMessages.forEach(msg => this.addMessage(msg));
// Only clear and replace if we actually decrypted something
if (decryptedMessages.length > 0) {
this.clearMessages();
decryptedMessages.forEach(msg => this.addMessage(msg));
} else {
console.warn("[DMPanel] No messages decrypted; keeping existing messages to avoid empty state after reload.");
}
this.setHasMoreMessages(has_more);
// Update last read ID
@@ -434,23 +462,56 @@ export class DMPanel extends MessagePanel {
// Mark as processed before attempting decryption
this.processedMessageIds.add(envelope.id);
const dmMsg = await this.parseTextPayload(envelope, this.getMessages());
// Check if this is a confirmation of a message we sent
const isOurMessage = envelope.senderId !== this.dmData.userId;
const isOurMessage = envelope.senderId === this.currentUser.currentUser?.id;
let dmMsg: Message;
if (isOurMessage) {
// For sent messages, fetch plaintext from server
const { fetchMessagePlaintextsForRecipient } = await import("@/utils/crypto/messagePlaintextSync");
const plaintexts = await fetchMessagePlaintextsForRecipient(this.dmData.userId);
const cachedPlaintext = plaintexts.get(envelope.id);
if (cachedPlaintext) {
// Parse the plaintext and create message
dmMsg = await this.parseTextPayload(envelope, this.getMessages(), cachedPlaintext);
} else {
// Plaintext not available yet - this might be a new message confirmation
// Try to get it from temp message content
const tempMessages = this.getMessages().filter(m => m.id === -1 && m.runtimeData?.sendingState?.tempId);
let tempMsgContent: string | null = null;
for (const tempMsg of tempMessages) {
if (tempMsg.runtimeData?.sendingState?.retryData?.content) {
tempMsgContent = tempMsg.content;
break;
}
}
if (tempMsgContent) {
dmMsg = await this.parseTextPayload(envelope, this.getMessages(), tempMsgContent);
} else {
// Can't display without plaintext - skip
console.warn(`Cannot display sent message ${envelope.id} - plaintext not available`);
return;
}
}
} else {
// Incoming message - decrypt normally
dmMsg = await this.parseTextPayload(envelope, this.getMessages());
}
if (isOurMessage) {
// This is our message being confirmed, find the temp message and replace it
const tempMessages = this.getMessages().filter(m => m.id === -1 && m.runtimeData?.sendingState?.tempId);
let tempMsgContent: string | null = null;
for (const tempMsg of tempMessages) {
if (tempMsg.runtimeData?.sendingState?.retryData?.content === dmMsg.content) {
if ((tempMsg.runtimeData?.sendingState?.retryData?.content === dmMsg.content ||
tempMsg.content === dmMsg.content) && tempMsg.runtimeData?.sendingState?.tempId) {
tempMsgContent = tempMsg.content; // Get plaintext from temp message
this.handleMessageConfirmed(tempMsg.runtimeData.sendingState.tempId!, dmMsg);
this.handleMessageConfirmed(tempMsg.runtimeData.sendingState.tempId, dmMsg);
// Upload the plaintext to server (encrypted) so we can display it in history
const { uploadMessagePlaintext } = await import("@/utils/crypto/messagePlaintextSync");
if (tempMsgContent) {
await uploadMessagePlaintext(envelope.id, this.dmData.userId, tempMsgContent);
await uploadMessagePlaintext(this.dmData.userId, envelope.id, tempMsgContent);
}
return;
}
@@ -460,9 +521,16 @@ export class DMPanel extends MessagePanel {
// (this might happen if the page was reloaded)
if (!tempMsgContent && dmMsg.content) {
const { uploadMessagePlaintext } = await import("@/utils/crypto/messagePlaintextSync");
await uploadMessagePlaintext(envelope.id, this.dmData.userId, dmMsg.content);
await uploadMessagePlaintext(this.dmData.userId, envelope.id, dmMsg.content);
}
// Add the message to the chat
this.addMessage(dmMsg);
return;
}
// Incoming message - add to chat
this.addMessage(dmMsg);
this.addMessage(dmMsg);
+15 -1
View File
@@ -9,6 +9,7 @@ import { typingManager } from "@/core/typingManager";
import type { UserState } from "./types";
import { clearSessionSync } from "@/utils/crypto/sessionSync";
import { clearMessagePlaintextSync } from "@/utils/crypto/messagePlaintextSync";
import { resetSessionRestoreState } from "@/utils/crypto/sessionRestoreState";
interface UserStore {
user: UserState;
@@ -65,6 +66,13 @@ export const useUserStore = create<UserStore>((set) => ({
// Clear session sync
clearSessionSync();
clearMessagePlaintextSync();
// Reset session restore state
try {
resetSessionRestoreState();
} catch (error) {
console.error("Failed to reset session restore state:", error);
}
set({
user: {
@@ -132,14 +140,20 @@ export const useUserStore = create<UserStore>((set) => ({
if (storedKey) {
console.log("[RestoreFromStorage] Stored session key found, restoring sessions from server...");
// We can restore sessions using the stored key (password not needed)
const { setRestoringSessions } = await import("@/utils/crypto/sessionRestoreState");
const restorePromise = restoreSessionsFromServer(user.id.toString(), null, token);
setRestoringSessions(restorePromise);
try {
await restoreSessionsFromServer(user.id.toString(), null, token);
await restorePromise;
console.log("[RestoreFromStorage] Sessions restored from server using stored key");
} catch (error) {
console.error("[RestoreFromStorage] Failed to restore sessions:", error);
}
} else {
console.warn("[RestoreFromStorage] No stored session key - user needs to log in to derive key");
// Mark restore as complete even if we couldn't restore (to avoid blocking message loading)
const { setRestoringSessions } = await import("@/utils/crypto/sessionRestoreState");
setRestoringSessions(Promise.resolve());
}
// Re-upload prekeys to ensure they are fresh
@@ -4,7 +4,7 @@
*/
import { encryptMessagePlaintext, decryptMessagePlaintext } from "./messagePlaintextEncryption";
import { uploadMessagePlaintexts, fetchMessagePlaintexts, type MessagePlaintextData } from "@/core/api/crypto/messagePlaintexts";
import { uploadMessagePlaintexts, fetchMessagePlaintexts } from "@/core/api/crypto/messagePlaintexts";
// Global state for message plaintext sync
let syncPassword: string | null = null;
@@ -75,21 +75,42 @@ export async function fetchMessagePlaintextsForRecipient(
): Promise<Map<number, string>> {
const plaintexts = new Map<number, string>();
if (!syncToken || !syncUserId) {
console.warn("Message plaintext sync not initialized (missing token/userId)");
return plaintexts; // Return empty map if not initialized
// Try to get token and userId from global state if sync isn't initialized
let token = syncToken;
let userId = syncUserId;
let password = syncPassword;
if (!token || !userId) {
// Fallback: try to get from user store
try {
const { useUserStore } = await import("@/state/user");
const userState = useUserStore.getState().user;
if (userState.authToken && userState.currentUser?.id) {
token = userState.authToken;
userId = userState.currentUser.id.toString();
console.log(`[MessagePlaintextSync] Using token/userId from user store (sync not initialized)`);
} else {
console.warn("Message plaintext sync not initialized (missing token/userId)");
return plaintexts; // Return empty map if not initialized
}
} catch (error) {
console.warn("Message plaintext sync not initialized (missing token/userId)");
return plaintexts; // Return empty map if not initialized
}
}
// Password can be null - decryptMessagePlaintext will use stored session key if password is null
try {
console.log(`Fetching encrypted plaintexts for recipient ${recipientId}...`);
const encryptedMessages = await fetchMessagePlaintexts(syncToken, recipientId);
const encryptedMessages = await fetchMessagePlaintexts(token, recipientId);
console.log(`Found ${encryptedMessages.length} encrypted plaintexts, decrypting...`);
for (const msg of encryptedMessages) {
try {
// Use stored key if available, otherwise use password to derive it
const plaintext = await decryptMessagePlaintext(syncPassword, syncUserId, msg.encryptedData);
const plaintext = await decryptMessagePlaintext(password, userId, msg.encryptedData);
plaintexts.set(msg.messageId, plaintext);
} catch (error) {
console.warn(`Failed to decrypt plaintext for message ${msg.messageId}:`, error);
@@ -0,0 +1,64 @@
/**
* Global state to track session restoration progress
* Used to ensure messages aren't loaded before sessions are restored
*/
let isRestoring = false;
let restorePromise: Promise<void> | null = null;
let restoreComplete = false;
/**
* Mark that session restoration has started
*/
export function setRestoringSessions(promise: Promise<void>): void {
isRestoring = true;
restoreComplete = false;
restorePromise = promise;
promise.finally(() => {
isRestoring = false;
restoreComplete = true;
});
}
/**
* Wait for session restoration to complete (if in progress)
*/
export async function waitForSessionRestore(): Promise<void> {
if (!isRestoring && restoreComplete) {
console.log("[SessionRestoreState] Session restoration already completed");
return; // Already completed
}
if (isRestoring && restorePromise) {
console.log("[SessionRestoreState] Waiting for session restoration to complete...");
await restorePromise;
console.log("[SessionRestoreState] Session restoration completed");
} else if (!restoreComplete) {
// No restoration in progress and not completed - mark as complete to avoid blocking
console.log("[SessionRestoreState] No session restoration in progress, marking as complete");
restoreComplete = true;
}
}
/**
* Check if session restoration is in progress
*/
export function isSessionRestoreInProgress(): boolean {
return isRestoring;
}
/**
* Check if session restoration has completed
*/
export function hasSessionRestoreCompleted(): boolean {
return restoreComplete;
}
/**
* Reset the restore state (e.g., on logout)
*/
export function resetSessionRestoreState(): void {
isRestoring = false;
restorePromise = null;
restoreComplete = false;
}
+12 -7
View File
@@ -110,14 +110,19 @@ export async function restoreSessionsFromServer(
try {
const address = `${sessionData.recipientId}.${sessionData.deviceId}`;
// Check if we already have a local session - if so, skip restoration
// This prevents overwriting a newer session with an older one
const existingSession = await storage.loadSession(address);
if (existingSession) {
console.log(`Skipping restoration for recipient ${sessionData.recipientId} - local session already exists`);
continue;
// Always restore from server to ensure we have a valid session
// Local sessions might be corrupted, so we restore from server on every reload
// The server has the authoritative copy encrypted with password-derived key
try {
const existingSession = await storage.loadSession(address);
if (existingSession) {
console.log(`[Session Sync] Local session exists for recipient ${sessionData.recipientId}, but restoring from server to ensure validity`);
}
} catch (error) {
console.log(`[Session Sync] Local session for recipient ${sessionData.recipientId} failed to load, restoring from server`);
}
// Always restore from server (don't skip)
const encryptedBlob = decodeSessionBlob(sessionData.encryptedData);
// Use stored key if available, otherwise use password to derive it
const sessionRecord = await decryptSessionWithPassword(password, userId, encryptedBlob);
@@ -125,7 +130,7 @@ export async function restoreSessionsFromServer(
// Store in IndexedDB (sync callback won't fire because isRestoring is true)
await storage.storeSession(address, sessionRecord);
restoredCount++;
console.log(`Restored session for recipient ${sessionData.recipientId}`);
console.log(`[Session Sync ✅] Restored session for recipient ${sessionData.recipientId} from server`);
} catch (error) {
failedCount++;
console.warn(`Failed to restore session for recipient ${sessionData.recipientId}:`, error);
@@ -73,8 +73,11 @@ export async function initializeSignalProtocol({
console.log("========================================");
console.log("[Signal Protocol Init] Step 5: ⚠️ RESTORING SESSIONS FROM SERVER");
console.log("========================================");
const { setRestoringSessions } = await import("./sessionRestoreState");
const restorePromise = restoreSessionsFromServer(userId, password, token);
setRestoringSessions(restorePromise);
try {
await restoreSessionsFromServer(userId, password, token);
await restorePromise;
console.log("========================================");
console.log("[Signal Protocol Init] Step 5: ✅ SESSIONS RESTORED FROM SERVER");
console.log("========================================");
@@ -85,6 +88,10 @@ export async function initializeSignalProtocol({
console.error("========================================");
// Continue even if restoration fails
}
} else {
// Mark restore as complete if we're not restoring (to avoid blocking message loading)
const { setRestoringSessions } = await import("./sessionRestoreState");
setRestoringSessions(Promise.resolve());
}
// Step 6: Upload prekey bundle
+7 -4
View File
@@ -350,6 +350,7 @@ export class SignalProtocolStorage implements StorageType {
request.onsuccess = () => {
const data = request.result;
if (data && data.record && typeof data.record === "string" && data.record.length > 0) {
console.log(`[SignalStorage] ✅ Loaded session for recipient ${recipientId} (address: ${encodedAddress})`);
resolve(data.record);
} else {
// Try with number if string didn't work (backward compatibility)
@@ -358,18 +359,19 @@ export class SignalProtocolStorage implements StorageType {
numRequest.onsuccess = () => {
const numData = numRequest.result;
if (numData && numData.record && typeof numData.record === "string" && numData.record.length > 0) {
console.log(`[SignalStorage] ✅ Loaded session for recipient ${recipientId} (address: ${encodedAddress}, using number key)`);
resolve(numData.record);
} else {
console.warn(`Session record missing or invalid for recipient ${recipientId} (address: ${encodedAddress})`);
console.warn(`[SignalStorage] ⚠️ Session record missing or invalid for recipient ${recipientId} (address: ${encodedAddress}) - checked both string and number keys`);
resolve(undefined);
}
};
numRequest.onerror = () => {
console.warn(`Session record missing for recipient ${recipientId} (address: ${encodedAddress})`);
console.warn(`[SignalStorage] ⚠️ Session record missing for recipient ${recipientId} (address: ${encodedAddress}) - IndexedDB error`);
resolve(undefined);
};
} else {
console.warn(`Session record missing or invalid for recipient ${recipientId} (address: ${encodedAddress})`);
console.warn(`[SignalStorage] ⚠️ Session record missing or invalid for recipient ${recipientId} (address: ${encodedAddress}) - no data found`);
resolve(undefined);
}
}
@@ -391,7 +393,7 @@ export class SignalProtocolStorage implements StorageType {
// Validate record
if (!record || typeof record !== "string" || record.length === 0) {
console.warn(`Invalid session record for address ${encodedAddress}`);
console.warn(`[SignalStorage] Invalid session record for address ${encodedAddress}`);
return;
}
@@ -405,6 +407,7 @@ export class SignalProtocolStorage implements StorageType {
record: record
});
request.onsuccess = () => {
console.log(`[SignalStorage] ✅ Stored session for recipient ${recipientId} (address: ${encodedAddress}, record length: ${record.length})`);
resolve();
// If session sync callback is set and we're not restoring, upload to server in background (non-blocking)
// Do this AFTER resolve() to ensure storage completes even if sync fails