import { resolveDialer } from '../../utils/telephony/dialAccess' import { startCall, normalizePhoneNumber } from '../../utils/telephony/callTracker' import { cleanExtension } from '../../utils/telephony/dialAccess' /** * POST /api/telephony/dial { number, targetUserId?, targetPartnerId?, targetLabel? } * * Originates a click-to-dial call for the logged-in staff user (must be a * system user with a SIP_Extension — server-authoritative check). Returns as * soon as Asterisk has ACCEPTED the originate; the call itself is followed by a * detached tracker (callTracker.ts) that writes the activity when it ends. * * Always answers with { status, message?, callId? } (HTTP 200) so the client * can show the message — except auth failures, which are real 401/403. */ const BLOCKED = new Set(['110', '112', '911']) export default defineEventHandler(async (event) => { const config = useRuntimeConfig() if ((config as any).asterisk?.enabled !== true) { return { status: 503, message: 'Click-to-Dial ist deaktiviert' } } const dialer = await resolveDialer(event) if (!dialer) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }) if (!dialer.canDial) throw createError({ statusCode: 403, statusMessage: 'Keine SIP-Nebenstelle für diesen Benutzer' }) const body: any = await readBody(event).catch(() => ({})) const number = normalizePhoneNumber(body?.number, (config as any).asterisk?.allowFeatureCodes === true) if (!number) return { status: 400, message: 'Ungültige Telefonnummer' } if (BLOCKED.has(number)) return { status: 400, message: 'Notrufnummern können nicht über Click-to-Dial gewählt werden' } const toId = (v: any) => { const n = parseInt(String(v ?? ''), 10); return Number.isFinite(n) && n > 0 ? n : null } const orgId = Number(getCookie(event, 'logship_organization_id') || 0) || dialer.orgId // Optional per-call extension override (chooser in the dial popover): digits // only, 2–6 long. It may be a local device OR an extension the dialplan routes // elsewhere (e.g. 2XX → the remote FreePBX via trunk) — the tracker picks // PJSIP/ for known devices and Local/@from-internal otherwise. let extension = dialer.extension const override = cleanExtension(body?.extension) if (override && override !== dialer.extension) { if (!/^\d{2,6}$/.test(override)) return { status: 400, message: `Ungültige Nebenstelle ${override}` } extension = override } try { const { callId } = await startCall({ callerUserId: dialer.userId, callerName: dialer.name, callerExtension: extension, orgId, number, targetUserId: toId(body?.targetUserId), targetPartnerId: toId(body?.targetPartnerId), targetLabel: String(body?.targetLabel || '').trim().substring(0, 120) }) return { status: 200, callId, extension, number } } catch (err: any) { return { status: Number(err?.statusCode || 500), message: err?.statusMessage || err?.message || 'Anruf fehlgeschlagen' } } })