Feat/electron softphone - #1469
Open
dlohvinov wants to merge 10 commits into
Open
Conversation
Shared native dependency for electron-workspace and the upcoming electron-softphone package. file: links outside a package root do not get their transitive deps installed by npm, so electron-sip now carries its own node_modules (CI installs them in a dedicated step). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tray-only Electron app that is the native SIP endpoint (pjsip via electron-sip) while the web workspace keeps the call-control UI. Hosts a loopback WebSocket control channel (default 127.0.0.1:10029): the web app delivers its access token via hello and sends answer/call commands; everything else stays on the web app's own socket. Runs its own webitel-sdk Client in the main process (registerWebDevice: false, SIP creds from user_default_device) with reconnect and duplicate-answer guards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When CLI.externalSoftphone.enabled, the browser stops registering the web SIP device (no mic prompt) and attaches a RemotePhone instead: a SipClient stub (ExternalClient pattern) that proxies answer/call to the local webitel-softphone utility over ws://127.0.0.1:<port>. Registration state from the utility feeds the existing phone_registered/isPhoneReg flow; un-answered calls get stub sessions on (re)attach so they stay answerable after reconnects. Hold/mute/DTMF/transfer/bridge are already websocket actions and stay unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
electron-builder config mirrors electron-workspace (asar off for the native addon, hardened+notarized mac build with microphone usage description); manual build-electron-softphone workflow reuses the committed electron-sip prebuilds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- serialize softphone lifecycle (restart/reconnect/destroy) through a promise chain so overlapping starts cannot clobber client/adapter - treat early 'unregistered' during REGISTERING as register_failed with reconnect (the addon resolves register() before the registrar answers) and schedule a fallback reconnect on lost registration - attach the phone before subscribeCall so snapshot calls get sip sessions; listen to sdk 'error' (Call.answer swallows SIP failures) and check answer()'s boolean result - graceful quit: tray uses app.quit(), before-quit awaits teardown; catch startup failures of whenReady - web: destroyClient() clears the external-softphone client reference so utility state can't re-attach onto a destroyed client; sessions created from sip_id never leak that id as callId - rotate softphone.log at 5MB; fix stale prebuilds path in install.js Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… found in first live smoke
ESM + typed sources (tsconfig, typecheck script, electron-sip .d.ts,
typed protocol/state/events); no require/module.exports left.
Bugs found and fixed while smoke-testing the built app for the first
time:
- SoftphoneTray no longer extends Tray: Electron's Tray constructor
returns a native wrapper that drops the subclass prototype, so
subclass methods didn't exist at runtime — the throw inside the ws
connection event silently killed message delivery. Composition now.
- every SDK setup step (connect/auth/subscribeCall/deviceConfig) is
raced against a timeout: in the Electron main process the global
WebSocket fires onerror but never onclose on unreachable endpoints,
and the SDK only wires onclose, leaving connect() pending forever.
- prevent-app-suspension power-save blocker: macOS App-Naps a tray-only
app with a hidden dock, freezing SIP and the local server.
- uncaughtException/unhandledRejection are logged (no window exists for
Electron's default error dialog).
- answer uses the SDK's actual field ({audio:true}; useAudio was never
read by the SDK).
Verified live: hello → connecting → connect timeout → error state
broadcast + ack → reconnect loop, all over the loopback socket against
the built app.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
electron 35→43, typescript 5.9→7, @types/node 24→26, webitel-sdk 26.1→26.4, ws 8.18→8.21, electron-builder 26.0→26.15. electron-vite stays on 6.0.0-beta.1 — it is newer than the latest stable (5.0.0) and matches electron-workspace. Electron 43's Node also fixes the onclose-never-fires WebSocket quirk (SDK connect now rejects with 1006 instantly; the setup timeouts remain as a safety net). Verified: typecheck (TS 7), build, live smoke under Electron 43 (hello → error state → reconnect loop), N-API addon loads, packaging dry run + packaged addon load. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When the last workspace connection drops, a linger timer (workspaceLingerSec, default 30s) suspends the session — SIP unregister + SDK socket close — so calls stop routing to an unattended device; the tray goes back to 'Waiting for workspace'. A page reload reconnects well within the window, so the registration doesn't flap. An active call postpones the suspend until it ends; the next hello brings the session back up. Standalone dev runs (config.dev.json) are exempt — they have no workspace connections by design. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The icon was copied from electron-workspace where it is a call-popup asset (256x256), not a tray icon — it rendered huge in the macOS menu bar. Now matches the other 32x32 tray state icons. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…port cycle The endpoint const moves to websocket/endpoint.ts. The cycle was runtime-safe (all cross-references were lazy), but one future top-level access to endpoint from the softphone module would have hit the TDZ. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.