chahinebrini 822053e11e feat(calls): CallKit/ConnectionService + VoIP-PushKit + EU-Ringback
Caller/Callee UX:
- lib/ringback.ts + assets/sounds/ringback_eu.mp3 (EU 425Hz Festnetz-Tone)
- stores/call.ts: stopRingback bei connected, hangup-reasons, logCallToChat fix
- locales: 'Wird angerufen…' statt 'Ruft an…'

CallKit (iOS) + ConnectionService (Android):
- lib/callkit.ts: setupCallKeep, displayIncomingCall, startOutgoingCall, reportConnected/Ended (appName 'ReBreak-Audio', includesCallsInRecents=false für DSGVO/DiGA)
- hooks/useCallKeepEvents.ts: native answer/end/mute → useCallStore-Actions
- stores/call.ts: CallKit-Aufrufe an allen lifecycle-Punkten
- app.config.ts: @config-plugins/react-native-callkeep + UIBackgroundModes voip/audio + Android-Telecom-Perms

VoIP-PushKit Backend:
- services/voip-push.ts: @parse/node-apn Provider mit .p12 (Topic org.rebreak.app.voip)
- services/push.ts sendCallRingPush: feuert beide Pfade (VoIP iOS + Expo Android/Fallback)
- prisma: push_tokens.voip_token Column + Migration 20260604
- api/users/me/push-token: optional voipToken im Body
- Env (Infisical): APNS_VOIP_P12_PATH/PASSWORD/TOPIC/PRODUCTION

Push-tap routing + cold-start handling:
- app/_layout.tsx: type:'call' Push → useCallStore.receiveIncoming + /call

Docs: ops/CALLKIT_SETUP.md (Apple-Portal-Steps für VoIP-Cert)
2026-06-04 09:27:13 +02:00

67 lines
1.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { sendDirectMessage } from "../../db/chat";
/** POST /api/chat/dm Direktnachricht senden */
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
const body = await readBody(event);
const {
receiverId,
content,
replyToId,
attachmentUrl,
attachmentType,
attachmentName,
} = body as {
receiverId: string;
content: string;
replyToId?: string;
attachmentUrl?: string;
attachmentType?: string;
attachmentName?: string;
};
if (!receiverId || (!content?.trim() && !attachmentUrl && attachmentType !== 'call')) {
throw createError({
statusCode: 400,
message: "receiverId und content/Anhang erforderlich",
});
}
if (receiverId === user.id) {
throw createError({
statusCode: 400,
message: "Nachrichten an sich selbst nicht möglich",
});
}
if ((content ?? "").trim().length > 2000) {
throw createError({
statusCode: 400,
message: "Nachricht zu lang (max. 2000 Zeichen)",
});
}
const data = await sendDirectMessage(
user.id,
receiverId,
(content ?? "").trim(),
{
replyToId,
attachmentUrl,
attachmentType,
attachmentName,
},
);
return {
id: data.id,
content: data.content,
createdAt: data.createdAt,
isOwn: true,
readAt: null,
replyTo: data.replyTo,
attachmentUrl: data.attachmentUrl,
attachmentType: data.attachmentType,
attachmentName: data.attachmentName,
likesCount: data.likesCount,
};
});