- Add WS events: dm_channel_created, dm_channel_closed, friend_removed, channel_created/updated/deleted, server_updated - Fix first-ever DM: broadcast dm_channel_created to recipient - Add DELETE /api/dm/:id for closing DMs with re-open support - Wire Close DM button in sidebar - Fix dm_message_created for unknown channels (safety net) - Broadcast friend_removed on friend deletion - Sound system: track realtimeMessageEvents separately from API loads, play notification for messages in all channels, not just current - Optimistic updates: send/edit/delete messages appear instantly with rollback on failure, temp message deduplication on WS echo - Channel CRUD broadcasts to all server members - Server update broadcast on PATCH - Server join registers user in connectionManager immediately - Reconnect: only reload current channel, preserve other channel caches - Activity panel, right panel, member list toggle components
120 lines
4.9 KiB
JavaScript
120 lines
4.9 KiB
JavaScript
const BASE_URL = '/api';
|
|
function getToken() {
|
|
return localStorage.getItem('opencord_token');
|
|
}
|
|
async function request(method, path, body, requireAuth = true) {
|
|
const headers = {};
|
|
if (body) {
|
|
headers['Content-Type'] = 'application/json';
|
|
}
|
|
if (requireAuth) {
|
|
const token = getToken();
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
}
|
|
const response = await fetch(`${BASE_URL}${path}`, {
|
|
method,
|
|
headers,
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ error: 'Request failed' }));
|
|
throw new Error(error.error || `HTTP ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
async function uploadFile(file) {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
const token = getToken();
|
|
const headers = {};
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
const response = await fetch(`${BASE_URL}/uploads`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: formData,
|
|
});
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ error: 'Upload failed' }));
|
|
throw new Error(error.error || `HTTP ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
export const api = {
|
|
auth: {
|
|
register: (data) => request('POST', '/auth/register', data, false),
|
|
login: (data) => request('POST', '/auth/login', data, false),
|
|
},
|
|
users: {
|
|
me: () => request('GET', '/users/@me'),
|
|
update: (data) => request('PATCH', '/users/@me', data),
|
|
get: (id) => request('GET', `/users/${id}`),
|
|
},
|
|
servers: {
|
|
list: () => request('GET', '/servers'),
|
|
get: (id) => request('GET', `/servers/${id}`),
|
|
create: (data) => request('POST', '/servers', data),
|
|
update: (id, data) => request('PATCH', `/servers/${id}`, data),
|
|
delete: (id) => request('DELETE', `/servers/${id}`),
|
|
invite: (id) => request('POST', `/servers/${id}/invite`),
|
|
join: (id, data) => request('POST', `/servers/${id}/join`, data),
|
|
joinByCode: (inviteCode) => request('POST', '/servers/join', { inviteCode }),
|
|
members: (id) => request('GET', `/servers/${id}/members`),
|
|
updateMember: (serverId, userId, data) => request('PATCH', `/servers/${serverId}/members/${userId}`, data),
|
|
removeMember: (serverId, userId) => request('DELETE', `/servers/${serverId}/members/${userId}`),
|
|
},
|
|
channels: {
|
|
list: (serverId) => request('GET', `/servers/${serverId}/channels`),
|
|
create: (serverId, data) => request('POST', `/servers/${serverId}/channels`, data),
|
|
update: (id, data) => request('PATCH', `/channels/${id}`, data),
|
|
delete: (id) => request('DELETE', `/channels/${id}`),
|
|
messages: (id, before, limit = 50) => {
|
|
const params = new URLSearchParams();
|
|
if (before)
|
|
params.set('before', before);
|
|
params.set('limit', String(limit));
|
|
return request('GET', `/channels/${id}/messages?${params}`);
|
|
},
|
|
sendMessage: (channelId, data) => request('POST', `/channels/${channelId}/messages`, data),
|
|
},
|
|
messages: {
|
|
update: (id, data) => request('PATCH', `/messages/${id}`, data),
|
|
delete: (id) => request('DELETE', `/messages/${id}`),
|
|
},
|
|
uploads: {
|
|
upload: uploadFile,
|
|
url: (filename) => `${BASE_URL}/uploads/${filename}`,
|
|
},
|
|
dm: {
|
|
list: () => request('GET', '/dm'),
|
|
create: (data) => request('POST', '/dm', data),
|
|
close: (id) => request('DELETE', `/dm/${id}`),
|
|
messages: (id, before, limit = 50) => {
|
|
const params = new URLSearchParams();
|
|
if (before)
|
|
params.set('before', before);
|
|
params.set('limit', String(limit));
|
|
return request('GET', `/dm/${id}/messages?${params}`);
|
|
},
|
|
sendMessage: (id, data) => request('POST', `/dm/${id}/messages`, data),
|
|
updateMessage: (id, data) => request('PATCH', `/dm/messages/${id}`, data),
|
|
deleteMessage: (id) => request('DELETE', `/dm/messages/${id}`),
|
|
},
|
|
social: {
|
|
friends: () => request('GET', '/social/friends'),
|
|
requests: () => request('GET', '/social/requests'),
|
|
sendRequest: (username) => request('POST', '/social/requests', { username }),
|
|
updateRequest: (id, status) => request('PATCH', `/social/requests/${id}`, { status }),
|
|
removeFriend: (id) => request('DELETE', `/social/friends/${id}`),
|
|
cancelRequest: (id) => request('DELETE', `/social/requests/${id}`),
|
|
search: (q) => request('GET', `/social/search?q=${encodeURIComponent(q)}`),
|
|
},
|
|
livekit: {
|
|
token: (channelId) => request('POST', '/livekit/token', { channelId }),
|
|
dmToken: (dmChannelId) => request('POST', '/livekit/token', { dmChannelId }),
|
|
},
|
|
};
|