diff --git a/webui-src/app/chat/chat.js b/webui-src/app/chat/chat.js
index ffe077e..c36f5c4 100644
--- a/webui-src/app/chat/chat.js
+++ b/webui-src/app/chat/chat.js
@@ -8,35 +8,74 @@ function loadLobbyDetails(id, apply) {
rs.rsJsonApiRequest(
'/rsChats/getChatLobbyInfo',
{
- id,
+ id: { xstr64: id },
},
- (detail) => {
- if (detail.retval) {
+ (detail, success) => {
+ if (success && detail.retval) {
+ detail.info.chatType = 3; // LOBBY
apply(detail.info);
+ } else {
+ apply(null);
}
},
- true,
- {},
- undefined,
- // Custom serializer NOTE:
- // Since id represents 64-bit int(see deserializer note below)
- // Instead of using JSON.stringify, this function directly
- // creates a json string manually.
- () => '{"id":' + id + '}'
+ true
+ );
+}
+
+function loadDistantChatDetails(pid, apply) {
+ // pid is DistantChatPeerId (uint32)
+ rs.rsJsonApiRequest(
+ '/rsChats/getDistantChatStatus',
+ {
+ pid: pid,
+ },
+ (detail, success) => {
+ if (success && detail.retval) {
+ // Map to lobby-like structure for UI compatibility
+ const info = detail.info;
+ info.chatType = 2; // DISTANT (matches TYPE_PRIVATE_DISTANT in rschats.h)
+ info.lobby_name = rs.userList.username(info.to_id) || 'Distant Chat ' + pid;
+ info.lobby_topic = 'Private Encrypted Chat';
+ info.gxs_id = info.own_id;
+ info.lobby_id = pid; // Distant IDs are 128-bit hex strings, NO xstr64 wrapper
+ apply(info);
+ } else {
+ apply(null);
+ }
+ },
+ true
);
}
function sortLobbies(lobbies) {
- if (lobbies !== undefined) {
+ if (lobbies !== undefined && lobbies !== null) {
const list = [...lobbies];
list.sort((a, b) => a.lobby_name.localeCompare(b.lobby_name));
return list;
}
- // return lobbies; // fallback on reload page in browser, keep undefiend
+ return []; // return empty array instead of undefined
}
// ***************************** models ***********************************
+const MobileState = {
+ showLobbies: false,
+ showUsers: false,
+ toggleLobbies() {
+ this.showLobbies = !this.showLobbies;
+ this.showUsers = false;
+ },
+ toggleUsers() {
+ this.showUsers = !this.showUsers;
+ this.showLobbies = false;
+ },
+ closeAll() {
+ this.showLobbies = false;
+ this.showUsers = false;
+ },
+};
+
+
const ChatRoomsModel = {
allRooms: [],
knownSubscrIds: [], // to exclude subscribed from public rooms (subscribedRooms filled to late)
@@ -47,40 +86,73 @@ const ChatRoomsModel = {
rs.rsJsonApiRequest(
'/rsChats/getListOfNearbyChatLobbies',
{},
- (data) => (ChatRoomsModel.allRooms = sortLobbies(data.public_lobbies))
+ (data) => {
+ if (data && data.public_lobbies) {
+ // Deduplicate by ID to avoid double display if backend returns redundant info
+ const seen = new Set();
+ const uniqueLobbies = data.public_lobbies.filter((lobby) => {
+ const id = rs.idToHex(lobby.lobby_id);
+ if (seen.has(id)) return false;
+ seen.add(id);
+ return true;
+ });
+ ChatRoomsModel.allRooms = sortLobbies(uniqueLobbies);
+ } else {
+ // No public lobbies
+ ChatRoomsModel.allRooms = [];
+ }
+ }
);
},
loadSubscribedRooms(after = null) {
- // ChatRoomsModel.subscribedRooms = {};
rs.rsJsonApiRequest(
'/rsChats/getChatLobbyList',
{},
- // JS uses double precision numbers of 64 bit. It is equivalent
- // to 53 bits of precision. All large precision ints will
- // get truncated to an approximation.
- // This API uses Cpp-style 64 bits for `id`.
- // So we use the string-value 'xstr64' instead
(data) => {
- const ids = data.cl_list.map((lid) => lid.xstr64);
- ChatRoomsModel.knownSubscrIds = ids;
- const rooms = {};
- ids.map((id) =>
- loadLobbyDetails(id, (info) => {
- rooms[id] = info;
- if (Object.keys(rooms).length === ids.length) {
- // apply rooms to subscribedRooms only after reading all room-details, so sorting all or nothing
- ChatRoomsModel.subscribedRooms = rooms;
+ if (data && data.cl_list) {
+ // Robust deduplication of IDs
+ const ids = [...new Set(data.cl_list.map((lid) => rs.idToHex(lid)))];
+ ChatRoomsModel.knownSubscrIds = ids;
+
+ // Remove stale entries that are no longer in the subscribed list
+ Object.keys(ChatRoomsModel.subscribedRooms).forEach((id) => {
+ if (!ids.includes(id)) {
+ delete ChatRoomsModel.subscribedRooms[id];
}
- })
- );
- if (after != null) {
- after();
+ });
+
+ if (ids.length === 0) {
+ ChatRoomsModel.loadPublicRooms();
+ if (after != null) after();
+ m.redraw();
+ return;
+ }
+
+ let count = 0;
+ ids.forEach((id) =>
+ loadLobbyDetails(id, (info) => {
+ if (info) {
+ ChatRoomsModel.subscribedRooms[id] = info;
+ }
+ count++;
+ if (count === ids.length) {
+ ChatRoomsModel.loadPublicRooms(); // Load public rooms after we know all subscribed IDs
+ if (after != null) {
+ after();
+ }
+ m.redraw();
+ }
+ })
+ );
+ } else {
+ // No subscribed lobbies
+ ChatRoomsModel.loadPublicRooms();
}
}
);
},
subscribed(info) {
- return this.knownSubscrIds.includes(info.lobby_id.xstr64);
+ return this.knownSubscrIds.includes(rs.idToHex(info.lobby_id));
},
};
@@ -90,28 +162,42 @@ const ChatRoomsModel = {
* msg: Message to Display
*/
const Message = () => {
- let msg = null; // message to display
- let text = ''; // extracted text to display
- let datetime = ''; // date time to display
- let username = ''; // username to display (later may be linked)
return {
- oninit: (vnode) => {
- console.info('chat Message', vnode);
- msg = vnode.attrs;
- datetime = new Date(msg.sendTime * 1000).toLocaleTimeString();
- username = rs.userList.username(msg.lobby_peer_gxs_id);
- text = msg.msg
+ view: (vnode) => {
+ const msg = vnode.attrs;
+ const datetime = new Date(msg.sendTime * 1000).toLocaleTimeString();
+ // Handle both HistoryMsg (peerId) and ChatMessage (lobby_peer_gxs_id)
+ const rawGxsId = msg.lobby_peer_gxs_id || msg.peerId;
+ let gxsId = rs.idToHex(rawGxsId);
+
+ // Fallback for 1-to-1 chats where sender ID might be missing (zeros)
+ const isZero = (id) => !id || id === '00000000000000000000000000000000';
+ if (isZero(gxsId)) {
+ const lobby = ChatLobbyModel.currentLobby;
+ // Types 1 (Private), 2 (Distant) are "private" conversations here
+ if (lobby && (lobby.chatType === 1 || lobby.chatType === 2)) {
+ gxsId = msg.incoming ? rs.idToHex(lobby.to_id || lobby.peer_id || lobby.distant_chat_id) : rs.idToHex(lobby.own_id || lobby.gxs_id);
+ }
+ }
+
+ let username = rs.userList.username(gxsId) || msg.peerName || '???';
+ // If we only have the hex ID, try to fallback to the peerName from the message
+ if (username === gxsId && msg.peerName) {
+ username = msg.peerName;
+ }
+ if (username === gxsId && gxsId && gxsId.length > 12) {
+ username = gxsId.substring(0, 8) + '...';
+ }
+ const text = (msg.msg || msg.message || '')
.replaceAll('
', '\n')
.replace(new RegExp('