diff --git a/app/package.json b/app/package.json index 3e9605a..a6850e6 100644 --- a/app/package.json +++ b/app/package.json @@ -8,15 +8,16 @@ "build": "tsc && vite build", "preview": "vite preview", "wasm": "wasm-pack build --target web ../core", - "wasm:dev": "wasm-pack build --target web --dev ../core" + "wasm:dev": "wasm-pack build --target web --dev ../core", + "test:ui": "playwright test --ui --headed" }, "dependencies": { - "@solidjs/router": "^0.15.3", + "@solidjs/router": "^0.15.4", "@sqlite.org/sqlite-wasm": "3.49.1-build4", - "idb": "^8.0.2", + "idb": "^8.0.3", "meal-core": "link:../core/pkg", "qrcode": "^1.5.4", - "solid-js": "^1.9.5", + "solid-js": "^1.9.10", "workbox-precaching": "^7.3.0" }, "devDependencies": { diff --git a/app/playwright.config.ts b/app/playwright.config.ts index 86aa9b4..28fa736 100644 --- a/app/playwright.config.ts +++ b/app/playwright.config.ts @@ -75,5 +75,5 @@ export default defineConfig({ // url: 'http://localhost:3000', // reuseExistingServer: !process.env.CI, // }, - timeout: 5 * 60 * 1_000, + // timeout: 5 * 60 * 1_000, }); diff --git a/app/src/components/AppContextProvider.tsx b/app/src/components/AppContextProvider.tsx index 61fac56..72f3be3 100644 --- a/app/src/components/AppContextProvider.tsx +++ b/app/src/components/AppContextProvider.tsx @@ -20,6 +20,7 @@ export function AppProvider(properties: ParentProps) { status: "ready", groups: [], getGroup(groupId: string): Group | undefined { + console.debug("Getting group", groupId, "from groups", app.groups); // Often used groups are at the top so this should be fine return app.groups?.find((group) => group.id === groupId); }, @@ -30,6 +31,7 @@ export function AppProvider(properties: ParentProps) { switch (event.data.type) { case "Group created": { + console.debug("Group created. Adding to groups", event.data.group); const group = event.data.group; setApp("groups", app.groups.length, group); return; diff --git a/app/src/routes/invite.tsx b/app/src/routes/invite.tsx index 0302567..6ada6f8 100644 --- a/app/src/routes/invite.tsx +++ b/app/src/routes/invite.tsx @@ -143,7 +143,9 @@ export default function Invite() { //TODO check if user has automatic accept enabled //TODO check if this welcome is for this key package + console.debug("Group created. Navigating to chat", event.data.group.id); navigate(ROUTES.chat(event.data.group.id)); + console.debug("Navigated to chat"); }); const [configuration] = createResource(getConfiguration); diff --git a/app/src/routes/join.tsx b/app/src/routes/join.tsx index cf7c490..e7b82e9 100644 --- a/app/src/routes/join.tsx +++ b/app/src/routes/join.tsx @@ -100,10 +100,10 @@ export default function Join() { autocomplete="username" required disabled={configuration.loading} - value={configuration()?.defaultUser?.name} + value={configuration()?.defaultUser?.name ?? ""} /> - diff --git a/app/src/service-worker/serviceWorker.ts b/app/src/service-worker/serviceWorker.ts index 87624ad..c312f8e 100644 --- a/app/src/service-worker/serviceWorker.ts +++ b/app/src/service-worker/serviceWorker.ts @@ -212,6 +212,7 @@ const handler = { }; await insertGroup(group); + broadcastMessage({ type: "Group created", group }); return group; }, @@ -327,6 +328,35 @@ async function receiveMessage(event: MessageEvent) { } } +function toState(reaedyState: number) { + switch (reaedyState) { + case WebSocket.CONNECTING: + return "connecting"; + case WebSocket.OPEN: + return "open"; + case WebSocket.CLOSING: + return "closing"; + case WebSocket.CLOSED: + return "closed"; + } +} + +function startKeepAlive(socket: WebSocket) { + const intervalId = setInterval(() => { + if (socket.readyState !== WebSocket.OPEN) { + //TODO clean up + console.debug("[Service worker] Socket not open. Not sending keep alive"); + return; + } + + console.debug( + "[Service worker] Sending keep alive. Socker state", + toState(socket.readyState) + ); + socket.send("keep alive"); + }, 10_000); +} + //TODO why did I decide to use a websocket instead of SSE? Replace with SSE async function setupWebsocket() { // Assume client id does not change @@ -339,7 +369,12 @@ async function setupWebsocket() { socket.binaryType = "arraybuffer"; socket.addEventListener("message", receiveMessage); socket.addEventListener("close", (event) => { - console.warn("Socket closed. Not implemented.", event); + console.warn("[Service worker] Socket closed. Not implemented.", event); + }); + + socket.addEventListener("open", () => startKeepAlive(socket)); + socket.addEventListener("error", (event) => { + console.error("[Service worker] Socket error", event); }); return socket; diff --git a/app/tests/invite.spec.ts b/app/tests/invite.spec.ts index 65e8522..ef38479 100644 --- a/app/tests/invite.spec.ts +++ b/app/tests/invite.spec.ts @@ -1,4 +1,4 @@ -import { test, expect, Page } from "@playwright/test"; +import { test, expect, Page, Browser } from "@playwright/test"; import { GenericContainer } from "testcontainers"; // test.beforeEach(async () => { @@ -23,11 +23,25 @@ async function readClipboard(page: Page) { }); } -test("A user can start a chat with another user", async ({ page, context }) => { +async function setUpFriend(browser: Browser): Promise<{ + page: Page; + [Symbol.asyncDispose](): Promise; +}> { + const friendContext = await browser.newContext(); + const friendPage = await friendContext.newPage(); + + return { page: friendPage, [Symbol.asyncDispose]: friendContext.close }; +} + +test("A user can start a chat with another user", async ({ + page, + context, + browser, +}) => { + const friendSetup = setUpFriend(browser); await page.goto("http://localhost:5173/invite"); // Page ready - const copyButton = page.getByText("Copy"); // await page.locator("button:has-text('Copy')").waitFor({ state: "attached" }); await copyButton.waitFor({ state: "visible" }); @@ -44,4 +58,29 @@ test("A user can start a chat with another user", async ({ page, context }) => { const invite = await readClipboard(page); // Starts with "http://localhost:5173/join/" expect(invite).toMatch(/^http:\/\/localhost:5173\/join\//); + + + + await using friend = await friendSetup; + await friend.page.goto(invite); + const acceptButton = friend.page.getByText("Accept"); + await acceptButton.waitFor({ state: "visible" }); + + const nameInputLabel = friend.page.getByText("Your name"); + await nameInputLabel.click(); + + await friend.page.keyboard.type("test friend"); + + // await friend.page.keyboard.press("Enter"); + await acceptButton.click(); + + // Wait for navigation + const chatUrlPattern = /^http:\/\/localhost:5173\/chat\//; + await friend.page.waitForURL(chatUrlPattern); + await friend.page.waitForSelector("h1:has-text('Chat')"); + + await page.waitForURL(chatUrlPattern); + + + }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 21466df..b9c4858 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,14 +11,14 @@ importers: app: dependencies: '@solidjs/router': - specifier: ^0.15.3 - version: 0.15.3(solid-js@1.9.5) + specifier: ^0.15.4 + version: 0.15.4(solid-js@1.9.10) '@sqlite.org/sqlite-wasm': specifier: 3.49.1-build4 version: 3.49.1-build4 idb: - specifier: ^8.0.2 - version: 8.0.2 + specifier: ^8.0.3 + version: 8.0.3 meal-core: specifier: link:../core/pkg version: link:../core/pkg @@ -26,8 +26,8 @@ importers: specifier: ^1.5.4 version: 1.5.4 solid-js: - specifier: ^1.9.5 - version: 1.9.5 + specifier: ^1.9.10 + version: 1.9.10 workbox-precaching: specifier: ^7.3.0 version: 7.3.0 @@ -70,7 +70,7 @@ importers: version: 1.0.0(vite@6.0.7(@types/node@25.0.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.6(solid-js@1.9.5)(vite@6.0.7(@types/node@25.0.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1)) + version: 2.11.6(solid-js@1.9.10)(vite@6.0.7(@types/node@25.0.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1)) vite-plugin-wasm: specifier: ^3.4.1 version: 3.4.1(vite@6.0.7(@types/node@25.0.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1)) @@ -1131,8 +1131,8 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@solidjs/router@0.15.3': - resolution: {integrity: sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==} + '@solidjs/router@0.15.4': + resolution: {integrity: sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ==} peerDependencies: solid-js: ^1.8.6 @@ -2036,8 +2036,8 @@ packages: idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} - idb@8.0.2: - resolution: {integrity: sha512-CX70rYhx7GDDQzwwQMDwF6kDRQi5vVs6khHUumDrMecBylKkwvZ8HWvKV08AGb7VbpoGCWUQ4aHzNDgoUiOIUg==} + idb@8.0.3: + resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==} ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2739,14 +2739,14 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - seroval-plugins@1.1.1: - resolution: {integrity: sha512-qNSy1+nUj7hsCOon7AO4wdAIo9P0jrzAMp18XhiOzA6/uO5TKtP7ScozVJ8T293oRIvi5wyCHSM4TrJo/c/GJA==} + seroval-plugins@1.3.3: + resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} engines: {node: '>=10'} peerDependencies: seroval: ^1.0 - seroval@1.1.1: - resolution: {integrity: sha512-rqEO6FZk8mv7Hyv4UCj3FD3b6Waqft605TLfsCe/BiaylRpyyMC0b+uA5TJKawX3KzMrdi3wsLbCaLplrQmBvQ==} + seroval@1.3.2: + resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} engines: {node: '>=10'} set-blocking@2.0.0: @@ -2798,8 +2798,8 @@ packages: smob@1.5.0: resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} - solid-js@1.9.5: - resolution: {integrity: sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==} + solid-js@1.9.10: + resolution: {integrity: sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==} solid-refresh@0.6.3: resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} @@ -3414,7 +3414,7 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-compilation-targets': 7.27.0 '@babel/helper-plugin-utils': 7.26.5 - debug: 4.3.7 + debug: 4.4.3 lodash.debounce: 4.0.8 resolve: 1.22.10 transitivePeerDependencies: @@ -4040,7 +4040,7 @@ snapshots: '@babel/parser': 7.27.0 '@babel/template': 7.27.0 '@babel/types': 7.27.0 - debug: 4.3.7 + debug: 4.4.3 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4394,9 +4394,9 @@ snapshots: '@sinclair/typebox@0.27.8': {} - '@solidjs/router@0.15.3(solid-js@1.9.5)': + '@solidjs/router@0.15.4(solid-js@1.9.10)': dependencies: - solid-js: 1.9.5 + solid-js: 1.9.10 '@sqlite.org/sqlite-wasm@3.49.1-build4': {} @@ -5385,7 +5385,7 @@ snapshots: idb@7.1.1: {} - idb@8.0.2: {} + idb@8.0.3: {} ieee754@1.2.1: {} @@ -6066,11 +6066,11 @@ snapshots: dependencies: randombytes: 2.1.0 - seroval-plugins@1.1.1(seroval@1.1.1): + seroval-plugins@1.3.3(seroval@1.3.2): dependencies: - seroval: 1.1.1 + seroval: 1.3.2 - seroval@1.1.1: {} + seroval@1.3.2: {} set-blocking@2.0.0: {} @@ -6136,18 +6136,18 @@ snapshots: smob@1.5.0: {} - solid-js@1.9.5: + solid-js@1.9.10: dependencies: csstype: 3.1.3 - seroval: 1.1.1 - seroval-plugins: 1.1.1(seroval@1.1.1) + seroval: 1.3.2 + seroval-plugins: 1.3.3(seroval@1.3.2) - solid-refresh@0.6.3(solid-js@1.9.5): + solid-refresh@0.6.3(solid-js@1.9.10): dependencies: '@babel/generator': 7.25.6 '@babel/helper-module-imports': 7.24.7 '@babel/types': 7.25.6 - solid-js: 1.9.5 + solid-js: 1.9.10 transitivePeerDependencies: - supports-color @@ -6495,14 +6495,14 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-solid@2.11.6(solid-js@1.9.5)(vite@6.0.7(@types/node@25.0.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1)): + vite-plugin-solid@2.11.6(solid-js@1.9.10)(vite@6.0.7(@types/node@25.0.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1)): dependencies: '@babel/core': 7.25.2 '@types/babel__core': 7.20.5 babel-preset-solid: 1.9.0(@babel/core@7.25.2) merge-anything: 5.1.7 - solid-js: 1.9.5 - solid-refresh: 0.6.3(solid-js@1.9.5) + solid-js: 1.9.10 + solid-refresh: 0.6.3(solid-js@1.9.10) vite: 6.0.7(@types/node@25.0.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1) vitefu: 1.0.5(vite@6.0.7(@types/node@25.0.1)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1)) transitivePeerDependencies: