From 6735d9ec73949046932716dba90ebd9cd63b513d Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 21 Sep 2026 22:49:19 -0700 Subject: Web flasher moves from /pos to /flash; /pos redirects there. coinos v3 takes /pos for the point-of-sale till, so the terminal flasher needs its own address. Old links keep working through the redirect. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp --- src/routes/(app)/flash/+page.server.ts | 58 ++++++ src/routes/(app)/flash/+page.svelte | 360 +++++++++++++++++++++++++++++++++ src/routes/(app)/pos/+page.server.ts | 58 ------ src/routes/(app)/pos/+page.svelte | 360 --------------------------------- src/routes/(app)/pos/+page.ts | 6 + 5 files changed, 424 insertions(+), 418 deletions(-) create mode 100644 src/routes/(app)/flash/+page.server.ts create mode 100644 src/routes/(app)/flash/+page.svelte delete mode 100644 src/routes/(app)/pos/+page.server.ts delete mode 100644 src/routes/(app)/pos/+page.svelte create mode 100644 src/routes/(app)/pos/+page.ts diff --git a/src/routes/(app)/flash/+page.server.ts b/src/routes/(app)/flash/+page.server.ts new file mode 100644 index 00000000..1e3e1e99 --- /dev/null +++ b/src/routes/(app)/flash/+page.server.ts @@ -0,0 +1,58 @@ +import { PUBLIC_COINOS_URL } from "$env/static/public"; +import { auth, fd, get } from "$lib/utils"; +import { hex } from "@scure/base"; +import { fail } from "@sveltejs/kit"; + +const LITTLEFS_SIZE = 0x20000; +const LITTLEFS_MAGIC = "littlefs"; + +// A valid image is exactly one partition long and carries the LittleFS +// superblock magic at byte 8. Anything else (e.g. a JSON error body from +// the server) must never reach the flasher. +const isLittleFS = (buf: Uint8Array) => + buf.length === LITTLEFS_SIZE && + new TextDecoder().decode(buf.subarray(8, 8 + LITTLEFS_MAGIC.length)) === + LITTLEFS_MAGIC; + +export async function load({ cookies }) { + let token; + + try { + token = await get("/ro", auth(cookies)); + } catch (e) {} + + return { token }; +} + +export const actions = { + default: async ({ fetch, request }) => { + const body = await fd(request); + const res = await fetch(`${PUBLIC_COINOS_URL}/flash`, { + method: "POST", + body: JSON.stringify(body), + headers: { + "content-type": "application/json", + accept: "application/octet-stream", + }, + }); + + const buf = new Uint8Array(await res.arrayBuffer()); + + if (!res.ok) { + let message = `Config server returned ${res.status}`; + try { + message = JSON.parse(new TextDecoder().decode(buf)).message || message; + } catch (e) {} + return fail(res.status >= 400 && res.status < 600 ? res.status : 500, { + error: message, + }); + } + + if (!isLittleFS(buf)) + return fail(500, { + error: `Config server returned an invalid image (${buf.length} bytes)`, + }); + + return { bytes: hex.encode(buf) }; + }, +}; diff --git a/src/routes/(app)/flash/+page.svelte b/src/routes/(app)/flash/+page.svelte new file mode 100644 index 00000000..5a11d148 --- /dev/null +++ b/src/routes/(app)/flash/+page.svelte @@ -0,0 +1,360 @@ + + +
+

+ ESP32-C3 Flasher +

