diff options
| -rw-r--r-- | client/public/blobs.ts | 9 | ||||
| -rw-r--r-- | client/public/chat.ts | 86 |
2 files changed, 69 insertions, 26 deletions
diff --git a/client/public/blobs.ts b/client/public/blobs.ts index 5ba99e9..fcb4ec0 100644 --- a/client/public/blobs.ts +++ b/client/public/blobs.ts @@ -71,6 +71,11 @@ export function createBlob(seeds: [number, number, number, number, number, numbe return `#${[r, g, b].map(v => v.toString(16).padStart(2, '0')).join('')}` } + seeds[4] = seeds[0] + seeds[5] = seeds[1] + seeds[6] = seeds[2] + seeds[7] = seeds[3] + const colors = seeds.map(seedToColor) const blob = document.createElement('blob') as html @@ -129,15 +134,13 @@ export function makeMessage(type: 'in' | 'out' | 'broadcast' | 'system', id: num const seeds: [number, number, number, number, number, number, number, number] = pubkey === '0' ? [27, 28, 32, 45, 49, 58, 91, 28] : pubkeyToBlobs(pubkey) const blob = createBlob(seeds) - blob.addEventListener('click', () => { + if (pubkey !== info?.keypair?.identity) blob.addEventListener('click', () => { if (confirm('do you want to open a direct message with this user?')) { const chat = `@${pubkey}` if (!followed_chats.includes(chat)) { followed_chats.push(chat) - //@ts-expect-error globalThis.refreshChatList() } - //@ts-expect-error globalThis.switchChat(chat) } }) diff --git a/client/public/chat.ts b/client/public/chat.ts index 4671a0c..04bc3f1 100644 --- a/client/public/chat.ts +++ b/client/public/chat.ts @@ -24,7 +24,7 @@ function status(dot: 'live' | 'connecting' | 'initializing' | 'disconnected', te estatus.innerHTML = text } -const socket = new WebSocket(`ws://${location.hostname}:10000/tcp/ws/localhost/5001`) +const socket = new WebSocket(`${location.protocol.startsWith('https') ? 'wss' : 'ws'}://${location.hostname}:${location.protocol.startsWith('https') ? '10000' : '10001'}/tcp/ws/localhost/5001`) socket.binaryType = 'arraybuffer' socket.addEventListener('error', e => { @@ -113,6 +113,7 @@ async function parseTrio(trio: string) { return undefined } +const onlineStatus = new Map<string, boolean>() let chats: Record<string, HTMLElement[]> = {} let current_chat: string = localStorage.getItem('active_chat') ?? 'main' @@ -142,8 +143,11 @@ function addMessage(chat: string, stored: StoredMessage) { function renderMessage(stored: StoredMessage) { const { obj, meta, direction } = stored - const nick = nicknames[meta.pubkey] ?? meta.id - const prefix = direction === 'system' ? '' : `[<name>${nick}</name> ${shortenPubkey(meta.pubkey)}]` + const nick = ((meta.pubkey === info.keypair?.identity ? (my_name ?? my_id) : nicknames[meta.pubkey])) ?? meta.id + const cur = new Date(obj.timestamp) + const fmt = `${cur.getFullYear()}/${cur.getMonth()}/${cur.getDay()} ${cur.getHours()}:${cur.getMinutes()}:${cur.getSeconds()}` + const prefix = direction === 'system' ? '' : `[${obj.timestamp ? fmt : 'sometime.'} <name>${nick}</name> ${shortenPubkey(meta.pubkey)}]` + return makeMessage(direction, meta.id, prefix, obj.text, meta.pubkey) } @@ -161,7 +165,6 @@ function switchChat(next_chat: string) { refreshChatList() } -//@ts-expect-error globalThis.switchChat = switchChat const pendingPings = new Map<string, (pubkey: string) => void>() @@ -224,7 +227,7 @@ document.querySelector('s-item[archived]')!.addEventListener('click', () => { }) async function chatHandler(time: number, packet: string) { - // console.log('catching all packets!', time, packet) + console.log('catching all packets!', time, packet.substring(0, 64)) if (packet.startsWith('M')) { packet = packet.substring(1) @@ -247,7 +250,7 @@ async function chatHandler(time: number, packet: string) { packet = packet.substring(5) const target = packet.split(':')[0] if (!target) return false - if (target !== info.keypair.identity) return true + if (target !== info?.keypair?.identity) return true packet = packet.substring(target.length + 1) const nonce = packet.trim() if (nonce.length !== 16) return false @@ -283,9 +286,9 @@ async function chatHandler(time: number, packet: string) { try { const obj = JSON.parse(msg.data) if (obj.chat.startsWith('@')) { - const sidekey = subtractStrings(obj.chat.substring(1), info.keypair.identity) + const sidekey = subtractStrings(obj.chat.substring(1), info?.keypair?.identity) obj.chat = '@' + sidekey - obj.text = decoder.decode(await crypt.decryptAsymmetric(info.keypair.cryptPrivate, obj.text)) + obj.text = decoder.decode(await crypt.decryptAsymmetric(info?.keypair?.cryptPrivate, obj.text)) } if (obj.text && obj.chat && followed_chats.includes(obj.chat)) { @@ -300,6 +303,8 @@ async function chatHandler(time: number, packet: string) { if (obj.chat === current_chat) { cont.appendChild(msgobj) } + + cont.scrollTop = cont.scrollHeight } } catch (e) { console.warn('invalid json', e) @@ -338,7 +343,7 @@ async function chatHandler(time: number, packet: string) { packet = packet.substring(11) const colon = packet.indexOf(':') const target = packet.substring(0, colon) - if (target !== info.keypair.identity) return true + if (target !== info?.keypair?.identity) return true packet = packet.substring(colon + 1) const msg = await parseTrio(packet) @@ -349,8 +354,11 @@ async function chatHandler(time: number, packet: string) { if (!chats[TRANSFERS_CHAT]) chats[TRANSFERS_CHAT] = [] + const cur = new Date() + const fmt = `${cur.getFullYear()}/${cur.getMonth()}/${cur.getDay()} ${cur.getHours()}:${cur.getMinutes()}:${cur.getSeconds()}` + const sender = nicknames[msg.pubkey] ?? shortenPubkey(msg.pubkey) - const row = makeMessage('system', 0, '', `📁 <b>${sender}</b> wants to send <b>${name}</b> (${(size / 1024).toFixed(1)} KB) — <u style="cursor:pointer" data-transfer-id="${id}">accept or reject</u>`, '0') + const row = makeMessage('system', 0, `[${fmt}]`, `📁 <b>${sender}</b> wants to send <b>${name}</b> (${(size / 1024).toFixed(1)} KB) — <u style="cursor:pointer" data-transfer-id="${id}">accept or reject</u>`, '0') row.querySelector('[data-transfer-id]')!.addEventListener('click', async () => { const offer = pendingOffers.get(id) @@ -397,7 +405,7 @@ async function chatHandler(time: number, packet: string) { packet = packet.substring(11) const parts = packet.split(':') const target = parts[0]! - if (target !== info.keypair.identity) return true + if (target !== info?.keypair?.identity) return true // skip target + index const firstColon = packet.indexOf(':') const secondColon = packet.indexOf(':', firstColon + 1) @@ -410,7 +418,7 @@ async function chatHandler(time: number, packet: string) { const receive = pendingReceives.get(id) if (!receive) return true - const decrypted = await crypt.decryptAsymmetric(info.keypair.cryptPrivate, data) + const decrypted = await crypt.decryptAsymmetric(info?.keypair?.cryptPrivate, data) const index = parseInt(parts[1]!) receive.chunks.set(index, decrypted) status('live', `receiving ${receive.name}: ${receive.chunks.size}/${receive.total}`) @@ -468,7 +476,6 @@ async function chatHandler(time: number, packet: string) { return true } -//@ts-expect-error yeah globalThis.refreshChatList = refreshChatList function refreshChatList() { @@ -483,7 +490,16 @@ function refreshChatList() { const target = dm ? c_directs : c_channels const elm = document.createElement('s-item') elm.setAttribute(dm ? 'dm' : 'channel', '') - elm.innerHTML = dm ? (nicknames[chat.substring(1)] ?? `${shortenPubkey(chat.substring(1))}?`) : chat + if (dm) { + const pubkey = chat.substring(1) + const label = nicknames[pubkey] ?? `${shortenPubkey(pubkey)}?` + const dotAttr = onlineStatus.has(pubkey) + ? (onlineStatus.get(pubkey) ? 'online' : 'offline') + : 'away' + elm.innerHTML = `${label} <s-dot ${dotAttr}></s-dot>` + } else { + elm.innerHTML = chat + } if (chat === current_chat) elm.setAttribute('active', '') elm.addEventListener('click', () => { switchChat(chat) @@ -497,6 +513,25 @@ function refreshChatList() { transfersBtn.toggleAttribute('pending', pendingOffers.size > 0) } +async function pollContactsLoop() { + while (true) { + const dms = followed_chats.filter(c => c.startsWith('@')) + + await Promise.all(dms.map(async (chat) => { + const pubkey = chat.substring(1) + if (pubkey === info?.keypair?.identity) return + let alive = await ping(pubkey) + // atleast 2 retries + for (let i = 0; i < 2 && !alive; i++) alive = await ping(pubkey) + onlineStatus.set(pubkey, alive) + })) + + refreshChatList() + + await new Promise(r => setTimeout(r, 30_000)) + } +} + async function runSetup() { status('live', 'setup1:keypair') const keypairText = localStorage.getItem('keypair') @@ -567,7 +602,9 @@ click on the blob until you're satisfied with the colors.<br><br>` while (!confirmed) await new Promise(r => setTimeout(r, 100)) } - document.querySelector('s-avatar')!.appendChild(createBlob(pubkeyToBlobs(info.keypair.identity))) + if (my_name) nicknames[info?.keypair?.identity] = my_name + + document.querySelector('s-avatar')!.appendChild(createBlob(pubkeyToBlobs(info?.keypair?.identity))) document.querySelector('s-name')!.innerHTML = my_name ?? `id${my_id}` msgHandler = chatHandler @@ -575,7 +612,7 @@ click on the blob until you're satisfied with the colors.<br><br>` status('live', 'setup2:subscribe') await setPassword(msg_pass) status('live', 'setup3:announce') - await sendMessage(msg_pass, 'pubkey:' + info.keypair.identity) + await sendMessage(msg_pass, 'pubkey:' + info?.keypair?.identity) if (my_name) { status('live', 'setup4:nickname') await sendMessage(msg_pass, `nick:${await createTrio(my_name)}`) @@ -590,6 +627,7 @@ click on the blob until you're satisfied with the colors.<br><br>` } switchChat(current_chat) + pollContactsLoop() status('live', 'live') } @@ -656,7 +694,7 @@ requestAnimationFrame(inboxProcessorLoop) // setPassword('hello') async function createTrio(text: string) { - return `${info.keypair.identity}:${await crypt.sign(info.keypair.signPrivate, encoder.encode(text))}:${text}` + return `${info?.keypair?.identity}:${await crypt.sign(info?.keypair?.signPrivate, encoder.encode(text))}:${text}` } async function _send() { @@ -670,9 +708,9 @@ async function _send() { localStorage.setItem('name', nick) my_name = nick - nicknames[info.keypair.identity] = nick + nicknames[info?.keypair?.identity] = nick - const nickname_elms = id_list[info.keypair.identity] + const nickname_elms = id_list[info?.keypair?.identity] if (nickname_elms) { for (const elm of nickname_elms) { elm.querySelector('name')!.innerHTML = nick ?? my_id @@ -703,19 +741,21 @@ async function _send() { let text if (chat.startsWith('@')) { const sidekey = chat.substring(1) - chat = '@' + addStrings(sidekey, info.keypair.identity) + chat = '@' + addStrings(sidekey, info?.keypair?.identity) text = await crypt.encryptAsymmetric(sidekey.substring(0, sidekey.length / 2), encoder.encode(val)) } else text = val + const time = Date.now() + await sendMessage(msg_pass, `msg:${await createTrio(JSON.stringify({ text, chat, - timestamp: Date.now() + timestamp: time }))}`) const msgobj = addMessage(current_chat, { - obj: { text: val, chat: current_chat }, - meta: { id: my_id, pubkey: info.keypair.identity, nickname: my_name ?? String(my_id) }, + obj: { text: val, chat: current_chat, timestamp: time }, + meta: { id: my_id, pubkey: info?.keypair?.identity, nickname: my_name ?? String(my_id) }, direction: 'out' }) cont.appendChild(msgobj) |
