KI-AGENT: Matrix durch nativen FEDEO-Chat ersetzen

This commit is contained in:
2026-09-08 22:42:40 +02:00
parent 47bd8e80e4
commit 358e40d749
47 changed files with 1027 additions and 9766 deletions

View File

@@ -6,7 +6,7 @@ import { HapticTab } from '@/components/haptic-tab';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Colors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';
import { fetchMatrixUnreadCounts } from '@/src/lib/api';
import { fetchChatUnreadCounts } from '@/src/lib/api';
import { useAuth } from '@/src/providers/auth-provider';
export default function TabLayout() {
@@ -22,7 +22,7 @@ export default function TabLayout() {
}
try {
const unread = await fetchMatrixUnreadCounts(token);
const unread = await fetchChatUnreadCounts(token);
const total = Object.values(unread).reduce((sum, room) => sum + (room.count || 0), 0);
setCommunicationUnread(total);
await Notifications.setBadgeCountAsync(total);

File diff suppressed because it is too large Load Diff

View File

@@ -259,27 +259,12 @@ export function isAuthenticationError(error: unknown): boolean {
return error instanceof ApiError && error.status === 401;
}
export type MatrixStatus = {
enabled?: boolean;
ready?: boolean;
configured?: boolean;
homeserverUrl?: string | null;
[key: string]: unknown;
};
export type MatrixIdentity = {
matrixUserId: string;
displayName?: string | null;
};
export type MatrixRoom = {
export type ChatRoom = {
key: string;
name: string;
topic?: string | null;
type?: 'room' | 'project' | 'direct' | string;
group?: string;
roomId?: string | null;
alias?: string | null;
exists?: boolean;
projectId?: number;
projectNumber?: string | null;
@@ -294,66 +279,31 @@ export type MatrixRoom = {
[key: string]: unknown;
};
export type MatrixAttachment = {
fileName?: string | null;
mimeType?: string | null;
size?: number | null;
mxcUri?: string | null;
previewUrl?: string | null;
downloadUrl?: string | null;
};
export type MatrixReaction = {
key: string;
count?: number;
own?: boolean;
senders?: string[];
[key: string]: unknown;
};
export type MatrixMessage = {
id: string;
export type ChatMessage = {
id: number;
sender: string;
senderDisplayName?: string | null;
body?: string | null;
timestamp?: string | number | null;
own?: boolean;
edited?: boolean;
redacted?: boolean;
msgtype?: string;
attachment?: MatrixAttachment | null;
replyToEventId?: string | null;
reactions?: MatrixReaction[];
[key: string]: unknown;
};
export type MatrixMember = {
matrixUserId: string;
displayName?: string | null;
avatarUrl?: string | null;
membership?: string;
[key: string]: unknown;
};
export type MatrixUser = {
export type ChatMember = {
userId: string;
matrixUserId: string;
displayName?: string | null;
email?: string | null;
own?: boolean;
[key: string]: unknown;
};
export type MatrixSyncResponse = {
nextBatch?: string;
messages?: MatrixMessage[];
replacements?: MatrixMessage[];
reactions?: (MatrixReaction & { targetEventId?: string })[];
redactions?: { redacts?: string; eventId?: string; targetEventId?: string }[];
members?: MatrixMember[];
export type ChatSyncResponse = {
nextId?: number;
messages?: ChatMessage[];
[key: string]: unknown;
};
export type MatrixUnreadCounts = Record<string, { count?: number; mentions?: number }>;
export type ChatUnreadCounts = Record<string, { count?: number; mentions?: number }>;
function buildUrl(path: string): string {
if (path.startsWith('http://') || path.startsWith('https://')) {
@@ -429,77 +379,26 @@ export async function apiRequest<T>(path: string, options: RequestOptions = {}):
return payload as T;
}
async function apiFormRequest<T>(path: string, token: string, formData: FormData): Promise<T> {
const { signal, cleanup } = createTimeoutSignal();
let response: Response;
try {
response = await fetch(buildUrl(path), {
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
},
body: formData,
signal,
});
} catch (error) {
if (isAbortError(error)) {
throw new Error(`Zeitüberschreitung beim Hochladen zum FEDEO-Server (${path}).`);
}
throw error;
} finally {
cleanup();
}
const payload = await parseJson(response);
if (!response.ok) {
const message =
(payload as { message?: string; error?: string } | null)?.message ||
(payload as { message?: string; error?: string } | null)?.error ||
`Request failed (${response.status}) for ${path}`;
throw new ApiError(message, response.status);
}
return payload as T;
}
function matrixRoomPath(roomKey: string, suffix = ''): string {
return `/api/communication/matrix/rooms/${encodeURIComponent(roomKey)}${suffix}`;
function chatRoomPath(roomKey: string, suffix = ''): string {
return `/api/communication/chat/rooms/${encodeURIComponent(roomKey)}${suffix}`;
}
export async function checkBackendHealth(): Promise<{ status: string; [key: string]: unknown }> {
return apiRequest<{ status: string; [key: string]: unknown }>('/health');
}
export async function fetchMatrixStatus(token: string): Promise<MatrixStatus> {
return apiRequest<MatrixStatus>('/api/communication/matrix/status', { token });
}
export async function fetchMatrixIdentity(token: string): Promise<MatrixIdentity> {
return apiRequest<MatrixIdentity>('/api/communication/matrix/me', { token });
}
export async function provisionMatrixUser(token: string): Promise<MatrixIdentity> {
return apiRequest<MatrixIdentity>('/api/communication/matrix/me/provision', {
method: 'POST',
token,
});
}
export async function fetchMatrixRooms(token: string): Promise<MatrixRoom[]> {
export async function fetchChatRooms(token: string): Promise<ChatRoom[]> {
const [rooms, projectRooms, directRooms, unread] = await Promise.all([
apiRequest<{ rooms?: MatrixRoom[] }>('/api/communication/matrix/rooms', { token }),
apiRequest<{ rooms?: MatrixRoom[] }>('/api/communication/matrix/project-rooms', { token }),
apiRequest<{ rooms?: MatrixRoom[] }>('/api/communication/matrix/direct-rooms', { token }),
apiRequest<{ rooms?: Record<string, { count?: number; mentions?: number }> }>('/api/communication/matrix/unread', {
apiRequest<{ rooms?: ChatRoom[] }>('/api/communication/chat/rooms', { token }),
apiRequest<{ rooms?: ChatRoom[] }>('/api/communication/chat/project-rooms', { token }),
apiRequest<{ rooms?: ChatRoom[] }>('/api/communication/chat/direct-rooms', { token }),
apiRequest<{ rooms?: ChatUnreadCounts }>('/api/communication/chat/unread', {
token,
}),
]);
const unreadByRoom = unread.rooms || {};
const decorate = (room: MatrixRoom, group: string): MatrixRoom => ({
const decorate = (room: ChatRoom, group: string): ChatRoom => ({
...room,
group,
unread: unreadByRoom[room.key]?.count || 0,
@@ -513,165 +412,70 @@ export async function fetchMatrixRooms(token: string): Promise<MatrixRoom[]> {
];
}
export async function fetchMatrixUnreadCounts(token: string): Promise<MatrixUnreadCounts> {
const response = await apiRequest<{ rooms?: MatrixUnreadCounts }>('/api/communication/matrix/unread', { token });
export async function fetchChatUnreadCounts(token: string): Promise<ChatUnreadCounts> {
const response = await apiRequest<{ rooms?: ChatUnreadCounts }>('/api/communication/chat/unread', { token });
return response.rooms || {};
}
export async function fetchMatrixUsers(token: string): Promise<MatrixUser[]> {
const response = await apiRequest<{ users?: MatrixUser[] }>('/api/communication/matrix/users', { token });
return response.users || [];
}
export async function createMatrixRoom(
export async function createChatRoom(
token: string,
payload: { key: string; name: string; topic?: string | null; type?: string }
): Promise<MatrixRoom> {
return apiRequest<MatrixRoom>('/api/communication/matrix/rooms', {
): Promise<ChatRoom> {
return apiRequest<ChatRoom>('/api/communication/chat/rooms', {
method: 'POST',
token,
body: payload,
});
}
export async function provisionMatrixRoom(token: string, room: MatrixRoom): Promise<MatrixRoom> {
if (room.provisionEndpoint) {
return apiRequest<MatrixRoom>(room.provisionEndpoint, { method: 'POST', token });
}
export async function provisionChatRoom(token: string, room: ChatRoom): Promise<ChatRoom> {
if (room.type === 'project' && room.projectId) {
return apiRequest<MatrixRoom>(`/api/communication/matrix/project-rooms/${room.projectId}/provision`, {
return apiRequest<ChatRoom>(`/api/communication/chat/project-rooms/${room.projectId}/provision`, {
method: 'POST',
token,
});
}
if (room.type === 'direct' && room.userId) {
return apiRequest<MatrixRoom>(`/api/communication/matrix/direct-rooms/${encodeURIComponent(room.userId)}/provision`, {
return apiRequest<ChatRoom>(`/api/communication/chat/direct-rooms/${encodeURIComponent(room.userId)}/provision`, {
method: 'POST',
token,
});
}
return apiRequest<MatrixRoom>(matrixRoomPath(room.key, '/provision'), {
method: 'POST',
token,
body: {
key: room.key,
name: room.name,
topic: room.topic,
type: room.type || 'room',
entityType: room.entityType,
entityId: room.entityId,
entityUuid: room.entityUuid,
},
});
return room;
}
export async function fetchMatrixMessages(token: string, roomKey: string): Promise<MatrixMessage[]> {
const response = await apiRequest<{ messages?: MatrixMessage[] }>(matrixRoomPath(roomKey, '/messages'), { token });
export async function fetchChatMessages(token: string, roomKey: string): Promise<ChatMessage[]> {
const response = await apiRequest<{ messages?: ChatMessage[] }>(chatRoomPath(roomKey, '/messages'), { token });
return response.messages || [];
}
export async function syncMatrixRoom(
token: string,
roomKey: string,
since?: string,
initial = false
): Promise<MatrixSyncResponse> {
const query = new URLSearchParams();
if (since) query.set('since', since);
if (initial) query.set('initial', '1');
const suffix = query.toString() ? `/sync?${query.toString()}` : '/sync';
return apiRequest<MatrixSyncResponse>(matrixRoomPath(roomKey, suffix), { token });
export async function syncChatRoom(token: string, roomKey: string, afterId = 0): Promise<ChatSyncResponse> {
return apiRequest<ChatSyncResponse>(chatRoomPath(roomKey, `/sync?afterId=${afterId}`), { token });
}
export async function fetchMatrixMembers(token: string, roomKey: string): Promise<MatrixMember[]> {
const response = await apiRequest<{ members?: MatrixMember[] }>(matrixRoomPath(roomKey, '/members'), { token });
export async function fetchChatMembers(token: string, roomKey: string): Promise<ChatMember[]> {
const response = await apiRequest<{ members?: ChatMember[] }>(chatRoomPath(roomKey, '/members'), { token });
return response.members || [];
}
export async function sendMatrixMessage(
token: string,
roomKey: string,
text: string,
replyToEventId?: string | null
): Promise<MatrixMessage> {
return apiRequest<MatrixMessage>(matrixRoomPath(roomKey, '/messages'), {
export async function sendChatMessage(token: string, roomKey: string, text: string): Promise<ChatMessage> {
return apiRequest<ChatMessage>(chatRoomPath(roomKey, '/messages'), {
method: 'POST',
token,
body: { text, replyToEventId },
});
}
export async function editMatrixMessage(token: string, roomKey: string, eventId: string, text: string): Promise<MatrixMessage> {
return apiRequest<MatrixMessage>(matrixRoomPath(roomKey, `/messages/${encodeURIComponent(eventId)}`), {
method: 'PUT',
token,
body: { text },
});
}
export async function deleteMatrixMessage(token: string, roomKey: string, eventId: string): Promise<void> {
await apiRequest(matrixRoomPath(roomKey, `/messages/${encodeURIComponent(eventId)}`), {
method: 'DELETE',
token,
});
}
export async function reactToMatrixMessage(token: string, roomKey: string, eventId: string, key: string): Promise<void> {
await apiRequest(matrixRoomPath(roomKey, `/messages/${encodeURIComponent(eventId)}/reactions`), {
export async function markChatRoomRead(token: string, roomKey: string, messageId?: number): Promise<void> {
await apiRequest(chatRoomPath(roomKey, '/read'), {
method: 'POST',
token,
body: { key },
body: { messageId },
});
}
export async function markMatrixRoomRead(token: string, roomKey: string, eventId?: string): Promise<void> {
await apiRequest(matrixRoomPath(roomKey, '/read'), {
method: 'POST',
token,
body: { eventId },
});
}
export async function syncMatrixMembers(token: string, roomKey: string): Promise<void> {
await apiRequest(matrixRoomPath(roomKey, '/members/sync'), {
method: 'POST',
token,
});
}
export async function inviteMatrixMember(token: string, roomKey: string, userId: string): Promise<void> {
await apiRequest(matrixRoomPath(roomKey, '/members/invite'), {
method: 'POST',
token,
body: { userId },
});
}
export async function removeMatrixMember(token: string, roomKey: string, matrixUserId: string): Promise<void> {
await apiRequest(matrixRoomPath(roomKey, `/members/${encodeURIComponent(matrixUserId)}`), {
method: 'DELETE',
token,
});
}
export async function uploadMatrixAttachment(
token: string,
roomKey: string,
file: { uri: string; name: string; mimeType?: string | null }
): Promise<MatrixMessage> {
const formData = new FormData();
formData.append('file', {
uri: file.uri,
name: file.name,
type: file.mimeType || 'application/octet-stream',
} as unknown as Blob);
return apiFormRequest<MatrixMessage>(matrixRoomPath(roomKey, '/attachments'), token, formData);
}
export async function renderPrintLabel(
token: string,
context: Record<string, unknown>,