can1357/oh-my-pi · error · Error

${normalized.error}

Error message

${normalized.error}

What it means

formatCollabLink first normalizes the relay URL via normalizeRelayOrigin; if the URL is not a valid http(s) origin it returns an error object and formatCollabLink throws with that message instead of producing a link. This fails fast rather than emitting a link pointing at a broken relay.

Source

Thrown at packages/collab-web/src/lib/link.ts:130

}

/**
 * Render the shareable link. Compact forms: the default relay collapses to
 * `<roomId>.<key>`; custom wss relays drop the scheme (`host[:port]/r/…`);
 * plain-ws localhost relays keep the full `ws://` URL.
 *
 * The room secret is dot-joined (`<roomId>.<key>`) rather than `#`-joined:
 * RFC 3986 forbids a raw `#` inside a fragment, so strict URL stacks (macOS
 * Foundation behind terminal click-to-open) percent-encode a second `#` to
 * `%23` and break the link. Parsers still accept the legacy `#` form and the
 * mangled `%23` form.
 *
 * Full links append the write token to the key
 * (`base64url(key ∥ writeToken)`); read-only (view) links carry the bare key.
 */
export function formatCollabLink(relayUrl: string, roomId: string, key: Uint8Array, writeToken?: Uint8Array): string {
	const normalized = normalizeRelayOrigin(relayUrl);
	if ("error" in normalized) throw new Error(normalized.error);
	let secret = key;
	if (writeToken) {
		secret = new Uint8Array(key.byteLength + writeToken.byteLength);
		secret.set(key, 0);
		secret.set(writeToken, key.byteLength);
	}
	const keyText = encodeBase64Url(secret);
	if (normalized.origin === DEFAULT_RELAY_URL) return `${roomId}.${keyText}`;
	const compact = normalized.origin.startsWith("wss://")
		? normalized.origin.slice("wss://".length)
		: normalized.origin;
	return `${compact}/r/${roomId}.${keyText}`;
}

export function parseCollabLink(link: string): ParsedCollabLink | { error: string } {
	// Lenient input: terminals that open OSC 8 links through strict URL stacks
	// (macOS Foundation) percent-encode the legacy second `#` to `%23`.
	let text = link.trim().replace(/%23/gi, "#");

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass a full origin URL including scheme (https:// or http://)
  2. Pre-normalize user-supplied relay settings: prepend https:// when no scheme is present
  3. Validate with normalizeRelayOrigin before calling and surface a friendly config error
  4. Check server config/env for empty or stale RELAY_URL values

Example fix

// before
const link = formatCollabLink(config.relayUrl, roomId, key); // throws if scheme missing
// after
const relay = config.relayUrl.startsWith("http") ? config.relayUrl : "https://" + config.relayUrl;
const link = formatCollabLink(relay, roomId, key);
Defensive patterns

Strategy: validation

Validate before calling

function toRelayOrigin(url: string): string | null {
  try {
    const u = new URL(url);
    return (u.protocol === "https:" || u.protocol === "http:") && u.pathname === "/" ? u.origin : null;
  } catch {
    return null;
  }
}

Try / catch

try {
  const link = formatCollabLink(relayUrl, roomId, key);
} catch (e) {
  throw new Error("Relay URL must be a full http(s) origin, got: " + relayUrl);
}

Prevention

When it happens

Trigger: Calling formatCollabLink with a relayUrl lacking a scheme (e.g. 'myrelay.example' instead of 'https://myrelay.example'), containing a path/query, or being an invalid/unparseable URL.

Common situations: Storing relay addresses in config without the https:// scheme, users typing 'localhost:8080' into a settings field, environment-specific relay URLs that are empty or contain whitespace.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/5c3eaf4637b788bd. Report an issue: GitHub.