Files
orca/src/main/browser/doc-preview-protocol.ts
T
25e0a29701 feat: stream desktop audio and video previews with native controls (#26120)
Open local and SSH video and music files with native playback controls. Stream bounded byte ranges through a scoped Electron URL instead of loading entire files into the editor.

Fixes #24859

Co-authored-by: ChangJun Park <40492343+ckdwns9121@users.noreply.github.com>
Co-authored-by: Lirone Levy <lirone88@outlook.fr>
Co-authored-by: dupi <david.li.du@gmail.com>
Co-authored-by: John Cusack <5961784+John-Cusack@users.noreply.github.com>
2026-10-07 01:29:55 -07:00

154 lines
6.1 KiB
TypeScript

import { protocol, session } from 'electron'
import {
DOC_PREVIEW_PARTITION,
DOC_PREVIEW_SCHEME,
parseDocPreviewUrl
} from '../../shared/doc-preview-scheme'
import { installBrowserSessionPartitionPolicies } from './browser-session-partition-policies'
import { readDocPreviewFile } from './doc-preview-file-reader'
import { publishDocPreviewFailure } from './doc-preview-failure-notice'
import { getDocPreviewGrant } from './doc-preview-grant-registry'
/** Must run before `app.whenReady()`; Electron freezes the privileged scheme table at ready. */
export function registerDocPreviewSchemePrivileges(
additionalSchemes: Electron.CustomScheme[] = []
): void {
protocol.registerSchemesAsPrivileged([
{
scheme: DOC_PREVIEW_SCHEME,
privileges: {
standard: true,
secure: true,
supportFetchAPI: true,
corsEnabled: true,
stream: true
}
},
...additionalSchemes
])
}
let docPreviewSession: Electron.Session | null = null
/** Non-persistent session, so preview bytes never land in a browsing profile's storage. */
export function getDocPreviewSession(): Electron.Session {
docPreviewSession ??= session.fromPartition(DOC_PREVIEW_PARTITION)
return docPreviewSession
}
/** Pure identity check: every guest attach consults it, and none should materialize a session. */
export function isDocPreviewSession(candidate: Electron.Session): boolean {
return docPreviewSession !== null && candidate === docPreviewSession
}
/**
* Product decision, not a hardening default: previewed documents are agent-authored, so any
* outbound request they can make is an exfiltration channel for whatever else the page can read.
* Self-contained documents — inline CSS/JS/SVG and in-grant assets — render in full; a CDN
* stylesheet, font, script, or analytics beacon deliberately does not load. Electron 43 still
* resolves explicit DNS-prefetch hints outside session hooks; the accepted residual is covered by
* `browser-route-dns-prefetch.electron.test.ts` and can beacon grant-readable bytes in DNS labels.
*/
const DOC_PREVIEW_CONTENT_SECURITY_POLICY = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
"font-src 'self' data:",
"connect-src 'self'",
"frame-src 'self'",
"object-src 'none'"
// Why no `webrtc 'block'` here, though a peer connection is exactly the outbound channel this
// policy is meant to close: Chromium 43-era answers that directive with "Unrecognized
// Content-Security-Policy directive 'webrtc'" and gathers candidates anyway, so listing it would
// read as a fence while fencing nothing. The guest's IP-handling policy is the one that holds —
// see `installDocPreviewGuestPolicy`.
].join('; ')
function notFound(message: string): Response {
return new Response(message, {
status: 404,
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
})
}
export async function handleDocPreviewRequest(request: Request): Promise<Response> {
const target = parseDocPreviewUrl(request.url)
if (!target) {
return notFound('Not found')
}
const grant = getDocPreviewGrant(target.grantId)
if (!grant) {
// Why: a revoked or unknown grant is indistinguishable from a missing file by design — but the
// shell still needs to know, or the guest paints this body where the document should be.
publishDocPreviewFailure({
grantId: target.grantId,
relativePath: target.relativePath,
reason: 'unreadable'
})
return notFound('Not found')
}
const relativePath = target.relativePath || grant.entryRelativePath
const outcome = await readDocPreviewFile(grant, relativePath)
if (!outcome.ok) {
publishDocPreviewFailure({ grantId: target.grantId, relativePath, reason: outcome.reason })
return new Response(outcome.message, {
status: outcome.status,
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
})
}
const bytes = outcome.bytes
const body =
bytes.buffer instanceof ArrayBuffer
? new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)
: new Uint8Array(bytes)
return new Response(body, {
status: 200,
headers: {
'Content-Type': outcome.contentType,
'Content-Security-Policy': DOC_PREVIEW_CONTENT_SECURITY_POLICY,
// Why: reload must re-read the workspace disk, so nothing may be cached.
'Cache-Control': 'no-store'
}
})
}
/** `data:`/`blob:` never leave the document; every other scheme would reach off-machine. */
export function isAllowedDocPreviewRequestUrl(url: string): boolean {
return (
url.startsWith(`${DOC_PREVIEW_SCHEME}://`) ||
url.startsWith('devtools://') ||
url.startsWith('data:') ||
url.startsWith('blob:')
)
}
export function installDocPreviewProtocolHandler(): void {
const previewSession = getDocPreviewSession()
if (previewSession.protocol.isProtocolHandled(DOC_PREVIEW_SCHEME)) {
return
}
previewSession.protocol.handle(DOC_PREVIEW_SCHEME, handleDocPreviewRequest)
// Why: the response CSP is the document's own promise to obey; this is the session refusing to
// carry network requests even if an element bypasses CSP. DNS-prefetch does not reach this hook.
previewSession.webRequest.onBeforeRequest((details, callback) => {
callback({ cancel: !isAllowedDocPreviewRequestUrl(details.url) })
})
// Why: the shared installer still owns certificate, UA, download and permission hooks, while
// preview content receives no ambient browser/device permissions.
installBrowserSessionPartitionPolicies(
{
id: DOC_PREVIEW_PARTITION,
scope: 'isolated',
partition: DOC_PREVIEW_PARTITION,
label: 'Document preview',
source: null
},
// Why downloads are the one policy that does not carry over: the browser download flow needs a
// page to attribute the file to, and a previewed document is not one. Routed here it would
// reserve a name in this desktop's Downloads folder and write remote-authored bytes into it
// with nothing in the UI naming the tab that asked, and no prompt in front of it.
{ downloads: 'deny', permissions: 'deny' }
)
}