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

Files changed (2) hide show
  1. src/chat.py +4 -4
  2. 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 && callTarget) {
665
- localInner.textContent = callTarget.display_name[0].toUpperCase();
666
- localInner.style.background = callTarget.avatar_color || "#6366f1";
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
- await apiFetch(`/chat/groups/${currentGroupData.id}/members/${userId}`, token, "DELETE");
 
 
 
 
 
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;