Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 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
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ PUBLIC_SITE_URL=https://openshock.app
PUBLIC_SITE_SHORT_URL=https://openshock.app
PUBLIC_BACKEND_API_URL=https://api.openshock.app
PUBLIC_GATEWAY_CSP_WILDCARD=https://*.openshock.app
PUBLIC_FIRMWARE_REPO_URL=https://repo.openshock.org

# Server-side only (Node adapter). When set to `true`, disables TLS certificate
# validation for the server's own outgoing requests, allowing SSR/API calls to a
Expand All @@ -41,4 +42,4 @@ PUBLIC_SIGNOZ_TRACE_PROPAGATION=false
PUBLIC_SIGNOZ_DEPLOYMENT_ENVIRONMENT=
# Extra OTel resource attributes, comma-separated key=value pairs (same format as the standard
# OTEL_RESOURCE_ATTRIBUTES env var). Example: deployment.region=eu,team=frontend
PUBLIC_SIGNOZ_RESOURCE_ATTRIBUTES=
PUBLIC_SIGNOZ_RESOURCE_ATTRIBUTES=
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ PUBLIC_SITE_URL=https://openshock.dev
PUBLIC_SITE_SHORT_URL=https://openshock.dev
PUBLIC_BACKEND_API_URL=https://api.openshock.dev
PUBLIC_GATEWAY_CSP_WILDCARD=https://*.openshock.dev
PUBLIC_FIRMWARE_REPO_URL=https://repo.openshock.dev

PUBLIC_TURNSTILE_DEV_BYPASS_VALUE=dev-bypass
PUBLIC_DEVELOPMENT_BANNER=true
125 changes: 0 additions & 125 deletions src/lib/api/firmwareCDN.ts

This file was deleted.

111 changes: 111 additions & 0 deletions src/lib/api/firmwareRepo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { PUBLIC_FIRMWARE_REPO_URL } from '$env/static/public';
import { HashBuffer } from '@openshock/svelte-core/utils/crypto.js';

export const FirmwareChannels = ['stable', 'beta', 'develop'] as const;
export type FirmwareChannel = (typeof FirmwareChannels)[number];

export interface FirmwareArtifact {
type: string;
url: string;
sha256Hash: string;
fileSize: number;
}

export interface FirmwareBoard {
chip: string;
deprecated: boolean;
artifacts: FirmwareArtifact[];
}

export interface FirmwareRelease {
version: string;
channel: string;
releaseDate: string;
changelog: string;
boards: Record<string, FirmwareBoard>;
}

export interface FirmwareUpdateResponse {
version: string;
artifact: FirmwareArtifact;
}

export interface FirmwareVersionSummary {
version: string;
channel: string;
releaseDate: string;
changelog: string;
}

const BASE_URL = PUBLIC_FIRMWARE_REPO_URL.replace(/\/+$/, '');

export async function FetchLatest(channel: FirmwareChannel): Promise<FirmwareRelease> {
const response = await fetch(`${BASE_URL}/v2/firmware/latest/${channel}`);
if (!response.ok)
throw new Error(`Failed to fetch latest firmware: ${response.status} ${response.statusText}`);
return await response.json();
}

export async function FetchVersion(
channel: FirmwareChannel,
version: string
): Promise<FirmwareRelease> {
const response = await fetch(`${BASE_URL}/v2/firmware/versions/${channel}/${version}`);
if (!response.ok)
throw new Error(`Failed to fetch firmware version: ${response.status} ${response.statusText}`);
return await response.json();
}

export async function FetchVersionHistory(
channel: FirmwareChannel,
limit = 20,
offset = 0
): Promise<{ versions: FirmwareVersionSummary[]; total: number }> {
const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
const response = await fetch(`${BASE_URL}/v2/firmware/versions/${channel}?${params}`);
if (!response.ok)
throw new Error(`Failed to fetch version history: ${response.status} ${response.statusText}`);
return await response.json();
}

export function ExtractBoards(
release: FirmwareRelease,
chip?: string | null,
includeDeprecated = false
): string[] {
const entries = Object.entries(release.boards);
const filtered = entries.filter(([, board]) => {
if (!includeDeprecated && board.deprecated) return false;
if (chip && board.chip !== chip) return false;
return true;
});
return filtered.map(([name]) => name).sort();
}

export function FindArtifact(
release: FirmwareRelease,
board: string,
type: string
): FirmwareArtifact | null {
const boardInfo = release.boards[board];
if (!boardInfo) return null;
return boardInfo.artifacts.find((a) => a.type === type) ?? null;
}

async function DownloadBinary(url: string): Promise<Uint8Array> {
const response = await fetch(url);
if (!response.ok)
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
return await response.bytes();
}
Comment thread
hhvrc marked this conversation as resolved.
Outdated

