feat(storage): persist siprouter data in smartdata and smartbucket
This commit is contained in:
+100
-72
@@ -8,7 +8,7 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { loadConfig, type IAppConfig } from './config.ts';
|
||||
import { applyConfigUpdates, type IAppConfig } from './config.ts';
|
||||
import { FaxBoxManager } from './faxbox.ts';
|
||||
import { FaxJobManager } from './faxjobs.ts';
|
||||
import { broadcastWs, initWebUi } from './frontend.ts';
|
||||
@@ -27,24 +27,21 @@ import {
|
||||
} from './proxybridge.ts';
|
||||
import { registerProxyEventHandlers } from './runtime/proxy-events.ts';
|
||||
import { StatusStore } from './runtime/status-store.ts';
|
||||
import { SiprouterStorage } from './storage.ts';
|
||||
import { WebRtcLinkManager, type IProviderMediaInfo } from './runtime/webrtc-linking.ts';
|
||||
|
||||
let appConfig: IAppConfig = loadConfig();
|
||||
let appConfig: IAppConfig;
|
||||
|
||||
const LOG_PATH = path.join(process.cwd(), 'sip_trace.log');
|
||||
const startTime = Date.now();
|
||||
const instanceId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
|
||||
const statusStore = new StatusStore(appConfig);
|
||||
const webRtcLinks = new WebRtcLinkManager();
|
||||
const faxBoxManager = new FaxBoxManager(log);
|
||||
const faxJobManager = new FaxJobManager(log);
|
||||
const voiceboxManager = new VoiceboxManager(log);
|
||||
|
||||
faxBoxManager.init(appConfig.faxboxes ?? []);
|
||||
faxJobManager.init();
|
||||
voiceboxManager.init(appConfig.voiceboxes ?? []);
|
||||
initWebRtcSignaling({ log });
|
||||
const storage = new SiprouterStorage(log);
|
||||
let statusStore: StatusStore;
|
||||
let webRtcLinks: WebRtcLinkManager;
|
||||
let faxBoxManager: FaxBoxManager;
|
||||
let faxJobManager: FaxJobManager;
|
||||
let voiceboxManager: VoiceboxManager;
|
||||
|
||||
function now(): string {
|
||||
return new Date().toISOString().replace('T', ' ').slice(0, 19);
|
||||
@@ -96,12 +93,12 @@ async function configureRuntime(config: IAppConfig): Promise<boolean> {
|
||||
async function reloadConfig(): Promise<void> {
|
||||
try {
|
||||
const previousConfig = appConfig;
|
||||
const nextConfig = loadConfig();
|
||||
const nextConfig = await storage.getAppConfig();
|
||||
|
||||
appConfig = nextConfig;
|
||||
statusStore.updateConfig(nextConfig);
|
||||
faxBoxManager.init(nextConfig.faxboxes ?? []);
|
||||
voiceboxManager.init(nextConfig.voiceboxes ?? []);
|
||||
await faxBoxManager.init(nextConfig.faxboxes ?? []);
|
||||
await voiceboxManager.init(nextConfig.voiceboxes ?? []);
|
||||
|
||||
if (nextConfig.proxy.lanPort !== previousConfig.proxy.lanPort) {
|
||||
log('[config] proxy.lanPort changed; restart required for SIP socket rebinding');
|
||||
@@ -121,6 +118,13 @@ async function reloadConfig(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateConfig(updatesArg: any): Promise<IAppConfig> {
|
||||
const nextConfig = applyConfigUpdates(appConfig, updatesArg);
|
||||
await storage.writeAppConfig(nextConfig);
|
||||
await reloadConfig();
|
||||
return appConfig;
|
||||
}
|
||||
|
||||
async function startProxyEngine(): Promise<void> {
|
||||
const started = await initProxyEngine(log);
|
||||
if (!started) {
|
||||
@@ -155,77 +159,101 @@ async function startProxyEngine(): Promise<void> {
|
||||
log(`proxy engine started | LAN ${appConfig.proxy.lanIp}:${appConfig.proxy.lanPort} | providers: ${providerList} | devices: ${deviceList}`);
|
||||
}
|
||||
|
||||
initWebUi({
|
||||
port: appConfig.proxy.webUiPort,
|
||||
getStatus,
|
||||
log,
|
||||
onStartCall: async (number, deviceId, providerId) => {
|
||||
log(`[dashboard] start call: ${number} device=${deviceId || 'any'} provider=${providerId || 'auto'}`);
|
||||
const callId = await makeCall(number, deviceId, providerId);
|
||||
if (!callId) {
|
||||
log(`[dashboard] call failed for ${number}`);
|
||||
return null;
|
||||
}
|
||||
log(`[dashboard] call started: ${callId}`);
|
||||
statusStore.noteDashboardCallStarted(callId, number, providerId);
|
||||
return { id: callId };
|
||||
},
|
||||
onHangupCall: (callId) => {
|
||||
void hangupCall(callId);
|
||||
return true;
|
||||
},
|
||||
onConfigSaved: reloadConfig,
|
||||
faxBoxManager,
|
||||
faxJobManager,
|
||||
voiceboxManager,
|
||||
onWebRtcOffer: async (sessionId, sdp, ws) => {
|
||||
log(`[webrtc] offer from browser session=${sessionId.slice(0, 8)} sdp_type=${typeof sdp} sdp_len=${sdp?.length || 0}`);
|
||||
if (!sdp || typeof sdp !== 'string' || sdp.length < 10) {
|
||||
log(`[webrtc] WARNING: invalid SDP (type=${typeof sdp}), skipping offer`);
|
||||
return;
|
||||
}
|
||||
async function main(): Promise<void> {
|
||||
await storage.init();
|
||||
appConfig = await storage.getAppConfig();
|
||||
|
||||
log(`[webrtc] sending offer to Rust (${sdp.length}b)...`);
|
||||
const result = await webrtcOffer(sessionId, sdp);
|
||||
log(`[webrtc] Rust result: ${JSON.stringify(result)?.slice(0, 200)}`);
|
||||
if (result?.sdp) {
|
||||
ws.send(JSON.stringify({ type: 'webrtc-answer', sessionId, sdp: result.sdp }));
|
||||
log(`[webrtc] answer sent to browser session=${sessionId.slice(0, 8)}`);
|
||||
return;
|
||||
}
|
||||
statusStore = new StatusStore(appConfig);
|
||||
webRtcLinks = new WebRtcLinkManager();
|
||||
faxBoxManager = new FaxBoxManager(log, storage);
|
||||
faxJobManager = new FaxJobManager(log, storage);
|
||||
voiceboxManager = new VoiceboxManager(log, storage);
|
||||
|
||||
log('[webrtc] ERROR: no answer SDP from Rust');
|
||||
},
|
||||
onWebRtcIce: async (sessionId, candidate) => {
|
||||
await webrtcIce(sessionId, candidate as Parameters<typeof webrtcIce>[1]);
|
||||
},
|
||||
onWebRtcClose: async (sessionId) => {
|
||||
webRtcLinks.removeSession(sessionId);
|
||||
await webrtcClose(sessionId);
|
||||
},
|
||||
onWebRtcAccept: (callId, sessionId) => {
|
||||
log(`[webrtc] accept: callId=${callId} sessionId=${sessionId.slice(0, 8)}`);
|
||||
await faxBoxManager.init(appConfig.faxboxes ?? []);
|
||||
await faxJobManager.init();
|
||||
await voiceboxManager.init(appConfig.voiceboxes ?? []);
|
||||
initWebRtcSignaling({ log });
|
||||
|
||||
const pendingMedia = webRtcLinks.acceptCall(callId, sessionId);
|
||||
if (pendingMedia) {
|
||||
requestWebRtcLink(callId, sessionId, pendingMedia);
|
||||
return;
|
||||
}
|
||||
initWebUi({
|
||||
port: appConfig.proxy.webUiPort,
|
||||
getStatus,
|
||||
getConfig: () => appConfig,
|
||||
updateConfig,
|
||||
log,
|
||||
onStartCall: async (number, deviceId, providerId) => {
|
||||
log(`[dashboard] start call: ${number} device=${deviceId || 'any'} provider=${providerId || 'auto'}`);
|
||||
const callId = await makeCall(number, deviceId, providerId);
|
||||
if (!callId) {
|
||||
log(`[dashboard] call failed for ${number}`);
|
||||
return null;
|
||||
}
|
||||
log(`[dashboard] call started: ${callId}`);
|
||||
statusStore.noteDashboardCallStarted(callId, number, providerId);
|
||||
return { id: callId };
|
||||
},
|
||||
onHangupCall: (callId) => {
|
||||
void hangupCall(callId);
|
||||
return true;
|
||||
},
|
||||
faxBoxManager,
|
||||
faxJobManager,
|
||||
voiceboxManager,
|
||||
onWebRtcOffer: async (sessionId, sdp, ws) => {
|
||||
log(`[webrtc] offer from browser session=${sessionId.slice(0, 8)} sdp_type=${typeof sdp} sdp_len=${sdp?.length || 0}`);
|
||||
if (!sdp || typeof sdp !== 'string' || sdp.length < 10) {
|
||||
log(`[webrtc] WARNING: invalid SDP (type=${typeof sdp}), skipping offer`);
|
||||
return;
|
||||
}
|
||||
|
||||
log(`[webrtc] session ${sessionId.slice(0, 8)} accepted, waiting for call_answered media info`);
|
||||
},
|
||||
log(`[webrtc] sending offer to Rust (${sdp.length}b)...`);
|
||||
const result = await webrtcOffer(sessionId, sdp);
|
||||
log(`[webrtc] Rust result: ${JSON.stringify(result)?.slice(0, 200)}`);
|
||||
if (result?.sdp) {
|
||||
ws.send(JSON.stringify({ type: 'webrtc-answer', sessionId, sdp: result.sdp }));
|
||||
log(`[webrtc] answer sent to browser session=${sessionId.slice(0, 8)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
log('[webrtc] ERROR: no answer SDP from Rust');
|
||||
},
|
||||
onWebRtcIce: async (sessionId, candidate) => {
|
||||
await webrtcIce(sessionId, candidate as Parameters<typeof webrtcIce>[1]);
|
||||
},
|
||||
onWebRtcClose: async (sessionId) => {
|
||||
webRtcLinks.removeSession(sessionId);
|
||||
await webrtcClose(sessionId);
|
||||
},
|
||||
onWebRtcAccept: (callId, sessionId) => {
|
||||
log(`[webrtc] accept: callId=${callId} sessionId=${sessionId.slice(0, 8)}`);
|
||||
|
||||
const pendingMedia = webRtcLinks.acceptCall(callId, sessionId);
|
||||
if (pendingMedia) {
|
||||
requestWebRtcLink(callId, sessionId, pendingMedia);
|
||||
return;
|
||||
}
|
||||
|
||||
log(`[webrtc] session ${sessionId.slice(0, 8)} accepted, waiting for call_answered media info`);
|
||||
},
|
||||
});
|
||||
|
||||
await startProxyEngine();
|
||||
}
|
||||
|
||||
void main().catch((error) => {
|
||||
log(`[FATAL] ${errorMessage(error)}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
void startProxyEngine();
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
log('SIGINT, exiting');
|
||||
shutdownProxyEngine();
|
||||
void storage.close();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
log('SIGTERM, exiting');
|
||||
shutdownProxyEngine();
|
||||
void storage.close();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user