mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-05 17:05:53 +08:00
hotkey support
This commit is contained in:
46
server/api/bridge/ptt.post.ts
Normal file
46
server/api/bridge/ptt.post.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { createError, readBody } from 'h3'
|
||||
import { BridgeToken } from '../../models/BridgeToken'
|
||||
import { getBridgeTokenFromHeader } from '../../utils/bridge'
|
||||
import { logBridgeEvent } from '../../utils/bridgeLog'
|
||||
import { pttBus, type PttState } from '../../utils/pttBus'
|
||||
|
||||
interface PttBody {
|
||||
state?: PttState
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const token = getBridgeTokenFromHeader(event)
|
||||
if (!token) {
|
||||
throw createError({ statusCode: 401, statusMessage: 'x-bridge-token header fehlt oder ist ungültig.' })
|
||||
}
|
||||
|
||||
const body = await readBody<PttBody>(event)
|
||||
const state = body?.state
|
||||
if (state !== 'down' && state !== 'up') {
|
||||
throw createError({ statusCode: 400, statusMessage: "state muss 'down' oder 'up' sein." })
|
||||
}
|
||||
|
||||
// Only relay for a linked token; an unknown/unlinked token is a no-op so the
|
||||
// bus is never driven by an unauthenticated caller.
|
||||
const exists = await BridgeToken.exists({ token })
|
||||
if (!exists) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Bridge-Token ist nicht verknüpft.' })
|
||||
}
|
||||
|
||||
console.info(
|
||||
`\x1b[33m[bridge:ptt]\x1b[0m token=\x1b[96m${token.slice(0, 6)}...\x1b[0m state=\x1b[92m${state}\x1b[0m`,
|
||||
)
|
||||
|
||||
pttBus.publish(token, state)
|
||||
|
||||
logBridgeEvent(token, {
|
||||
endpoint: '/api/bridge/ptt',
|
||||
method: 'POST',
|
||||
statusCode: 200,
|
||||
color: '#eab308',
|
||||
summary: `ptt=${state}`,
|
||||
data: { state },
|
||||
})
|
||||
|
||||
return { ok: true, state }
|
||||
})
|
||||
77
server/api/bridge/ws.ts
Normal file
77
server/api/bridge/ws.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
// server/api/bridge/ws.ts
|
||||
//
|
||||
// Low-latency push channel for push-to-talk. A /pm tab opens this socket and
|
||||
// sends { type: 'subscribe', token } using its `?token=` bridge link. The
|
||||
// Bridge POSTs key edges to /api/bridge/ptt, which drives pttBus; we relay each
|
||||
// edge to every peer subscribed to that token.
|
||||
import { defineWebSocketHandler } from 'h3'
|
||||
import { normalizeBridgeToken } from '../../utils/bridge'
|
||||
import { pttBus } from '../../utils/pttBus'
|
||||
|
||||
// token → set of connected /pm peers
|
||||
const subscribers = new Map<string, Set<any>>()
|
||||
// peerId → token, so close() can clean up without scanning every set
|
||||
const peerTokens = new Map<string, string>()
|
||||
|
||||
function peerId(peer: any): string {
|
||||
return peer?.id ?? String(peer)
|
||||
}
|
||||
|
||||
// Relay every PTT edge to the peers listening on that token.
|
||||
pttBus.subscribe((token, state) => {
|
||||
const peers = subscribers.get(token)
|
||||
if (!peers) return
|
||||
const payload = JSON.stringify({ type: 'ptt', state })
|
||||
for (const peer of peers) {
|
||||
try { peer.send(payload) } catch {}
|
||||
}
|
||||
})
|
||||
|
||||
function unsubscribe(peer: any) {
|
||||
const id = peerId(peer)
|
||||
const token = peerTokens.get(id)
|
||||
if (!token) return
|
||||
peerTokens.delete(id)
|
||||
const peers = subscribers.get(token)
|
||||
if (!peers) return
|
||||
peers.delete(peer)
|
||||
if (peers.size === 0) subscribers.delete(token)
|
||||
}
|
||||
|
||||
export default defineWebSocketHandler({
|
||||
message(peer, msg) {
|
||||
let data: any
|
||||
try {
|
||||
data = JSON.parse(typeof msg === 'string' ? msg : msg.toString())
|
||||
} catch {
|
||||
peer.send(JSON.stringify({ type: 'error', message: 'Invalid JSON' }))
|
||||
return
|
||||
}
|
||||
|
||||
if (data.type === 'subscribe') {
|
||||
const token = normalizeBridgeToken(data.token)
|
||||
if (!token) {
|
||||
peer.send(JSON.stringify({ type: 'error', message: 'Invalid token' }))
|
||||
return
|
||||
}
|
||||
// A peer only ever listens to one token; drop any previous binding.
|
||||
unsubscribe(peer)
|
||||
let peers = subscribers.get(token)
|
||||
if (!peers) {
|
||||
peers = new Set()
|
||||
subscribers.set(token, peers)
|
||||
}
|
||||
peers.add(peer)
|
||||
peerTokens.set(peerId(peer), token)
|
||||
peer.send(JSON.stringify({ type: 'subscribed' }))
|
||||
}
|
||||
},
|
||||
|
||||
close(peer) {
|
||||
unsubscribe(peer)
|
||||
},
|
||||
|
||||
error(peer) {
|
||||
unsubscribe(peer)
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user