+ + {#if !connected} +
+ {#if configDone} +
Config written ✔ Device restarted
+ {:else if fwDone} +
Firmware written ✔ Device restarted
+ {/if} + +
{portInfo}
+
+{/if} + + {#if connected} + +
+ + + +
+ + {#if tab === "config"} + + {#if bytes} + {#if configDone} +
Config written ✔
+ {:else} +
Generated config file ({bytes.length} bytes)
+ + (littlefsAddress = Number(e.target.value))} /> +
+ +
+ {#if configProgress > 0}
Progress: {configProgress}%
{/if} + {/if} + {:else} + {#if configError} +
{configError}
+ {/if} +
+ + + + +
+ {/if} + {:else} + +
+ {#if release} +
+
Latest release: {release.version}
+
Built {release.built} · flashes bootloader, partitions and app; keeps your wifi/token config
+ +
+ {:else if releaseError} +
{releaseError}
+ {/if} + + + + + + (fwAddress = Number(e.target.value))} /> + + + +
+ +
+ + {#if fwProgress > 0 && !fwDone} +
Progress: {fwProgress}%
+ {/if} + {#if fwDone} +
Firmware written ✔
+ {/if} + {#if fwError} +
{fwError}
+ {/if} +
+ {/if} + {/if} +
+ + diff --git a/src/routes/(app)/pos/+page.server.ts b/src/routes/(app)/pos/+page.server.ts deleted file mode 100644 index 1e3e1e99..00000000 --- a/src/routes/(app)/pos/+page.server.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { PUBLIC_COINOS_URL } from "$env/static/public"; -import { auth, fd, get } from "$lib/utils"; -import { hex } from "@scure/base"; -import { fail } from "@sveltejs/kit"; - -const LITTLEFS_SIZE = 0x20000; -const LITTLEFS_MAGIC = "littlefs"; - -// A valid image is exactly one partition long and carries the LittleFS -// superblock magic at byte 8. Anything else (e.g. a JSON error body from -// the server) must never reach the flasher. -const isLittleFS = (buf: Uint8Array) => - buf.length === LITTLEFS_SIZE && - new TextDecoder().decode(buf.subarray(8, 8 + LITTLEFS_MAGIC.length)) === - LITTLEFS_MAGIC; - -export async function load({ cookies }) { - let token; - - try { - token = await get("/ro", auth(cookies)); - } catch (e) {} - - return { token }; -} - -export const actions = { - default: async ({ fetch, request }) => { - const body = await fd(request); - const res = await fetch(`${PUBLIC_COINOS_URL}/flash`, { - method: "POST", - body: JSON.stringify(body), - headers: { - "content-type": "application/json", - accept: "application/octet-stream", - }, - }); - - const buf = new Uint8Array(await res.arrayBuffer()); - - if (!res.ok) { - let message = `Config server returned ${res.status}`; - try { - message = JSON.parse(new TextDecoder().decode(buf)).message || message; - } catch (e) {} - return fail(res.status >= 400 && res.status < 600 ? res.status : 500, { - error: message, - }); - } - - if (!isLittleFS(buf)) - return fail(500, { - error: `Config server returned an invalid image (${buf.length} bytes)`, - }); - - return { bytes: hex.encode(buf) }; - }, -}; diff --git a/src/routes/(app)/pos/+page.svelte b/src/routes/(app)/pos/+page.svelte deleted file mode 100644 index 5a11d148..00000000 --- a/src/routes/(app)/pos/+page.svelte +++ /dev/null @@ -1,360 +0,0 @@ - - -
-

- ESP32-C3 Flasher -

- - {#if !connected} -
- {#if configDone} -
Config written ✔ Device restarted
- {:else if fwDone} -
Firmware written ✔ Device restarted
- {/if} - -
{portInfo}
-
-{/if} - - {#if connected} - -
- - - -
- - {#if tab === "config"} - - {#if bytes} - {#if configDone} -
Config written ✔
- {:else} -
Generated config file ({bytes.length} bytes)
- - (littlefsAddress = Number(e.target.value))} /> -
- -
- {#if configProgress > 0}
Progress: {configProgress}%
{/if} - {/if} - {:else} - {#if configError} -
{configError}
- {/if} -
- - - - -
- {/if} - {:else} - -
- {#if release} -
-
Latest release: {release.version}
-
Built {release.built} · flashes bootloader, partitions and app; keeps your wifi/token config
- -
- {:else if releaseError} -
{releaseError}
- {/if} - - - - - - (fwAddress = Number(e.target.value))} /> - - - -
- -
- - {#if fwProgress > 0 && !fwDone} -
Progress: {fwProgress}%
- {/if} - {#if fwDone} -
Firmware written ✔
- {/if} - {#if fwError} -
{fwError}
- {/if} -
- {/if} - {/if} -
- - diff --git a/src/routes/(app)/pos/+page.ts b/src/routes/(app)/pos/+page.ts new file mode 100644 index 00000000..393950d8 --- /dev/null +++ b/src/routes/(app)/pos/+page.ts @@ -0,0 +1,6 @@ +import { redirect } from "@sveltejs/kit"; + +// The web flasher moved to /flash: /pos is the coinos v3 till. +export const load = () => { + redirect(301, "/flash"); +}; -- cgit v1.2.3