mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* feat(mobile): verified https pairing links; custom scheme carries no secret The desktop's copy-link now emits https://www.onorca.dev/pair#<code>, with the code only in the fragment. Orca Mobile claims that link as a verified app link and refuses any pairing code delivered through orca://, which any app can claim; orca://pair alone opens the scanner with an explanation. In-app QR scan and paste still accept the custom scheme, so older desktops and phones keep pairing. The confirm screen shows the desktop address and host key fingerprint before pairing. config/verified-links/ holds the apple-app-site-association, assetlinks.json and fallback page to deploy on www.onorca.dev, with a test that keeps them, mobile/app.config.js and the link builder in agreement. Part of #17397. Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): refuse pairing codes nested in or normalized out of unverified links The router unwraps expo-development-client ?url= links and normalizes dot segments after +native-intent runs, so an orca:// wrapper could still deliver a code to the confirm screen. Refuse any unverified link carrying code= at any encoding depth, and match pairing routes on decoded, dot-free segments. Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): escape the apostrophe in the refused-link notice Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): decode escapes byte by byte before refusing nested pairing codes An invalid UTF-8 escape made the whole escape run fail to decode, hiding an adjacent encoded &code= that the router still decodes leniently. Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): decode nested pairing links to any depth before refusing codes Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): ignore URL-stripped whitespace when refusing nested pairing codes Co-Authored-By: Claude <noreply@anthropic.com> * refactor(mobile): decide pairing-link refusal on the router's own parse Stop re-implementing URL decoding. The guard now runs expo-router's own extractExpoPathFromURL and parseUrlUsingCustomBase, the functions its linking applies after +native-intent, and refuses any link from which the router would read a `code` param or reach a pairing route with a payload. A link the router cannot parse is refused. Release builds refuse expo-development-client wrappers outright. A property-style test walks wrapping depths, encoding layers, stripped control characters, case and unicode, and asserts that the guard never passes on a link from which the router would read a code. Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): match pairing routes on the top-level segment only A host link with a segment named pair was sent to the scanner. Co-Authored-By: Claude <noreply@anthropic.com> * test(mobile): pin line-terminator fragment links as refused Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com>
90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
import type { MobileRelayStatusDetail } from '../../shared/mobile-relay-status'
|
|
import type { MobilePairingConnectionMode } from '../../shared/mobile-pairing-connection-mode'
|
|
import type { RuntimePairingReach } from '../../shared/runtime-pairing-reach'
|
|
import type { MobileRelayMintFailure } from '../../shared/mobile-relay-mint-failure'
|
|
import type { RuntimeAccessGrant } from '../../shared/runtime-access-grants'
|
|
|
|
export type MobileApi = {
|
|
listNetworkInterfaces: () => Promise<{
|
|
interfaces: { name: string; address: string; hasDefaultRoute?: boolean }[]
|
|
}>
|
|
getPairingQR: (args?: {
|
|
address?: string
|
|
connectionMode?: MobilePairingConnectionMode
|
|
rotate?: boolean
|
|
}) => Promise<
|
|
| {
|
|
available: false
|
|
reason?: string
|
|
guidance?: string
|
|
relayFailure?: MobileRelayMintFailure
|
|
}
|
|
| {
|
|
available: true
|
|
qrDataUrl: string | null
|
|
/** Natural bitmap width and height in pixels. */
|
|
qrSize: number | null
|
|
qrError?: 'encoding_failed'
|
|
/** Phone-facing copy link: verified https, code in the fragment. The QR keeps `orca://`. */
|
|
pairingUrl: string
|
|
/** Null when no direct address was advertised — the QR pairs over Relay alone. */
|
|
endpoint: string | null
|
|
deviceId: string
|
|
/** Mode the QR actually encodes. */
|
|
connectionMode: MobilePairingConnectionMode
|
|
}
|
|
>
|
|
getWindowsFirewallStatus: (args?: { address?: string }) => Promise<
|
|
| { supported: false }
|
|
| {
|
|
supported: true
|
|
port: number
|
|
ruleAllowed: boolean
|
|
blockingRuleDetected: boolean
|
|
privateFirewallEnabled: boolean
|
|
networkCategory: 'private' | 'public' | 'domain' | 'unknown'
|
|
inspectionAvailable: boolean
|
|
}
|
|
>
|
|
repairWindowsFirewall: () => Promise<
|
|
{ ok: true } | { ok: false; reason: 'cancelled' | 'failed' | 'unsupported' }
|
|
>
|
|
openWindowsNetworkSettings: () => Promise<boolean>
|
|
getRuntimePairingUrl: (args?: {
|
|
address?: string
|
|
rotate?: boolean
|
|
reach?: RuntimePairingReach
|
|
}) => Promise<
|
|
| {
|
|
available: false
|
|
reason?: 'network_exposure_failed'
|
|
guidance?: string
|
|
}
|
|
| {
|
|
available: true
|
|
pairingUrl: string
|
|
webClientUrl: string | null
|
|
endpoint: string
|
|
deviceId: string
|
|
}
|
|
>
|
|
listDevices: () => Promise<{
|
|
devices: {
|
|
deviceId: string
|
|
name: string
|
|
pairedAt: number
|
|
lastSeenAt: number
|
|
}[]
|
|
}>
|
|
revokeDevice: (args: { deviceId: string }) => Promise<{ revoked: boolean }>
|
|
listRuntimeAccessGrants: () => Promise<{ grants: RuntimeAccessGrant[] }>
|
|
revokeRuntimeAccess: (args: { deviceId: string }) => Promise<{ revoked: boolean }>
|
|
isWebSocketReady: () => Promise<{ ready: boolean; endpoint: string | null }>
|
|
getRelayStatus: () => Promise<MobileRelayStatusDetail>
|
|
onRelayStatusChanged: (callback: (detail: MobileRelayStatusDetail) => void) => () => void
|
|
/** Consumes an auth-failure notification that arrived before the renderer listener mounted. */
|
|
consumePendingUnpairedDeviceAuthFailure?: () => Promise<boolean>
|
|
/** Fires (throttled, once per session) when an unpaired phone repeatedly fails direct-transport auth. */
|
|
onUnpairedDeviceAuthFailure?: (callback: () => void) => () => void
|
|
}
|