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
This commit is contained in:
iamdoubz committed 2026-10-17 16:49:25 -05:00
1 parent 1cfc13ead3
commit c0c4036887
31 files changed
+4810 -28

No files matched your search

+126
View File
@@ -47,6 +47,9 @@ importers:
'@fastify/static':
specifier: ^10.1.6
version: 10.1.6
'@fastify/websocket':
specifier: ^11.3.4
version: 11.3.4
'@node-rs/argon2':
specifier: ^2.2.2
version: 2.2.2
@@ -69,6 +72,9 @@ importers:
'@types/pg':
specifier: ^8.23.1
version: 8.23.1
'@types/ws':
specifier: ^8.18.2
version: 8.18.2
drizzle-kit:
specifier: ^0.31.11
version: 0.31.11
@@ -99,6 +105,9 @@ importers:
zod:
specifier: ^4.6.7
version: 4.6.7
zustand:
specifier: ^5.0.15
version: 5.0.15(@types/react@19.3.0)(react@19.3.1)
devDependencies:
'@tailwindcss/vite':
specifier: ^4.3.3
@@ -727,6 +736,9 @@ packages:
'@fastify/static@10.1.6':
resolution: {integrity: sha512-f3x9XisI+y6AIhIfA/+Q3SFsuXeqNNk5HQh5htgHsPxmMLb4pLFskV6eFaLSW3IbI4azrS5np+/tkzXNH99LyQ==}
'@fastify/websocket@11.3.4':
resolution: {integrity: sha512-firnJtEOv6pfdoXIzFDMbd+omgWwFNfLtdM8JejNFr/9lT/9DHkEhjTShuRFeE4phqrevD2B3JRW0v6uyzWhHQ==}
'@hookform/resolvers@5.9.1':
resolution: {integrity: sha512-7b7vsbraJxKgjVSA1Nur9tLwj539WGJUBLA7QNvXnFoT2pM5Z7G+6rlukk4B2/QrTZy6huRtH6wKeESPKuIr6w==}
peerDependencies:
@@ -1185,6 +1197,9 @@ packages:
'@types/react@19.3.0':
resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==}
'@types/ws@8.18.2':
resolution: {integrity: sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==}
'@typescript-eslint/eslint-plugin@8.71.2':
resolution: {integrity: sha512-K72/pvT7qpm8q3k8G5u93NCSN1RcLxX5h7Gw0bQpbMXviBHeY6Nx1WEwmk9wP+eKTSQ8ecxvcgt/ZX/rB938xQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1611,9 +1626,15 @@ packages:
sqlite3:
optional: true
duplexify@4.1.3:
resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==}
electron-to-chromium@1.5.458:
resolution: {integrity: sha512-/hIH2gRSmxrf4/X4Nw/t/Wc8PVS22l8/3HnWjWTWsTOM5dsnMUPZF/+u+yr3LqweNuTBJE1rq6qAQzKkZVwMfA==}
end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
enhanced-resolve@5.26.0:
resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==}
engines: {node: '>=10.13.0'}
@@ -2078,6 +2099,9 @@ packages:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -2225,6 +2249,10 @@ packages:
resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==}
engines: {node: '>=0.10.0'}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
real-require@1.0.0:
resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==}
@@ -2251,6 +2279,9 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-regex2@5.1.2:
resolution: {integrity: sha512-tAtCGFPPP02VroDVzp4daGi6bC7jWG+41D47GKf18FeH/ubvKQHT9VmhAkvtjNOmJwqtApzWeUpEhkyEYULvmw==}
hasBin: true
@@ -2313,6 +2344,12 @@ packages:
std-env@4.3.0:
resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==}
stream-shift@1.0.3:
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
tailwindcss@4.3.3:
resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==}
@@ -2391,6 +2428,9 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
vite@8.3.4:
resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -2489,6 +2529,21 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@8.22.0:
resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
@@ -2509,6 +2564,24 @@ packages:
zod@4.6.7:
resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==}
zustand@5.0.15:
resolution: {integrity: sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
immer: '>=9.0.6'
react: '>=18.0.0'
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
snapshots:
'@babel/code-frame@7.29.7':
@@ -2941,6 +3014,15 @@ snapshots:
fastq: 1.20.3
glob: 13.0.6
'@fastify/websocket@11.3.4':
dependencies:
duplexify: 4.1.3
fastify-plugin: 6.0.1
ws: 8.22.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@hookform/resolvers@5.9.1(ajv@8.20.0)(react-hook-form@7.90.0(@types/react@19.3.0)(react@19.3.1))(zod@4.6.7)':
dependencies:
'@standard-schema/utils': 0.3.0
@@ -3215,6 +3297,10 @@ snapshots:
dependencies:
csstype: 3.2.3
'@types/ws@8.18.2':
dependencies:
'@types/node': 24.19.2
'@typescript-eslint/eslint-plugin@8.71.2(@typescript-eslint/parser@8.71.2(@typescript/typescript6@6.0.2)(eslint@10.13.0(jiti@2.7.0)))(@typescript/typescript6@6.0.2)(eslint@10.13.0(jiti@2.7.0))':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -3491,8 +3577,19 @@ snapshots:
'@types/pg': 8.23.1
pg: 8.23.1
duplexify@4.1.3:
dependencies:
end-of-stream: 1.4.5
inherits: 2.0.4
readable-stream: 3.6.2
stream-shift: 1.0.3
electron-to-chromium@1.5.458: {}
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
enhanced-resolve@5.26.0:
dependencies:
graceful-fs: 4.2.11
@@ -3981,6 +4078,10 @@ snapshots:
on-exit-leak-free@2.1.2: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -4117,6 +4218,12 @@ snapshots:
react@19.3.1: {}
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
real-require@1.0.0: {}
require-from-string@2.0.2: {}
@@ -4150,6 +4257,8 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.2.14
'@rolldown/binding-win32-x64-msvc': 1.2.14
safe-buffer@5.2.1: {}
safe-regex2@5.1.2:
dependencies:
ret: 0.5.0
@@ -4193,6 +4302,12 @@ snapshots:
std-env@4.3.0: {}
stream-shift@1.0.3: {}
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
tailwindcss@4.3.3: {}
tapable@2.3.3: {}
@@ -4279,6 +4394,8 @@ snapshots:
dependencies:
punycode: 2.3.1
util-deprecate@1.0.2: {}
vite@8.3.4(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15):
dependencies:
lightningcss: 1.33.0
@@ -4336,6 +4453,10 @@ snapshots:
word-wrap@1.2.5: {}
wrappy@1.0.2: {}
ws@8.22.0: {}
xtend@4.0.2: {}
yallist@3.1.1: {}
@@ -4347,3 +4468,8 @@ snapshots:
zod: 4.6.7
zod@4.6.7: {}
zustand@5.0.15(@types/react@19.3.0)(react@19.3.1):
optionalDependencies:
'@types/react': 19.3.0
react: 19.3.1