mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* feat(runtime): session-only browser keys and an opt-in for pairing from plain-http pages (host) - pairing.registerDeviceKey accepts ephemeral: true (advertised as pairing.device-key-ephemeral.v1). Such keyed rows are dropped when the host restarts and after 12 hours with no live socket; a sweep removes them and cuts any socket still bound to them, so later key auth gets device_revoked. - The WebSocket upgrade Origin is recorded per socket. A first pairing (a never-used pairing code) from an http:// page on another machine is refused with a sealed insecure_origin error unless the operator sets ORCA_ALLOW_INSECURE_WEB_PAIRING=1. Already-paired browsers, https and loopback pages, and native clients are unchanged. - Registry file reading moved to device-registry-file-read.ts to keep the registry under the size cap. * fix(runtime): gate first pairings only for the marked web client, never phones React Native sends an http Origin on every LAN WebSocket, so the Origin alone refused first-time QR pairing from released phones. The web bundle now marks its upgrade with ?orca-client=web; only a marked socket from an http page on another machine is gated. Released hosts ignore the query string. --------- Co-authored-by: m4air <m4air@Mac.localdomain>
31 lines
1.1 KiB
TypeScript
31 lines
1.1 KiB
TypeScript
import type { DeviceEntry } from './device-entry'
|
|
|
|
// Why: a session-only browser keeps its key in memory; an idle row is an abandoned tab, not a device.
|
|
export const EPHEMERAL_DEVICE_IDLE_MS = 12 * 60 * 60 * 1000
|
|
export const EPHEMERAL_DEVICE_SWEEP_INTERVAL_MS = 5 * 60 * 1000
|
|
|
|
/** Rows that survive a host restart: a session-only browser's key did not. */
|
|
export function withoutEphemeralRows(devices: readonly DeviceEntry[]): DeviceEntry[] {
|
|
return devices.filter((device) => device.ephemeral !== true)
|
|
}
|
|
|
|
/** Idle is measured from the last connect or disconnect; a row with a live socket never expires. */
|
|
export function planIdleEphemeralPrune(
|
|
devices: readonly DeviceEntry[],
|
|
now: number,
|
|
isLive: (deviceId: string) => boolean
|
|
): { nextDevices: DeviceEntry[]; removed: string[] } {
|
|
const removed = devices
|
|
.filter(
|
|
(device) =>
|
|
device.ephemeral === true &&
|
|
now - device.lastSeenAt > EPHEMERAL_DEVICE_IDLE_MS &&
|
|
!isLive(device.deviceId)
|
|
)
|
|
.map((device) => device.deviceId)
|
|
return {
|
|
nextDevices: devices.filter((device) => !removed.includes(device.deviceId)),
|
|
removed
|
|
}
|
|
}
|