mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 01:27:35 +00:00
* [comment audit] Align legacy failure-read counts with cached display * [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci] * [comment audit] Condense shared generation, path and preview comments [skip ci] * [comment audit] Condense host, file and Git contract rationale [skip ci] * [comment audit] Tighten Git lifecycle and provider contract comments [skip ci] * [comment audit] Clarify shared host authority and launch isolation [skip ci] * [comment audit] simplify local access mobile and native chat contracts [skip ci] * [comment audit] simplify native chat presentation and state contracts [skip ci] * [comment audit] simplify native runtime and provider contracts [skip ci] * [comment audit] simplify migration and orchestration contracts [skip ci] * [comment audit] simplify plugin trust and process capture rationale [skip ci] * [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci] * [comment audit] clarify remote recovery and launch wire rationale [skip ci] * [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci] * [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci] * [comment audit] clarify sidebar, skill and SSH invariants [skip ci] * [comment audit] condense structured chat delivery and timing rationale [skip ci] * [comment audit] condense terminal replay and telemetry invariants [skip ci] * [comment audit] clarify launch permission and terminal delivery rationale [skip ci] * [comment audit] clarify workspace ownership and layout contracts [skip ci] * [comment audit] wait for fitted and painted SSH terminal links * [comment audit] clarify layout persistence and session ownership [skip ci] * [comment audit] clarify host routing and workspace lifecycle contracts [skip ci] * [comment audit] clarify startup persistence and app lifetime comments [skip ci] * [comment audit] clarify recovery and activity boundaries [skip ci] * [comment audit] condense activity and artifact invariants [skip ci] * [comment audit] condense automation authority contracts [skip ci] * [comment audit] simplify automation scheduling and history contracts [skip ci] * [comment audit] clarify browser chrome and annotation lifecycles [skip ci] * [comment audit] clarify browser routing and retained guest ownership [skip ci] * [comment audit] clarify browser recovery and document ownership comments [skip ci] * [comment audit] restore hosted runners required by native IME fixtures * [comment audit] shorten preview, palette and dashboard invariants [skip ci] * [comment audit] clarify terminal preview, dashboard and retention comments [skip ci] * [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci] * [comment audit] shorten rich-text and combined-diff ownership comments [skip ci] * [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci] * [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci] * [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci] * [comment audit] clarify rich Markdown source and selection safeguards [skip ci] * [comment audit] clarify editor loading and emulator stream lifetimes [skip ci] * [comment audit] trim emulator and feature tour narration [skip ci] * [comment audit] clarify floating workspace and GitHub cache ownership [skip ci] * [comment audit] trim provider controls and Linear filter explanations [skip ci] * [comment audit] clarify pairing and chat resume lifetimes [skip ci] * [comment audit] clarify chat delivery and transcript lifetimes [skip ci] * [comment audit] clarify session options and host-owned status [skip ci] * [comment audit] clarify draft durability and live-status precedence [skip ci] * [comment audit] Trim chat send and model ownership explanations [skip ci] * [comment audit] Shorten transcript ownership and send recovery comments [skip ci] * [comment audit] Simplify native chat composer and transcript hook comments [skip ci] * [comment audit] Shorten native chat scroll and structured session explanations [skip ci] * [comment audit] Trim workspace picker and onboarding commentary [skip ci] * [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci] * [comment audit] Trim sidebar and session history explanations [skip ci] * [comment audit] Condense session resume and review checks comments [skip ci] * [comment audit] Shorten checks state and file explorer refresh comments [skip ci] * [comment audit] Trim plugin bridge and source-control action rationale [skip ci] * [comment audit] Trim source-control listing and review rationale [skip ci] * [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci] * [comment audit] Condense Settings account and appearance rationale [skip ci] * [comment audit] Trim pairing and repository Settings explanations [skip ci] * [comment audit] Condense Settings search SSH and terminal safeguards [skip ci] * [comment audit] Shorten Settings voice and provider ownership comments [skip ci] * [comment audit] Condense Settings draft persistence and host-state rationale [skip ci] * [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci] * [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci] * [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci] * [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci] * [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci] * [comment audit] Prune focus-read baseline after comment cleanup * [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci] * [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci] * [comment audit] Clarify viewport geometry and skill update safeguards [skip ci] * [comment audit] Tighten skill inventory, usage and host status comments [skip ci] * [comment audit] Clarify usage ownership and resource kill safeguards [skip ci] * [comment audit] Tighten tab selection, rename and search contracts [skip ci] * [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci] * [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci] * [comment audit] Tighten provider loading and terminal recovery comments [skip ci] * [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci] * [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci] * [comment audit] Tighten PTY startup and replay explanations [skip ci] * [comment audit] Clarify PTY identity and delivery ownership [skip ci] * [comment audit] Shorten remote reconnect and input replay rationale [skip ci] * [comment audit] Condense terminal parking clipboard and file link comments [skip ci] * [comment audit] Clarify IME ownership and trim trace history [skip ci] * [comment audit] Shorten pane recovery and parked watcher explanations [skip ci] * [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci] * [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci] * [comment audit] Trim renderer lifecycle and menu rationale [skip ci] * [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci] * [comment audit] Clarify renderer hook evidence and source ownership [skip ci] * [comment audit] Tighten IPC ownership and reconnect explanations [skip ci] * [comment audit] Trim renderer focus, paste and attention comments [skip ci] * [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci] * [comment audit] Trim agent launch, resume and ownership commentary [skip ci] * [comment audit] Shorten account, browser and editor comments [skip ci] * [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci] * [comment audit] Condense launch recovery and editor tokenization comments [skip ci] * [comment audit] Condense workspace selection and terminal pane rationale [skip ci] * [comment audit] Condense terminal fitting and rendering recovery comments [skip ci] * [comment audit] Condense terminal input and host routing explanations [skip ci] * [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci] * [comment audit] Condense structured launch and terminal link explanations [skip ci] * [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci] * [comment audit] Condense session persistence and workspace activation rationale [skip ci] * [comment audit] Condense workspace routing and browser ownership rationale [skip ci] * [comment audit] Condense mirror recovery and layout authority rationale [skip ci] * [comment audit] Condense runtime transport and file safety rationale [skip ci] * [comment audit] Condense runtime graph and structured session rationale [skip ci] * [comment audit] Condense browser creation and remote tab recovery rationale [skip ci] * [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci] * [comment audit] Condense frame, startup and provider cache rationale [skip ci] * [comment audit] Condense host twin and agent lifecycle rationale [skip ci] * [comment audit] Condense browser authority and editor cleanup rationale [skip ci] * [comment audit] Condense editor buffer identity and review cache rationale [skip ci] * [comment audit] Condense provider lifetime and repository ownership rationale [skip ci] * [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci] * [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci] * [comment audit] Condense cleanup ownership and listing admission rationale [skip ci] * [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci] * [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci] * [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci] * [comment audit] Condense cloud rollout and assignment safety rationale [skip ci] * [comment audit] Condense relay session and database recovery rationale [skip ci] * [comment audit] Condense relay placement and protocol compatibility rationale [skip ci] * [comment audit] Condense build and packaging constraints [skip ci] * [comment audit] Condense CI gates and validation isolation rationale [skip ci] * [comment audit] Condense catalog identity and browser validation rationale [skip ci] * [comment audit] Condense mobile browser and frame-budget rationale [skip ci] * [comment audit] Shorten mobile route and browser test explanations [skip ci] * [comment audit] Condense editor and session seam test rationale [skip ci] * [comment audit] Trim session and terminal closure history [skip ci] * [comment audit] Shorten terminal browser and census explanations [skip ci] * [comment audit] Preserve root-only E2E diagnostic baseline * [comment audit] Condense mobile seam and HTML preview test rationale [skip ci] * [comment audit] Condense preview instrumentation and render harness rationale [skip ci] * [comment audit] Condense bundle contracts and native-build constraints [skip ci] * [comment audit] Shorten native packaging and CI routing rationale [skip ci] * [comment audit] Condense release tooling and benchmark constraints [skip ci] * [comment audit] Condense packaging and platform gate rationale [skip ci] * [comment audit] Shorten mobile route and history rationale [skip ci] * [comment audit] Condense mobile browser and component rationale [skip ci] * [comment audit] Shorten drawer and workspace-form rationale [skip ci] * [comment audit] Trim mobile review and editor-document explanations [skip ci] * [comment audit] Condense editor lifecycle and Markdown explanations [skip ci] * [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci] * [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci] * [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci] * [comment audit] Condense bridge stream, native request and audio rationale [skip ci] * [comment audit] Reduce bridge schema and page-client commentary [skip ci] * [comment audit] Tighten mobile page bridge contracts [skip ci] * [comment audit] Clarify mobile cache and session constraints [skip ci] * [comment audit] Condense mobile route and lifecycle rationale [skip ci] * [comment audit] Tighten mobile shell hook timing rationale [skip ci] * [comment audit] Clarify route handoff and notification compatibility [skip ci] * [comment audit] Clarify push registration and dictation cleanup constraints [skip ci] * [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci] * [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci] * [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci] * [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci] * [comment audit] Tighten stream replay and queued session ownership rationale [skip ci] * [comment audit] Condense mobile composer and reply-schema constraints [skip ci] * [comment audit] Clarify mobile transcript recovery and session ownership [skip ci] * [comment audit] Clarify mobile queue and source-control compatibility [skip ci] * [comment audit] Condense storage and task launch constraints [skip ci] * [comment audit] Clarify task schemas and terminal document ownership [skip ci] * [comment audit] Tighten terminal input and viewport rationale [skip ci] * [comment audit] Shorten terminal and RPC recorder contracts [skip ci] * [comment audit] Condense recorder engine and connection contracts [skip ci] * [comment audit] Shorten host credential and relay recovery comments [skip ci] * [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci] * [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci] * [comment audit] Condense catalog and relay entry contracts [skip ci] * [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci] * [comment audit] Condense WSL relay and E2E regression rationale [skip ci] * [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci] * [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci] * [comment audit] Shorten layout oracle and regression explanations [skip ci] * [comment audit] Condense paired recovery and ownership regression contracts [skip ci] * [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci] * [comment audit] Condense terminal rendering and IME regression rationale [skip ci] * [comment audit] Shorten terminal recovery and benchmark contracts [skip ci] * [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci] * [comment audit] Condense workflow routing and release rationale [skip ci] * [comment audit] Condense mobile security and installer contracts [skip ci] * [comment audit] Shorten native rationale and stylesheet workarounds [skip ci] * [comment audit] Shorten Windows visibility probe rationale [skip ci] * [comment audit] Condense embedded release and signing rationale [skip ci] * [comment audit] Retain current main test behavior in final reconciliation [skip ci] * [comment audit] Complete final comment review across current source inventory * [comment audit] Handle consolidated audit reports before final CI * [comment audit] Preserve credential storage census detail [skip ci] * [comment audit] Scale broad changed E2E selections with verified timing shards * [comment audit] Align E2E assertions with browser close and settled session metadata * [comment audit] Match existing E2Es to current resource and windowing contracts * [comment audit] Observe the shared server outage notice in reconnect coverage * [comment audit] Make existing renderer probes measure settled tab and PTY work * [comment audit] Align palette E2E selector with task search * [comment audit] Refresh remaining palette search selectors * [comment audit] Keep selector correction within selected audit specs * [comment audit] Await emoji fixture settings inside renderer evaluation * [comment audit] Cancel fixture fit retries between terminal tests --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
311 lines
12 KiB
TypeScript
311 lines
12 KiB
TypeScript
import { PANEL_DESIGN_TOKEN_ALLOWLIST } from './plugins/plugin-panel-shell'
|
||
|
||
/** Shared Electron-free sandbox document. Parse CSP before visual markup so later HTML cannot loosen it.
|
||
* Allow pinned-CDN scripts/styles/fonts/images only; block fetch/sockets/frames/forms. Allowed-CDN requests can still carry data: disclosed accepted risk, not a no-egress guarantee. */
|
||
|
||
export const NATIVE_CHAT_VISUAL_CDN_ORIGINS = [
|
||
'https://cdn.jsdelivr.net',
|
||
'https://unpkg.com',
|
||
'https://cdnjs.cloudflare.com',
|
||
'https://esm.sh',
|
||
'https://fonts.googleapis.com',
|
||
'https://fonts.gstatic.com'
|
||
] as const
|
||
|
||
const ASSET_SOURCES = `'unsafe-inline' data: blob: ${NATIVE_CHAT_VISUAL_CDN_ORIGINS.join(' ')}`
|
||
const MEDIA_SOURCES = `data: blob: ${NATIVE_CHAT_VISUAL_CDN_ORIGINS.join(' ')}`
|
||
|
||
// Set about:srcdoc base before CSP to keep #links inside the frame; subsequent base-uri none rejects visual-supplied overrides.
|
||
const INERT_BASE = '<base href="about:srcdoc">'
|
||
|
||
const POLICY_WITHOUT_BASE = [
|
||
"default-src 'none'",
|
||
`script-src ${ASSET_SOURCES}`,
|
||
`style-src ${ASSET_SOURCES}`,
|
||
`font-src ${MEDIA_SOURCES}`,
|
||
`img-src ${MEDIA_SOURCES}`,
|
||
"connect-src 'none'",
|
||
"frame-src 'none'",
|
||
"child-src 'none'",
|
||
"worker-src 'none'",
|
||
"object-src 'none'",
|
||
"media-src 'none'",
|
||
"manifest-src 'none'",
|
||
"form-action 'none'"
|
||
]
|
||
|
||
export const NATIVE_CHAT_VISUAL_CSP = [...POLICY_WITHOUT_BASE, "base-uri 'none'"].join('; ')
|
||
|
||
/** Mobile URL-less visual hosts must admit about: bases so inherited CSP permits the child’s about:srcdoc base.
|
||
* The child’s own policy then rejects every other base; CSP cannot name that exact URL. */
|
||
export const NATIVE_CHAT_VISUAL_HOST_CSP = [...POLICY_WITHOUT_BASE, 'base-uri about:'].join('; ')
|
||
|
||
/** Frame names carry this prefix so the main process registers them before content runs. */
|
||
export const NATIVE_CHAT_VISUAL_FRAME_NAME_PREFIX = 'orca-chat-visual:'
|
||
|
||
export const NATIVE_CHAT_VISUAL_SIZE_TYPE = 'orca-visual-size'
|
||
export const NATIVE_CHAT_VISUAL_OPEN_LINK_TYPE = 'orca-visual-open-link'
|
||
export const NATIVE_CHAT_VISUAL_THEME_TYPE = 'orca-visual-theme'
|
||
export const NATIVE_CHAT_VISUAL_PING_TYPE = 'orca-visual-ping'
|
||
export const NATIVE_CHAT_VISUAL_PONG_TYPE = 'orca-visual-pong'
|
||
export const NATIVE_CHAT_VISUAL_READER_SCROLL_TYPE = 'orca-visual-reader-scroll'
|
||
/** Visuals' own wait for an answer: at 1 s, a busy visual in WebKit answered too late. */
|
||
export const NATIVE_CHAT_VISUAL_PONG_TIMEOUT_MS = 5_000
|
||
|
||
export const NATIVE_CHAT_VISUAL_MIN_HEIGHT = 80
|
||
export const NATIVE_CHAT_VISUAL_MAX_HEIGHT = 2000
|
||
const MAX_LINK_LENGTH = 2048
|
||
|
||
/** Theme variables a visual may style against: the plugin panel set plus the chart series. */
|
||
export const NATIVE_CHAT_VISUAL_THEME_TOKENS = [
|
||
...PANEL_DESIGN_TOKEN_ALLOWLIST,
|
||
'--chart-1',
|
||
'--chart-2',
|
||
'--chart-3',
|
||
'--chart-4',
|
||
'--chart-5',
|
||
'--font-sans',
|
||
'--font-mono'
|
||
] as const
|
||
|
||
export type NativeChatVisualTheme = {
|
||
colorScheme: 'light' | 'dark'
|
||
tokens: Readonly<Record<string, string>>
|
||
}
|
||
|
||
const TOKEN_NAME_PATTERN = /^--[a-z0-9-]+$/
|
||
|
||
/** Token values land inside a <style> block, so structural characters are stripped. */
|
||
function sanitizeTokenValue(value: string): string {
|
||
return value.replaceAll(/[{}<>;\\]/g, '').trim()
|
||
}
|
||
|
||
export function nativeChatVisualThemeCss(theme: NativeChatVisualTheme): string {
|
||
const declarations = [`color-scheme:${theme.colorScheme === 'dark' ? 'dark' : 'light'}`]
|
||
for (const [name, value] of Object.entries(theme.tokens)) {
|
||
const clean = sanitizeTokenValue(value)
|
||
if (TOKEN_NAME_PATTERN.test(name) && clean.length > 0) {
|
||
declarations.push(`${name}:${clean}`)
|
||
}
|
||
}
|
||
return `:root{${declarations.join(';')}}`
|
||
}
|
||
|
||
// Grow frames to fit, hide their scrollbar and inherit the thread background; later visual CSS may override defaults.
|
||
const BASE_CSS =
|
||
'html{background:var(--background);color:var(--foreground);font-family:var(--font-sans);' +
|
||
'font-size:14px;line-height:1.5;scrollbar-width:none}html::-webkit-scrollbar{display:none}' +
|
||
'body{margin:0}code,pre,kbd,samp{font-family:var(--font-mono)}'
|
||
|
||
function bootstrapScript(channel: string): string {
|
||
const constants = JSON.stringify({
|
||
channel,
|
||
size: NATIVE_CHAT_VISUAL_SIZE_TYPE,
|
||
link: NATIVE_CHAT_VISUAL_OPEN_LINK_TYPE,
|
||
theme: NATIVE_CHAT_VISUAL_THEME_TYPE,
|
||
ping: NATIVE_CHAT_VISUAL_PING_TYPE,
|
||
pong: NATIVE_CHAT_VISUAL_PONG_TYPE,
|
||
scroll: NATIVE_CHAT_VISUAL_READER_SCROLL_TYPE
|
||
})
|
||
// Plain ES5 so it runs before, and independent of, anything the visual loads.
|
||
return `(function () {
|
||
'use strict'
|
||
var C = ${constants}
|
||
var host = window.parent
|
||
function send(message) {
|
||
message.channel = C.channel
|
||
try { host.postMessage(message, '*') } catch (_) {}
|
||
}
|
||
// Containment: a visual is a document, never a browsing context. The host also refuses navigation.
|
||
if (window.navigation && typeof window.navigation.addEventListener === 'function') {
|
||
window.navigation.addEventListener('navigate', function (event) {
|
||
if (!event.hashChange && event.cancelable) event.preventDefault()
|
||
})
|
||
}
|
||
// Keep open configurable for visual declarations; the iframe sandbox blocks popups.
|
||
try { window.open = function () { return null } } catch (_) {}
|
||
document.addEventListener('click', function (event) {
|
||
var node = event.target
|
||
while (node && node.nodeType !== 1) node = node.parentNode
|
||
var anchor = node && node.closest ? node.closest('a[href]') : null
|
||
if (!anchor) return
|
||
var href = anchor.getAttribute('href') || ''
|
||
if (href.charAt(0) === '#') return
|
||
event.preventDefault()
|
||
if (!event.isTrusted) return
|
||
var url
|
||
try { url = new URL(href, document.baseURI) } catch (_) { return }
|
||
if (url.protocol === 'http:' || url.protocol === 'https:') send({ type: C.link, url: url.href })
|
||
}, true)
|
||
document.addEventListener('submit', function (event) { event.preventDefault() }, true)
|
||
// Only the host window may restyle the visual, or ask whether this document still runs the shell.
|
||
var themeStyle = document.getElementById('orca-visual-theme')
|
||
window.addEventListener('message', function (event) {
|
||
var data = event.data
|
||
if (event.source !== host || !data || data.channel !== C.channel) return
|
||
if (data.type === C.ping) return send({ type: C.pong, id: data.id })
|
||
if (data.type !== C.theme || typeof data.css !== 'string' || !themeStyle) return
|
||
themeStyle.textContent = data.css
|
||
document.documentElement.classList.toggle('dark', data.colorScheme === 'dark')
|
||
})
|
||
// The host pairs uncancelled input with actual transcript movement; only the browser knows
|
||
// whether a visual's nested scroller takes it. Wait for handlers that cancel chart gestures.
|
||
window.addEventListener('wheel', function (event) {
|
||
if (event.ctrlKey || !event.deltaY) return
|
||
setTimeout(function () {
|
||
if (!event.defaultPrevented) send({ type: C.scroll, input: 'wheel', deltaY: event.deltaY })
|
||
}, 0)
|
||
}, { capture: true, passive: true })
|
||
window.addEventListener('touchmove', function () { send({ type: C.scroll, input: 'touch' }) }, { capture: true, passive: true })
|
||
// Height: report the document's own height whenever layout changes; the host fits the frame.
|
||
var reported = -1
|
||
var pending = false
|
||
function measure() {
|
||
pending = false
|
||
var height = document.documentElement.getBoundingClientRect().height
|
||
var body = document.body
|
||
// A page pinned to the frame (html/body height 100%) overflows its body instead of growing it;
|
||
// add only what overflows, so a padded full-height body does not grow the frame by itself.
|
||
if (body && body.scrollHeight > body.clientHeight) height += body.scrollHeight - body.clientHeight
|
||
height = Math.ceil(height)
|
||
if (height !== reported && height > 0) {
|
||
reported = height
|
||
send({ type: C.size, height: height })
|
||
}
|
||
}
|
||
function schedule() {
|
||
if (pending) return
|
||
pending = true
|
||
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(measure)
|
||
else setTimeout(measure, 16)
|
||
}
|
||
if (typeof ResizeObserver === 'function') {
|
||
var observer = new ResizeObserver(schedule)
|
||
observer.observe(document.documentElement)
|
||
document.addEventListener('DOMContentLoaded', function () { if (document.body) observer.observe(document.body) })
|
||
}
|
||
// Content that overflows a fixed-size body changes no observed box, so watch the content too.
|
||
if (typeof MutationObserver === 'function') {
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
if (document.body) new MutationObserver(schedule).observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true })
|
||
})
|
||
}
|
||
document.addEventListener('DOMContentLoaded', schedule)
|
||
window.addEventListener('load', schedule)
|
||
if (document.fonts && document.fonts.ready) document.fonts.ready.then(schedule)
|
||
})()`
|
||
}
|
||
|
||
/** Build per-document srcdoc; unique channel tags every shell message so the host can reject other documents’ traffic. */
|
||
export function buildNativeChatVisualDocument(args: {
|
||
html: string
|
||
channel: string
|
||
theme: NativeChatVisualTheme
|
||
}): string {
|
||
const scheme = args.theme.colorScheme === 'dark' ? 'dark' : 'light'
|
||
return `<!doctype html>
|
||
<html class="${scheme}">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
${INERT_BASE}
|
||
<meta http-equiv="Content-Security-Policy" content="${NATIVE_CHAT_VISUAL_CSP}">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<style id="orca-visual-theme">${nativeChatVisualThemeCss(args.theme)}</style>
|
||
<style>${BASE_CSS}</style>
|
||
<script>${bootstrapScript(args.channel)}</script>
|
||
</head>
|
||
${args.html}`
|
||
}
|
||
|
||
export type NativeChatVisualFrameMessage =
|
||
| { kind: 'size'; height: number }
|
||
| { kind: 'open-link'; url: string }
|
||
/** Uncancelled input; the host checks whether the transcript actually moved. */
|
||
| { kind: 'reader-scroll'; input: 'wheel'; deltaY: number }
|
||
| { kind: 'reader-scroll'; input: 'touch' }
|
||
|
||
/** A message from a visual's shell, validated; null for anything else. */
|
||
export function readNativeChatVisualFrameMessage(
|
||
data: unknown,
|
||
channel: string
|
||
): NativeChatVisualFrameMessage | null {
|
||
if (typeof data !== 'object' || data === null) {
|
||
return null
|
||
}
|
||
if (!('channel' in data) || data.channel !== channel || !('type' in data)) {
|
||
return null
|
||
}
|
||
if (data.type === NATIVE_CHAT_VISUAL_SIZE_TYPE) {
|
||
const height = 'height' in data ? data.height : undefined
|
||
return typeof height === 'number' && Number.isFinite(height) && height > 0
|
||
? { kind: 'size', height }
|
||
: null
|
||
}
|
||
if (data.type === NATIVE_CHAT_VISUAL_READER_SCROLL_TYPE) {
|
||
const input = 'input' in data ? data.input : undefined
|
||
if (input === 'touch') {
|
||
return { kind: 'reader-scroll', input }
|
||
}
|
||
const deltaY = 'deltaY' in data ? data.deltaY : undefined
|
||
return input === 'wheel' &&
|
||
typeof deltaY === 'number' &&
|
||
Number.isFinite(deltaY) &&
|
||
deltaY !== 0
|
||
? { kind: 'reader-scroll', input, deltaY }
|
||
: null
|
||
}
|
||
if (data.type === NATIVE_CHAT_VISUAL_OPEN_LINK_TYPE) {
|
||
const url = 'url' in data ? data.url : undefined
|
||
if (typeof url !== 'string' || url.length > MAX_LINK_LENGTH) {
|
||
return null
|
||
}
|
||
try {
|
||
const parsed = new URL(url)
|
||
return parsed.protocol === 'http:' || parsed.protocol === 'https:'
|
||
? { kind: 'open-link', url: parsed.href }
|
||
: null
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
return null
|
||
}
|
||
|
||
/** The ping id a visual's shell answered on `channel`, or null for anything else. */
|
||
export function readNativeChatVisualPong(data: unknown, channel: string): number | null {
|
||
if (typeof data !== 'object' || data === null || !('channel' in data) || !('type' in data)) {
|
||
return null
|
||
}
|
||
if (data.channel !== channel || data.type !== NATIVE_CHAT_VISUAL_PONG_TYPE) {
|
||
return null
|
||
}
|
||
const id = 'id' in data ? data.id : undefined
|
||
return typeof id === 'number' && Number.isSafeInteger(id) ? id : null
|
||
}
|
||
|
||
export function clampNativeChatVisualHeight(height: number): number {
|
||
return Math.min(
|
||
NATIVE_CHAT_VISUAL_MAX_HEIGHT,
|
||
Math.max(NATIVE_CHAT_VISUAL_MIN_HEIGHT, Math.round(height))
|
||
)
|
||
}
|
||
|
||
export function nativeChatVisualPingMessage(
|
||
id: number,
|
||
channel: string
|
||
): { type: string; channel: string; id: number } {
|
||
return { type: NATIVE_CHAT_VISUAL_PING_TYPE, channel, id }
|
||
}
|
||
|
||
export function nativeChatVisualThemeMessage(
|
||
theme: NativeChatVisualTheme,
|
||
channel: string
|
||
): { type: string; channel: string; css: string; colorScheme: 'light' | 'dark' } {
|
||
return {
|
||
type: NATIVE_CHAT_VISUAL_THEME_TYPE,
|
||
channel,
|
||
css: nativeChatVisualThemeCss(theme),
|
||
colorScheme: theme.colorScheme
|
||
}
|
||
}
|