Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions server/setupSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Server} from 'socket.io';

import type {HttpServer} from 'vite';
import type { ClientToServerEvents, ServerClientEvents } from '$types/socket';

/**
* Init the global websocket server object
* Intended to be called once, in vite plugin or express middleware, before sveltekit server hook import
*/
export function initServerWebsocket(server: HttpServer | null) {
if (!server) {
console.error('could not start websocket services');
return;
}

if (globalThis.myServerSocket) {
globalThis.myServerSocket.close();
globalThis.myServerSocket = null;
}
const io = new Server<ClientToServerEvents, ServerClientEvents>(server);
globalThis.myServerSocket = io;
}

export function getServerSocket() {
const io = globalThis.myServerSocket;
if (!io) {
throw new Error('Websocket server not initialized');
}
return io;
}
68 changes: 0 additions & 68 deletions server/websocket.ts

This file was deleted.

14 changes: 14 additions & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
// See https://svelte.dev/docs/kit/types#app.d.ts

import type { validateSessionToken } from '$lib/server/auth';
import type { ClientToServerEvents, ServerClientEvents } from '$types/socket';
import type { Server } from 'socket.io';

// for information about these interfaces
declare global {
var myServerSocket: Server<
ClientToServerEvents,
ServerClientEvents,
object,
{
auth: Awaited<ReturnType<typeof validateSessionToken>>
tape: string;
}
> | null;
namespace App {
// interface Error {}
interface Locals {
Expand Down
3 changes: 3 additions & 0 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { Handle } from '@sveltejs/kit';
import * as auth from '$lib/server/auth';
import { registerSvelteKitWebsocket } from './socket.server';

registerSvelteKitWebsocket();

const handleAuth: Handle = async ({ event, resolve }) => {
const sessionToken = event.cookies.get(auth.sessionCookieName);
Expand Down
47 changes: 37 additions & 10 deletions src/lib/websocket.ts → src/lib/clientSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,50 @@ import ioClient, {Socket} from 'socket.io-client';

import { createSubscriber } from 'svelte/reactivity';
import { browser } from '$app/environment';
import { page } from '$app/state';
import { getFileTree } from './remotes/files.remote';

import type { CoreAPI } from '$core/CoreAPI.svelte';
import type { ClientToServerEvents, ServerClientEvents } from '$types/socket';

let socket: ClientSocket | null = null;
let clientSocket: ClientSocket | null = null;
export function getSocket(core: CoreAPI) {
if (!browser) return null;

if (!socket) {
if (!clientSocket) {
const { protocol, host } = window.location;
const url = `${protocol}//${host}`;
const iosocket = ioClient(url);
socket = new ClientSocket(iosocket, core);
} else if (!socket.socket.connected) {
socket.socket.connect();

const tape = page.params.tape;
if (!tape) {
console.warn('Could not find tape name');
return null;
};

const socket = ioClient(url, {extraHeaders: {
'x-tape-name': tape
}});
clientSocket = new ClientSocket(
core,
socket,
);
} else if (!clientSocket.socket.connected) {
clientSocket.socket.connect();
}
return socket;
return clientSocket;
}

export class ClientSocket {
/**
* Small wrapper around socket.io-client instance to handle reactivity in Svelte
*/
class ClientSocket {
#socket;
#subscribe;

constructor(socket: Socket<ServerClientEvents, ClientToServerEvents>, private core: CoreAPI) {
constructor(
private core: CoreAPI,
socket: Socket<ServerClientEvents, ClientToServerEvents>,
) {
this.#socket = socket;

this.#subscribe = createSubscriber((update) => {
Expand All @@ -36,6 +57,10 @@ export class ClientSocket {
}
update();
});
socket.on('remoteModification', async (modifications) => {
await getFileTree().refresh();
await this.core.syncStates(modifications, 'socket');
});
});
}

Expand All @@ -44,4 +69,6 @@ export class ClientSocket {

return this.#socket;
}
}
}

export type {ClientSocket};
2 changes: 2 additions & 0 deletions src/lib/components/SideBar/FileTree/Entry.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
<Context.Item
data-testid="rename-entry-button"
inset
disabled={!coreAPI.clientSocket?.connected}
onclick={(e) => {
e.stopPropagation();
renaming = true;
Expand All @@ -92,6 +93,7 @@
<Context.Item
data-testid="delete-entry-button"
inset
disabled={!coreAPI.clientSocket?.connected}
onclick={async (e) => {
e.stopPropagation();
open = false;
Expand Down
3 changes: 3 additions & 0 deletions src/lib/components/SideBar/Header.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { coreAPI } from '$core/CoreAPI.svelte';
import { page } from '$app/state';
import { CassetteTape } from '@lucide/svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
Expand All @@ -22,6 +23,8 @@
'flex items-center gap-3 px-3 h-12 text-gray-200 bg-gray-800 hover:bg-gray-700',
'border-b-4 border-black',
'transition-all duration-150',
'border-l',
coreAPI.clientSocket?.connected ? 'border-l-green-500' : 'border-l-red-500',
className
]}
href={resolve('/')}
Expand Down
10 changes: 9 additions & 1 deletion src/lib/components/SideBar/SideBar.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import FileTreeComp from './FileTree/FileTreeComp.svelte';
import { coreAPI } from '$core/CoreAPI.svelte';
import { WifiOff } from '@lucide/svelte';
import type { Snippet } from 'svelte';

type Props = {
header?: Snippet;
Expand All @@ -15,13 +17,19 @@
</script>

<section class={[
'relative',
'overflow-auto overscroll-none flex flex-col justify-between',
'm-2',
'bg-gray-800',
'border-4 border-black',
'shadow-black shadow-[2px_2px]',
className ?? '',
]}>
{#if !coreAPI.clientSocket?.connected}
<div class="absolute w-full h-full bg-black/50 z-10 flex justify-center items-center">
<WifiOff class="w-8 h-8" />
</div>
{/if}
<div class="h-full">
{@render header?.()}
<FileTreeComp />
Expand Down
28 changes: 20 additions & 8 deletions src/lib/core/CoreAPI.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { FoldState } from '$core/internal/FoldState.svelte';
import { getCurrentTape } from '$lib/remotes/files.remote';
import { ViewMap } from '$components/Main/View';
import { Page } from './Page.svelte';
import { InfoUi } from './InfosUi.svelte';
import { ClientSocket, getSocket } from '$lib/websocket';
import { InfoUi, type ModificationOrigin } from './InfosUi.svelte';
import { type ClientSocket, getSocket } from '$lib/clientSocket';
import type { FileEntry } from '$types/files';
import type { EntryModification } from '$types/modification';
import type { TabFileEntry } from '$types/tabs';
Expand All @@ -22,7 +22,7 @@ class CoreAPI {

readonly infoUi: InfoUi;

readonly clientSocket: ClientSocket | null;
#clientSocket: ClientSocket | null = null;

constructor() {
// Internal
Expand All @@ -34,14 +34,23 @@ class CoreAPI {
this.pageStore = new Page(this);

this.infoUi = new InfoUi(this);

this.clientSocket = getSocket(this);
}

async init() {
const tapeName = await getCurrentTape();
this.foldState.init(tapeName);
this.pageStore.init();
this.#clientSocket = getSocket(this);
}

/**
* @reactive call to the socket instance in a reactive context to subscribe to its events and get the latest socket data
*/
get clientSocket() {
if (!this.#clientSocket) {
this.#clientSocket = getSocket(this);
}
return this.#clientSocket?.socket ?? null;
}

/**
Expand Down Expand Up @@ -155,13 +164,15 @@ class CoreAPI {
* Method to sync changes between the server and the client
* @fires {@linkcode CoreAPI.tabs}
* @fires {@linkcode CoreAPI.activeTab}
* @fires {@linkcode CoreAPI.activeTabInfos}
* @fires {@linkcode InfoUi.addModificationMessage}
*/
async syncStates(modifications: EntryModification[]) {
async syncStates(modifications: EntryModification[], origin: ModificationOrigin = 'local') {
await this.#tabStore.syncModifications(modifications);
await this.foldState.syncModifications(modifications);

for (const mod of modifications) {
this.infoUi.addModificationMessage(mod);
this.infoUi.addModificationMessage(mod, origin);
}
}

Expand All @@ -178,6 +189,7 @@ class CoreAPI {
async clear() {
this.#tabStore.clear();
this.foldState.clear();
this.#clientSocket?.socket.disconnect();
}
}

Expand Down
16 changes: 9 additions & 7 deletions src/lib/core/InfosUi.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
import type { EntryModification } from '$types/modification';
import type { CoreAPI } from './CoreAPI.svelte';

export type ModificationOrigin = 'local' | 'socket';

export class InfoUi {
#maxMessages = 100;
messageQueue: string[] = $state([]);
lastMessage: string | null = $derived(this.messageQueue.at(-1) ?? null);

constructor(private coreAPI: CoreAPI) {}

addModificationMessage(mod: EntryModification) {
let message = '';
addModificationMessage(mod: EntryModification, kind: ModificationOrigin = 'local') {
let message = `[${kind}] `;
switch (mod.type) {
case 'created':
message = `Created entry "${mod.newPath}"`;
message += `Created entry "${mod.newPath}"`;
break;
case 'removed':
message = `Deleted entry "${mod.oldPath}"`;
message += `Deleted entry "${mod.oldPath}"`;
break;
case 'renamed':
message = `Renamed entry "${mod.oldPath}" to "${mod.newPath}"`;
message += `Renamed entry "${mod.oldPath}" to "${mod.newPath}"`;
break;
case 'moved':
message = `Moved entry "${mod.oldPath}" to "${mod.newPath}"`;
message += `Moved entry "${mod.oldPath}" to "${mod.newPath}"`;
break;
default:
message = `Unknown modification on entry "${mod.oldPath}"`;
message += `Unknown modification on entry "${mod.oldPath}"`;
}
this.addMessage(message);
}
Expand Down
Loading
Loading