sandboxedjs 0.2.19 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -8
- package/bin/sandboxedjs-pack.mjs +4 -0
- package/dist/index.cjs +626 -100
- package/dist/index.js +626 -100
- package/dist/worker-entry.js +1 -1
- package/docs/agent/README.md +35 -60
- package/docs/to_implement/agent.md +261 -0
- package/docs/vireo.md +58 -0
- package/package.json +12 -3
package/dist/index.js
CHANGED
|
@@ -6841,9 +6841,9 @@ var WasiHost = class {
|
|
|
6841
6841
|
if (!(oflags & O_CREAT)) throw new WasiError(WASI_ENOENT);
|
|
6842
6842
|
vfs.writeFile(abs, new Uint8Array(0), { cred, mode: 420 });
|
|
6843
6843
|
}
|
|
6844
|
-
const
|
|
6845
|
-
const data =
|
|
6846
|
-
if (
|
|
6844
|
+
const truncate2 = (oflags & O_TRUNC) !== 0;
|
|
6845
|
+
const data = truncate2 ? new Uint8Array(0) : vfs.readFile(abs, cred);
|
|
6846
|
+
if (truncate2) vfs.writeFile(abs, data, { cred });
|
|
6847
6847
|
const size = data.length;
|
|
6848
6848
|
const rights = rightsBase === 0n ? RIGHTS_ALL : rightsBase;
|
|
6849
6849
|
const append = (fdflags & FD_APPEND) !== 0;
|
|
@@ -21086,7 +21086,7 @@ var wheels_default = {
|
|
|
21086
21086
|
|
|
21087
21087
|
// package.json
|
|
21088
21088
|
var package_default = {
|
|
21089
|
-
version: "0.2.
|
|
21089
|
+
version: "0.2.21"};
|
|
21090
21090
|
|
|
21091
21091
|
// src/python/extension-abi.ts
|
|
21092
21092
|
var EXTENSION_ABI = {
|
|
@@ -23896,7 +23896,7 @@ async function resolve2(options) {
|
|
|
23896
23896
|
const clauses = (state.constraints.get(name) ?? []).flatMap((entry) => entry.spec.map((s) => `${s.op}${s.version}`)).sort();
|
|
23897
23897
|
return `${name}|${[...new Set(clauses)].join(",")}`;
|
|
23898
23898
|
};
|
|
23899
|
-
const
|
|
23899
|
+
const search3 = async (queue, state) => {
|
|
23900
23900
|
checkBudget();
|
|
23901
23901
|
if (queue.length === 0) return state;
|
|
23902
23902
|
const [edge, ...rest] = queue;
|
|
@@ -23904,7 +23904,7 @@ async function resolve2(options) {
|
|
|
23904
23904
|
const name = requirement.name;
|
|
23905
23905
|
if (!markerApplies(requirement.marker, environment, extras)) {
|
|
23906
23906
|
if (requirement.marker) skipped.push({ name, marker: requirement.marker });
|
|
23907
|
-
return
|
|
23907
|
+
return search3(rest, state);
|
|
23908
23908
|
}
|
|
23909
23909
|
const previous = state.constraints.get(name) ?? [];
|
|
23910
23910
|
const constraints = new Map(state.constraints);
|
|
@@ -23921,7 +23921,7 @@ async function resolve2(options) {
|
|
|
23921
23921
|
explain(name, next.constraints, already.version)
|
|
23922
23922
|
);
|
|
23923
23923
|
}
|
|
23924
|
-
return
|
|
23924
|
+
return search3(rest, next);
|
|
23925
23925
|
}
|
|
23926
23926
|
const signature = signatureOf(name, next);
|
|
23927
23927
|
const learned = nogoods.get(signature);
|
|
@@ -23984,7 +23984,7 @@ async function resolve2(options) {
|
|
|
23984
23984
|
const chosen = new Map(next.chosen);
|
|
23985
23985
|
chosen.set(name, { ...candidate, requires });
|
|
23986
23986
|
try {
|
|
23987
|
-
return await
|
|
23987
|
+
return await search3([...edges, ...rest], { chosen, constraints: next.constraints });
|
|
23988
23988
|
} catch (error) {
|
|
23989
23989
|
if (!(error instanceof ResolutionError)) throw error;
|
|
23990
23990
|
if (/cancelled|budget/.test(error.message)) throw error;
|
|
@@ -23999,7 +23999,7 @@ async function resolve2(options) {
|
|
|
23999
23999
|
if (specifiers.length === 0 || !markerApplies(marker, environment, root.extras)) continue;
|
|
24000
24000
|
seeded.set(name, [...seeded.get(name) ?? [], { spec: specifiers, origin: root.origin }]);
|
|
24001
24001
|
}
|
|
24002
|
-
const solved = await
|
|
24002
|
+
const solved = await search3(roots, { chosen: /* @__PURE__ */ new Map(), constraints: seeded });
|
|
24003
24003
|
const distributions = [...solved.chosen.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
24004
24004
|
const reported = new Map(skipped.map((s) => [`${s.name}\0${s.marker}`, s]));
|
|
24005
24005
|
return { distributions, skipped: [...reported.values()] };
|
|
@@ -24246,7 +24246,7 @@ function consoleScripts(text2) {
|
|
|
24246
24246
|
return result;
|
|
24247
24247
|
}
|
|
24248
24248
|
function consoleLauncher(target) {
|
|
24249
|
-
const [module,
|
|
24249
|
+
const [module, attribute2 = ""] = target.split(":", 2);
|
|
24250
24250
|
return `#!/usr/bin/python3
|
|
24251
24251
|
import sys
|
|
24252
24252
|
from importlib import import_module
|
|
@@ -24254,7 +24254,7 @@ from importlib import import_module
|
|
|
24254
24254
|
|
|
24255
24255
|
def _entry():
|
|
24256
24256
|
entry = import_module(${JSON.stringify(module)})
|
|
24257
|
-
for part in ${JSON.stringify(
|
|
24257
|
+
for part in ${JSON.stringify(attribute2)}.split('.'):
|
|
24258
24258
|
if part:
|
|
24259
24259
|
entry = getattr(entry, part)
|
|
24260
24260
|
return entry
|
|
@@ -27631,7 +27631,7 @@ var defaultRealmFactory = async (hooks) => {
|
|
|
27631
27631
|
vm = void 0;
|
|
27632
27632
|
}
|
|
27633
27633
|
if (vm?.createContext) {
|
|
27634
|
-
const context = vm.createContext({ console: console2, setTimeout, clearTimeout, setInterval, clearInterval, queueMicrotask });
|
|
27634
|
+
const context = vm.createContext({ console: console2, URL, setTimeout, clearTimeout, setInterval, clearInterval, queueMicrotask });
|
|
27635
27635
|
return { evaluate: (source) => vm.runInContext(source, context), dispose: () => {
|
|
27636
27636
|
} };
|
|
27637
27637
|
}
|
|
@@ -27758,6 +27758,28 @@ var CdpServer = class {
|
|
|
27758
27758
|
return {};
|
|
27759
27759
|
case "Runtime.runIfWaitingForDebugger":
|
|
27760
27760
|
return {};
|
|
27761
|
+
case "Input.insertText": {
|
|
27762
|
+
const target = page ?? this.pages.values().next().value;
|
|
27763
|
+
if (!target) return {};
|
|
27764
|
+
for (const context of target.contexts.values()) context.realm.evaluate(`(() => { const element = document.activeElement; if (!element || element === document.body) return; element.value = (element.value || '') + ${JSON.stringify(String(params.text ?? ""))}; element.dispatchEvent(new Event('input', { bubbles: true })); })()`);
|
|
27765
|
+
return {};
|
|
27766
|
+
}
|
|
27767
|
+
case "Input.dispatchKeyEvent": {
|
|
27768
|
+
const target = page ?? this.pages.values().next().value;
|
|
27769
|
+
if (!target) return {};
|
|
27770
|
+
if (params.type === "char" || params.type === "keyDown" && params.text) {
|
|
27771
|
+
for (const context of target.contexts.values()) context.realm.evaluate(`(() => { const element = document.activeElement; if (!element || element === document.body) return; const text = ${JSON.stringify(String(params.text ?? ""))}; element.value = (element.value || '') + text; element.dispatchEvent(new KeyboardEvent('keydown', { key: text, bubbles: true })); element.dispatchEvent(new Event('input', { bubbles: true })); })()`);
|
|
27772
|
+
}
|
|
27773
|
+
return {};
|
|
27774
|
+
}
|
|
27775
|
+
case "Input.dispatchMouseEvent": {
|
|
27776
|
+
const target = page ?? this.pages.values().next().value;
|
|
27777
|
+
if (!target) return {};
|
|
27778
|
+
if (params.type === "mouseReleased") {
|
|
27779
|
+
for (const context of target.contexts.values()) context.realm.evaluate(`document.elementFromPoint(${Number(params.x) || 0}, ${Number(params.y) || 0})?.click()`);
|
|
27780
|
+
}
|
|
27781
|
+
return {};
|
|
27782
|
+
}
|
|
27761
27783
|
}
|
|
27762
27784
|
const [, name = ""] = method.split(".");
|
|
27763
27785
|
if (name === "enable" || name === "disable" || /^set[A-Z]/.test(name)) return {};
|
|
@@ -27779,7 +27801,8 @@ var CdpServer = class {
|
|
|
27779
27801
|
url: "about:blank",
|
|
27780
27802
|
loader: 1,
|
|
27781
27803
|
contexts: /* @__PURE__ */ new Map(),
|
|
27782
|
-
worlds: /* @__PURE__ */ new Set()
|
|
27804
|
+
worlds: /* @__PURE__ */ new Set(),
|
|
27805
|
+
document: void 0
|
|
27783
27806
|
};
|
|
27784
27807
|
this.pages.set(page.sessionId, page);
|
|
27785
27808
|
this.options.send({
|
|
@@ -27829,6 +27852,22 @@ var CdpServer = class {
|
|
|
27829
27852
|
timestamp: Date.now()
|
|
27830
27853
|
})
|
|
27831
27854
|
});
|
|
27855
|
+
if (page.document) realm.evaluate(domBootstrap(page.document));
|
|
27856
|
+
if (page.document) {
|
|
27857
|
+
for (const script of documentScripts(page.document.root)) {
|
|
27858
|
+
try {
|
|
27859
|
+
realm.evaluate(`document.currentScript = document.__nodeByPath(${JSON.stringify(script.path)});
|
|
27860
|
+
${script.code}
|
|
27861
|
+
//# sourceURL=${script.url}`);
|
|
27862
|
+
} catch (error) {
|
|
27863
|
+
this.event(page, "Runtime.exceptionThrown", {
|
|
27864
|
+
timestamp: Date.now(),
|
|
27865
|
+
exceptionDetails: { text: String(error), lineNumber: 0, columnNumber: 0 }
|
|
27866
|
+
});
|
|
27867
|
+
}
|
|
27868
|
+
}
|
|
27869
|
+
realm.evaluate("document.currentScript = null");
|
|
27870
|
+
}
|
|
27832
27871
|
const context = { id, name, isDefault, realm, objects: /* @__PURE__ */ new Map(), nextObject: 0 };
|
|
27833
27872
|
page.contexts.set(id, context);
|
|
27834
27873
|
this.announceContext(page, context);
|
|
@@ -27864,6 +27903,13 @@ var CdpServer = class {
|
|
|
27864
27903
|
return DEFERRED;
|
|
27865
27904
|
}
|
|
27866
27905
|
async commitNavigation(page, url) {
|
|
27906
|
+
if (this.options.navigate && /^(https?|data):/i.test(url)) {
|
|
27907
|
+
const loaded = await this.options.navigate(url);
|
|
27908
|
+
url = loaded.url;
|
|
27909
|
+
page.document = loaded.document;
|
|
27910
|
+
} else {
|
|
27911
|
+
page.document = void 0;
|
|
27912
|
+
}
|
|
27867
27913
|
page.loader += 1;
|
|
27868
27914
|
page.url = url;
|
|
27869
27915
|
this.destroyContexts(page, true);
|
|
@@ -27896,14 +27942,22 @@ var CdpServer = class {
|
|
|
27896
27942
|
callFunctionOn(page, params) {
|
|
27897
27943
|
const context = params.objectId ? this.contextOfObject(page, params.objectId) : this.contextFor(page, params.executionContextId);
|
|
27898
27944
|
const receiver = params.objectId ? context.objects.get(params.objectId) : void 0;
|
|
27899
|
-
const args = (params.arguments ?? []).map((argument) => this.argument(page, argument));
|
|
27945
|
+
const args = (params.arguments ?? []).map((argument) => this.argument(page, argument, context));
|
|
27900
27946
|
return this.settle(context, () => {
|
|
27901
27947
|
const fn = context.realm.evaluate(`(${params.functionDeclaration})`);
|
|
27902
27948
|
return fn.apply(receiver, args);
|
|
27903
27949
|
}, params);
|
|
27904
27950
|
}
|
|
27905
|
-
argument(page, argument) {
|
|
27906
|
-
if (argument.objectId)
|
|
27951
|
+
argument(page, argument, target) {
|
|
27952
|
+
if (argument.objectId) {
|
|
27953
|
+
const source = this.contextOfObject(page, argument.objectId);
|
|
27954
|
+
const value = source.objects.get(argument.objectId);
|
|
27955
|
+
if (target && source !== target && value && typeof value === "object" && "__vireoPath" in value) {
|
|
27956
|
+
const path = value.__vireoPath;
|
|
27957
|
+
return target.realm.evaluate(`document.__nodeByPath(${JSON.stringify(path)})`);
|
|
27958
|
+
}
|
|
27959
|
+
return value;
|
|
27960
|
+
}
|
|
27907
27961
|
if (argument.unserializableValue !== void 0) {
|
|
27908
27962
|
const raw = String(argument.unserializableValue);
|
|
27909
27963
|
if (raw === "NaN") return NaN;
|
|
@@ -27953,83 +28007,321 @@ var CdpServer = class {
|
|
|
27953
28007
|
return { type, objectId, className, description: type === "function" ? "function" : className, ...subtype ? { subtype } : {} };
|
|
27954
28008
|
}
|
|
27955
28009
|
};
|
|
28010
|
+
function documentScripts(root) {
|
|
28011
|
+
const scripts = [];
|
|
28012
|
+
const visit = (node2, path) => {
|
|
28013
|
+
if (node2.type === "element" && node2.name === "script") {
|
|
28014
|
+
const type = node2.attrs?.type?.trim().toLowerCase();
|
|
28015
|
+
if (!type || type === "text/javascript" || type === "application/javascript") {
|
|
28016
|
+
const inline = (node2.children ?? []).filter((child) => child.type === "text").map((child) => child.text ?? "").join("");
|
|
28017
|
+
scripts.push({ path, code: node2.resourceText ?? inline, url: node2.attrs?.src ?? "vireo:inline-script" });
|
|
28018
|
+
}
|
|
28019
|
+
}
|
|
28020
|
+
node2.children?.forEach((child, index) => visit(child, [...path, index]));
|
|
28021
|
+
};
|
|
28022
|
+
visit(root, []);
|
|
28023
|
+
return scripts;
|
|
28024
|
+
}
|
|
28025
|
+
function domBootstrap(document2) {
|
|
28026
|
+
return `(() => {
|
|
28027
|
+
const snapshot = ${JSON.stringify(document2)};
|
|
28028
|
+
const escapeText = value => String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
28029
|
+
const escapeAttr = value => escapeText(value).replaceAll('"', '"');
|
|
28030
|
+
class VireoNode {
|
|
28031
|
+
constructor(data, parent = null) {
|
|
28032
|
+
this.__data = data; this.parentNode = parent;
|
|
28033
|
+
this.__listeners = new Map();
|
|
28034
|
+
this.childNodes = (data.children || []).map(child => new VireoNode(child, this));
|
|
28035
|
+
}
|
|
28036
|
+
get nodeType() { return this.__data.type === 'document' ? 9 : this.__data.type === 'text' ? 3 : 1; }
|
|
28037
|
+
get nodeName() { return this.nodeType === 9 ? '#document' : this.nodeType === 3 ? '#text' : this.__data.name.toUpperCase(); }
|
|
28038
|
+
get textContent() { return this.nodeType === 3 ? this.__data.text : this.childNodes.map(node => node.textContent).join(''); }
|
|
28039
|
+
set textContent(value) { if (this.nodeType === 3) this.__data.text = String(value); else { const child = new VireoNode({ type:'text', text:String(value) }, this); this.childNodes = [child]; } notify(this, 'characterData'); }
|
|
28040
|
+
get children() { return this.childNodes.filter(node => node.nodeType === 1); }
|
|
28041
|
+
get firstChild() { return this.childNodes[0] || null; }
|
|
28042
|
+
get parentElement() { return this.parentNode?.nodeType === 1 ? this.parentNode : null; }
|
|
28043
|
+
get ownerDocument() { let node = this; while (node.parentNode) node = node.parentNode; return node.nodeType === 9 ? node : null; }
|
|
28044
|
+
getRootNode() { let node = this; while (node.parentNode) node = node.parentNode; return node; }
|
|
28045
|
+
get isConnected() { let node = this; while (node.parentNode) node = node.parentNode; return node.nodeType === 9; }
|
|
28046
|
+
contains(other) { for (let node = other; node; node = node.parentNode) if (node === this) return true; return false; }
|
|
28047
|
+
addEventListener(type, listener) { const set = this.__listeners.get(type) || new Set(); set.add(listener); this.__listeners.set(type, set); }
|
|
28048
|
+
removeEventListener(type, listener) { this.__listeners.get(type)?.delete(listener); }
|
|
28049
|
+
dispatchEvent(event) { for (const listener of this.__listeners.get(event.type) || []) listener.call(this, event); return true; }
|
|
28050
|
+
querySelector(selector) { return query(this, selector)[0] || null; }
|
|
28051
|
+
querySelectorAll(selector) { return query(this, selector); }
|
|
28052
|
+
appendChild(child) { child.remove(); child.parentNode = this; this.childNodes.push(child); notify(this, 'childList'); return child; }
|
|
28053
|
+
append(...items) { for (const item of items) this.appendChild(typeof item === 'string' ? document.createTextNode(item) : item); }
|
|
28054
|
+
prepend(...items) { for (const item of items.reverse()) { const child = typeof item === 'string' ? document.createTextNode(item) : item; child.remove(); child.parentNode = this; this.childNodes.unshift(child); } notify(this, 'childList'); }
|
|
28055
|
+
removeChild(child) { const index = this.childNodes.indexOf(child); if (index < 0) throw new Error('NotFoundError'); this.childNodes.splice(index, 1); child.parentNode = null; notify(this, 'childList'); return child; }
|
|
28056
|
+
remove() { this.parentNode?.removeChild(this); }
|
|
28057
|
+
replaceChildren(...items) { for (const child of this.childNodes) child.parentNode = null; this.childNodes = []; this.append(...items); }
|
|
28058
|
+
insertBefore(child, reference) { if (reference == null) return this.appendChild(child); const index = this.childNodes.indexOf(reference); if (index < 0) throw new Error('NotFoundError'); child.remove(); child.parentNode = this; this.childNodes.splice(index, 0, child); notify(this, 'childList'); return child; }
|
|
28059
|
+
}
|
|
28060
|
+
class VireoElement extends VireoNode {}
|
|
28061
|
+
Object.assign(VireoNode, { ELEMENT_NODE: 1, TEXT_NODE: 3, DOCUMENT_NODE: 9 });
|
|
28062
|
+
Object.defineProperties(VireoNode.prototype, {
|
|
28063
|
+
tagName: { get() { return this.nodeType === 1 ? this.__data.name.toUpperCase() : undefined; } },
|
|
28064
|
+
localName: { get() { return this.nodeType === 1 ? this.__data.name : undefined; } },
|
|
28065
|
+
namespaceURI: { get() { return this.nodeType === 1 ? 'http://www.w3.org/1999/xhtml' : null; } },
|
|
28066
|
+
id: { get() { return this.getAttribute?.('id') || ''; }, set(value) { this.setAttribute('id', value); } },
|
|
28067
|
+
className: { get() { return this.getAttribute?.('class') || ''; }, set(value) { this.setAttribute('class', value); } },
|
|
28068
|
+
classList: { get() { const node = this; const values = () => node.className.split(/\\s+/).filter(Boolean); return { add(...names) { node.className = [...new Set([...values(), ...names])].join(' '); }, remove(...names) { node.className = values().filter(value => !names.includes(value)).join(' '); }, contains(name) { return values().includes(name); }, toggle(name, force) { const present = values().includes(name); const next = force === undefined ? !present : Boolean(force); next ? this.add(name) : this.remove(name); return next; }, toString() { return node.className; } }; } },
|
|
28069
|
+
innerText: { get() { return this.textContent.replace(/\\s+/g, ' ').trim(); } },
|
|
28070
|
+
attributes: { get() { return Object.entries(this.__data.attrs || {}).map(([name, value]) => ({ name, value })); } },
|
|
28071
|
+
innerHTML: { get() { return this.childNodes.map(serialize).join(''); } },
|
|
28072
|
+
outerHTML: { get() { return serialize(this); } },
|
|
28073
|
+
value: { get() { return this.__value ?? this.getAttribute?.('value') ?? ''; }, set(value) { this.__value = String(value); this.setAttribute?.('value', this.__value); } },
|
|
28074
|
+
selectionStart: { get() { return this.__selectionStart ?? 0; }, set(value) { this.__selectionStart = Number(value); } },
|
|
28075
|
+
selectionEnd: { get() { return this.__selectionEnd ?? this.value.length; }, set(value) { this.__selectionEnd = Number(value); } },
|
|
28076
|
+
checked: { get() { return this.__checked ?? this.hasAttribute?.('checked') ?? false; }, set(value) { this.__checked = Boolean(value); } },
|
|
28077
|
+
disabled: { get() { return this.hasAttribute?.('disabled') ?? false; }, set(value) { value ? this.setAttribute('disabled', '') : this.removeAttribute('disabled'); } },
|
|
28078
|
+
readOnly: { get() { return this.hasAttribute?.('readonly') ?? false; }, set(value) { value ? this.setAttribute('readonly', '') : this.removeAttribute('readonly'); } },
|
|
28079
|
+
type: { get() { return this.getAttribute?.('type') || ''; }, set(value) { this.setAttribute('type', value); } },
|
|
28080
|
+
isContentEditable: { get() { return this.getAttribute?.('contenteditable') === 'true'; } },
|
|
28081
|
+
contentDocument: { get() { return this.__contentDocument || null; } },
|
|
28082
|
+
contentWindow: { get() { return this.__contentDocument?.defaultView || null; } },
|
|
28083
|
+
});
|
|
28084
|
+
VireoNode.prototype.getAttribute = function(name) { const attrs = this.__data.attrs || {}; return Object.hasOwn(attrs, name) ? attrs[name] : null; };
|
|
28085
|
+
VireoNode.prototype.hasAttribute = function(name) { return this.getAttribute(name) !== null; };
|
|
28086
|
+
VireoNode.prototype.setAttribute = function(name, value) { (this.__data.attrs ||= {})[String(name)] = String(value); notify(this, 'attributes'); };
|
|
28087
|
+
VireoNode.prototype.removeAttribute = function(name) { delete (this.__data.attrs ||= {})[String(name)]; notify(this, 'attributes'); };
|
|
28088
|
+
VireoNode.prototype.matches = function(selector) { return matches(this, selector); };
|
|
28089
|
+
VireoNode.prototype.focus = function() { document.activeElement = this; this.dispatchEvent(new Event('focus')); };
|
|
28090
|
+
VireoNode.prototype.blur = function() { if (document.activeElement === this) document.activeElement = document.body; this.dispatchEvent(new Event('blur')); };
|
|
28091
|
+
VireoNode.prototype.click = function() { this.focus(); if (this.localName === 'input' && ['checkbox','radio'].includes(this.getAttribute('type'))) this.checked = !this.checked; this.dispatchEvent(new MouseEvent('click', { bubbles: true })); };
|
|
28092
|
+
VireoNode.prototype.getBoundingClientRect = function() { const all = descendants(document).filter(node => node.nodeType === 1); const index = Math.max(0, all.indexOf(this)); const top = index * 24; return { x: 0, y: top, top, left: 0, width: 800, height: 20, right: 800, bottom: top + 20, toJSON() { return { x:0, y:top, width:800, height:20, top, left:0, right:800, bottom:top+20 }; } }; };
|
|
28093
|
+
VireoNode.prototype.getClientRects = function() { return [this.getBoundingClientRect()]; };
|
|
28094
|
+
VireoNode.prototype.scrollIntoView = function() {};
|
|
28095
|
+
VireoNode.prototype.select = function() { this.selectionStart = 0; this.selectionEnd = this.value.length; };
|
|
28096
|
+
VireoNode.prototype.setSelectionRange = function(start, end) { this.selectionStart = start; this.selectionEnd = end; };
|
|
28097
|
+
VireoNode.prototype.submit = function() { this.dispatchEvent(new Event('submit', { bubbles: true })); };
|
|
28098
|
+
VireoNode.prototype.requestSubmit = function() { this.submit(); };
|
|
28099
|
+
const make = (data, parent = null, path = []) => {
|
|
28100
|
+
const node = data.type === 'element' ? new VireoElement(data, parent) : new VireoNode(data, parent);
|
|
28101
|
+
node.__vireoPath = path;
|
|
28102
|
+
node.childNodes = (data.children || []).map((child, index) => make(child, node, [...path, index]));
|
|
28103
|
+
if (data.contentDocument) node.__pendingContentDocument = data.contentDocument;
|
|
28104
|
+
return node;
|
|
28105
|
+
};
|
|
28106
|
+
const root = make(snapshot.root, null, []);
|
|
28107
|
+
function descendants(node) { return node.childNodes.flatMap(child => [child, ...descendants(child)]); }
|
|
28108
|
+
function matches(node, selector) {
|
|
28109
|
+
if (node.nodeType !== 1) return false;
|
|
28110
|
+
const part = selector.trim();
|
|
28111
|
+
const attr = /\\[([\\w:-]+)(?:=["']?([^\\]"']+)["']?)?\\]/.exec(part);
|
|
28112
|
+
const id = /#([\\w-]+)/.exec(part); const classes = [...part.matchAll(/\\.([\\w-]+)/g)].map(m => m[1]);
|
|
28113
|
+
const tag = /^([a-z][\\w-]*|\\*)/i.exec(part)?.[1];
|
|
28114
|
+
return (!tag || tag === '*' || node.localName === tag.toLowerCase())
|
|
28115
|
+
&& (!id || node.id === id[1])
|
|
28116
|
+
&& classes.every(name => node.className.split(/\\s+/).includes(name))
|
|
28117
|
+
&& (!attr || (node.hasAttribute(attr[1]) && (attr[2] === undefined || node.getAttribute(attr[1]) === attr[2])));
|
|
28118
|
+
}
|
|
28119
|
+
function query(node, selector) {
|
|
28120
|
+
const groups = selector.split(',').map(value => value.trim()).filter(Boolean); const out = [];
|
|
28121
|
+
for (const group of groups) {
|
|
28122
|
+
let scope = [node];
|
|
28123
|
+
for (const part of group.split(/\\s+/)) scope = scope.flatMap(parent => descendants(parent).filter(child => matches(child, part)));
|
|
28124
|
+
for (const match of scope) if (!out.includes(match)) out.push(match);
|
|
28125
|
+
}
|
|
28126
|
+
return out;
|
|
28127
|
+
}
|
|
28128
|
+
function serialize(node) {
|
|
28129
|
+
if (node.nodeType === 3) return escapeText(node.textContent);
|
|
28130
|
+
if (node.nodeType === 9) return node.childNodes.map(serialize).join('');
|
|
28131
|
+
const attrs = Object.entries(node.__data.attrs || {}).map(([k,v]) => ' ' + k + '="' + escapeAttr(v) + '"').join('');
|
|
28132
|
+
return '<' + node.localName + attrs + '>' + node.childNodes.map(serialize).join('') + '</' + node.localName + '>';
|
|
28133
|
+
}
|
|
28134
|
+
const document = root;
|
|
28135
|
+
document.URL = snapshot.url; document.documentURI = snapshot.url; document.readyState = 'complete';
|
|
28136
|
+
document.title = snapshot.title;
|
|
28137
|
+
document.documentElement = document.querySelector('html'); document.head = document.querySelector('head'); document.body = document.querySelector('body');
|
|
28138
|
+
document.getElementById = id => document.querySelector('#' + id);
|
|
28139
|
+
document.createElement = name => make({ type:'element', name:String(name).toLowerCase(), attrs:{}, children:[] });
|
|
28140
|
+
document.createTextNode = text => make({ type:'text', text:String(text) });
|
|
28141
|
+
document.activeElement = document.body;
|
|
28142
|
+
document.hasFocus = () => true;
|
|
28143
|
+
document.elementFromPoint = (_x, y) => descendants(document).filter(node => node.nodeType === 1).find(node => { const rect = node.getBoundingClientRect(); return y >= rect.top && y <= rect.bottom; }) || document.body;
|
|
28144
|
+
document.__nodeByPath = path => path.reduce((node, index) => node?.childNodes[index], document);
|
|
28145
|
+
const listeners = new Map();
|
|
28146
|
+
globalThis.addEventListener = (type, listener) => { const set = listeners.get(type) || new Set(); set.add(listener); listeners.set(type, set); };
|
|
28147
|
+
globalThis.removeEventListener = (type, listener) => listeners.get(type)?.delete(listener);
|
|
28148
|
+
globalThis.dispatchEvent = event => { for (const listener of listeners.get(event.type) || []) listener.call(globalThis, event); return true; };
|
|
28149
|
+
const observers = new Set();
|
|
28150
|
+
const notify = (target, type) => queueMicrotask(() => { for (const observer of observers) observer.callback([{ target, type }], observer); });
|
|
28151
|
+
class MutationObserver { constructor(callback) { this.callback = callback; } observe() { observers.add(this); } disconnect() { observers.delete(this); } takeRecords() { return []; } }
|
|
28152
|
+
class Event { constructor(type, init = {}) { this.type = type; Object.assign(this, init); } }
|
|
28153
|
+
class CustomEvent extends Event { constructor(type, init = {}) { super(type, init); this.detail = init.detail; } }
|
|
28154
|
+
class MouseEvent extends Event {}
|
|
28155
|
+
class KeyboardEvent extends Event {}
|
|
28156
|
+
const requestAnimationFrame = callback => setTimeout(() => callback(Date.now()), 0);
|
|
28157
|
+
const cancelAnimationFrame = id => clearTimeout(id);
|
|
28158
|
+
const getComputedStyle = element => ({ display: element.hasAttribute?.('hidden') ? 'none' : 'block', visibility: 'visible', opacity: '1', pointerEvents: 'auto', getPropertyValue() { return ''; } });
|
|
28159
|
+
Object.assign(globalThis, { window: globalThis, self: globalThis, document, Node: VireoNode, Element: VireoElement, HTMLElement: VireoElement, HTMLInputElement: VireoElement, HTMLTextAreaElement: VireoElement, HTMLSelectElement: VireoElement, HTMLButtonElement: VireoElement, HTMLFormElement: VireoElement, Event, CustomEvent, MouseEvent, KeyboardEvent, MutationObserver, requestAnimationFrame, cancelAnimationFrame, getComputedStyle, innerWidth: 1280, innerHeight: 720, scrollX: 0, scrollY: 0, location: new URL(snapshot.url), navigator: { userAgent: ${JSON.stringify(VIRTUAL_USER_AGENT)} } });
|
|
28160
|
+
document.defaultView = globalThis;
|
|
28161
|
+
for (const frame of document.querySelectorAll('iframe')) if (frame.__pendingContentDocument) frame.__contentDocument = buildNestedDocument(frame.__pendingContentDocument);
|
|
28162
|
+
function buildNestedDocument(childSnapshot) {
|
|
28163
|
+
const child = make(childSnapshot.root); child.URL = childSnapshot.url; child.title = childSnapshot.title; child.readyState = 'complete';
|
|
28164
|
+
child.documentElement = child.querySelector('html'); child.head = child.querySelector('head'); child.body = child.querySelector('body'); child.defaultView = { document: child, frameElement: null, location: new URL(childSnapshot.url) }; return child;
|
|
28165
|
+
}
|
|
28166
|
+
})()`;
|
|
28167
|
+
}
|
|
27956
28168
|
var DEFERRED = /* @__PURE__ */ Symbol("reply already sent");
|
|
27957
28169
|
|
|
27958
|
-
// src/browser/
|
|
27959
|
-
var
|
|
27960
|
-
|
|
27961
|
-
|
|
27962
|
-
|
|
27963
|
-
|
|
27964
|
-
|
|
27965
|
-
|
|
27966
|
-
|
|
27967
|
-
|
|
27968
|
-
|
|
27969
|
-
|
|
27970
|
-
|
|
27971
|
-
|
|
27972
|
-
|
|
27973
|
-
|
|
27974
|
-
|
|
27975
|
-
|
|
27976
|
-
const server = new CdpServer({
|
|
27977
|
-
send: (message) => {
|
|
27978
|
-
if (output.closed) return;
|
|
27979
|
-
output.write(`${JSON.stringify(message)}\0`);
|
|
27980
|
-
},
|
|
27981
|
-
onClose: () => {
|
|
27982
|
-
closed = true;
|
|
27983
|
-
input.close();
|
|
28170
|
+
// src/browser/search-command.ts
|
|
28171
|
+
var DEFAULT_SEARCH_ENDPOINT = "https://html.duckduckgo.com/html/?q=";
|
|
28172
|
+
var USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
28173
|
+
function decodeEntities(value) {
|
|
28174
|
+
const named = {
|
|
28175
|
+
amp: "&",
|
|
28176
|
+
apos: "'",
|
|
28177
|
+
gt: ">",
|
|
28178
|
+
hellip: "\u2026",
|
|
28179
|
+
lt: "<",
|
|
28180
|
+
nbsp: " ",
|
|
28181
|
+
quot: '"'
|
|
28182
|
+
};
|
|
28183
|
+
return value.replace(/&(#x[\da-f]+|#\d+|[a-z][\da-z]+);/gi, (whole, entity) => {
|
|
28184
|
+
if (entity[0] === "#") {
|
|
28185
|
+
const hex = entity[1]?.toLowerCase() === "x";
|
|
28186
|
+
const code = Number.parseInt(entity.slice(hex ? 2 : 1), hex ? 16 : 10);
|
|
28187
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
|
|
27984
28188
|
}
|
|
28189
|
+
return named[entity.toLowerCase()] ?? whole;
|
|
27985
28190
|
});
|
|
27986
|
-
|
|
27987
|
-
|
|
27988
|
-
|
|
27989
|
-
|
|
27990
|
-
|
|
27991
|
-
const
|
|
28191
|
+
}
|
|
28192
|
+
function plainText(html) {
|
|
28193
|
+
return decodeEntities(html.replace(/<[^>]*>/g, " ")).replace(/\s+/g, " ").trim();
|
|
28194
|
+
}
|
|
28195
|
+
function attribute(tag2, name) {
|
|
28196
|
+
const match2 = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, "i").exec(tag2);
|
|
28197
|
+
return match2 ? decodeEntities(match2[1] ?? match2[2] ?? match2[3] ?? "") : void 0;
|
|
28198
|
+
}
|
|
28199
|
+
function absoluteUrl(raw, base2) {
|
|
27992
28200
|
try {
|
|
27993
|
-
|
|
27994
|
-
|
|
27995
|
-
|
|
27996
|
-
|
|
27997
|
-
|
|
27998
|
-
while ((end = pending.indexOf("\0")) >= 0) {
|
|
27999
|
-
const frame = pending.slice(0, end);
|
|
28000
|
-
pending = pending.slice(end + 1);
|
|
28001
|
-
if (!frame) continue;
|
|
28002
|
-
let message;
|
|
28003
|
-
try {
|
|
28004
|
-
message = JSON.parse(frame);
|
|
28005
|
-
} catch {
|
|
28006
|
-
ctx.warn(`dropping malformed DevTools message: ${frame.slice(0, 80)}`);
|
|
28007
|
-
continue;
|
|
28008
|
-
}
|
|
28009
|
-
void server.dispatch(message);
|
|
28010
|
-
}
|
|
28011
|
-
}
|
|
28012
|
-
} finally {
|
|
28013
|
-
server.dispose();
|
|
28014
|
-
output.end();
|
|
28201
|
+
const url = new URL(raw, base2);
|
|
28202
|
+
if (!/^https?:$/.test(url.protocol)) return void 0;
|
|
28203
|
+
return url.toString();
|
|
28204
|
+
} catch {
|
|
28205
|
+
return void 0;
|
|
28015
28206
|
}
|
|
28016
|
-
return 0;
|
|
28017
28207
|
}
|
|
28018
|
-
function
|
|
28019
|
-
|
|
28020
|
-
|
|
28021
|
-
|
|
28022
|
-
|
|
28023
|
-
|
|
28024
|
-
usage: USAGE,
|
|
28025
|
-
run: run2
|
|
28026
|
-
})
|
|
28027
|
-
];
|
|
28208
|
+
function resultUrl(raw, base2) {
|
|
28209
|
+
const resolved = absoluteUrl(raw, base2);
|
|
28210
|
+
if (!resolved) return void 0;
|
|
28211
|
+
const url = new URL(resolved);
|
|
28212
|
+
const redirected = url.searchParams.get("uddg");
|
|
28213
|
+
return redirected ? absoluteUrl(redirected, base2) : resolved;
|
|
28028
28214
|
}
|
|
28029
|
-
|
|
28030
|
-
|
|
28031
|
-
|
|
28032
|
-
|
|
28215
|
+
function parseSearchResults(html, base2 = "https://html.duckduckgo.com/") {
|
|
28216
|
+
const results = [];
|
|
28217
|
+
const anchors = /<a\b[^>]*class\s*=\s*["'][^"']*\bresult__a\b[^"']*["'][^>]*>[\s\S]*?<\/a>/gi;
|
|
28218
|
+
for (const match2 of html.matchAll(anchors)) {
|
|
28219
|
+
const tag2 = /^<a\b[^>]*>/i.exec(match2[0])?.[0] ?? "";
|
|
28220
|
+
const url = resultUrl(attribute(tag2, "href") ?? "", base2);
|
|
28221
|
+
if (!url) continue;
|
|
28222
|
+
const title = plainText(match2[0]);
|
|
28223
|
+
const tail2 = html.slice((match2.index ?? 0) + match2[0].length, (match2.index ?? 0) + match2[0].length + 4e3);
|
|
28224
|
+
const snippetMatch = /<(?:a|div)\b[^>]*class\s*=\s*["'][^"']*\bresult__snippet\b[^"']*["'][^>]*>([\s\S]*?)<\/(?:a|div)>/i.exec(tail2);
|
|
28225
|
+
results.push({ title, url, snippet: snippetMatch ? plainText(snippetMatch[1]) : "" });
|
|
28226
|
+
}
|
|
28227
|
+
return results;
|
|
28228
|
+
}
|
|
28229
|
+
function extractPage(html, url, status = 200) {
|
|
28230
|
+
const titleMatch = /<title\b[^>]*>([\s\S]*?)<\/title>/i.exec(html);
|
|
28231
|
+
const links = [];
|
|
28232
|
+
for (const match2 of html.matchAll(/<a\b[^>]*>[\s\S]*?<\/a>/gi)) {
|
|
28233
|
+
const tag2 = /^<a\b[^>]*>/i.exec(match2[0])?.[0] ?? "";
|
|
28234
|
+
const href = absoluteUrl(attribute(tag2, "href") ?? "", url);
|
|
28235
|
+
const text2 = plainText(match2[0]);
|
|
28236
|
+
if (href && text2 && !links.some((link) => link.url === href)) links.push({ text: text2, url: href });
|
|
28237
|
+
if (links.length >= 50) break;
|
|
28238
|
+
}
|
|
28239
|
+
const visible2 = html.replace(/<!--([\s\S]*?)-->/g, " ").replace(/<head\b[^>]*>[\s\S]*?<\/head>/gi, " ").replace(/<title\b[^>]*>[\s\S]*?<\/title>/gi, " ").replace(/<(script|style|noscript|svg|template)\b[^>]*>[\s\S]*?<\/\1>/gi, " ").replace(/<(br|hr)\b[^>]*>|<\/(p|div|article|section|main|header|footer|li|h[1-6])>/gi, "\n");
|
|
28240
|
+
return {
|
|
28241
|
+
type: "page",
|
|
28242
|
+
url,
|
|
28243
|
+
status,
|
|
28244
|
+
title: titleMatch ? plainText(titleMatch[1]) : "",
|
|
28245
|
+
text: decodeEntities(visible2.replace(/<[^>]*>/g, " ")).replace(/[ \t\f\v]+/g, " ").replace(/ *\n */g, "\n").replace(/\n{3,}/g, "\n\n").trim(),
|
|
28246
|
+
links
|
|
28247
|
+
};
|
|
28248
|
+
}
|
|
28249
|
+
function targetUrl(raw) {
|
|
28250
|
+
try {
|
|
28251
|
+
if (/^https?:\/\//i.test(raw)) return new URL(raw);
|
|
28252
|
+
if (/^(localhost|127\.0\.0\.1|\[::1\])(?::\d+)?(?:\/|$)/i.test(raw)) return new URL(`http://${raw}`);
|
|
28253
|
+
if (/^[\w.-]+\.[a-z]{2,}(?::\d+)?(?:\/|$)/i.test(raw)) return new URL(`https://${raw}`);
|
|
28254
|
+
} catch {
|
|
28255
|
+
}
|
|
28256
|
+
return void 0;
|
|
28257
|
+
}
|
|
28258
|
+
async function get(ctx, url, redirects = 5) {
|
|
28259
|
+
let current2 = url;
|
|
28260
|
+
for (let n = 0; ; n++) {
|
|
28261
|
+
const result = await performRequest(ctx, current2, {
|
|
28262
|
+
method: "GET",
|
|
28263
|
+
headers: { accept: "text/html,application/xhtml+xml,text/plain;q=0.9,*/*;q=0.1", "user-agent": USER_AGENT }
|
|
28264
|
+
});
|
|
28265
|
+
const location2 = result.headers.location;
|
|
28266
|
+
if (result.status < 300 || result.status >= 400 || !location2) return result;
|
|
28267
|
+
if (n >= redirects) throw new Error("too many redirects");
|
|
28268
|
+
current2 = new URL(location2, current2);
|
|
28269
|
+
}
|
|
28270
|
+
}
|
|
28271
|
+
function truncate(text2, max) {
|
|
28272
|
+
return text2.length <= max ? text2 : `${text2.slice(0, Math.max(0, max - 1)).trimEnd()}\u2026`;
|
|
28273
|
+
}
|
|
28274
|
+
var search = defineCommand({
|
|
28275
|
+
name: "search",
|
|
28276
|
+
path: "/usr/bin/search",
|
|
28277
|
+
summary: "search the web or read a URL headlessly",
|
|
28278
|
+
usage: "search [--json] [-n count] [--max-chars count] <query or URL>",
|
|
28279
|
+
manual: "Searches DuckDuckGo's HTML endpoint for a query, or fetches and extracts readable text from an HTTP(S) URL. Outbound access must be enabled. This text browser does not execute page JavaScript.",
|
|
28280
|
+
async run(ctx) {
|
|
28281
|
+
const args = parseArgs(ctx.args, [
|
|
28282
|
+
{ long: "json" },
|
|
28283
|
+
{ short: "n", long: "count", arg: true, default: "5" },
|
|
28284
|
+
{ long: "max-chars", arg: true, default: "12000" }
|
|
28285
|
+
]);
|
|
28286
|
+
const raw = args.positional.join(" ").trim();
|
|
28287
|
+
if (!raw) return ctx.fail("missing query or URL", 2);
|
|
28288
|
+
const count = Math.max(1, Math.min(20, args.num("count", 5) ?? 5));
|
|
28289
|
+
const maxChars = Math.max(200, Math.min(1e5, args.num("max-chars", 12e3) ?? 12e3));
|
|
28290
|
+
try {
|
|
28291
|
+
const direct = targetUrl(raw);
|
|
28292
|
+
if (direct) {
|
|
28293
|
+
const response2 = await get(ctx, direct);
|
|
28294
|
+
const contentType = response2.headers["content-type"] ?? "";
|
|
28295
|
+
const decoded = new TextDecoder().decode(response2.body);
|
|
28296
|
+
const page = /html|xhtml/i.test(contentType) || /^\s*</.test(decoded) ? extractPage(decoded, response2.url, response2.status) : { type: "page", url: response2.url, status: response2.status, title: "", text: decoded, links: [] };
|
|
28297
|
+
page.text = truncate(page.text, maxChars);
|
|
28298
|
+
if (args.has("json")) ctx.line(JSON.stringify(page, null, 2));
|
|
28299
|
+
else {
|
|
28300
|
+
ctx.line(`# ${page.title || page.url}`);
|
|
28301
|
+
ctx.line(`URL: ${page.url}`);
|
|
28302
|
+
ctx.line(`Status: ${page.status}`);
|
|
28303
|
+
ctx.line();
|
|
28304
|
+
ctx.line(page.text);
|
|
28305
|
+
}
|
|
28306
|
+
return response2.status >= 400 ? 22 : 0;
|
|
28307
|
+
}
|
|
28308
|
+
const endpoint = ctx.env.SEARCH_ENDPOINT || DEFAULT_SEARCH_ENDPOINT;
|
|
28309
|
+
const response = await get(ctx, new URL(`${endpoint}${encodeURIComponent(raw)}`));
|
|
28310
|
+
const results = parseSearchResults(new TextDecoder().decode(response.body), response.url).slice(0, count);
|
|
28311
|
+
const output = { type: "search", query: raw, results };
|
|
28312
|
+
if (args.has("json")) ctx.line(JSON.stringify(output, null, 2));
|
|
28313
|
+
else if (!results.length) ctx.line("No search results found.");
|
|
28314
|
+
else results.forEach((result, index) => {
|
|
28315
|
+
ctx.line(`${index + 1}. ${result.title}`);
|
|
28316
|
+
ctx.line(` ${result.url}`);
|
|
28317
|
+
if (result.snippet) ctx.line(` ${result.snippet}`);
|
|
28318
|
+
});
|
|
28319
|
+
return response.status >= 400 ? 22 : 0;
|
|
28320
|
+
} catch (error) {
|
|
28321
|
+
return ctx.fail(error instanceof Error ? error.message : String(error), 1);
|
|
28322
|
+
}
|
|
28323
|
+
}
|
|
28324
|
+
});
|
|
28033
28325
|
|
|
28034
28326
|
// src/apps/registry.ts
|
|
28035
28327
|
var APPS_ROOT = "/opt";
|
|
@@ -28126,8 +28418,242 @@ function validateManifest2(manifest, source) {
|
|
|
28126
28418
|
throw new AppRegistryError(`${source}: a service needs an integer port`);
|
|
28127
28419
|
}
|
|
28128
28420
|
}
|
|
28421
|
+
if (app.browserEngine) {
|
|
28422
|
+
if (app.browserEngine.abi !== "sandboxedjs-browser-engine-v1") {
|
|
28423
|
+
throw new AppRegistryError(`${source}: unsupported browser engine ABI ${JSON.stringify(app.browserEngine.abi)}`);
|
|
28424
|
+
}
|
|
28425
|
+
if (typeof app.browserEngine.wasm !== "string" || !safeRelativePath(app.browserEngine.wasm)) {
|
|
28426
|
+
throw new AppRegistryError(`${source}: browserEngine.wasm must be a safe package-relative path`);
|
|
28427
|
+
}
|
|
28428
|
+
if (typeof app.browserEngine.product !== "string" || !app.browserEngine.product) {
|
|
28429
|
+
throw new AppRegistryError(`${source}: browserEngine.product is required`);
|
|
28430
|
+
}
|
|
28431
|
+
}
|
|
28129
28432
|
return app;
|
|
28130
28433
|
}
|
|
28434
|
+
function safeRelativePath(path) {
|
|
28435
|
+
return path !== "" && !path.startsWith("/") && !path.split("/").some((part) => part === "" || part === "." || part === "..");
|
|
28436
|
+
}
|
|
28437
|
+
|
|
28438
|
+
// src/browser/vireo.ts
|
|
28439
|
+
var VIREO_APP = "vireo";
|
|
28440
|
+
var VireoEngine = class _VireoEngine {
|
|
28441
|
+
constructor(product, wasm) {
|
|
28442
|
+
this.product = product;
|
|
28443
|
+
this.wasm = wasm;
|
|
28444
|
+
}
|
|
28445
|
+
product;
|
|
28446
|
+
wasm;
|
|
28447
|
+
static async load(ctx) {
|
|
28448
|
+
const app = installedVireo(ctx);
|
|
28449
|
+
if (!app?.browserEngine) return null;
|
|
28450
|
+
const manifest = app.browserEngine;
|
|
28451
|
+
const bytes2 = ctx.vfs.readFile(`${app.root}/${manifest.wasm}`, ctx.cred);
|
|
28452
|
+
const loaded = await WebAssembly.instantiate(bytes2, {});
|
|
28453
|
+
const instance = loaded instanceof WebAssembly.Instance ? loaded : loaded.instance;
|
|
28454
|
+
const wasm = instance.exports;
|
|
28455
|
+
for (const name of ["memory", "vireo_alloc", "vireo_load_html", "vireo_snapshot_pointer", "vireo_snapshot_length", "vireo_free_document"]) {
|
|
28456
|
+
if (!(name in wasm)) throw new Error(`Vireo's ${manifest.abi} module does not export ${name}`);
|
|
28457
|
+
}
|
|
28458
|
+
return new _VireoEngine(manifest.product, wasm);
|
|
28459
|
+
}
|
|
28460
|
+
parse(html, url) {
|
|
28461
|
+
const urlBytes = new TextEncoder().encode(url);
|
|
28462
|
+
const htmlPointer = this.copy(html);
|
|
28463
|
+
const urlPointer = this.copy(urlBytes);
|
|
28464
|
+
const handle = this.wasm.vireo_load_html(htmlPointer, html.length, urlPointer, urlBytes.length);
|
|
28465
|
+
if (!handle) throw new Error("Vireo could not parse the document");
|
|
28466
|
+
try {
|
|
28467
|
+
const pointer = this.wasm.vireo_snapshot_pointer(handle);
|
|
28468
|
+
const length = this.wasm.vireo_snapshot_length(handle);
|
|
28469
|
+
if (!pointer || !length) throw new Error("Vireo returned an empty document snapshot");
|
|
28470
|
+
const json = new TextDecoder().decode(new Uint8Array(this.wasm.memory.buffer, pointer, length).slice());
|
|
28471
|
+
return JSON.parse(json);
|
|
28472
|
+
} finally {
|
|
28473
|
+
this.wasm.vireo_free_document(handle);
|
|
28474
|
+
}
|
|
28475
|
+
}
|
|
28476
|
+
copy(bytes2) {
|
|
28477
|
+
if (!bytes2.length) return 0;
|
|
28478
|
+
const pointer = this.wasm.vireo_alloc(bytes2.length);
|
|
28479
|
+
if (!pointer) throw new Error("Vireo ran out of memory");
|
|
28480
|
+
new Uint8Array(this.wasm.memory.buffer, pointer, bytes2.length).set(bytes2);
|
|
28481
|
+
return pointer;
|
|
28482
|
+
}
|
|
28483
|
+
};
|
|
28484
|
+
function installedVireo(ctx) {
|
|
28485
|
+
try {
|
|
28486
|
+
const state = JSON.parse(ctx.vfs.readText(APP_STATE, ctx.cred));
|
|
28487
|
+
return state.apps?.find((app) => app.name === VIREO_APP);
|
|
28488
|
+
} catch {
|
|
28489
|
+
return void 0;
|
|
28490
|
+
}
|
|
28491
|
+
}
|
|
28492
|
+
async function ensureVireo(ctx) {
|
|
28493
|
+
const present = await VireoEngine.load(ctx);
|
|
28494
|
+
if (present) return present;
|
|
28495
|
+
if (ctx.env.SBX_VIREO_AUTOINSTALL === "0") return null;
|
|
28496
|
+
const result = await ctx.kernel.run(["pm", "install", VIREO_APP], {
|
|
28497
|
+
cwd: ctx.cwd,
|
|
28498
|
+
env: ctx.env,
|
|
28499
|
+
cred: ctx.cred,
|
|
28500
|
+
ppid: ctx.proc.pid,
|
|
28501
|
+
timeoutMs: 12e4
|
|
28502
|
+
});
|
|
28503
|
+
if (result.exitCode !== 0) {
|
|
28504
|
+
ctx.warn(`Vireo is not installed and automatic installation failed: ${(result.stderr || result.stdout).trim()}`);
|
|
28505
|
+
return null;
|
|
28506
|
+
}
|
|
28507
|
+
return VireoEngine.load(ctx);
|
|
28508
|
+
}
|
|
28509
|
+
|
|
28510
|
+
// src/browser/chrome-command.ts
|
|
28511
|
+
var USAGE = "chrome --remote-debugging-pipe [chromium flags\u2026]";
|
|
28512
|
+
async function run2(ctx) {
|
|
28513
|
+
if (ctx.args.includes("--version")) {
|
|
28514
|
+
ctx.line(`Chromium ${VIRTUAL_CHROMIUM_VERSION} (SandboxedJs virtual browser)`);
|
|
28515
|
+
return 0;
|
|
28516
|
+
}
|
|
28517
|
+
if (!ctx.args.includes("--remote-debugging-pipe")) {
|
|
28518
|
+
return ctx.fail(
|
|
28519
|
+
"only --remote-debugging-pipe is supported: this is the SandboxedJs virtual browser, driven through the DevTools protocol (Playwright, Puppeteer), not an interactive Chromium"
|
|
28520
|
+
);
|
|
28521
|
+
}
|
|
28522
|
+
const input = ctx.proc.fds.get(3);
|
|
28523
|
+
const output = ctx.proc.fds.get(4);
|
|
28524
|
+
if (!input || !output) {
|
|
28525
|
+
return ctx.fail("--remote-debugging-pipe needs descriptors 3 and 4 open as pipes (stdio: ['ignore','pipe','pipe','pipe','pipe'])");
|
|
28526
|
+
}
|
|
28527
|
+
const vireo = await ensureVireo(ctx);
|
|
28528
|
+
let closed = false;
|
|
28529
|
+
const server = new CdpServer({
|
|
28530
|
+
send: (message) => {
|
|
28531
|
+
if (output.closed) return;
|
|
28532
|
+
output.write(`${JSON.stringify(message)}\0`);
|
|
28533
|
+
},
|
|
28534
|
+
onClose: () => {
|
|
28535
|
+
closed = true;
|
|
28536
|
+
input.close();
|
|
28537
|
+
},
|
|
28538
|
+
...vireo ? {
|
|
28539
|
+
navigate: (rawUrl) => loadDocument(ctx, vireo, rawUrl)
|
|
28540
|
+
} : {}
|
|
28541
|
+
});
|
|
28542
|
+
ctx.stderr.write(`
|
|
28543
|
+
DevTools listening on pipe (${vireo?.product ?? `SandboxedJs virtual browser ${VIRTUAL_CHROMIUM_VERSION}`})
|
|
28544
|
+
`);
|
|
28545
|
+
const decoder9 = new TextDecoder();
|
|
28546
|
+
let pending = "";
|
|
28547
|
+
const aborted = new Promise((resolve3) => ctx.signal.addEventListener("abort", () => resolve3(null), { once: true }));
|
|
28548
|
+
try {
|
|
28549
|
+
while (!closed) {
|
|
28550
|
+
const chunk = await Promise.race([input.read(), aborted]);
|
|
28551
|
+
if (chunk === null) break;
|
|
28552
|
+
pending += decoder9.decode(chunk, { stream: true });
|
|
28553
|
+
let end;
|
|
28554
|
+
while ((end = pending.indexOf("\0")) >= 0) {
|
|
28555
|
+
const frame = pending.slice(0, end);
|
|
28556
|
+
pending = pending.slice(end + 1);
|
|
28557
|
+
if (!frame) continue;
|
|
28558
|
+
let message;
|
|
28559
|
+
try {
|
|
28560
|
+
message = JSON.parse(frame);
|
|
28561
|
+
} catch {
|
|
28562
|
+
ctx.warn(`dropping malformed DevTools message: ${frame.slice(0, 80)}`);
|
|
28563
|
+
continue;
|
|
28564
|
+
}
|
|
28565
|
+
void server.dispatch(message);
|
|
28566
|
+
}
|
|
28567
|
+
}
|
|
28568
|
+
} finally {
|
|
28569
|
+
server.dispose();
|
|
28570
|
+
output.end();
|
|
28571
|
+
}
|
|
28572
|
+
return 0;
|
|
28573
|
+
}
|
|
28574
|
+
async function requestResource(ctx, rawUrl, accept) {
|
|
28575
|
+
let url = new URL(rawUrl);
|
|
28576
|
+
let response;
|
|
28577
|
+
for (let redirects = 0; ; redirects += 1) {
|
|
28578
|
+
response = await performRequest(ctx, url, {
|
|
28579
|
+
method: "GET",
|
|
28580
|
+
headers: { accept, "user-agent": "Mozilla/5.0 (X11; Linux x86_64) Vireo/0.2" }
|
|
28581
|
+
});
|
|
28582
|
+
const location2 = response.headers.location;
|
|
28583
|
+
if (response.status < 300 || response.status >= 400 || !location2) break;
|
|
28584
|
+
if (redirects >= 5) throw new Error("net::ERR_TOO_MANY_REDIRECTS");
|
|
28585
|
+
url = new URL(location2, url);
|
|
28586
|
+
}
|
|
28587
|
+
if (response.status >= 400) throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
28588
|
+
return { response, url: response.url || url.toString() };
|
|
28589
|
+
}
|
|
28590
|
+
async function loadDocument(ctx, engine, rawUrl, depth = 0) {
|
|
28591
|
+
if (rawUrl.startsWith("data:")) {
|
|
28592
|
+
const comma = rawUrl.indexOf(",");
|
|
28593
|
+
if (comma < 0) throw new Error("net::ERR_INVALID_URL");
|
|
28594
|
+
const metadata = rawUrl.slice(5, comma);
|
|
28595
|
+
const raw = rawUrl.slice(comma + 1);
|
|
28596
|
+
const text2 = /;base64(?:;|$)/i.test(metadata) ? new TextDecoder().decode(Uint8Array.from(atob(raw), (character) => character.charCodeAt(0))) : decodeURIComponent(raw);
|
|
28597
|
+
const document3 = engine.parse(new TextEncoder().encode(text2), rawUrl);
|
|
28598
|
+
await loadSubresources(ctx, engine, document3.root, rawUrl, depth);
|
|
28599
|
+
return { url: rawUrl, document: document3 };
|
|
28600
|
+
}
|
|
28601
|
+
const loaded = await requestResource(ctx, rawUrl, "text/html,application/xhtml+xml");
|
|
28602
|
+
const document2 = engine.parse(loaded.response.body, loaded.url);
|
|
28603
|
+
await loadSubresources(ctx, engine, document2.root, loaded.url, depth);
|
|
28604
|
+
return { url: loaded.url, document: document2 };
|
|
28605
|
+
}
|
|
28606
|
+
async function loadSubresources(ctx, engine, node2, baseUrl, depth) {
|
|
28607
|
+
const tasks = [];
|
|
28608
|
+
if (node2.type === "element" && node2.name === "script" && node2.attrs?.src) {
|
|
28609
|
+
tasks.push((async () => {
|
|
28610
|
+
try {
|
|
28611
|
+
const resource = await requestResource(ctx, new URL(node2.attrs.src, baseUrl).toString(), "text/javascript,application/javascript,*/*;q=0.1");
|
|
28612
|
+
node2.resourceText = new TextDecoder().decode(resource.response.body);
|
|
28613
|
+
node2.attrs.src = resource.url;
|
|
28614
|
+
} catch {
|
|
28615
|
+
node2.resourceText = "";
|
|
28616
|
+
}
|
|
28617
|
+
})());
|
|
28618
|
+
}
|
|
28619
|
+
if (node2.type === "element" && node2.name === "link" && /stylesheet/i.test(node2.attrs?.rel ?? "") && node2.attrs?.href) {
|
|
28620
|
+
tasks.push((async () => {
|
|
28621
|
+
try {
|
|
28622
|
+
const resource = await requestResource(ctx, new URL(node2.attrs.href, baseUrl).toString(), "text/css,*/*;q=0.1");
|
|
28623
|
+
node2.resourceText = new TextDecoder().decode(resource.response.body);
|
|
28624
|
+
node2.attrs.href = resource.url;
|
|
28625
|
+
} catch {
|
|
28626
|
+
node2.resourceText = "";
|
|
28627
|
+
}
|
|
28628
|
+
})());
|
|
28629
|
+
}
|
|
28630
|
+
if (depth < 3 && node2.type === "element" && node2.name === "iframe" && node2.attrs?.src) {
|
|
28631
|
+
tasks.push((async () => {
|
|
28632
|
+
try {
|
|
28633
|
+
node2.contentDocument = (await loadDocument(ctx, engine, new URL(node2.attrs.src, baseUrl).toString(), depth + 1)).document;
|
|
28634
|
+
} catch {
|
|
28635
|
+
}
|
|
28636
|
+
})());
|
|
28637
|
+
}
|
|
28638
|
+
for (const child of node2.children ?? []) tasks.push(loadSubresources(ctx, engine, child, baseUrl, depth));
|
|
28639
|
+
await Promise.all(tasks);
|
|
28640
|
+
}
|
|
28641
|
+
function browserCommands() {
|
|
28642
|
+
return [
|
|
28643
|
+
defineCommand({
|
|
28644
|
+
name: "chrome",
|
|
28645
|
+
aliases: ["chromium", "chromium-browser", "google-chrome", "chrome-headless-shell"],
|
|
28646
|
+
summary: "SandboxedJs virtual browser, driven over the DevTools protocol",
|
|
28647
|
+
usage: USAGE,
|
|
28648
|
+
run: run2
|
|
28649
|
+
}),
|
|
28650
|
+
search
|
|
28651
|
+
];
|
|
28652
|
+
}
|
|
28653
|
+
|
|
28654
|
+
// src/apps/pm.ts
|
|
28655
|
+
init_binary();
|
|
28656
|
+
init_zip();
|
|
28131
28657
|
|
|
28132
28658
|
// src/apps/sbjs.ts
|
|
28133
28659
|
init_binary();
|
|
@@ -28315,7 +28841,7 @@ since. It does not say the program is safe to run.`,
|
|
|
28315
28841
|
case "info":
|
|
28316
28842
|
return await info(ctx, rest);
|
|
28317
28843
|
case "search":
|
|
28318
|
-
return await
|
|
28844
|
+
return await search2(ctx, rest);
|
|
28319
28845
|
case "available":
|
|
28320
28846
|
return await available(ctx);
|
|
28321
28847
|
case "trust":
|
|
@@ -28665,7 +29191,7 @@ async function info(ctx, args) {
|
|
|
28665
29191
|
}
|
|
28666
29192
|
return 0;
|
|
28667
29193
|
}
|
|
28668
|
-
async function
|
|
29194
|
+
async function search2(ctx, args) {
|
|
28669
29195
|
const term = (args[0] ?? "").toLowerCase();
|
|
28670
29196
|
const index = await fetchIndex(ctx);
|
|
28671
29197
|
const matches3 = index.apps.filter(
|
|
@@ -29462,7 +29988,7 @@ function applyEdits2(source, edits) {
|
|
|
29462
29988
|
function isNode4(value) {
|
|
29463
29989
|
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
29464
29990
|
}
|
|
29465
|
-
var EXTENSIONS = [".js", ".mjs", ".cjs", ".json", ".node"];
|
|
29991
|
+
var EXTENSIONS = [".js", ".mjs", ".cjs", ".json", ".node", ".ts", ".tsx"];
|
|
29466
29992
|
var PREFIX_ONLY_BUILTINS = /* @__PURE__ */ new Set(["test", "test/reporters", "sea", "sqlite"]);
|
|
29467
29993
|
var CONDITION_SETS = {
|
|
29468
29994
|
import: [["node", "import", "module", "default"], ["node", "require", "default"], ["default"]],
|
|
@@ -30216,9 +30742,9 @@ var vmModule = {
|
|
|
30216
30742
|
measureMemory: async () => ({ total: { jsMemoryEstimate: 0, jsMemoryRange: [0, 0] } })
|
|
30217
30743
|
};
|
|
30218
30744
|
var vm_module_default = vmModule;
|
|
30219
|
-
function define(proto, name,
|
|
30745
|
+
function define(proto, name, get2) {
|
|
30220
30746
|
if (Object.getOwnPropertyDescriptor(proto, name)) return;
|
|
30221
|
-
Object.defineProperty(proto, name, { configurable: true, enumerable: false, get });
|
|
30747
|
+
Object.defineProperty(proto, name, { configurable: true, enumerable: false, get: get2 });
|
|
30222
30748
|
}
|
|
30223
30749
|
var installed = false;
|
|
30224
30750
|
function installWebStreamAdapters(stream) {
|
|
@@ -32125,7 +32651,7 @@ function createHttpModule(router, owner, options = {}, defaultProtocol = "http:"
|
|
|
32125
32651
|
if (callback) req.on("response", callback);
|
|
32126
32652
|
return req;
|
|
32127
32653
|
};
|
|
32128
|
-
const
|
|
32654
|
+
const get2 = (input, second, third) => {
|
|
32129
32655
|
const req = request(input, second, third);
|
|
32130
32656
|
req.end();
|
|
32131
32657
|
return req;
|
|
@@ -32133,7 +32659,7 @@ function createHttpModule(router, owner, options = {}, defaultProtocol = "http:"
|
|
|
32133
32659
|
return {
|
|
32134
32660
|
createServer,
|
|
32135
32661
|
request,
|
|
32136
|
-
get,
|
|
32662
|
+
get: get2,
|
|
32137
32663
|
Server: VirtualHttpServer,
|
|
32138
32664
|
ServerResponse: VirtualServerResponse,
|
|
32139
32665
|
IncomingMessage: VirtualIncomingMessage,
|
|
@@ -39396,7 +39922,7 @@ var str = (s) => {
|
|
|
39396
39922
|
var section = (id, data) => [id, ...leb(data.length), ...data];
|
|
39397
39923
|
function compileInstruction({ op, next }) {
|
|
39398
39924
|
const c = [];
|
|
39399
|
-
const
|
|
39925
|
+
const get2 = (r) => c.push(35, r);
|
|
39400
39926
|
const constant = (n) => c.push(66, ...signed(BigInt.asIntN(64, n)));
|
|
39401
39927
|
if (op.kind === "jump") {
|
|
39402
39928
|
c.push(65, ...signed(BigInt.asIntN(32, BigInt(op.target))));
|
|
@@ -39408,13 +39934,13 @@ function compileInstruction({ op, next }) {
|
|
|
39408
39934
|
} else {
|
|
39409
39935
|
if ("dst" in op) {
|
|
39410
39936
|
if (op.kind !== "mov") {
|
|
39411
|
-
|
|
39937
|
+
get2(op.dst);
|
|
39412
39938
|
if (!op.wide) {
|
|
39413
39939
|
constant(0xffffffffn);
|
|
39414
39940
|
c.push(131);
|
|
39415
39941
|
}
|
|
39416
39942
|
}
|
|
39417
|
-
if (op.src !== void 0)
|
|
39943
|
+
if (op.src !== void 0) get2(op.src);
|
|
39418
39944
|
else constant(op.imm);
|
|
39419
39945
|
if (!op.wide) {
|
|
39420
39946
|
constant(0xffffffffn);
|
|
@@ -39429,7 +39955,7 @@ function compileInstruction({ op, next }) {
|
|
|
39429
39955
|
else {
|
|
39430
39956
|
c.push(36, op.dst);
|
|
39431
39957
|
if (op.kind !== "mov") {
|
|
39432
|
-
|
|
39958
|
+
get2(op.dst);
|
|
39433
39959
|
c.push(80, 36, 16);
|
|
39434
39960
|
}
|
|
39435
39961
|
}
|
|
@@ -39478,7 +40004,7 @@ async function execute2(ctx, source, translated, budget) {
|
|
|
39478
40004
|
const image = { ...source, memory: source.memory.slice() };
|
|
39479
40005
|
const globals = Array.from({ length: 17 }, (_, i) => new WebAssembly.Global({ value: i === 16 ? "i32" : "i64", mutable: true }, i === 16 ? 0 : 0n));
|
|
39480
40006
|
const registers = Object.fromEntries(globals.map((g, i) => [String(i), g]));
|
|
39481
|
-
const
|
|
40007
|
+
const get2 = (r) => globals[r].value;
|
|
39482
40008
|
const set = (r, n) => {
|
|
39483
40009
|
globals[r].value = u64(n);
|
|
39484
40010
|
};
|
|
@@ -39510,15 +40036,15 @@ async function execute2(ctx, source, translated, budget) {
|
|
|
39510
40036
|
if (!op.condition || (op.condition === "zero" ? globals[16].value !== 0 : globals[16].value === 0)) pc = op.target;
|
|
39511
40037
|
} else if ("dst" in op) {
|
|
39512
40038
|
const width = op.wide ? 64 : 32;
|
|
39513
|
-
const a = BigInt.asUintN(width,
|
|
39514
|
-
const b = BigInt.asUintN(width, op.src !== void 0 ?
|
|
40039
|
+
const a = BigInt.asUintN(width, get2(op.dst));
|
|
40040
|
+
const b = BigInt.asUintN(width, op.src !== void 0 ? get2(op.src) : op.imm);
|
|
39515
40041
|
const value = BigInt.asUintN(width, op.kind === "mov" ? b : op.kind === "xor" ? a ^ b : op.kind === "add" ? a + b : a - b);
|
|
39516
40042
|
if (op.kind !== "mov") globals[16].value = Number(value === 0n);
|
|
39517
40043
|
if (op.kind !== "cmp") set(op.dst, value);
|
|
39518
40044
|
}
|
|
39519
40045
|
}
|
|
39520
40046
|
if (op.kind === "syscall") {
|
|
39521
|
-
const number =
|
|
40047
|
+
const number = get2(0), fd = get2(7), address = get2(6), length = get2(2);
|
|
39522
40048
|
set(1, BigInt(next));
|
|
39523
40049
|
set(11, 2n | (globals[16].value ? 64n : 0n));
|
|
39524
40050
|
if (number === 60n || number === 231n) return Number(fd & 255n);
|