diff --git a/src/phone.ts b/src/phone.ts index 9be461c..acb177f 100644 --- a/src/phone.ts +++ b/src/phone.ts @@ -119,6 +119,11 @@ const startBaresip = async (phoneService: PhoneService, hook: GPIO.Input, ringer phoneService.send({ type: "remote-hang-up" }) }) + baresip.dialFailed.on(({ reason }) => { + log.error("🐻 dial failed:", reason) + phoneService.send({ type: "dial-failed" } as any) + }) + baresip.connect().catch((error) => { log.error("🐻 connection error:", error) phoneService.send({ type: "error", message: error.message }) @@ -342,6 +347,16 @@ const answerCall = (ctx: PhoneContext) => { ctx.baresip.accept() } +const playDialFailedMessage = async () => { + log.error("📞 Call failed - playing error tone") + // Play fast busy signal (reorder tone) + const errorTone = await player.playTone([480, 620], 500) + await sleep(500) + errorTone.stop() + await player.playTone([480, 620], 500) + await sleep(500) +} + const makeCall = async (ctx: PhoneContext) => { log(`Dialing number: ${ctx.numberDialed}`) if (ctx.numberDialed === 1) { @@ -455,8 +470,9 @@ const phoneMachine = createMachine( t("dial-stop", "outgoing"), t("digit_increment", "dialing", r(digitIncrement)), t("hang-up", "idle")), - outgoing: invoke(makeCall, + outgoing: invoke(makeCall, t("answered", "connected"), + t("dial-failed", "ready", a(playDialFailedMessage)), t("hang-up", "idle", a(hangUp))), aborted: state( t("hang-up", "idle")), diff --git a/src/sip.ts b/src/sip.ts index 4626d84..267118c 100644 --- a/src/sip.ts +++ b/src/sip.ts @@ -8,6 +8,7 @@ export class Baresip { callEstablished = new Emitter<{ contact: string }>() callReceived = new Emitter<{ contact: string }>() hungUp = new Emitter() + dialFailed = new Emitter<{ reason: string }>() error = new Emitter<{ message: string; statusCode?: string; reason?: string }>() registrationSuccess = new Emitter() @@ -39,8 +40,11 @@ export class Baresip { executeCommand("a") } - dial(phoneNumber: string) { - executeCommand(`d${phoneNumber}`) + async dial(phoneNumber: string) { + const success = await executeCommand(`d${phoneNumber}`) + if (!success) { + this.dialFailed.emit({ reason: "Command timed out - registration may have failed" }) + } } hangUp() { @@ -52,6 +56,7 @@ export class Baresip { this.callReceived.removeAllListeners() this.hungUp.removeAllListeners() this.registrationSuccess.removeAllListeners() + this.dialFailed.removeAllListeners() this.error.removeAllListeners() } @@ -113,15 +118,21 @@ export class Baresip { } } -const executeCommand = async (command: string) => { +const executeCommand = async (command: string): Promise => { try { const url = new URL(`/?${command}`, "http://127.0.0.1:8000") - const response = await Bun.fetch(url) + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), 10000) + + const response = await fetch(url, { signal: controller.signal }) + clearTimeout(timeout) if (!response.ok) { throw new Error(`Error executing command: ${response.statusText}`) } + return true } catch (error) { log.error("Failed to execute command:", error) + return false } }