diff --git a/client/client.ts b/client/client.ts index be4770d7..e6dcf8ef 100644 --- a/client/client.ts +++ b/client/client.ts @@ -77,7 +77,12 @@ import { CheckPathSpacePrimitives } from "./spaces/checked_space_primitives.ts"; import { notFoundError, offlineError, + wrongSpacePathError, } from "@silverbulletmd/silverbullet/constants"; +import { + flushCachesAndUnregisterServiceWorker, + unregisterServiceWorkers, +} from "./service_worker/util.ts"; const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/; @@ -1312,7 +1317,10 @@ export class Client { } case "auth-error": { alert(message.message); - if (message.actionOrRedirectHeader) { + if ( + message.actionOrRedirectHeader && + message.actionOrRedirectHeader !== "reload" + ) { location.href = message.actionOrRedirectHeader; } else { location.reload(); diff --git a/client/cm_plugins/inline_content.ts b/client/cm_plugins/inline_content.ts index 68a42d63..4c10b338 100644 --- a/client/cm_plugins/inline_content.ts +++ b/client/cm_plugins/inline_content.ts @@ -9,7 +9,10 @@ import { } from "./util.ts"; import type { Client } from "../client.ts"; import { LuaWidget } from "./lua_widget.ts"; -import { inlineContentFromURL, parseTransclusion } from "../markdown/inline.ts"; +import { + inlineContentFromURL, + parseTransclusion, +} from "../markdown_renderer/inline.ts"; export function inlineContentPlugin(client: Client) { return decoratorStateField((state: EditorState) => { diff --git a/client/cm_plugins/lua_widget.ts b/client/cm_plugins/lua_widget.ts index 436d93c5..bd2dd259 100644 --- a/client/cm_plugins/lua_widget.ts +++ b/client/cm_plugins/lua_widget.ts @@ -1,6 +1,6 @@ import { WidgetType } from "@codemirror/view"; import type { Client } from "../client.ts"; -import { renderMarkdownToHtml } from "../markdown/markdown_render.ts"; +import { renderMarkdownToHtml } from "../markdown_renderer/markdown_render.ts"; import { isLocalURL, resolveMarkdownLink, @@ -12,9 +12,12 @@ import { attachWidgetEventHandlers, moveCursorIntoText, } from "./widget_util.ts"; -import { expandMarkdown } from "../markdown/inline.ts"; +import { expandMarkdown } from "../markdown_renderer/inline.ts"; import { LuaStackFrame, LuaTable } from "../space_lua/runtime.ts"; -import { isBlockMarkdown, jsonToMDTable } from "../markdown/result_render.ts"; +import { + isBlockMarkdown, + jsonToMDTable, +} from "../markdown_renderer/result_render.ts"; import { activeWidgets } from "./code_widget.ts"; export type LuaWidgetCallback = ( diff --git a/client/cm_plugins/table.ts b/client/cm_plugins/table.ts index b2aeda5d..16147778 100644 --- a/client/cm_plugins/table.ts +++ b/client/cm_plugins/table.ts @@ -7,7 +7,7 @@ import { isCursorInRange, } from "./util.ts"; -import { renderMarkdownToHtml } from "../markdown/markdown_render.ts"; +import { renderMarkdownToHtml } from "../markdown_renderer/markdown_render.ts"; import { type ParseTree, renderToText, @@ -18,7 +18,7 @@ import { isLocalURL, resolveMarkdownLink, } from "@silverbulletmd/silverbullet/lib/resolve"; -import { expandMarkdown } from "../markdown/inline.ts"; +import { expandMarkdown } from "../markdown_renderer/inline.ts"; import { LuaStackFrame } from "../space_lua/runtime.ts"; import { attachWidgetEventHandlers } from "./widget_util.ts"; diff --git a/client/markdown/html_render.test.ts b/client/markdown_renderer/html_render.test.ts similarity index 100% rename from client/markdown/html_render.test.ts rename to client/markdown_renderer/html_render.test.ts diff --git a/client/markdown/html_render.ts b/client/markdown_renderer/html_render.ts similarity index 100% rename from client/markdown/html_render.ts rename to client/markdown_renderer/html_render.ts diff --git a/client/markdown/inline.ts b/client/markdown_renderer/inline.ts similarity index 100% rename from client/markdown/inline.ts rename to client/markdown_renderer/inline.ts diff --git a/client/markdown/justified_tables.ts b/client/markdown_renderer/justified_tables.ts similarity index 100% rename from client/markdown/justified_tables.ts rename to client/markdown_renderer/justified_tables.ts diff --git a/client/markdown/markdown_render.test.ts b/client/markdown_renderer/markdown_render.test.ts similarity index 100% rename from client/markdown/markdown_render.test.ts rename to client/markdown_renderer/markdown_render.test.ts diff --git a/client/markdown/markdown_render.ts b/client/markdown_renderer/markdown_render.ts similarity index 100% rename from client/markdown/markdown_render.ts rename to client/markdown_renderer/markdown_render.ts diff --git a/client/markdown/result_render.ts b/client/markdown_renderer/result_render.ts similarity index 100% rename from client/markdown/result_render.ts rename to client/markdown_renderer/result_render.ts diff --git a/client/plugos/syscalls/markdown.ts b/client/plugos/syscalls/markdown.ts index 5c317c4d..c9004d0c 100644 --- a/client/plugos/syscalls/markdown.ts +++ b/client/plugos/syscalls/markdown.ts @@ -5,13 +5,13 @@ import { renderToText, } from "@silverbulletmd/silverbullet/lib/tree"; import { extendedMarkdownLanguage } from "../../markdown_parser/parser.ts"; -import { expandMarkdown } from "../../markdown/inline.ts"; +import { expandMarkdown } from "../../markdown_renderer/inline.ts"; import type { Client } from "../../client.ts"; import { LuaEnv, LuaStackFrame } from "../../space_lua/runtime.ts"; import { type MarkdownRenderOptions, renderMarkdownToHtml, -} from "../../markdown/markdown_render.ts"; +} from "../../markdown_renderer/markdown_render.ts"; export function markdownSyscalls(client: Client): SysCallMapping { return { diff --git a/client/service_worker.ts b/client/service_worker.ts index 3d4e6bd6..4c180fc1 100644 --- a/client/service_worker.ts +++ b/client/service_worker.ts @@ -12,6 +12,7 @@ import { fsEndpoint } from "./spaces/constants.ts"; import { DataStoreSpacePrimitives } from "./spaces/datastore_space_primitives.ts"; import { HttpSpacePrimitives } from "./spaces/http_space_primitives.ts"; import { throttleImmediately } from "@silverbulletmd/silverbullet/lib/async"; +import { wrongSpacePathError } from "@silverbulletmd/silverbullet/constants"; const logger = initLogger("[Service Worker]"); @@ -69,6 +70,10 @@ self.addEventListener("message", async (event: any) => { self.skipWaiting(); break; } + case "shutdown": { + proxyRouter.reset(); + break; + } case "flush-cache": { const cacheNames = await caches.keys(); @@ -182,6 +187,9 @@ self.addEventListener("message", async (event: any) => { message, actionOrRedirectHeader, ); + if (message === wrongSpacePathError.message) { + proxyRouter.reset(); + } broadcastMessage({ type: "auth-error", message, diff --git a/client/service_worker/proxy_router.ts b/client/service_worker/proxy_router.ts index e74c81a2..61c11a94 100644 --- a/client/service_worker/proxy_router.ts +++ b/client/service_worker/proxy_router.ts @@ -5,6 +5,8 @@ import { fileMetaToHeaders, headersToFileMeta } from "../util.ts"; import { notFoundError, offlineError, + pingInterval, + pingTimeout, } from "@silverbulletmd/silverbullet/constants"; import type { SyncEngine } from "./sync_engine.ts"; import { EventEmitter } from "../plugos/event.ts"; @@ -18,9 +20,6 @@ const alwaysProxy = [ "/.logs", ]; -const pingTimeout = 2000; -const pingInterval = 5000; - export type ProxyRouterEvents = { // Use case: the user likely has this file open in the editor, so it's good to prioritize syncing it observedRequest: (path: string) => void; @@ -36,6 +35,7 @@ export class ProxyRouter extends EventEmitter { online = false; localSpacePrimitives?: SpacePrimitives; syncEngine?: SyncEngine; + forcedStatus = false; constructor( @@ -69,18 +69,36 @@ export class ProxyRouter extends EventEmitter { }); } + /** + * Stops service worker operation only to be continued after reconfiguration + */ + reset() { + console.log("Shutting down proxy router and linked components"); + if (this.syncEngine) { + this.syncEngine.stop(); + this.syncEngine = undefined; + } + } + async checkOnline() { - try { - await fetch(this.baseURI + "/.ping", { - signal: AbortSignal.timeout(pingTimeout), - }); - // If the ping is successful, we are online - this.online = true; - } catch { - // Otherwise we're not - this.online = false; - } finally { - this.emit("onlineStatusUpdated", this.online); + if (this.syncEngine) { + try { + this.syncEngine.remote.ping(); + await fetch(this.baseURI + "/.ping", { + signal: AbortSignal.timeout(pingTimeout), + }); + // If the ping is successful, we are online + this.online = true; + } catch { + // Otherwise we're not + this.online = false; + } finally { + this.emit("onlineStatusUpdated", this.online); + } + } else { + console.info( + "Sync engine not initialized yet, cannot check online status", + ); } } diff --git a/client/service_worker/sync_engine.ts b/client/service_worker/sync_engine.ts index 8a7e3f59..9a89ae1f 100644 --- a/client/service_worker/sync_engine.ts +++ b/client/service_worker/sync_engine.ts @@ -5,6 +5,7 @@ import { EventEmitter } from "../plugos/event.ts"; import { plugPrefix, stdLibPrefix } from "../spaces/constants.ts"; import type { SpacePrimitives } from "../spaces/space_primitives.ts"; import { SpaceSync, SyncSnapshot, type SyncStatus } from "../spaces/sync.ts"; +import { HttpSpacePrimitives } from "../spaces/http_space_primitives.ts"; const syncSnapshotKey = ["$sync", "snapshot"]; const syncInterval = 20; @@ -47,7 +48,7 @@ export class SyncEngine extends EventEmitter { constructor( private kv: KvPrimitives, readonly local: SpacePrimitives, - readonly remote: SpacePrimitives, + readonly remote: HttpSpacePrimitives, ) { super(); } @@ -71,6 +72,10 @@ export class SyncEngine extends EventEmitter { this.run(); } + stop() { + this.stopping = true; + } + async run() { while (true) { if (this.stopping) { @@ -158,7 +163,7 @@ export class SyncEngine extends EventEmitter { } async wipe() { - this.stopping = true; + this.stop(); console.log("Wiping sync database"); await this.kv.clear(); console.log("Done wiping"); diff --git a/client/spaces/disk_space_primitives.ts b/client/spaces/disk_space_primitives.ts index e120ebbc..1d26d547 100644 --- a/client/spaces/disk_space_primitives.ts +++ b/client/spaces/disk_space_primitives.ts @@ -3,7 +3,7 @@ import { readAll } from "@std/io/read-all"; import type { SpacePrimitives } from "./space_primitives.ts"; import { mime } from "mimetypes"; -import type { FileMeta } from "../../plug-api/types/index.ts"; +import type { FileMeta } from "@silverbulletmd/silverbullet/type/index"; import { notFoundError } from "@silverbulletmd/silverbullet/constants"; function lookupContentType(path: string): string { diff --git a/client/spaces/http_space_primitives.ts b/client/spaces/http_space_primitives.ts index 53a67e81..7f36ee58 100644 --- a/client/spaces/http_space_primitives.ts +++ b/client/spaces/http_space_primitives.ts @@ -7,6 +7,8 @@ import type { FileMeta } from "@silverbulletmd/silverbullet/type/index"; import { notFoundError, offlineError, + pingTimeout, + wrongSpacePathError, } from "@silverbulletmd/silverbullet/constants"; import { headersToFileMeta } from "../util.ts"; @@ -132,6 +134,14 @@ export class HttpSpacePrimitives implements SpacePrimitives { method: "GET", }); + await this.validateSpacePathFromHeaders(resp); + return resp.json(); + } + + /** + * The /.fs file listing and /.ping endpoints both expose the currently exposed space path, if this doesn't match what the client expects, the client has to restart + */ + async validateSpacePathFromHeaders(resp: Response) { if ( resp.status === 200 && this.expectedSpacePath && @@ -142,13 +152,10 @@ export class HttpSpacePrimitives implements SpacePrimitives { console.log("Got space path", resp.headers.get("X-Space-Path")); await flushCachesAndUnregisterServiceWorker(); this.authErrorCallback( - "Space folder path different on server, reloading the page", + wrongSpacePathError.message, "reload", ); - // location.reload(); } - - return resp.json(); } async readFile( @@ -237,14 +244,16 @@ export class HttpSpacePrimitives implements SpacePrimitives { async ping() { const parentEndpoint = this.url.split("/").slice(0, -1).join("/") + "/.ping"; - const response = await this.authenticatedFetch(parentEndpoint, { + const resp = await this.authenticatedFetch(parentEndpoint, { method: "GET", headers: { Accept: "application/json", }, - }, 5000); + }, pingTimeout); + + await this.validateSpacePathFromHeaders(resp); // Consume the response body to avoid leaks - await response.text(); + await resp.text(); } } diff --git a/client/ui_types.ts b/client/ui_types.ts index f23a3003..6283adc2 100644 --- a/client/ui_types.ts +++ b/client/ui_types.ts @@ -197,6 +197,7 @@ export type ServiceWorkerTargetMessage = } | { type: "config"; config: BootConfig } | { type: "flush-cache" } + | { type: "shutdown" } | { type: "wipe-data" } | { type: "perform-file-sync"; path: string } | { type: "perform-space-sync" } diff --git a/plug-api/constants.ts b/plug-api/constants.ts index 92d1a286..7016bfd9 100644 --- a/plug-api/constants.ts +++ b/plug-api/constants.ts @@ -3,3 +3,8 @@ export const defaultLinkStyle = "wikilink"; export const offlineError = new Error("Offline"); export const notFoundError = new Error("Not found"); export const notAuthenticatedError = new Error("Unauthenticated"); +export const wrongSpacePathError = new Error( + "Space folder path different on server, reloading the page", +); +export const pingTimeout = 2000; +export const pingInterval = 5000; diff --git a/server/server.go b/server/server.go index 504c7360..0d751ba6 100644 --- a/server/server.go +++ b/server/server.go @@ -43,7 +43,9 @@ func RunServer(config *ServerConfig) error { routes := chi.NewRouter() routes.Get("/.ping", func(w http.ResponseWriter, r *http.Request) { + spaceConfig := spaceConfigFromContext(r.Context()) w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("X-Space-Path", spaceConfig.SpaceFolderPath) w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) })