115 lines
3.2 KiB
TypeScript
115 lines
3.2 KiB
TypeScript
import { log } from "./utils/log.ts"
|
|
import { Signal } from "./utils/signal.ts"
|
|
import { processStdout, processStderr } from "./utils/stdio.ts"
|
|
|
|
export class Baresip {
|
|
baresipArgs: string[]
|
|
process?: Bun.PipedSubprocess
|
|
callEstablished = new Signal<{ contact: string }>()
|
|
callReceived = new Signal<{ contact: string }>()
|
|
hungUp = new Signal()
|
|
error = new Signal<{ message: string }>()
|
|
registrationSuccess = new Signal()
|
|
|
|
constructor(baresipArgs: string[]) {
|
|
this.baresipArgs = baresipArgs
|
|
}
|
|
|
|
async connect() {
|
|
this.process = Bun.spawn(this.baresipArgs, {
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
onExit: (_proc, exitCode, signalCode, error) => {
|
|
log.debug(`📞 Baresip process exited (code: ${exitCode}, signal: ${signalCode})`)
|
|
if (error) {
|
|
log.error("Process error:", error)
|
|
}
|
|
},
|
|
})
|
|
|
|
Promise.all([
|
|
processStdout(this.process, (line) => this.#parseLine(line)),
|
|
processStderr(this.process),
|
|
]).catch((error) => {
|
|
log.error("Error processing output:", error)
|
|
})
|
|
}
|
|
|
|
accept() {
|
|
executeCommand("a")
|
|
}
|
|
|
|
dial(phoneNumber: string) {
|
|
executeCommand(`d${phoneNumber}`)
|
|
}
|
|
|
|
hangUp() {
|
|
executeCommand("b")
|
|
}
|
|
|
|
disconnectAll() {
|
|
this.callEstablished.disconnect()
|
|
this.callReceived.disconnect()
|
|
this.hungUp.disconnect()
|
|
this.registrationSuccess.disconnect()
|
|
}
|
|
|
|
kill() {
|
|
if (!this.process) throw new Error("Process not started")
|
|
this.process.kill()
|
|
this.disconnectAll()
|
|
this.process = undefined
|
|
}
|
|
|
|
#parseLine(line: string) {
|
|
log.debug(`📞 Baresip: ${line}`)
|
|
const callEstablishedMatch = line.match(/Call established: (.+)/)
|
|
if (callEstablishedMatch) {
|
|
log.debug(`Call established with "${line}"`)
|
|
this.callEstablished.emit({ contact: callEstablishedMatch[1]! })
|
|
}
|
|
|
|
const callReceivedMatch = line.match(/Incoming call from: \+\d+ (\S+) -/)
|
|
if (callReceivedMatch) {
|
|
log.debug(`Incoming call from "${line}"`)
|
|
this.callReceived.emit({ contact: callReceivedMatch[1]!?.trim() })
|
|
}
|
|
|
|
const hangUpMatch = line.match(/(.+): session closed/)
|
|
if (hangUpMatch) {
|
|
log.debug(`Call hung up with "${line}"`)
|
|
this.hungUp.emit()
|
|
}
|
|
|
|
const callTerminatedMatch = line.match(/(.+) terminated \(duration: /)
|
|
if (callTerminatedMatch) {
|
|
log.debug(`⁉️ NOT HANDLED: Call terminated with "${line}"`)
|
|
}
|
|
|
|
const registrationSuccessMatch = line.match(/\[\d+ bindings?\]/)
|
|
if (registrationSuccessMatch) {
|
|
this.registrationSuccess.emit()
|
|
}
|
|
|
|
const registrationFailedMatch = line.match(/reg: sip:\S+ 403 Forbidden/)
|
|
const socketInUseMatch = line.match(/tcp: sock_bind:/)
|
|
if (registrationFailedMatch || socketInUseMatch) {
|
|
log.error(`⁉️ NOT HANDLED: Registration failed with "${line}"`)
|
|
this.error.emit({ message: line })
|
|
}
|
|
}
|
|
}
|
|
|
|
const executeCommand = async (command: string) => {
|
|
try {
|
|
const url = new URL(`/?${command}`, "http://127.0.0.1:8000")
|
|
const response = await Bun.fetch(url)
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Error executing command: ${response.statusText}`)
|
|
}
|
|
} catch (error) {
|
|
log.error("Failed to execute command:", error)
|
|
}
|
|
}
|