mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 08:02:31 +00:00
* fix(browser-preview): require explicit preview capabilities (STA-5758) Scope document reads to approved directories, confirm external links before opening them, revoke grants with tab lifecycle, and keep document-preview session state rollback-safe across mixed client/runtime versions. * Harden document preview lifecycle and permissions * Document preview DNS prefetch residual * Make preview E2E guest focus explicit * fix(browser-preview): entry-file-only authority for root-level docs, contained chip layout, re-issued gate paths (STA-5758) A grant whose document directory is its own request base — a doc at the workspace root, or outside any workspace — now reads nothing but the entry file until the reader approves a directory, at both the lexical and the canonical containment pass. The DNS-prefetch residual can only beacon what the page can read, and a root-level document could previously read the whole worktree silently. The identity chip's host badge overflowed the chip's layout box under squeeze (Linux CI): every row member can now shrink and truncate, verified by a width sweep in isolated Chromium down to ~120px chips. The Allow banner says what it grants: 'Allow folder', reading files in the named directory, for the life of the preview. The reliability-gate manifest command, testFiles entry, assertion refs and dated evidence naming the deleted doc-preview-external-link-bridge.test.ts are re-issued at doc-preview-external-link-confirmation.test.ts with a fresh 189/189 run; the focus-gate assertion text follows the shipped gate. * fix(browser-preview): hide the chip identity row below 24rem instead of clipping it, ellipsize the host badge, catalog the new i18n keys (STA-5758) CI's preview pane leaves the chip ~40px: no truncation shows anything there, so the Workspace-file label and host badge now hide whole below a 24rem container threshold sized so that visible implies contained. The badge text gains an inner text box — text directly inside the flex pill clipped both ends with no ellipsis. The e2e geometry oracle asserts containment when the row shows and the threshold when it does not. verify:localization-catalog: the hardening's new preview keys (and the renamed allowDirectory) join en.json via sync:localization-catalog. * feat(browser-preview): batch blocked folders into one access decision (STA-5758) Sequential per-folder banners trained the allow reflex without adding judgment — a reader cannot weigh assets/ against data/. The banner now accumulates every folder a load surfaces, names them (three, then a count, full list in the title), and grants exactly that set with one Allow-N-folders click and one reload. Dismiss fences the whole named set. The map lives behind a ref with a version tick so a dismissal fences an offer landing in the same event batch.
111 lines
4.1 KiB
TypeScript
111 lines
4.1 KiB
TypeScript
/**
|
|
* Wire shape for the local document-preview scheme. The main process answers
|
|
* `orca-preview://<grantId>/<relative-path>` by reading the owning workspace's
|
|
* disk over the same channels the editor uses, so remote HTML docs render in a
|
|
* local webview instead of being routed through the remote-browsing machinery.
|
|
*/
|
|
export const DOC_PREVIEW_SCHEME = 'orca-preview'
|
|
|
|
/** Why: non-persistent and its own partition — preview bytes never share storage with user browsing or workspace browser profiles. */
|
|
export const DOC_PREVIEW_PARTITION = 'orca-doc-preview'
|
|
|
|
export const DOC_PREVIEW_MINT_GRANT_CHANNEL = 'docPreview:mintGrant'
|
|
export const DOC_PREVIEW_REVOKE_GRANT_CHANNEL = 'docPreview:revokeGrant'
|
|
export const DOC_PREVIEW_AUTHORIZE_DIRECTORY_CHANNEL = 'docPreview:authorizeDirectory'
|
|
export const DOC_PREVIEW_EXTERNAL_LINK_CHANNEL = 'docPreview:externalLink'
|
|
/**
|
|
* The preview guest's preload reports a trusted anchor click here. Renderer↔main only — no paired
|
|
* client ever sees it, and main gates every report on the sender being a live, grant-bound preview guest.
|
|
*/
|
|
export const DOC_PREVIEW_LINK_CLICK_CHANNEL = 'docPreview:linkClick'
|
|
/** The one out-of-band route from the preview's main-side fences to the shell hosting it. */
|
|
export const DOC_PREVIEW_LOAD_FAILURE_CHANNEL = 'docPreview:loadFailure'
|
|
|
|
/** Why: an unreadable document still answers with a real HTTP status, so the guest paints the
|
|
* handler's plain-text body instead of failing to load. The shell needs the reason out-of-band. */
|
|
export type DocPreviewFileFailureReason =
|
|
| 'authorization-required'
|
|
| 'too-large'
|
|
| 'unsupported-asset'
|
|
| 'unreadable'
|
|
|
|
export type DocPreviewFileFailure = {
|
|
grantId: string
|
|
relativePath: string
|
|
reason: DocPreviewFileFailureReason
|
|
}
|
|
|
|
/**
|
|
* A download the preview partition refused. Why it carries no path: the document names the file it
|
|
* offers, and the notice this becomes is Orca's chrome — a payload with a path invites rendering
|
|
* page-authored text in the app's own UI, and a path equal to the entry document's would route a
|
|
* refused download into the panel that hides the page.
|
|
*/
|
|
export type DocPreviewDownloadBlocked = {
|
|
grantId: string
|
|
reason: 'download-blocked'
|
|
}
|
|
|
|
export type DocPreviewFailure = DocPreviewFileFailure | DocPreviewDownloadBlocked
|
|
|
|
export const DOC_PREVIEW_GRANT_ID_PATTERN = /^[0-9a-f]{32}$/
|
|
|
|
export function isDocPreviewGrantId(value: string): boolean {
|
|
return DOC_PREVIEW_GRANT_ID_PATTERN.test(value)
|
|
}
|
|
|
|
/** Encodes each segment separately so `/` keeps its separator meaning and `#`/`?` cannot split the path. */
|
|
export function buildDocPreviewUrl(grantId: string, relativePath: string): string {
|
|
const segments = relativePath
|
|
.replace(/\\/g, '/')
|
|
.split('/')
|
|
.filter((segment) => segment.length > 0)
|
|
.map((segment) => encodeURIComponent(segment))
|
|
return `${DOC_PREVIEW_SCHEME}://${grantId}/${segments.join('/')}`
|
|
}
|
|
|
|
export type DocPreviewUrlTarget = {
|
|
grantId: string
|
|
/** Slash-joined, percent-decoded path segments; never leading-slashed. */
|
|
relativePath: string
|
|
}
|
|
|
|
/**
|
|
* Whether a string is, or is trying to look like, a preview URL. Deliberately a prefix test rather
|
|
* than a parse: this answers for text a document chose — a title Chromium fell back to, most of
|
|
* all — where anything carrying a grant must be refused even when it would not parse.
|
|
*/
|
|
export function isDocPreviewUrl(candidate: string): boolean {
|
|
return candidate.trimStart().toLowerCase().startsWith(`${DOC_PREVIEW_SCHEME}://`)
|
|
}
|
|
|
|
export function parseDocPreviewUrl(rawUrl: string): DocPreviewUrlTarget | null {
|
|
let parsed: URL
|
|
try {
|
|
parsed = new URL(rawUrl)
|
|
} catch {
|
|
return null
|
|
}
|
|
if (parsed.protocol !== `${DOC_PREVIEW_SCHEME}:`) {
|
|
return null
|
|
}
|
|
const grantId = parsed.hostname
|
|
if (!isDocPreviewGrantId(grantId)) {
|
|
return null
|
|
}
|
|
const segments: string[] = []
|
|
for (const rawSegment of parsed.pathname.split('/')) {
|
|
if (rawSegment.length === 0) {
|
|
continue
|
|
}
|
|
let segment: string
|
|
try {
|
|
segment = decodeURIComponent(rawSegment)
|
|
} catch {
|
|
return null
|
|
}
|
|
segments.push(segment)
|
|
}
|
|
return { grantId, relativePath: segments.join('/') }
|
|
}
|