import axios from 'axios'; import { get } from 'svelte/store'; import { currentUser, channelsList, logins, messages } from '$lib/store'; export const apiServer = axios.create({ baseURL: '/api/', validateStatus: () => true, }); export async function boot() { return apiServer.get('/boot'); } export async function setup(name, password) { return apiServer.post('/setup', { name, password }); } export async function logIn(name, password) { return apiServer.post('/auth/login', { name, password }); } export async function logOut() { return apiServer.post('/auth/logout', {}); } export async function changePassword(password, to) { return apiServer.post('/password', { password, to }); } export async function createChannel(name) { return apiServer.post('/channels', { name }); } export async function postToChannel(channelId, body) { return apiServer.post(`/channels/${channelId}`, { body }); } export async function deleteMessage(messageId) { // TODO } export async function createInvite() { return apiServer.post(`/invite`, {}); } export async function getInvite(inviteId) { return apiServer.get(`/invite/${inviteId}`); } export async function acceptInvite(inviteId, username, password) { const data = { name: username, password, }; return apiServer.post(`/invite/${inviteId}`, data); } export function subscribeToEvents(resume_point) { const eventsUrl = new URL('/api/events', window.location); eventsUrl.searchParams.append('resume_point', resume_point); const evtSource = new EventSource(eventsUrl.toString()); // TODO: this should process all incoming events and store them. // TODO: eventually we'll need to handle expiring old info, so as not to use // infinite browser memory. /* * Known message types as of now: * - created: a channel is created. * - action: ignore. * - message: a message is created. * - action: display message in channel. * - message_deleted: a message is deleted. * - action: replace message with <...>. * - deleted: a channel is deleted. * - action: remove channel from sidebar. */ evtSource.onmessage = (evt) => { const data = JSON.parse(evt.data); switch (data.type) { case 'login': onLoginEvent(data); break; case 'channel': onChannelEvent(data); break; case 'message': onMessageEvent(data); break; } } return evtSource; } function onLoginEvent(data) { switch (data.event) { case 'created': logins.update((value) => value.addLogin(data.id, data.name)) break; } } function onChannelEvent(data) { switch (data.event) { case 'created': channelsList.update((value) => value.addChannel(data.id, data.name)) break; case 'deleted': channelsList.update((value) => value.deleteChannel(data.id)); messages.update((value) => value.deleteChannel(data.id)); break; } } function onMessageEvent(data) { switch (data.event) { case 'sent': messages.update((value) => value.addMessage(data.channel, data.id, data.at, data.sender, data.body)); displayToast(data); break; case 'deleted': messages.update((value) => value.deleteMessage(data.id)); break; } } function displayToast(data) { // we use get throughout this as this function is not reactive, and just // needs the values of the stores at a moment in time. const currentUserId = get(currentUser).id; if (currentUserId === data.sender) { return; } const senderName = get(logins).get(data.sender); const channelName = get(channelsList).get(data.channel); const title = `${senderName} in ${channelName}`; const opts = { body: data.body, tag: title, // TODO: we need to inject the understory/hi icon in a more principled way here: icon: "/ui/lib/assets/logo.png", // TODO: support onclick bringing you to the relevant channel? onclick: null } new Notification(title, opts); }