fix: reactions in browser mode (BrowserAdapter)
- Include mid on the wire in sendChat() so recipients can reference it - Handle incoming 'reaction' wire message in PeerConn._onControl - Add PeerConn.sendReaction() to send the reaction wire message - Handle 'send_reaction' IPC command in BrowserAdapter.send() - Emit 'reaction' IPC event on receive and on local send - Use wire mid (not synthetic peer-ts mid) when receiving chat messages Reactions now work across all combinations: daemon↔daemon, browser↔browser, and daemon↔browser. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -433,9 +433,17 @@ class PeerConn {
|
|||||||
nick: m['nick'] as string || '', caps: m['caps'] || []
|
nick: m['nick'] as string || '', caps: m['caps'] || []
|
||||||
})
|
})
|
||||||
} else if (m['type'] === 'chat') {
|
} else if (m['type'] === 'chat') {
|
||||||
|
const ts = (m['ts'] as number) || Date.now()
|
||||||
this.on('chat', {
|
this.on('chat', {
|
||||||
peer: this.peerId, room: m['room'] as string || 'general',
|
peer: this.peerId, room: m['room'] as string || 'general',
|
||||||
text: m['text'] as string, ts: m['ts'] as number || Date.now()
|
text: m['text'] as string, ts,
|
||||||
|
mid: (m['mid'] as string) || `${this.peerId}-${ts}`,
|
||||||
|
})
|
||||||
|
} else if (m['type'] === 'reaction') {
|
||||||
|
this.on('reaction', {
|
||||||
|
peer: this.peerId,
|
||||||
|
mid: m['reaction_mid'] as string,
|
||||||
|
emoji: m['reaction_emoji'] as string,
|
||||||
})
|
})
|
||||||
} else if (m['type'] === 'pm') {
|
} else if (m['type'] === 'pm') {
|
||||||
this.on('pm', { peer: this.peerId, text: m['text'] as string, ts: m['ts'] as number || Date.now() })
|
this.on('pm', { peer: this.peerId, text: m['text'] as string, ts: m['ts'] as number || Date.now() })
|
||||||
@@ -556,14 +564,18 @@ class PeerConn {
|
|||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
sendChat(room: string, text: string) {
|
sendChat(room: string, text: string, mid: string) {
|
||||||
this._dc({ type: 'chat', room, text, ts: Date.now() })
|
this._dc({ type: 'chat', room, text, ts: Date.now(), mid })
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPm(text: string) {
|
sendPm(text: string) {
|
||||||
this._dc({ type: 'pm', text, ts: Date.now() })
|
this._dc({ type: 'pm', text, ts: Date.now() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendReaction(mid: string, emoji: string) {
|
||||||
|
this._dc({ type: 'reaction', reaction_mid: mid, reaction_emoji: emoji })
|
||||||
|
}
|
||||||
|
|
||||||
private _dc(obj: object) {
|
private _dc(obj: object) {
|
||||||
if (this.dc?.readyState === 'open') this.dc.send(JSON.stringify(obj))
|
if (this.dc?.readyState === 'open') this.dc.send(JSON.stringify(obj))
|
||||||
}
|
}
|
||||||
@@ -755,9 +767,17 @@ export class BrowserAdapter {
|
|||||||
public_key: data['peer'] as string, created_at: new Date().toISOString()
|
public_key: data['peer'] as string, created_at: new Date().toISOString()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
} else if (event === 'reaction') {
|
||||||
|
this.emit({
|
||||||
|
type: 'reaction',
|
||||||
|
network_id: this.networkId,
|
||||||
|
peer_id: data['peer'] as unknown as import('../types').PeerID,
|
||||||
|
reaction_mid: data['mid'] as string,
|
||||||
|
reaction_emoji: data['emoji'] as string,
|
||||||
|
})
|
||||||
} else if (event === 'chat') {
|
} else if (event === 'chat') {
|
||||||
const ts = (data['ts'] as number) || Date.now()
|
const ts = (data['ts'] as number) || Date.now()
|
||||||
const mid = `${data['peer']}-${ts}`
|
const mid = (data['mid'] as string) || `${data['peer']}-${ts}`
|
||||||
this.emit({
|
this.emit({
|
||||||
type: 'message_received',
|
type: 'message_received',
|
||||||
network_id: this.networkId,
|
network_id: this.networkId,
|
||||||
@@ -910,8 +930,8 @@ export class BrowserAdapter {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Broadcast
|
// Broadcast — include mid on the wire so recipients can reference it in reactions
|
||||||
this.peers.forEach(p => p.sendChat(room, text))
|
this.peers.forEach(p => p.sendChat(room, text, mid))
|
||||||
this.emit({
|
this.emit({
|
||||||
type: 'message_received',
|
type: 'message_received',
|
||||||
network_id: this.networkId,
|
network_id: this.networkId,
|
||||||
@@ -924,6 +944,22 @@ export class BrowserAdapter {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (msg.type === 'send_reaction') {
|
||||||
|
const mid = msg.reaction_mid
|
||||||
|
const emoji = msg.reaction_emoji
|
||||||
|
if (!mid || !emoji) return
|
||||||
|
this.peers.forEach(p => p.sendReaction(mid, emoji))
|
||||||
|
// Emit locally so the sender sees their own reaction immediately
|
||||||
|
this.emit({
|
||||||
|
type: 'reaction',
|
||||||
|
network_id: this.networkId,
|
||||||
|
peer_id: this.identity.id as unknown as import('../types').PeerID,
|
||||||
|
reaction_mid: mid,
|
||||||
|
reaction_emoji: emoji,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.type === 'export_identity') {
|
if (msg.type === 'export_identity') {
|
||||||
if (!msg.passphrase) return
|
if (!msg.passphrase) return
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user