KI-AGENT: Mobile Matrix-Kommunikation vollständig integrieren
This commit is contained in:
@@ -183,6 +183,100 @@ type RequestOptions = {
|
||||
body?: unknown;
|
||||
};
|
||||
|
||||
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 = {
|
||||
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;
|
||||
userId?: string;
|
||||
email?: string | null;
|
||||
entityType?: string | null;
|
||||
entityId?: number | null;
|
||||
entityUuid?: string | null;
|
||||
unread?: number;
|
||||
mentions?: number;
|
||||
provisionEndpoint?: string;
|
||||
[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;
|
||||
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 = {
|
||||
userId: string;
|
||||
matrixUserId: string;
|
||||
displayName?: string | null;
|
||||
email?: string | null;
|
||||
[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[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
function buildUrl(path: string): string {
|
||||
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||||
return path;
|
||||
@@ -227,10 +321,231 @@ 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 response = await fetch(buildUrl(path), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
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 Error(message);
|
||||
}
|
||||
|
||||
return payload as T;
|
||||
}
|
||||
|
||||
function matrixRoomPath(roomKey: string, suffix = ''): string {
|
||||
return `/api/communication/matrix/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[]> {
|
||||
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', {
|
||||
token,
|
||||
}),
|
||||
]);
|
||||
|
||||
const unreadByRoom = unread.rooms || {};
|
||||
const decorate = (room: MatrixRoom, group: string): MatrixRoom => ({
|
||||
...room,
|
||||
group,
|
||||
unread: unreadByRoom[room.key]?.count || 0,
|
||||
mentions: unreadByRoom[room.key]?.mentions || 0,
|
||||
});
|
||||
|
||||
return [
|
||||
...(rooms.rooms || []).map((room) => decorate(room, 'Räume')),
|
||||
...(projectRooms.rooms || []).map((room) => decorate(room, 'Projekte')),
|
||||
...(directRooms.rooms || []).map((room) => decorate(room, 'Direkt')),
|
||||
];
|
||||
}
|
||||
|
||||
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(
|
||||
token: string,
|
||||
payload: { key: string; name: string; topic?: string | null; type?: string }
|
||||
): Promise<MatrixRoom> {
|
||||
return apiRequest<MatrixRoom>('/api/communication/matrix/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 });
|
||||
}
|
||||
|
||||
if (room.type === 'project' && room.projectId) {
|
||||
return apiRequest<MatrixRoom>(`/api/communication/matrix/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`, {
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchMatrixMessages(token: string, roomKey: string): Promise<MatrixMessage[]> {
|
||||
const response = await apiRequest<{ messages?: MatrixMessage[] }>(matrixRoomPath(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 fetchMatrixMembers(token: string, roomKey: string): Promise<MatrixMember[]> {
|
||||
const response = await apiRequest<{ members?: MatrixMember[] }>(matrixRoomPath(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'), {
|
||||
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`), {
|
||||
method: 'POST',
|
||||
token,
|
||||
body: { key },
|
||||
});
|
||||
}
|
||||
|
||||
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>,
|
||||
|
||||
Reference in New Issue
Block a user