Commit Graph
24 Commits
Author SHA1 Message Date
iamdoubz 6f87d6f663 api: spam check settings, lookups and cache
SD1 (14 §9): the spam/lookups tables and calls columns, the
IPQualityScore and FTC lookups with the cache, the spam settings with
write-only keys, the provider Test routes and Settings → Spam check.
2026-10-27 15:32:31 -05:00
iamdoubz a3dfcb6b14 docs: spam check spec
SD1 (spec 14, decision 0006): IPQualityScore first, then the FTC Do Not
Call complaint list, for outside US callers who aren't in anyone's
contacts. Information only, off by default; credits running out pause
IPQualityScore until its credit usage API says they're back.
2026-10-27 13:51:08 -05:00
iamdoubz 0cdac0bcdc api: pair the mobile app with device tokens
MB1 (spec 13). Decision 0005: our own Android/iPhone app, Expo in the
mobile repo, user portal only, no push in v1.

- mobile_devices (migration 0013): one-time pairing codes from My settings
  (QR or server + code, 10 minutes), traded by the app for an spm_ device
  token that acts as its user and never as an admin
- POST /api/me/mobile-devices/pair (web session only), POST /api/mobile/pair,
  GET/DELETE /api/me/mobile-devices
- signing out everywhere (password change or reset, disabling) removes
  paired phones too
- My settings: Mobile app section with the QR code and paired phones
- mobile/ (its own repo) ignored here
2026-10-26 17:12:53 -05:00
iamdoubz ba293f8703 spec: single sign-on (12), OpenID Connect and LDAP
Owner decisions 2026-10-25: a user connects their OpenID account and signs in with either it or
their password; the admin picks passwords only, OpenID only (admins keep a password way in), or
mixed. No automatic accounts. OIDC sessions re-checked every 15 min with the refresh token;
signing out also signs out at the provider; password sign-ins keep two-step. LDAP is one more way
to check a password. Decision 0004: openid-client and ldapts. Tasks S1-S5.
2026-10-25 13:09:45 -05:00
iamdoubz 32f4590369 spec: contacts (11), Simple or Contrack, picked at setup
Owner decisions 2026-10-23: vCard import for admins and users; desk-phone caller name while
ringing is its own task (C4); the company is one Contrack account shown to everyone, each user
has their own; sharing offers a copy the colleague accepts or denies; imports skip duplicates.
2026-10-23 14:53:21 -05:00
iamdoubz 274242ce32 roadmap: a backlog, the HT802 V2 setup first
ci / check (push) Successful in 1m0s
ci / images (push) Successful in 26s
2026-10-21 17:57:32 -05:00
iamdoubz 0deff2a637 dial plan: made from routes and number lengths; Yealink dial-now, the ATA plan on the Credentials tab 2026-10-21 17:14:13 -05:00
iamdoubz eacc571093 docs: analog phones (ATAs) set up by hand; the ATA plan in the roadmap
ci / check (push) Successful in 57s
ci / images (push) Successful in 30s
2026-10-21 17:01:42 -05:00
iamdoubz 4d9b5dab8a update: go back to the running release; docs for the first update from v0.0.2 2026-10-21 11:25:46 -05:00
iamdoubz 3c8828eedb docs: simpler README with the logo; ready-for-v1 checklist
ci / check (push) Successful in 58s
ci / images (push) Successful in 4s
release / images (release) Successful in 34s
2026-10-21 07:42:33 -05:00
iamdoubz 0a2264d8af release: version 0.0.2; note more SIP providers (Telnyx, Flowroute, Twilio) as next
ci / check (push) Successful in 1m26s
ci / images (push) Successful in 2m59s
2026-10-21 07:38:17 -05:00
iamdoubz a6ba61756c docs: no update.sh until v1
ci / check (push) Successful in 58s
ci / images (push) Successful in 1m38s
Nothing has shipped, so there is nothing to update (the script itself was removed in
a152508). The v1.0 definition of done now requires update.sh written, reviewed and
tested on a real server; the admin guide says there are no releases yet.

