aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client/public/blobs.ts9
-rw-r--r--client/public/chat.ts86
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)