fix(pairing): shrink mobile QR payload

This commit is contained in:
Ryan Vogel
2026-04-12 21:17:36 +00:00
parent e8b4eb8972
commit a4d4a3f545
4 changed files with 24 additions and 22 deletions

View File

@@ -4,3 +4,4 @@
- Need to figure out a good way to start new sessions.
- When an agent returns a generation, we should be able to expand it into a reader mode view.
- Work on the live activity widget.
- In the OpenCode Control app, if a link is generated in Markdown, it should be tappable and open in the device's default browser.

View File

@@ -370,7 +370,6 @@ function formatWorkingDirectory(directory?: string): string {
type DropdownMode = "none" | "server" | "session"
type Pair = {
v: 1
serverID?: string
relayURL: string
relaySecret: string
@@ -454,18 +453,19 @@ type Cam = {
function parsePairShape(data: unknown): Pair | undefined {
if (!data || typeof data !== "object") return
if ((data as { v?: unknown }).v !== 1) return
if (typeof (data as { relayURL?: unknown }).relayURL !== "string") return
const version = (data as { v?: unknown }).v
if (version !== undefined && version !== 1) return
if (typeof (data as { relaySecret?: unknown }).relaySecret !== "string") return
if (!Array.isArray((data as { hosts?: unknown }).hosts)) return
const hosts = (data as { hosts: unknown[] }).hosts.filter((item): item is string => typeof item === "string")
if (!hosts.length) return
const relayURLRaw = (data as { relayURL?: unknown }).relayURL
const relayURL = typeof relayURLRaw === "string" && relayURLRaw.length > 0 ? relayURLRaw : DEFAULT_RELAY_URL
const serverIDRaw = (data as { serverID?: unknown }).serverID
const serverID = typeof serverIDRaw === "string" && serverIDRaw.length > 0 ? serverIDRaw : undefined
return {
v: 1,
serverID,
relayURL: (data as { relayURL: string }).relayURL,
relayURL,
relaySecret: (data as { relaySecret: string }).relaySecret,
hosts,
}

View File

@@ -15,13 +15,17 @@ import * as QRCode from "qrcode"
const log = Log.create({ service: "serve" })
type PairPayload = {
v: 1
serverID?: string
relayURL: string
relaySecret: string
hosts: string[]
}
type PairQRCodePayload = {
relaySecret: string
hosts: string[]
}
type TailscaleStatus = {
Self?: {
DNSName?: unknown
@@ -102,12 +106,12 @@ function hosts(hostname: string, port: number, advertised: string[] = [], includ
return [...preferred, ...entries.map((item) => item.url)]
}
function pairLink(pair: unknown) {
return `mobilevoice:///?pair=${encodeURIComponent(JSON.stringify(pair))}`
}
function pairServerID(input: { relayURL: string; relaySecret: string }) {
return createHash("sha256").update(`${input.relayURL}|${input.relaySecret}`).digest("hex").slice(0, 16)
function pairLink(pair: PairQRCodePayload) {
const payload: PairQRCodePayload = {
relaySecret: pair.relaySecret,
hosts: pair.hosts,
}
return `mobilevoice:///?pair=${encodeURIComponent(JSON.stringify(payload))}`
}
function secretHash(input: string) {
@@ -240,8 +244,6 @@ export const ServeCommand = cmd({
console.log("printing connect qr without starting the server")
await printPairQR({
v: 1,
serverID: pairServerID({ relayURL, relaySecret }),
relayURL,
relaySecret,
hosts: pairHosts,
@@ -274,8 +276,6 @@ export const ServeCommand = cmd({
})
const pair = started ??
PushRelay.pair() ?? {
v: 1 as const,
serverID: pairServerID({ relayURL, relaySecret }),
relayURL,
relaySecret,
hosts: hosts(host, port, advertiseHosts),

View File

@@ -21,9 +21,6 @@ import { Agent } from "@/agent/agent"
const PushPairPayload = z
.object({
v: z.literal(1),
serverID: z.string().optional(),
relayURL: z.string(),
relaySecret: z.string(),
hosts: z.array(z.string()),
})
@@ -49,12 +46,16 @@ const pushPairQROptions = {
width: 256,
}
function pushPairLink(payload: z.infer<typeof PushPairPayload>) {
function pushPairLink(input: { relaySecret: string; hosts: string[] }) {
const payload: z.infer<typeof PushPairPayload> = {
relaySecret: input.relaySecret,
hosts: input.hosts,
}
return `mobilevoice:///?pair=${encodeURIComponent(JSON.stringify(payload))}`
}
async function pushPairQRCode(payload: z.infer<typeof PushPairPayload>) {
return QRCode.toDataURL(pushPairLink(payload), pushPairQROptions)
async function pushPairQRCode(input: { relaySecret: string; hosts: string[] }) {
return QRCode.toDataURL(pushPairLink(input), pushPairQROptions)
}
const ConsoleOrgOption = z.object({