export async function DownloadAndVerifyArtifact(artifact: FirmwareArtifact): Promise<Uint8Array> {
const binary = await DownloadBinary(artifact.url);

const calculatedHash = await HashBuffer(binary.buffer as ArrayBuffer, 'SHA-256');
if (calculatedHash.toUpperCase() !== artifact.sha256Hash.toUpperCase()) {
throw new Error(`Hash mismatch: expected ${artifact.sha256Hash}, got ${calculatedHash}`);
}

return binary;
}
36 changes: 22 additions & 14 deletions src/lib/components/FirmwareChannelSelector.svelte
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
<script lang="ts">
import { CircleCheckBig, TriangleAlert } from '@lucide/svelte';
import {
FetchChannelVersion,
FetchLatest,
type FirmwareChannel,
FirmwareChannels,
} from '$lib/api/firmwareCDN';
type FirmwareRelease,
} from '$lib/api/firmwareRepo';
import * as ToggleGroup from '@openshock/svelte-core/components/ui/toggle-group';
import { handleApiError } from '$lib/errorhandling/apiErrorHandling';

/** Optional chip to constrain the list of boards to */
//export let chip: string | null = null;
interface Props {
channel?: FirmwareChannel;
version?: string | null;
latestResponse?: FirmwareRelease | null;
disabled?: boolean;
}

let {
channel = $bindable<FirmwareChannel>('stable'),
version = $bindable(null),

Check warning on line 21 in src/lib/components/FirmwareChannelSelector.svelte

View workflow job for this annotation

GitHub Actions / build-and-test

The value assigned to 'version' is not used in subsequent statements
latestResponse = $bindable(null),

Check warning on line 22 in src/lib/components/FirmwareChannelSelector.svelte

View workflow job for this annotation

GitHub Actions / build-and-test

The value assigned to 'latestResponse' is not used in subsequent statements
disabled = false,
}: Props = $props();

let versions = $state<{ [key in FirmwareChannel]?: string | null }>({});
let cache = $state<{ [key in FirmwareChannel]?: FirmwareRelease | null }>({});

// Reactive effect to update the version based on the selected channel.
$effect(() => {
Expand All @@ -32,22 +33,29 @@
}

// Use a cached value if available.
const cachedVersion = versions[channel];
if (cachedVersion !== undefined) {
version = cachedVersion;
const cached = cache[channel];
if (cached !== undefined) {
latestResponse = cached;
version = cached?.version ?? null;
return;
}

// Capture the current channel to avoid race conditions if channel changes.
const currentChannel = channel;

Comment on lines 45 to 47
// Fetch the channel version from the API.
FetchChannelVersion(currentChannel)
.then((ver) => {
version = ver ?? null;
versions[currentChannel] = version;
// Fetch the latest firmware info from the repository server.
FetchLatest(currentChannel)
.then((resp) => {
latestResponse = resp;
version = resp.version;
cache[currentChannel] = resp;
})
.catch(handleApiError);
.catch((error) => {
latestResponse = null;
version = null;
cache[currentChannel] = null;
handleApiError(error);
});
});
</script>

Expand Down
2 changes: 1 addition & 1 deletion src/routes/(app)/hubs/[hubId=guid]/update/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
import { cn } from '@openshock/svelte-core/utils/shadcn.js';
import { NumberToHexPadded } from '@openshock/svelte-core/utils/convert.js';
import { onMount } from 'svelte';
import type { FirmwareChannel } from '$lib/api/firmwareCDN';
import type { FirmwareChannel } from '$lib/api/firmwareRepo';
import { PageHeader } from '@openshock/svelte-core/components';

let hubLoaded = $state(false);
Expand Down
10 changes: 6 additions & 4 deletions src/routes/terminal/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Zap,
} from '@lucide/svelte';
import { browser } from '$app/env';
import type { FirmwareChannel } from '$lib/api/firmwareCDN';
import type { FirmwareChannel, FirmwareRelease } from '$lib/api/firmwareRepo';
import { Container } from '@openshock/svelte-core/components';
import FirmwareChannelSelector from '$lib/components/FirmwareChannelSelector.svelte';
import { ChromeLogo } from '@openshock/svelte-core/components/svg';
Expand Down Expand Up @@ -46,6 +46,7 @@

let channel = $state<FirmwareChannel>('stable');
let version = $state<string | null>(null);
let latestResponse = $state<FirmwareRelease | null>(null);
// Tracks which channel+version the user explicitly confirmed. Changing either invalidates it.
let confirmedChannel = $state<FirmwareChannel | null>(null);
let confirmedVersion = $state<string | null>(null);
Expand Down Expand Up @@ -325,6 +326,7 @@
<FirmwareChannelSelector
bind:channel
bind:version
bind:latestResponse
disabled={isFlashing}
/>
{#if version}
Expand Down Expand Up @@ -352,7 +354,7 @@
{#if isCurrent}
<div class="flex flex-col gap-4">
<FirmwareBoardSelector
{version}
{latestResponse}
bind:selectedBoard={board}
disabled={isFlashing}
/>
Expand Down Expand Up @@ -381,9 +383,9 @@

<!-- Step 3: Flash -->
{#if i === 2}
{#if isCurrent && version && board && connection}
{#if isCurrent && latestResponse && board && connection}
<FirmwareFlasher
{version}
{latestResponse}
{board}
{connection}
{eraseBeforeFlash}
Expand Down
Loading