Files
siprouter/ts/faxjobs.ts
T

154 lines
4.0 KiB
TypeScript
Raw Permalink Normal View History

import fs from 'node:fs';
import path from 'node:path';
import type {
IFaxCompletedEvent,
IFaxFailedEvent,
IFaxStartedEvent,
} from './shared/proxy-events.ts';
export interface IFaxJob {
id: string;
callId: string;
number?: string;
providerId?: string;
direction: 'outbound' | 'inbound';
status: 'dialing' | 'started' | 'completed' | 'failed';
transport?: 'audio' | 't38';
filePath?: string;
codec?: string;
remoteMedia?: string;
success?: boolean;
completionCode?: number | null;
completionLabel?: string | null;
error?: string;
stats?: IFaxCompletedEvent['stats'];
createdAt: number;
updatedAt: number;
}
export class FaxJobManager {
private readonly basePath: string;
private readonly jobsPath: string;
private readonly log: (msg: string) => void;
constructor(log: (msg: string) => void) {
this.basePath = path.join(process.cwd(), '.nogit', 'fax');
this.jobsPath = path.join(this.basePath, 'jobs.json');
this.log = log;
}
init(): void {
fs.mkdirSync(this.basePath, { recursive: true });
if (!fs.existsSync(this.jobsPath)) {
this.writeJobs([]);
}
}
noteDialing(callId: string, number: string, providerId: string): void {
const jobs = this.loadJobs();
const now = Date.now();
const existing = jobs.find((job) => job.callId === callId);
if (existing) {
existing.number = number;
existing.providerId = providerId;
existing.updatedAt = now;
} else {
jobs.unshift({
id: callId,
callId,
number,
providerId,
direction: 'outbound',
status: 'dialing',
createdAt: now,
updatedAt: now,
});
}
this.writeJobs(jobs);
}
noteStarted(event: IFaxStartedEvent): void {
const jobs = this.loadJobs();
const now = Date.now();
const job = this.getOrCreateJob(jobs, event.call_id, event.direction, now);
job.status = 'started';
job.transport = event.transport;
job.filePath = event.file_path;
job.codec = event.codec;
job.remoteMedia = event.remote_media;
job.updatedAt = now;
this.writeJobs(jobs);
}
noteCompleted(event: IFaxCompletedEvent): void {
const jobs = this.loadJobs();
const now = Date.now();
const job = this.getOrCreateJob(jobs, event.call_id, event.direction, now);
job.status = 'completed';
job.transport = event.transport;
job.filePath = event.file_path;
job.codec = event.codec;
job.success = event.success;
job.completionCode = event.completion_code ?? null;
job.completionLabel = event.completion_label ?? null;
job.stats = event.stats;
job.updatedAt = now;
this.writeJobs(jobs);
}
noteFailed(event: IFaxFailedEvent): void {
const jobs = this.loadJobs();
const now = Date.now();
const job = this.getOrCreateJob(jobs, event.call_id, event.direction, now);
job.status = 'failed';
job.transport = event.transport;
job.filePath = event.file_path;
job.error = event.error;
job.success = false;
job.updatedAt = now;
this.writeJobs(jobs);
}
getJobs(): IFaxJob[] {
return this.loadJobs();
}
private getOrCreateJob(
jobs: IFaxJob[],
callId: string,
direction: 'outbound' | 'inbound',
now: number,
): IFaxJob {
let job = jobs.find((entry) => entry.callId === callId);
if (!job) {
job = {
id: callId,
callId,
direction,
status: 'dialing',
createdAt: now,
updatedAt: now,
};
jobs.unshift(job);
}
return job;
}
private loadJobs(): IFaxJob[] {
try {
const content = fs.readFileSync(this.jobsPath, 'utf8');
const parsed = JSON.parse(content);
return Array.isArray(parsed) ? parsed as IFaxJob[] : [];
} catch {
return [];
}
}
private writeJobs(jobs: IFaxJob[]): void {
fs.mkdirSync(this.basePath, { recursive: true });
fs.writeFileSync(this.jobsPath, JSON.stringify(jobs, null, 2));
this.log(`[fax] persisted ${jobs.length} job(s)`);
}
}