softphone: dock panel, incoming dialog, hold/mute/DTMF, transfers, one tab
The dock opens a panel with a 12-key dialpad (typing, paste normalization, Enter) and microphone/speaker selection. Call cards have mute, hold, keypad (DTMF) and transfer: "Transfer now" (blind) or "Talk first" (attended, with Complete/Cancel). An incoming call opens a dialog when idle, or a card during a call (call waiting holds the first call). A synthesized WebAudio ringtone and desktop notifications, auto-answer for Call-Info answer-after=0, and a poor-connection warning from getStats. One tab registers: a Web Lock plus a BroadcastChannel "Use here". Found by e2e: Chrome can report canInsertDTMF=false, so DTMF falls back to SIP INFO; after a REFER's 2xx NOTIFY the browser sends BYE itself (RFC 5589), otherwise SIP.js keeps the call up. apps/web/TESTING.md is the 12-step manual run for Chrome and Firefox (still open, roadmap note); e2e m5-t2 automates most of it in headless Edge. Task: M5-T2
This commit is contained in:
1 parent
ae3a723a3c
commit
c7fd4df77c
12 files changed
+1035
-94
No files matched your search
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// baresip "desk phones" for the e2e scripts: one container, one baresip per phone.
|
||||
// Each phone auto-answers (or not) and "talks" with a sine tone; its log shows MWI and DTMF.
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const docker = (...a) => {
|
||||
const r = spawnSync('docker', a, { encoding: 'utf8' });
|
||||
return { ok: r.status === 0, out: `${r.stdout ?? ''}${r.stderr ?? ''}` };
|
||||
};
|
||||
|
||||
/**
|
||||
* phones: [{ ext, password, answer: 'auto' | 'manual', toneHz = 440 }]
|
||||
* Returns { log(ext) → text so far, stop() }.
|
||||
*/
|
||||
export function startPhones({ name, net, ast, phones }) {
|
||||
const dir = mkdtempSync(join(tmpdir(), `${name}-`));
|
||||
phones.forEach((p, i) => {
|
||||
const d = join(dir, p.ext);
|
||||
mkdirSync(d);
|
||||
writeFileSync(
|
||||
join(d, 'config'),
|
||||
[
|
||||
`sip_listen 0.0.0.0:${5080 + 2 * i}`,
|
||||
'module_path /usr/lib/baresip/modules',
|
||||
...['g711', 'ausine', 'aufile', 'menu', 'account', 'mwi'].map((m) => `module ${m}.so`),
|
||||
`audio_source ausine,${p.toneHz ?? 440}`,
|
||||
'ausrc_srate 48000',
|
||||
'auplay_srate 48000',
|
||||
'audio_player aufile,/dev/null',
|
||||
'audio_alert aufile,/dev/null',
|
||||
`rtp_ports ${30000 + 100 * i}-${30099 + 100 * i}`,
|
||||
'',
|
||||
].join('\n'),
|
||||
);
|
||||
writeFileSync(
|
||||
join(d, 'accounts'),
|
||||
`<sip:${p.ext}@${ast};transport=udp>;auth_pass=${p.password};answermode=${p.answer};audio_codecs=PCMU\n`,
|
||||
);
|
||||
});
|
||||
const run = phones.map((p) => `(baresip -f /phones/${p.ext} > /phones/${p.ext}/log 2>&1 &)`);
|
||||
docker('rm', '-f', name);
|
||||
const r = docker(
|
||||
'run',
|
||||
'-d',
|
||||
'--rm',
|
||||
'--name',
|
||||
name,
|
||||
'--network',
|
||||
net,
|
||||
'-v',
|
||||
`${dir}:/phones`,
|
||||
'debian:trixie-slim',
|
||||
'sh',
|
||||
'-c',
|
||||
`apt-get update -qq >/dev/null && apt-get install -y -qq --no-install-recommends baresip-core >/dev/null 2>&1 && ${run.join(' && ')} && sleep 3600`,
|
||||
);
|
||||
if (!r.ok) throw new Error(`phones: ${r.out}`);
|
||||
return {
|
||||
log: (ext) => {
|
||||
const f = join(dir, ext, 'log');
|
||||
return existsSync(f) ? readFileSync(f, 'utf8') : '';
|
||||
},
|
||||
stop: () => docker('rm', '-f', name),
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user