Files
paseo/packages/protocol/src/connection-offer.ts
Mohamed Boudra 53c14d9855 Extract client SDK package (#1052)
* Extract client SDK package

* Polish SDK client identity defaults

* Build client before dependent CI jobs

* Restore daemon client server export

* Extract protocol and client SDK packages

* Fix provider override schema validation

* Fix app test daemon client imports

* Simplify workspace build targets

* Fix CLI test server build bootstrap

* Run SDK package tests in CI

* Fix rebase package split drift

* Restore lockfile registry metadata

* Update SDK config test for prompt default

* Move terminal stream router test to client package

* Fix rebase drift for protocol imports

* Fix SDK agent capability fixture

* Restore legacy server client exports

* Fix server export compatibility test

* Advertise custom mode icon client capability

* Remove server daemon-client exports

* Format rebased mode control import

* Fix rebase drift for protocol imports

Files added by upstream PRs (#893, #1147, #1154) referenced the pre-split
shared/ paths that this branch moves into @getpaseo/protocol. Redirect
those imports to the protocol package so typecheck stays green after the
rebase.
2026-05-28 01:58:18 +08:00

60 lines
2.0 KiB
TypeScript

import { z } from "zod";
/**
* Relay-only pairing offer.
*
* `serverId` is a stable daemon identifier scoped to `PASEO_HOME`, and is also
* used as the relay session identifier.
*/
export const ConnectionOfferV2Schema = z.object({
v: z.literal(2),
serverId: z.string().min(1),
daemonPublicKeyB64: z.string().min(1),
relay: z.object({
endpoint: z.string().min(1),
useTls: z.boolean().optional(),
}),
});
export type ConnectionOfferV2 = z.infer<typeof ConnectionOfferV2Schema>;
export const ConnectionOfferSchema = ConnectionOfferV2Schema;
export type ConnectionOffer = ConnectionOfferV2;
function decodeBase64UrlToUtf8(input: string): string {
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=");
const binary = globalThis.atob(padded);
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}
export function decodeOfferFragmentPayload(encoded: string): unknown {
const json = decodeBase64UrlToUtf8(encoded);
return JSON.parse(json) as unknown;
}
const OFFER_FRAGMENT_PREFIX = "#offer=";
function extractOfferFragmentEncoded(input: string): string | null {
const trimmed = input.trim();
if (!trimmed) return null;
const fragmentIndex = trimmed.indexOf(OFFER_FRAGMENT_PREFIX);
if (fragmentIndex === -1) return null;
const encoded = trimmed.slice(fragmentIndex + OFFER_FRAGMENT_PREFIX.length).trim();
return encoded.length > 0 ? encoded : null;
}
/**
* Parse a pairing-offer URL of the form `https://app.paseo.sh/#offer=<base64url>`.
*
* Returns `null` if the input has no `#offer=` fragment. Throws if the fragment
* exists but the payload is malformed or fails schema validation.
*/
export function parseConnectionOfferFromUrl(input: string): ConnectionOffer | null {
const encoded = extractOfferFragmentEncoded(input);
if (!encoded) return null;
const payload = decodeOfferFragmentPayload(encoded);
return ConnectionOfferSchema.parse(payload);
}