Task: M11-T7
2026-10-20 09:58:53 -05:00
iamdoubz 62939f0138 live: listen, whisper and barge on calls in progress
ci / check (push) Successful in 54s
ci / images (push) Successful in 23s
POST /api/live/calls/:linkedid/spy {mode} per 04 §7.3: rings the
admin's desk phone (409 if it isn't connected, 400 without an
extension) and, when they answer, runs ChanSpy on the call's first
user leg (never a trunk or Local channel, never the admin's own),
which for an inbound or queue call is the agent. Options qE / qEw /
qEB: no beep or name, the spy leg ends with the call, whisper to that
user only, or barge (both sides hear). Audited as call_spy.

Dashboard: Listen / Whisper / Barge on talking calls with a user on
them, and "your desk phone is ringing" once sent. The spy leg shows as
its own call; its Hang up stops listening.

e2e m10-t4: 380 calls 381, the admin's 382 runs ChanSpy with each
mode's options, and the barge leg ends with the call. The roadmap
records that M10's milestone check (real Yealink/Grandstream, Linphone
scanning the QR) waits for hardware the owner doesn't have yet.

Task: M10-T4
2026-10-19 14:30:48 -05:00
iamdoubz dc79120de0 roadmap: park SMS (M9-T2, M9-T3) until the owner wants it
ci / check (push) Successful in 53s
ci / images (push) Successful in 20s
The owner's voip.ms account has no SMS. M9-T1 (API client, number
import, server list) stays: it doesn't need SMS. Its check against a
real account (API enabled, this server's IP allowed) is still open.
Nothing in later milestones depends on SMS.

Task: M9-T1
2026-10-19 13:21:59 -05:00
iamdoubz 4742ed52ea queues: service level counts callers who weren't answered
ci / check (push) Successful in 1m4s
ci / images (push) Successful in 19s
withinSlaPercent was "answered within sla_seconds / answered", so a
queue where a caller timed out still showed 100%. It is now the usual
service level: answered within sla_seconds out of every caller the
queue is done with, leaving out those who hung up before sla_seconds
(nobody could have answered them in time). Timed out and turned away
count as missed; callers still waiting aren't counted. Written into
04; the page label says "Callers answered within N s".

Real calls: m8-t1 now shows 50% (1 answered in time, 1 timed out, the
early hang-up left out) and the milestone check 67% (2 of 3), and
asserts it. The check moves to queue 552 (*45552) so it doesn't clash
with m8-t1's 550 on a shared database.

Task: M8-T1
2026-10-19 12:13:39 -05:00
iamdoubz 088372bff8 e2e: M8 milestone check
ci / images (push) Successful in 19s
ci / check (push) Successful in 1m6s
scripts/e2e/m8-check.mjs, the scenario from 09 M8 with real calls from
a DID: queue 550 with agents 340 and 341, max wait 30 s, then 342. 340
pauses with *45550 and 341 is paused by an admin, so three callers
wait. 341 comes back and gets the longest waiting caller, parks them
on 701 (blind transfer to 700) and 343 picks up by dialing 701; 341
takes the next caller and the last one rings 342 after 30 s. Stats:
3 offered, 2 answered, 1 timed out.

Roadmap marks the M8 milestone check done; the e2e README describes
m8-t4, m8-t5 and m8-check.

Task: M8-check
2026-10-19 11:26:54 -05:00
iamdoubz e95271e694 paging: paging groups, *80 intercom, auto-answer headers
ci / check (push) Successful in 1m7s
ci / images (push) Successful in 3m20s
/api/paging-groups CRUD (members as memberIds). The generator renders
sp-page-{id} per 03 §6.11 (desk phones only, auto-answer headers via
sp-sub-autoanswer) plus the number in sp-local-numbers. Added the `s`
option so phones already on a call aren't paged, which also keeps a
pager who is a member from being paged themselves. A group with no
members just hangs up.

*80<ext> intercom checks the target first: digits only and an endpoint
in sp-from-ext, else ss-noservice (the spec's one-liner passed
${EXTEN:3} straight into Page()). Both written back into 03.

Web: Paging page (list, drawer form with member checkboxes). Golden
case paging. e2e m8-t4 with baresip phones that honor the headers
(sip_autoanswer): an ordinary call rings, a page is answered by itself
and muted, the busy pager is skipped, *80 is two-way, *80999 refused.
The check on a real Yealink/Grandstream stays open (09).

Task: M8-T4
2026-10-19 09:40:22 -05:00
iamdoubz c7fd4df77c 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
2026-10-18 09:43:41 -05:00
iamdoubz ae3a723a3c softphone: SIP.js engine, /api/me/webrtc, minimal dock
GET /api/me/webrtc returns the <ext>-web identity, WebSocket URL
(WEBRTC_WS_URL overrides wss://PUBLIC_HOSTNAME/ws) and STUN/TURN servers.
SoftphoneEngine wraps SIP.js 0.21.2: register with backoff reconnect,
call, answer, decline, hang up, remote audio. A minimal dock on /me
enables it (asks for the microphone on the click) and dials/answers;
M5-T2 grows it into the full panel.

Generator: ice_host_candidates keeps the LAN address
(include_local_address), so LAN browsers don't need hairpin NAT, and -web
endpoints set from_domain = selfphoneng.invalid because SIP.js drops
INVITEs whose From host starts with a digit (Docker/VPS hostnames).

e2e m5-t1 (Docker): browser registers as 170-web and has two-way audio
with a baresip phone both ways. The LAN and hotspot check on the owner's
server is still open (roadmap note).

Task: M5-T1
2026-10-18 09:27:45 -05:00
iamdoubz 1e50d12c6e docs: track open real voip.ms acceptance checks for M3
Task: M3-T1..T3
2026-10-17 22:07:42 -05:00
iamdoubz c2b9fff126 setup: first-run wizard, telephony restart, system status
ci / check (push) Canceled after 0s
ci / images (push) Canceled after 0s
/setup walks a fresh install to working calls: password (done by the
forced change), company, network (detect public IP, LAN IP from
/api/system/status, router checklist; restarts telephony because the
transports changed), email (skippable), voip.ms trunk (Next unlocks on
Registered), phone numbers, users (extension auto-numbered, temporary
passwords shown once), main number routing, emergency (default E911
caller ID required once a trunk exists), done. Steps 7-8 ship now
because the acceptance needs a user and a routed number; their M6
choices (everyone, IVR) come later. Admins land on /setup until it is
finished or skipped.

api: /api/setup/status and /complete, POST
/api/settings/network/detect-public-ip (ipify, then ifconfig.me),
GET /api/system/status (M3 subset), POST /api/system/asterisk/restart
(an AMI disconnect during a restart counts as success).
restartRequired clears on AMI connect once Asterisk's uptime shows it
started after the change. The first phone number becomes the default
outbound caller ID when none is set (found by the acceptance run:
calls otherwise left with the provider's caller ID).

Verified: scripts/e2e/m3-t5.mjs on a fresh database against the fake
voip.ms: wizard end to end, then an inbound call to the DID rings the
new user's softphone and an outbound call leaves as 13125550000 with
the DID in Remote-Party-ID. m1-t4 now passes through "Skip setup".

Task: M3-T5
2026-10-17 21:11:04 -05:00
iamdoubz c0c4036887 live: AMI-fed live state, /api/live WebSocket, dashboard and status dots
ci / check (push) Canceled after 0s
ci / images (push) Canceled after 0s
LiveState loads extension states and registered devices from Asterisk on
every AMI connect and follows ExtensionStatus/ContactStatus; the hub sends
a snapshot then deltas batched every 250 ms (devices for admins only),
pings every 30 s and closes unauthenticated sockets with 4401. The web UI
keeps one reconnecting socket in a zustand store, shows a Reconnecting
banner, a Dashboard (online, on a call, telephony engine) and status dots.

Fix: Asterisk 24 has no 'pjsip reload'; reload with
'module reload res_pjsip.so'. AMI errors now carry the CLI output.

Milestone check, automated: call-test.ts registers two baresip phones and
holds a 30 s call with RTP both ways (pnpm test:asterisk); the recorded
session drives the live-state tests. scripts/e2e/m2-live.mjs runs api +
Asterisk + phones + browser on a private Docker network: a new extension
is live in Asterisk in ~1.3 s, the dashboard shows 2/2 online and red dots
during the call.

Task: M2-T6
2026-10-17 16:49:25 -05:00
iamdoubz 909ac2d62d asterisk: 24.0.0-rc2 image built from source with static config
Adds stub configs and noloads so a clean boot logs no ERROR lines,
ships stock indications.conf, seeds default MOH from the entrypoint.
Spec 01 synced with the real files.

Task: M0-T5
2026-10-17 14:31:05 -05:00
iamdoubz 547bb68a6f docs: initial SelfPhoneNG spec
Fix leftover __OP_* dialplan variables from the openphone rename (all
readers use SP_*). Record the reverse-proxy decision in 00 §10.
2026-10-17 14:11:09 -05:00