mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
feat(gesture): port session improvements — direct detach, Run/Run Shell taps, self-hosted MediaPipe, cache-bust
Updates the opt-in gesture overlay (still gated by CODEMAN_GESTURE=1):
- Bundle (gesture-codeman.js) rebuilt from Ark0N/codeman-gesture-control:
- Detach now calls window.app.detachSession(id) directly (the on-tab pop-out
hook) instead of a separate /session/:id window.open reimplementation.
- Pinch a session tab → ghost follows your hand → pull out to undock.
- Pinch the Run (#runBtn → app.run()) or Run Shell (.btn-shell →
app.runShell()) toolbar button to fire it; drift cancels the tap.
- Camera shows fullscreen-dimmed by default (⛶ toggles a corner preview).
- Robust start-error reporting; GPU→CPU MediaPipe delegate fallback.
- Self-hosted MediaPipe (no CDN): serves the wasm runtime + gesture_recognizer
.task from /gesture/ so a browser content-blocker can't break startup. The
overlay points wasmBase/modelUrl there. (~27MB of assets; could later be a
build/postinstall fetch instead of committed blobs.)
- server.ts: cache-bust the injected bundle URL with its mtime (?v=), since
static is served with a 1-year cache — a redeploy is now never stale.
format:check / lint scope (src/**/*.ts) clean; server.ts typechecks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ceca85365c
commit
eea84db05e
7 files changed
+224
-45
No files matched your search
@@ -4352,10 +4352,10 @@ var GestureController = class {
|
||||
}
|
||||
async loadRecognizer() {
|
||||
const fileset = await Po.forVisionTasks(this.opts.wasmBase);
|
||||
this.recognizer = await fc.createFromOptions(fileset, {
|
||||
const build = (delegate) => fc.createFromOptions(fileset, {
|
||||
baseOptions: {
|
||||
modelAssetPath: this.opts.modelUrl,
|
||||
delegate: "GPU"
|
||||
delegate
|
||||
},
|
||||
runningMode: "VIDEO",
|
||||
numHands: this.opts.numHands,
|
||||
@@ -4363,6 +4363,12 @@ var GestureController = class {
|
||||
minHandPresenceConfidence: this.opts.minDetectionConfidence,
|
||||
minTrackingConfidence: this.opts.minTrackingConfidence
|
||||
});
|
||||
try {
|
||||
this.recognizer = await build("GPU");
|
||||
} catch (err) {
|
||||
console.warn("[gesture] GPU delegate failed; falling back to CPU.", err);
|
||||
this.recognizer = await build("CPU");
|
||||
}
|
||||
}
|
||||
trackFps(nowMs) {
|
||||
this.frameTimes.push(nowMs);
|
||||
@@ -4445,7 +4451,10 @@ var GestureController = class {
|
||||
|
||||
// src/codeman/entry.ts
|
||||
var TAB_SELECTOR = ".session-tab";
|
||||
var CLICK_SELECTOR = "#runBtn, .btn-shell";
|
||||
var Z2 = 2147483e3;
|
||||
var DETACH_PULL_PX = 70;
|
||||
var TAP_CANCEL_PX = 45;
|
||||
var handColor = (handedness, pinching) => pinching ? "#4ade80" : handedness === "Right" ? "#a78bfa" : "#38bdf8";
|
||||
var GestureBridge = class {
|
||||
constructor() {
|
||||
@@ -4454,44 +4463,59 @@ var GestureBridge = class {
|
||||
__publicField(this, "ctx");
|
||||
__publicField(this, "video");
|
||||
__publicField(this, "button");
|
||||
__publicField(this, "camBtn");
|
||||
__publicField(this, "status");
|
||||
__publicField(this, "gc");
|
||||
__publicField(this, "running", false);
|
||||
/** Per-hand id of the session tab grabbed at pinch-start (detached on drop). */
|
||||
__publicField(this, "grabbedByHand", /* @__PURE__ */ new Map());
|
||||
/** Camera view: full-viewport dimmed background, or small corner preview. */
|
||||
__publicField(this, "camMode", "full");
|
||||
/** Per-hand in-progress grab (the tab grabbed at pinch-start). */
|
||||
__publicField(this, "grabs", /* @__PURE__ */ new Map());
|
||||
/** Per-hand in-progress button pinch (fires on release if it didn't drift). */
|
||||
__publicField(this, "taps", /* @__PURE__ */ new Map());
|
||||
injectStyles();
|
||||
this.surface = el("div", "cg-surface");
|
||||
this.canvas = el("canvas", "cg-canvas");
|
||||
this.video = el("video", "cg-preview");
|
||||
this.video.muted = true;
|
||||
this.video.playsInline = true;
|
||||
this.applyCamMode();
|
||||
const dock = el("div", "cg-dock");
|
||||
this.button = el("button", "cg-btn");
|
||||
this.button.textContent = "\u{1F590} Gesture";
|
||||
this.camBtn = el("button", "cg-btn cg-btn-icon");
|
||||
this.camBtn.textContent = "\u26F6";
|
||||
this.camBtn.title = "Toggle camera size (fullscreen / corner)";
|
||||
this.status = el("span", "cg-status");
|
||||
this.status.textContent = "off";
|
||||
dock.append(this.button, this.status);
|
||||
document.body.append(this.surface, this.canvas, this.video, dock);
|
||||
dock.append(this.button, this.camBtn, this.status);
|
||||
document.body.append(this.surface, this.video, this.canvas, dock);
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
this.sizeCanvas();
|
||||
window.addEventListener("resize", () => this.sizeCanvas());
|
||||
this.gc = new GestureController({
|
||||
video: this.video,
|
||||
surface: this.surface,
|
||||
numHands: 1
|
||||
numHands: 1,
|
||||
// Self-host the MediaPipe runtime + model from Codeman (same-origin) instead
|
||||
// of the CDN, so an ad/content blocker, offline desk, or strict browser
|
||||
// can't break startup (the CDN failure surfaced as `failed: {isTrusted}` —
|
||||
// a resource load-error Event). Served from public/gesture/.
|
||||
wasmBase: "/gesture/wasm",
|
||||
modelUrl: "/gesture/gesture_recognizer.task"
|
||||
});
|
||||
this.gc.on("grab", (p2) => this.onGrab(p2.hand, p2.x, p2.y));
|
||||
this.gc.on("drag", (p2) => this.onDrag(p2.hand, p2.x, p2.y));
|
||||
this.gc.on("drop", (p2) => this.onDrop(p2.hand));
|
||||
this.gc.on("status", ({ fps, hands }) => this.onStatus(fps, hands));
|
||||
this.button.addEventListener("click", () => void this.toggle());
|
||||
this.camBtn.addEventListener("click", () => this.toggleCamMode());
|
||||
}
|
||||
async toggle() {
|
||||
if (this.running) {
|
||||
this.gc.stop();
|
||||
this.running = false;
|
||||
this.grabbedByHand.clear();
|
||||
this.clearHighlights();
|
||||
this.cancelAllGrabs();
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.button.classList.remove("on");
|
||||
this.status.textContent = "off";
|
||||
@@ -4503,42 +4527,118 @@ var GestureBridge = class {
|
||||
await this.gc.start();
|
||||
this.running = true;
|
||||
this.button.classList.add("on");
|
||||
this.status.textContent = "on \u2014 pinch a tab";
|
||||
this.status.textContent = "on \u2014 pinch a tab or button";
|
||||
} catch (err) {
|
||||
this.status.textContent = `failed: ${err.message}`;
|
||||
const msg = describeError(err);
|
||||
this.status.textContent = `failed: ${msg}`;
|
||||
this.status.title = msg;
|
||||
console.error("[gesture] start failed", err);
|
||||
} finally {
|
||||
this.button.disabled = false;
|
||||
}
|
||||
}
|
||||
/** Tab element at a viewport point (our overlays are click-through). */
|
||||
tabAt(x2, y2) {
|
||||
toggleCamMode() {
|
||||
this.camMode = this.camMode === "full" ? "pip" : "full";
|
||||
this.applyCamMode();
|
||||
}
|
||||
applyCamMode() {
|
||||
this.video.classList.toggle("cg-full", this.camMode === "full");
|
||||
this.video.classList.toggle("cg-pip", this.camMode === "pip");
|
||||
}
|
||||
/** Top-most element matching `sel` at a viewport point (overlays are
|
||||
* click-through, so elementFromPoint sees the dashboard beneath). */
|
||||
hitClosest(x2, y2, sel) {
|
||||
const hit = document.elementFromPoint(x2, y2);
|
||||
return hit?.closest(TAB_SELECTOR) ?? null;
|
||||
return hit?.closest(sel) ?? null;
|
||||
}
|
||||
onGrab(hand, x2, y2) {
|
||||
const tab = this.tabAt(x2, y2);
|
||||
const tab = this.hitClosest(x2, y2, TAB_SELECTOR);
|
||||
const id = tab?.dataset.id;
|
||||
if (id) {
|
||||
this.grabbedByHand.set(hand, id);
|
||||
if (tab && id) {
|
||||
const rect = tab.getBoundingClientRect();
|
||||
const ghost = tab.cloneNode(true);
|
||||
ghost.classList.add("cg-ghost");
|
||||
ghost.removeAttribute("id");
|
||||
ghost.style.width = `${rect.width}px`;
|
||||
ghost.style.height = `${rect.height}px`;
|
||||
document.body.append(ghost);
|
||||
tab.classList.add("cg-grabbed");
|
||||
const grab = { id, tab, ghost, ox: x2, oy: y2, armed: false };
|
||||
this.grabs.set(hand, grab);
|
||||
this.positionGhost(grab, x2, y2);
|
||||
return;
|
||||
}
|
||||
const btn = this.hitClosest(x2, y2, CLICK_SELECTOR);
|
||||
if (btn) {
|
||||
const label = (btn.textContent || btn.getAttribute("title") || "button").trim();
|
||||
btn.classList.add("cg-tap-armed");
|
||||
this.taps.set(hand, { el: btn, label, ox: x2, oy: y2 });
|
||||
this.status.textContent = `release to ${label.toLowerCase()}`;
|
||||
}
|
||||
}
|
||||
onDrag(hand, _x, _y) {
|
||||
onDrag(hand, x2, y2) {
|
||||
const grab = this.grabs.get(hand);
|
||||
if (grab) {
|
||||
this.positionGhost(grab, x2, y2);
|
||||
const pulled = Math.hypot(x2 - grab.ox, y2 - grab.oy) >= DETACH_PULL_PX;
|
||||
if (pulled !== grab.armed) {
|
||||
grab.armed = pulled;
|
||||
grab.ghost.classList.toggle("cg-armed", pulled);
|
||||
this.status.textContent = pulled ? "release to detach" : "on \u2014 pinch a tab";
|
||||
}
|
||||
return;
|
||||
}
|
||||
const tap = this.taps.get(hand);
|
||||
if (tap && Math.hypot(x2 - tap.ox, y2 - tap.oy) > TAP_CANCEL_PX) {
|
||||
tap.el.classList.remove("cg-tap-armed");
|
||||
this.taps.delete(hand);
|
||||
this.status.textContent = "on \u2014 pinch a tab or button";
|
||||
}
|
||||
}
|
||||
onDrop(hand) {
|
||||
const id = this.grabbedByHand.get(hand);
|
||||
this.grabbedByHand.delete(hand);
|
||||
if (!id) return;
|
||||
document.querySelector(`${TAB_SELECTOR}[data-id="${cssEscape(id)}"]`)?.classList.remove("cg-grabbed");
|
||||
const app = window.app;
|
||||
if (app?.detachSession) {
|
||||
app.detachSession(id);
|
||||
this.flash(`detached`);
|
||||
} else {
|
||||
this.flash("app.detachSession unavailable");
|
||||
console.warn("[gesture] window.app.detachSession not found");
|
||||
const grab = this.grabs.get(hand);
|
||||
if (grab) {
|
||||
this.grabs.delete(hand);
|
||||
grab.ghost.remove();
|
||||
grab.tab.classList.remove("cg-grabbed");
|
||||
if (grab.armed) this.detach(grab.id);
|
||||
else this.flash("cancelled");
|
||||
return;
|
||||
}
|
||||
const tap = this.taps.get(hand);
|
||||
if (tap) {
|
||||
this.taps.delete(hand);
|
||||
tap.el.classList.remove("cg-tap-armed");
|
||||
tap.el.click();
|
||||
this.flash(tap.label.toLowerCase());
|
||||
}
|
||||
}
|
||||
/** Undock a session by calling Codeman's own wired entry point directly. */
|
||||
detach(id) {
|
||||
const app = window.app;
|
||||
if (app && typeof app.detachSession === "function") {
|
||||
app.detachSession(id);
|
||||
this.flash("undocked");
|
||||
return;
|
||||
}
|
||||
this.flash("undock unavailable");
|
||||
console.warn(
|
||||
"[gesture] window.app.detachSession not found \u2014 is Codeman on the session-detach build?"
|
||||
);
|
||||
}
|
||||
positionGhost(grab, x2, y2) {
|
||||
grab.ghost.style.left = `${x2}px`;
|
||||
grab.ghost.style.top = `${y2}px`;
|
||||
}
|
||||
cancelAllGrabs() {
|
||||
for (const grab of this.grabs.values()) {
|
||||
grab.ghost.remove();
|
||||
grab.tab.classList.remove("cg-grabbed");
|
||||
}
|
||||
this.grabs.clear();
|
||||
for (const tap of this.taps.values()) tap.el.classList.remove("cg-tap-armed");
|
||||
this.taps.clear();
|
||||
document.querySelectorAll(`${TAB_SELECTOR}.cg-grabbed, .cg-tap-armed`).forEach((t2) => t2.classList.remove("cg-grabbed", "cg-tap-armed"));
|
||||
}
|
||||
onStatus(fps, hands) {
|
||||
const { width, height } = this.canvas;
|
||||
@@ -4555,14 +4655,13 @@ var GestureBridge = class {
|
||||
this.ctx.fill();
|
||||
this.ctx.globalAlpha = 1;
|
||||
}
|
||||
if (this.running) this.status.textContent = `on \xB7 ${fps}fps`;
|
||||
if (this.running && this.grabs.size === 0 && this.taps.size === 0) {
|
||||
this.status.textContent = `on \xB7 ${fps}fps`;
|
||||
}
|
||||
}
|
||||
flash(msg) {
|
||||
this.status.textContent = msg;
|
||||
}
|
||||
clearHighlights() {
|
||||
document.querySelectorAll(`${TAB_SELECTOR}.cg-grabbed`).forEach((t2) => t2.classList.remove("cg-grabbed"));
|
||||
}
|
||||
sizeCanvas() {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
this.canvas.width = Math.floor(window.innerWidth * dpr);
|
||||
@@ -4574,9 +4673,20 @@ function el(tag, className) {
|
||||
node.className = className;
|
||||
return node;
|
||||
}
|
||||
function cssEscape(s2) {
|
||||
const cssApi = window.CSS;
|
||||
return cssApi?.escape ? cssApi.escape(s2) : s2.replace(/["\\]/g, "\\$&");
|
||||
function describeError(err) {
|
||||
if (err instanceof Error) return err.message || err.name || "Error";
|
||||
if (typeof err === "string") return err;
|
||||
if (typeof err === "number") return `code ${err}`;
|
||||
if (err && typeof err === "object") {
|
||||
const m2 = err.message;
|
||||
if (typeof m2 === "string" && m2) return m2;
|
||||
try {
|
||||
return JSON.stringify(err);
|
||||
} catch {
|
||||
return Object.prototype.toString.call(err);
|
||||
}
|
||||
}
|
||||
return String(err);
|
||||
}
|
||||
function injectStyles() {
|
||||
if (document.getElementById("cg-styles")) return;
|
||||
@@ -4584,22 +4694,36 @@ function injectStyles() {
|
||||
.cg-surface, .cg-canvas { position: fixed; inset: 0; pointer-events: none; }
|
||||
.cg-surface { z-index: ${Z2}; }
|
||||
.cg-canvas { z-index: ${Z2 + 1}; width: 100vw; height: 100vh; }
|
||||
.cg-preview {
|
||||
position: fixed; right: 12px; bottom: 12px; width: 200px; height: 112px;
|
||||
object-fit: cover; transform: scaleX(-1); border-radius: 8px; z-index: ${Z2 + 2};
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,.5); pointer-events: none; background: #000;
|
||||
.cg-preview { transform: scaleX(-1); pointer-events: none; background: #000; }
|
||||
.cg-preview.cg-pip {
|
||||
position: fixed; right: 12px; bottom: 12px; width: 240px; height: 135px;
|
||||
object-fit: cover; border-radius: 8px; z-index: ${Z2 + 2};
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,.5); opacity: 1;
|
||||
}
|
||||
.cg-preview.cg-full {
|
||||
position: fixed; inset: 0; width: 100vw; height: 100vh;
|
||||
object-fit: cover; opacity: .28; z-index: ${Z2};
|
||||
}
|
||||
.cg-ghost {
|
||||
position: fixed; left: 0; top: 0; transform: translate(-50%, -50%) scale(1.06);
|
||||
z-index: ${Z2 + 2}; pointer-events: none; opacity: .92;
|
||||
box-shadow: 0 8px 28px rgba(0,0,0,.55); border-radius: 8px;
|
||||
outline: 2px solid #38bdf8; outline-offset: -2px;
|
||||
}
|
||||
.cg-ghost.cg-armed { outline-color: #4ade80; box-shadow: 0 8px 28px rgba(74,222,128,.5); }
|
||||
.cg-dock {
|
||||
position: fixed; right: 12px; bottom: 132px; z-index: ${Z2 + 3};
|
||||
position: fixed; right: 12px; bottom: 156px; z-index: ${Z2 + 3};
|
||||
display: flex; align-items: center; gap: 8px; font: 12px/1 system-ui, sans-serif;
|
||||
}
|
||||
.cg-btn {
|
||||
padding: 6px 12px; border-radius: 6px; border: 1px solid #3a3a40;
|
||||
background: #1b1b1f; color: #e5e5e7; cursor: pointer;
|
||||
}
|
||||
.cg-btn-icon { padding: 6px 9px; }
|
||||
.cg-btn.on { background: #16331f; border-color: #2f6b41; color: #4ade80; }
|
||||
.cg-status { color: #9aa0a6; }
|
||||
.session-tab.cg-grabbed { outline: 2px solid #4ade80; outline-offset: -2px; }
|
||||
.cg-status { color: #9aa0a6; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.session-tab.cg-grabbed { opacity: .35; outline: 2px dashed #4ade80; outline-offset: -2px; }
|
||||
.cg-tap-armed { outline: 2px solid #4ade80 !important; outline-offset: 2px; box-shadow: 0 0 0 4px rgba(74,222,128,.25) !important; }
|
||||
`;
|
||||
const style = document.createElement("style");
|
||||
style.id = "cg-styles";
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,20 @@
|
||||
// Build 704377259
|
||||
|
||||
var ModuleFactory = (() => {
|
||||
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
|
||||
if (typeof __filename != 'undefined') _scriptName ||= __filename;
|
||||
return (
|
||||
function(moduleArg = {}) {
|
||||
var moduleRtn;
|
||||
|
||||
var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);var ret=fs.readFileSync(filename);return ret};readAsync=(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return new Promise((resolve,reject)=>{fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)reject(err);else resolve(binary?data.buffer:data)})})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=url=>{if(isFileURI(url)){return new Promise((reject,resolve)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response)}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}return fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))})}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";Line truncated
|
||||
|
||||
|
||||
return moduleRtn;
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = ModuleFactory;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], () => ModuleFactory);
|
||||
Binary file not shown.
@@ -0,0 +1,20 @@
|
||||
// Build 704377259
|
||||
|
||||
var ModuleFactory = (() => {
|
||||
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
|
||||
if (typeof __filename != 'undefined') _scriptName ||= __filename;
|
||||
return (
|
||||
function(moduleArg = {}) {
|
||||
var moduleRtn;
|
||||
|
||||
var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);var ret=fs.readFileSync(filename);return ret};readAsync=(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return new Promise((resolve,reject)=>{fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)reject(err);else resolve(binary?data.buffer:data)})})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=url=>{if(isFileURI(url)){return new Promise((reject,resolve)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response)}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}return fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))})}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";Line truncated
|
||||
|
||||
|
||||
return moduleRtn;
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = ModuleFactory;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], () => ModuleFactory);
|
||||
Binary file not shown.
+17
-2
@@ -36,7 +36,7 @@ import fastifyMultipart from '@fastify/multipart';
|
||||
import { startPasteImageGc } from './paste-image-gc.js';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { existsSync, mkdirSync, readFileSync, chmodSync, rmSync } from 'node:fs';
|
||||
import { existsSync, mkdirSync, readFileSync, chmodSync, rmSync, statSync } from 'node:fs';
|
||||
import fs from 'node:fs/promises';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { hostname as getHostname } from 'node:os';
|
||||
@@ -1012,11 +1012,26 @@ export class WebServer extends EventEmitter {
|
||||
// same-origin from /gesture/ so 'self' covers it; CSP is widened to match in
|
||||
// registerSecurityHeaders under the same flag.
|
||||
if (!soloSessionId && process.env.CODEMAN_GESTURE === '1') {
|
||||
html = html.replace('</head>', `<script type="module" src="/gesture/gesture-codeman.js"></script>\n</head>`);
|
||||
const v = this.gestureBundleVersion();
|
||||
html = html.replace('</head>', `<script type="module" src="/gesture/gesture-codeman.js${v}"></script>\n</head>`);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
/** Cache-busting query for the gesture bundle: its mtime, re-read per render.
|
||||
* The bundle is served from /gesture/ with a 1-year cache, so without a
|
||||
* version that changes on redeploy the browser would keep running a stale
|
||||
* bundle forever. Re-stat'ing each render means a freshly copied-in bundle is
|
||||
* picked up with no server restart. Empty string if the file is missing. */
|
||||
private gestureBundleVersion(): string {
|
||||
try {
|
||||
const p = join(__dirname, 'public', 'gesture', 'gesture-codeman.js');
|
||||
return `?v=${Math.floor(statSync(p).mtimeMs)}`;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private async setupSessionListeners(session: Session): Promise<void> {
|
||||
// Create run summary tracker for this session
|
||||
const summaryTracker = new RunSummaryTracker(session.id, session.name);
|
||||
|
||||
Reference in new issue
Block a user