Skip to main content

APP_JS

Constant APP_JS 

Source
pub const APP_JS: &[u8] = b"// afhttp ops panel \xe2\x80\x94 live JPEG screencast + timing-preserved input replay.\n//\n// Two WebSocket flows against the host:\n//   /ops/screencast/ws    (inbound)   \xe2\x80\x94 binary JPEG frames -> <canvas>\n//   /ops/screencast/input (outbound)  \xe2\x80\x94 pointer/keyboard events with performance.now() ts\n//\n// The canvas backing store tracks the actual screencast frame size, but\n// pointer coordinates are mapped to the target\'s CSS pixels using the\n// viewport metadata (deviceWidth/deviceHeight) the host sends as a text\n// \"meta\" message. The frame is the viewport scaled to fit\n// startScreencast.maxWidth/Height, so frame pixels != CSS pixels whenever\n// the viewport isn\'t exactly 1280\xc3\x97720 \xe2\x80\x94 mapping via frame pixels would put\n// clicks off by that scale factor.\n\nconst status = document.getElementById(\"status\");\nconst canvas = document.getElementById(\"screen\");\nconst ctx = canvas.getContext(\"2d\");\n\nctx.fillStyle = \"#111\";\nctx.fillRect(0, 0, canvas.width, canvas.height);\nctx.fillStyle = \"#888\";\nctx.font = \"14px sans-serif\";\nctx.fillText(\"connecting\xe2\x80\xa6\", 12, 24);\n\nconst tokenMatch = window.location.search.match(/token=([^&]+)/);\nconst token = tokenMatch ? decodeURIComponent(tokenMatch[1]) : null;\nconst tokenQS = token ? `?token=${encodeURIComponent(token)}` : \"\";\n\nconst proto = window.location.protocol === \"https:\" ? \"wss\" : \"ws\";\nconst screencastUrl = `${proto}://${window.location.host}/ops/screencast/ws${tokenQS}`;\nconst inputUrl = `${proto}://${window.location.host}/ops/screencast/input${tokenQS}`;\n\n// ---- screencast --------------------------------------------------------\n\nconst screencast = new WebSocket(screencastUrl);\nscreencast.binaryType = \"blob\";\n\nscreencast.onopen = () => {\n  status.textContent = \"screencast: live\";\n  window.__opsScreencastOpen = true;\n};\nscreencast.onerror = () => {\n  status.textContent = \"screencast: error\";\n};\nscreencast.onclose = () => {\n  status.textContent = \"screencast: closed\";\n  window.__opsScreencastOpen = false;\n};\n// Latest target viewport metadata (CSS pixels). Sent by the host as a text\n// frame whenever it changes; used by canvasCoords() to map operator input to\n// the page\'s CSS pixel space regardless of how the screencast frame is scaled.\nlet viewportMeta = null;\n\nscreencast.onmessage = async (ev) => {\n  if (typeof ev.data === \"string\") {\n    // Text frames are JSON: viewport metadata, or error envelopes.\n    try {\n      const msg = JSON.parse(ev.data);\n      if (msg && msg.type === \"meta\") {\n        viewportMeta = msg;\n      }\n    } catch (e) {\n      // Not JSON we understand; ignore for the canvas.\n    }\n    return;\n  }\n  try {\n    const bmp = await createImageBitmap(ev.data);\n    // Match the canvas backing store to the actual frame size for a crisp,\n    // unscaled draw. Coordinate mapping no longer depends on this (it uses\n    // the CSS deviceWidth/Height from the meta message). Setting .width/.height\n    // clears the canvas, so we only do it when dimensions actually change \xe2\x80\x94\n    // typically once, on the first frame, then again if the target resizes.\n    if (canvas.width !== bmp.width || canvas.height !== bmp.height) {\n      canvas.width = bmp.width;\n      canvas.height = bmp.height;\n    }\n    ctx.drawImage(bmp, 0, 0, canvas.width, canvas.height);\n    if (bmp.close) bmp.close();\n  } catch (e) {\n    // Ignore one bad frame; the next one will land.\n  }\n};\n\n// ---- input replay ------------------------------------------------------\n\nconst input = new WebSocket(inputUrl);\n\ninput.onopen = () => {\n  window.__opsInputOpen = true;\n};\ninput.onclose = () => {\n  window.__opsInputOpen = false;\n};\ninput.onerror = () => {\n  // status already reflects screencast state; keep this quiet\n};\n\nfunction send(ev) {\n  if (input.readyState !== WebSocket.OPEN) return;\n  ev.timestamp_ms = performance.now();\n  input.send(JSON.stringify(ev));\n}\n\nfunction canvasCoords(e) {\n  const rect = canvas.getBoundingClientRect();\n  // Map the operator\'s pointer to the target\'s CSS pixel space. The displayed\n  // canvas fills `rect` and shows exactly the viewport, so the normalized\n  // position within `rect` scales by the CSS viewport size (deviceWidth/Height\n  // from the host\'s meta message). Falling back to the frame-pixel size keeps\n  // the panel usable before the first meta frame arrives.\n  const dw = viewportMeta ? viewportMeta.deviceWidth : canvas.width;\n  const dh = viewportMeta ? viewportMeta.deviceHeight : canvas.height;\n  return {\n    x: ((e.clientX - rect.left) / rect.width) * dw,\n    y: ((e.clientY - rect.top) / rect.height) * dh,\n  };\n}\n\nfunction pointerButton(e) {\n  switch (e.button) {\n    case 1: return \"middle\";\n    case 2: return \"right\";\n    case 3: return \"back\";\n    case 4: return \"forward\";\n    default: return \"left\";\n  }\n}\n\ncanvas.addEventListener(\"pointermove\", (e) => {\n  const { x, y } = canvasCoords(e);\n  send({ type: \"pointer_move\", x, y, timestamp_ms: 0 });\n});\ncanvas.addEventListener(\"pointerdown\", (e) => {\n  const { x, y } = canvasCoords(e);\n  send({ type: \"pointer_down\", x, y, button: pointerButton(e), timestamp_ms: 0 });\n});\ncanvas.addEventListener(\"pointerup\", (e) => {\n  const { x, y } = canvasCoords(e);\n  send({ type: \"pointer_up\", x, y, button: pointerButton(e), timestamp_ms: 0 });\n});\ncanvas.addEventListener(\"wheel\", (e) => {\n  e.preventDefault();\n  const { x, y } = canvasCoords(e);\n  send({ type: \"wheel\", x, y, dx: e.deltaX, dy: e.deltaY, timestamp_ms: 0 });\n}, { passive: false });\n\nfunction keyModifiers(e) {\n  // CDP modifier bits: 1=Alt, 2=Ctrl, 4=Meta, 8=Shift.\n  let m = 0;\n  if (e.altKey) m |= 1;\n  if (e.ctrlKey) m |= 2;\n  if (e.metaKey) m |= 4;\n  if (e.shiftKey) m |= 8;\n  return m;\n}\n\n// The paste shortcut (Ctrl/\xe2\x8c\x98+V) is special-cased: the keystroke can\'t carry\n// the operator\'s clipboard text to the target browser, so we read it here and\n// relay it as Input.insertText instead (see relayPaste). Every other key \xe2\x80\x94\n// including shortcuts like Ctrl/\xe2\x8c\x98+A (select-all) or Ctrl/\xe2\x8c\x98+R (reload) \xe2\x80\x94 is\n// relayed normally; the host drops the `text` field whenever Ctrl/\xe2\x8c\x98 is held\n// (see one_char_text), so chromium runs them as shortcuts rather than typing\n// the bare letter.\nfunction isPasteCombo(e) {\n  return (e.ctrlKey || e.metaKey) && (e.key === \"v\" || e.key === \"V\");\n}\n\n// Paste. The operator\'s clipboard never reaches the target browser, so we\n// read it here and relay it as Input.insertText (inserted at the focused\n// element\'s caret \xe2\x80\x94 click the target field first). We can\'t rely on the DOM\n// `paste` event: it only fires when an *editable* element is focused, but the\n// focused element here is the (non-editable) <canvas>, so the browser never\n// emits it. Instead we read the clipboard on the Ctrl/\xe2\x8c\x98+V keydown \xe2\x80\x94 a user\n// gesture, and 127.0.0.1 is a secure context, so navigator.clipboard is\n// allowed (the browser may prompt for clipboard-read permission once).\nfunction relayPaste() {\n  if (!navigator.clipboard || !navigator.clipboard.readText) return;\n  navigator.clipboard\n    .readText()\n    .then((text) => {\n      if (text) send({ type: \"insert_text\", text, timestamp_ms: 0 });\n    })\n    .catch(() => {\n      /* permission denied or empty clipboard \xe2\x80\x94 nothing to relay */\n    });\n}\n\nwindow.addEventListener(\"keydown\", (e) => {\n  if (isPasteCombo(e)) {\n    e.preventDefault();\n    relayPaste();\n    return;\n  }\n  send({\n    type: \"key_down\",\n    key: e.key,\n    code: e.code,\n    modifiers: keyModifiers(e),\n    timestamp_ms: 0,\n  });\n});\nwindow.addEventListener(\"keyup\", (e) => {\n  if (isPasteCombo(e)) {\n    e.preventDefault();\n    return;\n  }\n  send({\n    type: \"key_up\",\n    key: e.key,\n    code: e.code,\n    modifiers: keyModifiers(e),\n    timestamp_ms: 0,\n  });\n});\n\n// Make the canvas focusable + keyboard-driven.\ncanvas.tabIndex = 0;\ncanvas.style.cursor = \"crosshair\";\n";