diff --git a/.gitignore b/.gitignore index a52a0f9..9d5dbd7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /node_modules .env database.db* +# Created by workbox and vite pwa plugin +dev-dist/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 82037ac..ac097c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,12 +15,14 @@ "pnpx", "popovertarget", "popovertargetaction", + "precaching", "pumpe", "qrcode", "reqwest", "santaclaas", "serde", "solidjs", - "thiserror" + "thiserror", + "vite" ] } diff --git a/app/package.json b/app/package.json index f742df7..7f4f45d 100644 --- a/app/package.json +++ b/app/package.json @@ -7,11 +7,13 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "wasm": "wasm-pack build ../core" + "wasm": "wasm-pack build --target web ../core", + "wasm:dev": "wasm-pack build --target web --dev ../core" }, "dependencies": { "@solidjs/router": "^0.15.3", "idb": "^8.0.2", + "meal-core": "link:../core/pkg", "qrcode": "^1.5.4", "solid-js": "^1.9.5", "workbox-precaching": "^7.3.0" diff --git a/app/src/components/AppContext.jsx b/app/src/components/AppContext.jsx index 02e5597..46a0cfb 100644 --- a/app/src/components/AppContext.jsx +++ b/app/src/components/AppContext.jsx @@ -1,6 +1,5 @@ import { createContext, createEffect, useContext } from "solid-js"; -//TODO register rust wasm pack package as package in workspace -import { Client } from "../../../core/pkg"; +import init, { Client } from "meal-core"; import { createStore } from "solid-js/store"; /** @@ -18,6 +17,7 @@ import { createStore } from "solid-js/store"; const id = localStorage.getItem("id"); const name = localStorage.getItem("name"); const isOnboarded = localStorage.getItem("isOnboarded") !== null; +await init(); const client = new Client(id ?? undefined, name ?? undefined); // const isLocalhost = @@ -33,6 +33,9 @@ const [app, setApp] = createStore({ get id() { return client.id; }, + /** + * @deprecated Use client through single service worker instance instead + */ client, /** * @type {Group[]} diff --git a/app/src/routes/chat.jsx b/app/src/routes/chat.jsx index 51d2d3d..9a70ab6 100644 --- a/app/src/routes/chat.jsx +++ b/app/src/routes/chat.jsx @@ -1,6 +1,6 @@ import { Navigate, useParams } from "@solidjs/router"; import { createMemo, For, Show } from "solid-js"; -import { useAppContext, messagesUrl } from "../components/AppContext"; +import { useAppContext } from "../components/AppContext"; //@ts-expect-error TS6192 Can not handle new JSDoc syntax (yet?) /** @import { JSX } from "solid-js" */ //@ts-expect-error TS6192 Can not handle new JSDoc syntax (yet?) @@ -52,28 +52,13 @@ export default function Chat() { }; setApp("groups", groupIndex(), "messages", messages().length, message); - //TODO Should use SolidJS signal system to make sending messages an effect of adding messages to the chat - const sendDate = new Date().toISOString(); - const messageContent = { - sent: sendDate, + await postMessage({ + type: "sendMessage", + groupId: group().id, + friendId: group().friend.id, + sent: new Date(), text: messageText, - }; - - const body = app.client.send_message(group().id, messageContent); - - const url = new URL(group().friend.id, messagesUrl); - const request = new Request(url, { - method: "post", - headers: { - //https://www.rfc-editor.org/rfc/rfc9420.html#name-the-message-mls-media-type - "Content-Type": "message/mls", - }, - body, }); - - //TODO error handling - //TODO retry - await fetch(request); } return ( diff --git a/app/src/sendMessage.js b/app/src/sendMessage.js new file mode 100644 index 0000000..8e05ee5 --- /dev/null +++ b/app/src/sendMessage.js @@ -0,0 +1,13 @@ +/** + * + * @param {ServiceWorkerRequest} message + */ +async function postMessage(message) { + if (!("serviceWorker" in navigator)) + throw new Error("Service worker not supported"); + + const worker = await navigator.serviceWorker.ready; + // This should not happen as we awaited ready + if (worker.active === null) throw new Error("No active service worker"); + worker.active.postMessage(message); +} diff --git a/app/src/service-worker/schema.d.ts b/app/src/service-worker/schema.d.ts new file mode 100644 index 0000000..8770556 --- /dev/null +++ b/app/src/service-worker/schema.d.ts @@ -0,0 +1,10 @@ +import { DBSchema } from "idb"; + +interface Schema extends DBSchema { + configuration: { + user?: { + name?: string; + }; + clientId?: string; + }; +} diff --git a/app/src/service-worker/serviceMessage.d.ts b/app/src/service-worker/serviceMessage.d.ts new file mode 100644 index 0000000..a097d6b --- /dev/null +++ b/app/src/service-worker/serviceMessage.d.ts @@ -0,0 +1,11 @@ +type SendMessage = { + type: "sendMessage"; + groupId: string; + friendId: string; + sent: Date; + text: string; +}; +/** + * A message sent to the service worker from a browsing context. + */ +type ServiceWorkerRequest = SendMessage; diff --git a/app/src/service-worker/serviceWorker.js b/app/src/service-worker/serviceWorker.js index def9b37..0314ee9 100644 --- a/app/src/service-worker/serviceWorker.js +++ b/app/src/service-worker/serviceWorker.js @@ -1,4 +1,72 @@ import { precacheAndRoute } from "workbox-precaching"; +import { openDB } from "idb"; +import init, { Client } from "meal-core"; +/** @import { Schema } from "./schema" */ + +console.debug("Service worker: environment", process.env.NODE_ENV); // @ts-expect-error This variable is replaced by workbox through vite pwa plugin precacheAndRoute(self.__WB_MANIFEST); + +const openDatabase = /** @type {typeof openDB} */ (openDB)("meal", 1, { + upgrade(database) { + console.debug("Service worker: upgrading database"); + if (!database.objectStoreNames.contains("configuration")) { + database.createObjectStore("configuration", { autoIncrement: true }); + } + }, +}); + +async function initializeClient() { + // Get configuration + const database = await openDatabase; + const transaction = database.transaction("configuration", "readonly"); + const store = transaction.objectStore("configuration"); + // Get first item + const cursor = await store.openCursor(); + const configuration = /** @type {Schema["configuration"]} */ (cursor?.value); + // Have to use wasm-pack --target web to build the wasm package to get the init function because with the bundler target + // it is included as a top level await which is not supported by service workers according to the web spec + await init(); + const client = new Client(configuration?.clientId, configuration?.user?.name); + return client; +} + +const setupClient = initializeClient(); + +// const id = localStorage.getItem("id"); +// const name = localStorage.getItem("name"); +// const isOnboarded = localStorage.getItem("isOnboarded") !== null; +// const client = new Client(id ?? undefined, name ?? undefined); + +/** The url for messages endpoint. Don't forget the trailing slash. */ +const messagesUrl = new URL("/messages/", self.location.origin); + +/** + * @param {MessageEvent} event + */ +async function handleMessage(event) { + console.debug("Service worker: received message", event.data); + + switch (event.data.type) { + case "sendMessage": + const client = await setupClient; + const body = client.send_message(event.data.groupId, event.data); + const url = new URL(event.data.friendId, messagesUrl); + const request = new Request(url, { + method: "post", + headers: { + //https://www.rfc-editor.org/rfc/rfc9420.html#name-the-message-mls-media-type + "Content-Type": "message/mls", + }, + body, + }); + + //TODO error handling + //TODO retry + await fetch(request); + return; + } +} + +self.addEventListener("message", handleMessage); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13e7668..1acfe3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,12 +16,18 @@ importers: idb: specifier: ^8.0.2 version: 8.0.2 + meal-core: + specifier: link:../core/pkg + version: link:../core/pkg qrcode: specifier: ^1.5.4 version: 1.5.4 solid-js: specifier: ^1.9.5 version: 1.9.5 + workbox-precaching: + specifier: ^7.3.0 + version: 7.3.0 devDependencies: '@claas.dev/material-tailwind': specifier: ^0.6.0 @@ -56,12 +62,6 @@ importers: vite-plugin-wasm: specifier: ^3.4.1 version: 3.4.1(vite@6.0.7(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1)) - workbox-build: - specifier: ^7.3.0 - version: 7.3.0(@types/babel__core@7.20.5) - workbox-precaching: - specifier: ^7.3.0 - version: 7.3.0 tugboat: devDependencies: