Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
1 change: 0 additions & 1 deletion js/hang/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
],
"files": [
"./src",
"./dist",
"README.md",
"tsconfig.json"
],
Expand Down
8 changes: 1 addition & 7 deletions js/hang/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type ConnectionProps = {
maxDelay?: Time.Milli;
};

export type ConnectionStatus = "connecting" | "connected" | "disconnected" | "unsupported";
export type ConnectionStatus = "connecting" | "connected" | "disconnected";

export class Connection {
url: Signal<URL | undefined>;
Expand All @@ -44,12 +44,6 @@ export class Connection {

this.#delay = this.delay;

if (typeof WebTransport === "undefined") {
console.warn("WebTransport is not supported");
this.status.set("unsupported");
return;
}

// Create a reactive root so cleanup is easier.
this.signals.effect(this.#connect.bind(this));
}
Expand Down
2 changes: 1 addition & 1 deletion js/hang/src/support/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export default class HangSupport extends HTMLElement {
container.appendChild(col3Div);
};

addRow("WebTransport", "", binary(support.webtransport));
addRow("WebTransport", "", partial(support.webtransport));

if (mode !== "core") {
if (mode !== "watch") {
Expand Down
4 changes: 2 additions & 2 deletions js/hang/src/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type Video = {
};

export type Full = {
webtransport: boolean;
webtransport: Partial;
audio: {
capture: boolean;
encoding: Audio | undefined;
Expand Down Expand Up @@ -115,7 +115,7 @@ async function videoEncoderSupported(codec: keyof typeof CODECS) {

export async function isSupported(): Promise<Full> {
return {
webtransport: typeof WebTransport !== "undefined",
webtransport: typeof WebTransport !== "undefined" ? "full" : "partial",
audio: {
capture: typeof AudioWorkletNode !== "undefined",
encoding:
Expand Down
8 changes: 4 additions & 4 deletions js/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ default:

# Run the web server
web url='http://localhost:4443/anon':
pnpm -r i
pnpm i
VITE_RELAY_URL="{{url}}" pnpm -r run dev

# Run the CI checks
check flags="":
pnpm -r install {{flags}}
pnpm install {{flags}}

# Make sure Typescript compiles
pnpm -r run check
Expand All @@ -30,7 +30,7 @@ check flags="":
# Automatically fix some issues.
fix flags="":
# Fix the JS packages
pnpm -r install {{flags}}
pnpm install {{flags}}

# Format and lint
pnpm exec biome check --fix
Expand All @@ -44,5 +44,5 @@ upgrade:

# Build the packages
build flags="":
pnpm -r install {{flags}}
pnpm install {{flags}}
pnpm -r run build
5 changes: 3 additions & 2 deletions js/moq/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
"scripts": {
"build": "rimraf dist && tsc -b && tsx ../scripts/package.ts",
"check": "tsc --noEmit",
"test": "tsx --test",
"test": "tsx --test 'src/**/*.test.ts'",
"release": "tsx ../scripts/release.ts"
},
"dependencies": {
"async-mutex": "^0.5.0"
"async-mutex": "^0.5.0",
"@kixelated/web-transport-ws": "^0.1"
},
"peerDependencies": {
"zod": "^4.0.0"
Expand Down
60 changes: 39 additions & 21 deletions js/moq/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import type * as Path from "./path";
import { Stream } from "./stream";
import * as Hex from "./util/hex";

// Check if we need to load the WebSocket polyfill.
let polyfill: Promise<typeof import("@kixelated/web-transport-ws")>;
if (typeof globalThis !== "undefined" && !("WebTransport" in globalThis)) {
polyfill = import("@kixelated/web-transport-ws");
}

export interface Connection {
readonly url: URL;

Expand All @@ -26,33 +32,45 @@ export async function connect(url: URL): Promise<Connection> {
const options: WebTransportOptions = {
allowPooling: false,
congestionControl: "low-latency",
requireUnreliable: true,
};

let adjustedUrl = url;
let finalUrl = url;

let quic: WebTransport;
if (polyfill) {
console.warn("Using web-transport-ws polyfill; user experience may suffer during congestion.");
const WebTransportWs = (await polyfill).default;
quic = new WebTransportWs(finalUrl, options);
} else {
// Only perform certificate fetch and URL rewrite when polyfill is not needed
// This is needed because WebTransport is a butt to work with in local development.
if (url.protocol === "http:") {
const fingerprintUrl = new URL(url);
fingerprintUrl.pathname = "/certificate.sha256";
fingerprintUrl.search = "";
console.warn(
fingerprintUrl.toString(),
"performing an insecure fingerprint fetch; use https:// in production",
);

if (url.protocol === "http:") {
const fingerprintUrl = new URL(url);
fingerprintUrl.pathname = "/certificate.sha256";
fingerprintUrl.search = "";
console.warn(fingerprintUrl.toString(), "performing an insecure fingerprint fetch; use https:// in production");
// Fetch the fingerprint from the server.
const fingerprint = await fetch(fingerprintUrl);
const fingerprintText = await fingerprint.text();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden fingerprint fetch: check status and trim whitespace.
Avoids opaque failures on 404/HTML and handles trailing newlines.

Apply this diff:

-			// Fetch the fingerprint from the server.
-			const fingerprint = await fetch(fingerprintUrl);
-			const fingerprintText = await fingerprint.text();
+			// Fetch the fingerprint from the server.
+			const res = await fetch(fingerprintUrl);
+			if (!res.ok) {
+				throw new Error(`fingerprint fetch failed: ${res.status} ${res.statusText}`);
+			}
+			const fingerprintText = (await res.text()).trim();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Fetch the fingerprint from the server.
const fingerprint = await fetch(fingerprintUrl);
const fingerprintText = await fingerprint.text();
// Fetch the fingerprint from the server.
- const fingerprint = await fetch(fingerprintUrl);
const res = await fetch(fingerprintUrl);
if (!res.ok) {
throw new Error(`fingerprint fetch failed: ${res.status} ${res.statusText}`);
}
const fingerprintText = (await res.text()).trim();
🤖 Prompt for AI Agents
In js/moq/src/connection.ts around lines 56 to 59, the code fetches the
fingerprint but doesn't validate the HTTP status or trim whitespace; update the
fetch to check response.ok (or status === 200) and if not, read the response
text and throw an error including the HTTP status and response body to avoid
opaque failures on 404/HTML, then call .text(), trim the result (e.g.
fingerprintText.trim()) and use the trimmed fingerprint; ensure any thrown
errors include context (URL and status) for easier debugging.

// Fetch the fingerprint from the server.
const fingerprint = await fetch(fingerprintUrl);
const fingerprintText = await fingerprint.text();
options.serverCertificateHashes = [
{
algorithm: "sha-256",
value: Hex.toBytes(fingerprintText),
},
];

options.serverCertificateHashes = [
{
algorithm: "sha-256",
value: Hex.toBytes(fingerprintText),
},
];
finalUrl = new URL(url);
finalUrl.protocol = "https:";
}

adjustedUrl = new URL(url);
adjustedUrl.protocol = "https:";
quic = new WebTransport(finalUrl, options);
}

const quic = new WebTransport(adjustedUrl, options);
await quic.ready;

// moq-rs currently requires the ROLE extension to be set.
Expand All @@ -75,10 +93,10 @@ export async function connect(url: URL): Promise<Connection> {
const server = await Lite.SessionServer.decode(stream.reader);
if (server.version === Lite.CURRENT_VERSION) {
console.debug("moq-lite session established");
return new Lite.Connection(adjustedUrl, quic, stream);
return new Lite.Connection(finalUrl, quic, stream);
} else if (server.version === Ietf.CURRENT_VERSION) {
console.debug("moq-ietf session established");
return new Ietf.Connection(adjustedUrl, quic, stream);
return new Ietf.Connection(finalUrl, quic, stream);
} else {
throw new Error(`unsupported server version: ${server.version.toString()}`);
}
Expand Down
16 changes: 9 additions & 7 deletions js/moq/src/lite/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,16 @@ export class Connection implements ConnectionInterface {
}

async #runSession() {
// Receive messages until the connection is closed.
for (;;) {
const msg = await SessionInfo.decodeMaybe(this.#session.reader);
if (!msg) break;
// TODO use the session info
try {
// Receive messages until the connection is closed.
for (;;) {
const msg = await SessionInfo.decodeMaybe(this.#session.reader);
if (!msg) break;
// TODO use the session info
}
} finally {
console.warn("session stream closed");
}

console.warn("session stream closed");
}

async #runBidis() {
Expand Down
12 changes: 10 additions & 2 deletions js/moq/src/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,19 @@ export class Reader {

// Adds more data to the buffer, returning true if more data was added.
async #fill(): Promise<boolean> {
const result = await this.#reader?.read();
if (!result || result.done) {
if (!this.#reader) {
return false;
}

const result = await this.#reader.read();
if (result.done) {
return false;
}

if (result.value.byteLength === 0) {
throw new Error("unexpected empty chunk");
}

const buffer = new Uint8Array(result.value);

if (this.#buffer.byteLength === 0) {
Expand Down
8 changes: 8 additions & 0 deletions js/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions js/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
"moduleResolution": "bundler",

"declaration": true,
"declarationMap": true,
//"declarationMap": true,
"isolatedModules": true,
"sourceMap": true,
//"sourceMap": true,

// https://www.typescriptlang.org/tsconfig/#Type_Checking_6248
"allowUnreachableCode": false,
Expand Down
2 changes: 0 additions & 2 deletions rs/.cargo/config.toml

This file was deleted.

Loading
Loading