Spaces:
Sleeping
Sleeping
ausername-12345 commited on
Commit ·
e0701ea
1
Parent(s): 987f439
fix GC [no key], camera-off avatar, timestamps, GC remove error handling
Browse files- GC [no key]: cache own public key in pubKeyCache on app init
(setupSession + completeGoogleSetup) so sendGroupMsg always has
the sender's key to wrap the AES key. Without this, encrypted_aes_key
for self was missing and the echo showed [Encrypted message].
- Camera-off avatar: local video avatar now uses me.display_name/avatar_color
instead of callTarget (which was showing the other person's avatar
even when YOUR camera was off).
- Timestamps always 'now': changed str(msg.created_at) to
msg.created_at.isoformat() so JS Date parses correctly regardless
of timezone, fixing the 2-minutes-ago-showing-as-now bug.
- GC remove: added try/catch to removeFromGroup so silent failures
don't leave stale UI
- src/chat.py +4 -4
- src/static/app.js +19 -6
src/chat.py
CHANGED
|
@@ -75,7 +75,7 @@ def list_conversations(request: Request, db: Session = Depends(get_db)):
|
|
| 75 |
result.append({
|
| 76 |
"id": conv.id,
|
| 77 |
"other_user": user_to_dict(other),
|
| 78 |
-
"last_message_at": str(last_msg.created_at) if last_msg else None,
|
| 79 |
"unread_count": sum(1 for msg in conv.messages if not msg.is_read and msg.sender_id != me.id)
|
| 80 |
})
|
| 81 |
result.sort(key=lambda x: x["last_message_at"] or "", reverse=True)
|
|
@@ -193,7 +193,7 @@ def msg_to_dict(msg: Message, viewer_id: int) -> dict:
|
|
| 193 |
"sender_id": msg.sender_id,
|
| 194 |
"sender_name": msg.sender.display_name if msg.sender else "Unknown",
|
| 195 |
"ciphertext": msg.ciphertext_for_sender if is_sender else msg.ciphertext_for_recipient,
|
| 196 |
-
"created_at": str(msg.created_at),
|
| 197 |
"is_read": msg.is_read,
|
| 198 |
}
|
| 199 |
|
|
@@ -206,7 +206,7 @@ def group_msg_to_dict(msg: GroupMessage, viewer_id: int) -> dict:
|
|
| 206 |
"ciphertext": msg.ciphertext,
|
| 207 |
"iv": msg.iv,
|
| 208 |
"encrypted_aes_key": keys.get(str(viewer_id)),
|
| 209 |
-
"created_at": str(msg.created_at),
|
| 210 |
}
|
| 211 |
|
| 212 |
def group_to_dict(group: Group, viewer_id: int) -> dict:
|
|
@@ -217,5 +217,5 @@ def group_to_dict(group: Group, viewer_id: int) -> dict:
|
|
| 217 |
"created_by": group.created_by,
|
| 218 |
"avatar_color": group.avatar_color,
|
| 219 |
"member_count": len(group.members),
|
| 220 |
-
"created_at": str(group.created_at),
|
| 221 |
}
|
|
|
|
| 75 |
result.append({
|
| 76 |
"id": conv.id,
|
| 77 |
"other_user": user_to_dict(other),
|
| 78 |
+
"last_message_at": last_msg.created_at.isoformat() if last_msg and hasattr(last_msg.created_at, 'isoformat') else str(last_msg.created_at) if last_msg else None,
|
| 79 |
"unread_count": sum(1 for msg in conv.messages if not msg.is_read and msg.sender_id != me.id)
|
| 80 |
})
|
| 81 |
result.sort(key=lambda x: x["last_message_at"] or "", reverse=True)
|
|
|
|
| 193 |
"sender_id": msg.sender_id,
|
| 194 |
"sender_name": msg.sender.display_name if msg.sender else "Unknown",
|
| 195 |
"ciphertext": msg.ciphertext_for_sender if is_sender else msg.ciphertext_for_recipient,
|
| 196 |
+
"created_at": msg.created_at.isoformat() if hasattr(msg.created_at, 'isoformat') else str(msg.created_at),
|
| 197 |
"is_read": msg.is_read,
|
| 198 |
}
|
| 199 |
|
|
|
|
| 206 |
"ciphertext": msg.ciphertext,
|
| 207 |
"iv": msg.iv,
|
| 208 |
"encrypted_aes_key": keys.get(str(viewer_id)),
|
| 209 |
+
"created_at": msg.created_at.isoformat() if hasattr(msg.created_at, 'isoformat') else str(msg.created_at),
|
| 210 |
}
|
| 211 |
|
| 212 |
def group_to_dict(group: Group, viewer_id: int) -> dict:
|
|
|
|
| 217 |
"created_by": group.created_by,
|
| 218 |
"avatar_color": group.avatar_color,
|
| 219 |
"member_count": len(group.members),
|
| 220 |
+
"created_at": group.created_at.isoformat() if hasattr(group.created_at, 'isoformat') else str(group.created_at),
|
| 221 |
}
|
src/static/app.js
CHANGED
|
@@ -87,6 +87,11 @@ async function setupSession(tok, generateKeys = false) {
|
|
| 87 |
me.public_key = pubKeyStr;
|
| 88 |
}
|
| 89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
showApp();
|
| 91 |
updateMyProfile();
|
| 92 |
await loadChats();
|
|
@@ -247,6 +252,9 @@ async function completeGoogleSetup() {
|
|
| 247 |
const pubKeyStr = JSON.stringify(kp.publicKeyJwk);
|
| 248 |
await apiFetch("/auth/profile", token, "PUT", { public_key: pubKeyStr });
|
| 249 |
me.public_key = pubKeyStr;
|
|
|
|
|
|
|
|
|
|
| 250 |
showApp();
|
| 251 |
updateMyProfile();
|
| 252 |
try { await loadChats(); } catch (e) { console.error("loadChats error:", e); }
|
|
@@ -659,13 +667,13 @@ function cleanupCall() {
|
|
| 659 |
function setupCallMedia() {
|
| 660 |
const lv = document.getElementById("local-video");
|
| 661 |
if (lv) lv.srcObject = localStream;
|
| 662 |
-
// Init local avatar
|
| 663 |
const localInner = document.getElementById("local-video-avatar-inner");
|
| 664 |
-
if (localInner &&
|
| 665 |
-
localInner.textContent =
|
| 666 |
-
localInner.style.background =
|
| 667 |
}
|
| 668 |
-
// Init remote avatar
|
| 669 |
const remoteInner = document.getElementById("remote-video-avatar-inner");
|
| 670 |
if (remoteInner && callTarget) {
|
| 671 |
remoteInner.textContent = callTarget.display_name[0].toUpperCase();
|
|
@@ -1206,7 +1214,12 @@ async function addToGroup(userId) {
|
|
| 1206 |
}
|
| 1207 |
|
| 1208 |
async function removeFromGroup(userId) {
|
| 1209 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1210 |
if (userId === me.id) {
|
| 1211 |
closeModal("modal-group-settings");
|
| 1212 |
currentChat = null;
|
|
|
|
| 87 |
me.public_key = pubKeyStr;
|
| 88 |
}
|
| 89 |
|
| 90 |
+
// Cache our own public key for group message encryption
|
| 91 |
+
if (me.public_key) {
|
| 92 |
+
try { pubKeyCache[me.id] = await Crypto.importPublicKey(me.public_key); } catch (e) {}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
showApp();
|
| 96 |
updateMyProfile();
|
| 97 |
await loadChats();
|
|
|
|
| 252 |
const pubKeyStr = JSON.stringify(kp.publicKeyJwk);
|
| 253 |
await apiFetch("/auth/profile", token, "PUT", { public_key: pubKeyStr });
|
| 254 |
me.public_key = pubKeyStr;
|
| 255 |
+
if (me.public_key) {
|
| 256 |
+
try { pubKeyCache[me.id] = await Crypto.importPublicKey(me.public_key); } catch (e) {}
|
| 257 |
+
}
|
| 258 |
showApp();
|
| 259 |
updateMyProfile();
|
| 260 |
try { await loadChats(); } catch (e) { console.error("loadChats error:", e); }
|
|
|
|
| 667 |
function setupCallMedia() {
|
| 668 |
const lv = document.getElementById("local-video");
|
| 669 |
if (lv) lv.srcObject = localStream;
|
| 670 |
+
// Init local avatar (shows me when my camera is off)
|
| 671 |
const localInner = document.getElementById("local-video-avatar-inner");
|
| 672 |
+
if (localInner && me) {
|
| 673 |
+
localInner.textContent = me.display_name[0].toUpperCase();
|
| 674 |
+
localInner.style.background = me.avatar_color || "#6366f1";
|
| 675 |
}
|
| 676 |
+
// Init remote avatar (shows other person when their camera is off)
|
| 677 |
const remoteInner = document.getElementById("remote-video-avatar-inner");
|
| 678 |
if (remoteInner && callTarget) {
|
| 679 |
remoteInner.textContent = callTarget.display_name[0].toUpperCase();
|
|
|
|
| 1214 |
}
|
| 1215 |
|
| 1216 |
async function removeFromGroup(userId) {
|
| 1217 |
+
try {
|
| 1218 |
+
await apiFetch(`/chat/groups/${currentGroupData.id}/members/${userId}`, token, "DELETE");
|
| 1219 |
+
} catch (e) {
|
| 1220 |
+
console.error("removeFromGroup error:", e);
|
| 1221 |
+
return;
|
| 1222 |
+
}
|
| 1223 |
if (userId === me.id) {
|
| 1224 |
closeModal("modal-group-settings");
|
| 1225 |
currentChat = null;
|