sandboxedjs 0.2.19 → 0.2.20
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 +625 -99
- package/dist/index.js +625 -99
- package/docs/agent/README.md +35 -60
- package/docs/vireo.md +58 -0
- package/package.json +11 -3
package/dist/index.cjs
CHANGED
|
@@ -6858,9 +6858,9 @@ var WasiHost = class {
|
|
|
6858
6858
|
if (!(oflags & O_CREAT)) throw new WasiError(WASI_ENOENT);
|
|
6859
6859
|
vfs.writeFile(abs, new Uint8Array(0), { cred, mode: 420 });
|
|
6860
6860
|
}
|
|
6861
|
-
const
|
|
6862
|
-
const data =
|
|
6863
|
-
if (
|
|
6861
|
+
const truncate2 = (oflags & O_TRUNC) !== 0;
|
|
6862
|
+
const data = truncate2 ? new Uint8Array(0) : vfs.readFile(abs, cred);
|
|
6863
|
+
if (truncate2) vfs.writeFile(abs, data, { cred });
|
|
6864
6864
|
const size = data.length;
|
|
6865
6865
|
const rights = rightsBase === 0n ? RIGHTS_ALL : rightsBase;
|
|
6866
6866
|
const append = (fdflags & FD_APPEND) !== 0;
|
|
@@ -21103,7 +21103,7 @@ var wheels_default = {
|
|
|
21103
21103
|
|
|
21104
21104
|
// package.json
|
|
21105
21105
|
var package_default = {
|
|
21106
|
-
version: "0.2.
|
|
21106
|
+
version: "0.2.20"};
|
|
21107
21107
|
|
|
21108
21108
|
// src/python/extension-abi.ts
|
|
21109
21109
|
var EXTENSION_ABI = {
|
|
@@ -23913,7 +23913,7 @@ async function resolve2(options) {
|
|
|
23913
23913
|
const clauses = (state.constraints.get(name) ?? []).flatMap((entry) => entry.spec.map((s) => `${s.op}${s.version}`)).sort();
|
|
23914
23914
|
return `${name}|${[...new Set(clauses)].join(",")}`;
|
|
23915
23915
|
};
|
|
23916
|
-
const
|
|
23916
|
+
const search3 = async (queue, state) => {
|
|
23917
23917
|
checkBudget();
|
|
23918
23918
|
if (queue.length === 0) return state;
|
|
23919
23919
|
const [edge, ...rest] = queue;
|
|
@@ -23921,7 +23921,7 @@ async function resolve2(options) {
|
|
|
23921
23921
|
const name = requirement.name;
|
|
23922
23922
|
if (!markerApplies(requirement.marker, environment, extras)) {
|
|
23923
23923
|
if (requirement.marker) skipped.push({ name, marker: requirement.marker });
|
|
23924
|
-
return
|
|
23924
|
+
return search3(rest, state);
|
|
23925
23925
|
}
|
|
23926
23926
|
const previous = state.constraints.get(name) ?? [];
|
|
23927
23927
|
const constraints = new Map(state.constraints);
|
|
@@ -23938,7 +23938,7 @@ async function resolve2(options) {
|
|
|
23938
23938
|
explain(name, next.constraints, already.version)
|
|
23939
23939
|
);
|
|
23940
23940
|
}
|
|
23941
|
-
return
|
|
23941
|
+
return search3(rest, next);
|
|
23942
23942
|
}
|
|
23943
23943
|
const signature = signatureOf(name, next);
|
|
23944
23944
|
const learned = nogoods.get(signature);
|
|
@@ -24001,7 +24001,7 @@ async function resolve2(options) {
|
|
|
24001
24001
|
const chosen = new Map(next.chosen);
|
|
24002
24002
|
chosen.set(name, { ...candidate, requires });
|
|
24003
24003
|
try {
|
|
24004
|
-
return await
|
|
24004
|
+
return await search3([...edges, ...rest], { chosen, constraints: next.constraints });
|
|
24005
24005
|
} catch (error) {
|
|
24006
24006
|
if (!(error instanceof ResolutionError)) throw error;
|
|
24007
24007
|
if (/cancelled|budget/.test(error.message)) throw error;
|
|
@@ -24016,7 +24016,7 @@ async function resolve2(options) {
|
|
|
24016
24016
|
if (specifiers.length === 0 || !markerApplies(marker, environment, root.extras)) continue;
|
|
24017
24017
|
seeded.set(name, [...seeded.get(name) ?? [], { spec: specifiers, origin: root.origin }]);
|
|
24018
24018
|
}
|
|
24019
|
-
const solved = await
|
|
24019
|
+
const solved = await search3(roots, { chosen: /* @__PURE__ */ new Map(), constraints: seeded });
|
|
24020
24020
|
const distributions = [...solved.chosen.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
24021
24021
|
const reported = new Map(skipped.map((s) => [`${s.name}\0${s.marker}`, s]));
|
|
24022
24022
|
return { distributions, skipped: [...reported.values()] };
|
|
@@ -24263,7 +24263,7 @@ function consoleScripts(text2) {
|
|
|
24263
24263
|
return result;
|
|
24264
24264
|
}
|
|
24265
24265
|
function consoleLauncher(target) {
|
|
24266
|
-
const [module,
|
|
24266
|
+
const [module, attribute2 = ""] = target.split(":", 2);
|
|
24267
24267
|
return `#!/usr/bin/python3
|
|
24268
24268
|
import sys
|
|
24269
24269
|
from importlib import import_module
|
|
@@ -24271,7 +24271,7 @@ from importlib import import_module
|
|
|
24271
24271
|
|
|
24272
24272
|
def _entry():
|
|
24273
24273
|
entry = import_module(${JSON.stringify(module)})
|
|
24274
|
-
for part in ${JSON.stringify(
|
|
24274
|
+
for part in ${JSON.stringify(attribute2)}.split('.'):
|
|
24275
24275
|
if part:
|
|
24276
24276
|
entry = getattr(entry, part)
|
|
24277
24277
|
return entry
|
|
@@ -27648,7 +27648,7 @@ var defaultRealmFactory = async (hooks) => {
|
|
|
27648
27648
|
vm = void 0;
|
|
27649
27649
|
}
|
|
27650
27650
|
if (vm?.createContext) {
|
|
27651
|
-
const context = vm.createContext({ console: console2, setTimeout, clearTimeout, setInterval, clearInterval, queueMicrotask });
|
|
27651
|
+
const context = vm.createContext({ console: console2, URL, setTimeout, clearTimeout, setInterval, clearInterval, queueMicrotask });
|
|
27652
27652
|
return { evaluate: (source) => vm.runInContext(source, context), dispose: () => {
|
|
27653
27653
|
} };
|
|
27654
27654
|
}
|
|
@@ -27775,6 +27775,28 @@ var CdpServer = class {
|
|
|
27775
27775
|
return {};
|
|
27776
27776
|
case "Runtime.runIfWaitingForDebugger":
|
|
27777
27777
|
return {};
|
|
27778
|
+
case "Input.insertText": {
|
|
27779
|
+
const target = page ?? this.pages.values().next().value;
|
|
27780
|
+
if (!target) return {};
|
|
27781
|
+
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 })); })()`);
|
|
27782
|
+
return {};
|
|
27783
|
+
}
|
|
27784
|
+
case "Input.dispatchKeyEvent": {
|
|
27785
|
+
const target = page ?? this.pages.values().next().value;
|
|
27786
|
+
if (!target) return {};
|
|
27787
|
+
if (params.type === "char" || params.type === "keyDown" && params.text) {
|
|
27788
|
+
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 })); })()`);
|
|
27789
|
+
}
|
|
27790
|
+
return {};
|
|
27791
|
+
}
|
|
27792
|
+
case "Input.dispatchMouseEvent": {
|
|
27793
|
+
const target = page ?? this.pages.values().next().value;
|
|
27794
|
+
if (!target) return {};
|
|
27795
|
+
if (params.type === "mouseReleased") {
|
|
27796
|
+
for (const context of target.contexts.values()) context.realm.evaluate(`document.elementFromPoint(${Number(params.x) || 0}, ${Number(params.y) || 0})?.click()`);
|
|
27797
|
+
}
|
|
27798
|
+
return {};
|
|
27799
|
+
}
|
|
27778
27800
|
}
|
|
27779
27801
|
const [, name = ""] = method.split(".");
|
|
27780
27802
|
if (name === "enable" || name === "disable" || /^set[A-Z]/.test(name)) return {};
|
|
@@ -27796,7 +27818,8 @@ var CdpServer = class {
|
|
|
27796
27818
|
url: "about:blank",
|
|
27797
27819
|
loader: 1,
|
|
27798
27820
|
contexts: /* @__PURE__ */ new Map(),
|
|
27799
|
-
worlds: /* @__PURE__ */ new Set()
|
|
27821
|
+
worlds: /* @__PURE__ */ new Set(),
|
|
27822
|
+
document: void 0
|
|
27800
27823
|
};
|
|
27801
27824
|
this.pages.set(page.sessionId, page);
|
|
27802
27825
|
this.options.send({
|
|
@@ -27846,6 +27869,22 @@ var CdpServer = class {
|
|
|
27846
27869
|
timestamp: Date.now()
|
|
27847
27870
|
})
|
|
27848
27871
|
});
|
|
27872
|
+
if (page.document) realm.evaluate(domBootstrap(page.document));
|
|
27873
|
+
if (page.document) {
|
|
27874
|
+
for (const script of documentScripts(page.document.root)) {
|
|
27875
|
+
try {
|
|
27876
|
+
realm.evaluate(`document.currentScript = document.__nodeByPath(${JSON.stringify(script.path)});
|
|
27877
|
+
${script.code}
|
|
27878
|
+
//# sourceURL=${script.url}`);
|
|
27879
|
+
} catch (error) {
|
|
27880
|
+
this.event(page, "Runtime.exceptionThrown", {
|
|
27881
|
+
timestamp: Date.now(),
|
|
27882
|
+
exceptionDetails: { text: String(error), lineNumber: 0, columnNumber: 0 }
|
|
27883
|
+
});
|
|
27884
|
+
}
|
|
27885
|
+
}
|
|
27886
|
+
realm.evaluate("document.currentScript = null");
|
|
27887
|
+
}
|
|
27849
27888
|
const context = { id, name, isDefault, realm, objects: /* @__PURE__ */ new Map(), nextObject: 0 };
|
|
27850
27889
|
page.contexts.set(id, context);
|
|
27851
27890
|
this.announceContext(page, context);
|
|
@@ -27881,6 +27920,13 @@ var CdpServer = class {
|
|
|
27881
27920
|
return DEFERRED;
|
|
27882
27921
|
}
|
|
27883
27922
|
async commitNavigation(page, url) {
|
|
27923
|
+
if (this.options.navigate && /^(https?|data):/i.test(url)) {
|
|
27924
|
+
const loaded = await this.options.navigate(url);
|
|
27925
|
+
url = loaded.url;
|
|
27926
|
+
page.document = loaded.document;
|
|
27927
|
+
} else {
|
|
27928
|
+
page.document = void 0;
|
|
27929
|
+
}
|
|
27884
27930
|
page.loader += 1;
|
|
27885
27931
|
page.url = url;
|
|
27886
27932
|
this.destroyContexts(page, true);
|
|
@@ -27913,14 +27959,22 @@ var CdpServer = class {
|
|
|
27913
27959
|
callFunctionOn(page, params) {
|
|
27914
27960
|
const context = params.objectId ? this.contextOfObject(page, params.objectId) : this.contextFor(page, params.executionContextId);
|
|
27915
27961
|
const receiver = params.objectId ? context.objects.get(params.objectId) : void 0;
|
|
27916
|
-
const args = (params.arguments ?? []).map((argument) => this.argument(page, argument));
|
|
27962
|
+
const args = (params.arguments ?? []).map((argument) => this.argument(page, argument, context));
|
|
27917
27963
|
return this.settle(context, () => {
|
|
27918
27964
|
const fn = context.realm.evaluate(`(${params.functionDeclaration})`);
|
|
27919
27965
|
return fn.apply(receiver, args);
|
|
27920
27966
|
}, params);
|
|
27921
27967
|
}
|
|
27922
|
-
argument(page, argument) {
|
|
27923
|
-
if (argument.objectId)
|
|
27968
|
+
argument(page, argument, target) {
|
|
27969
|
+
if (argument.objectId) {
|
|
27970
|
+
const source = this.contextOfObject(page, argument.objectId);
|
|
27971
|
+
const value = source.objects.get(argument.objectId);
|
|
27972
|
+
if (target && source !== target && value && typeof value === "object" && "__vireoPath" in value) {
|
|
27973
|
+
const path = value.__vireoPath;
|
|
27974
|
+
return target.realm.evaluate(`document.__nodeByPath(${JSON.stringify(path)})`);
|
|
27975
|
+
}
|
|
27976
|
+
return value;
|
|
27977
|
+
}
|
|
27924
27978
|
if (argument.unserializableValue !== void 0) {
|
|
27925
27979
|
const raw = String(argument.unserializableValue);
|
|
27926
27980
|
if (raw === "NaN") return NaN;
|
|
@@ -27970,83 +28024,321 @@ var CdpServer = class {
|
|
|
27970
28024
|
return { type, objectId, className, description: type === "function" ? "function" : className, ...subtype ? { subtype } : {} };
|
|
27971
28025
|
}
|
|
27972
28026
|
};
|
|
28027
|
+
function documentScripts(root) {
|
|
28028
|
+
const scripts = [];
|
|
28029
|
+
const visit = (node2, path) => {
|
|
28030
|
+
if (node2.type === "element" && node2.name === "script") {
|
|
28031
|
+
const type = node2.attrs?.type?.trim().toLowerCase();
|
|
28032
|
+
if (!type || type === "text/javascript" || type === "application/javascript") {
|
|
28033
|
+
const inline = (node2.children ?? []).filter((child) => child.type === "text").map((child) => child.text ?? "").join("");
|
|
28034
|
+
scripts.push({ path, code: node2.resourceText ?? inline, url: node2.attrs?.src ?? "vireo:inline-script" });
|
|
28035
|
+
}
|
|
28036
|
+
}
|
|
28037
|
+
node2.children?.forEach((child, index) => visit(child, [...path, index]));
|
|
28038
|
+
};
|
|
28039
|
+
visit(root, []);
|
|
28040
|
+
return scripts;
|
|
28041
|
+
}
|
|
28042
|
+
function domBootstrap(document2) {
|
|
28043
|
+
return `(() => {
|
|
28044
|
+
const snapshot = ${JSON.stringify(document2)};
|
|
28045
|
+
const escapeText = value => String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
28046
|
+
const escapeAttr = value => escapeText(value).replaceAll('"', '"');
|
|
28047
|
+
class VireoNode {
|
|
28048
|
+
constructor(data, parent = null) {
|
|
28049
|
+
this.__data = data; this.parentNode = parent;
|
|
28050
|
+
this.__listeners = new Map();
|
|
28051
|
+
this.childNodes = (data.children || []).map(child => new VireoNode(child, this));
|
|
28052
|
+
}
|
|
28053
|
+
get nodeType() { return this.__data.type === 'document' ? 9 : this.__data.type === 'text' ? 3 : 1; }
|
|
28054
|
+
get nodeName() { return this.nodeType === 9 ? '#document' : this.nodeType === 3 ? '#text' : this.__data.name.toUpperCase(); }
|
|
28055
|
+
get textContent() { return this.nodeType === 3 ? this.__data.text : this.childNodes.map(node => node.textContent).join(''); }
|
|
28056
|
+
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'); }
|
|
28057
|
+
get children() { return this.childNodes.filter(node => node.nodeType === 1); }
|
|
28058
|
+
get firstChild() { return this.childNodes[0] || null; }
|
|
28059
|
+
get parentElement() { return this.parentNode?.nodeType === 1 ? this.parentNode : null; }
|
|
28060
|
+
get ownerDocument() { let node = this; while (node.parentNode) node = node.parentNode; return node.nodeType === 9 ? node : null; }
|
|
28061
|
+
getRootNode() { let node = this; while (node.parentNode) node = node.parentNode; return node; }
|
|
28062
|
+
get isConnected() { let node = this; while (node.parentNode) node = node.parentNode; return node.nodeType === 9; }
|
|
28063
|
+
contains(other) { for (let node = other; node; node = node.parentNode) if (node === this) return true; return false; }
|
|
28064
|
+
addEventListener(type, listener) { const set = this.__listeners.get(type) || new Set(); set.add(listener); this.__listeners.set(type, set); }
|
|
28065
|
+
removeEventListener(type, listener) { this.__listeners.get(type)?.delete(listener); }
|
|
28066
|
+
dispatchEvent(event) { for (const listener of this.__listeners.get(event.type) || []) listener.call(this, event); return true; }
|
|
28067
|
+
querySelector(selector) { return query(this, selector)[0] || null; }
|
|
28068
|
+
querySelectorAll(selector) { return query(this, selector); }
|
|
28069
|
+
appendChild(child) { child.remove(); child.parentNode = this; this.childNodes.push(child); notify(this, 'childList'); return child; }
|
|
28070
|
+
append(...items) { for (const item of items) this.appendChild(typeof item === 'string' ? document.createTextNode(item) : item); }
|
|
28071
|
+
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'); }
|
|
28072
|
+
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; }
|
|
28073
|
+
remove() { this.parentNode?.removeChild(this); }
|
|
28074
|
+
replaceChildren(...items) { for (const child of this.childNodes) child.parentNode = null; this.childNodes = []; this.append(...items); }
|
|
28075
|
+
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; }
|
|
28076
|
+
}
|
|
28077
|
+
class VireoElement extends VireoNode {}
|
|
28078
|
+
Object.assign(VireoNode, { ELEMENT_NODE: 1, TEXT_NODE: 3, DOCUMENT_NODE: 9 });
|
|
28079
|
+
Object.defineProperties(VireoNode.prototype, {
|
|
28080
|
+
tagName: { get() { return this.nodeType === 1 ? this.__data.name.toUpperCase() : undefined; } },
|
|
28081
|
+
localName: { get() { return this.nodeType === 1 ? this.__data.name : undefined; } },
|
|
28082
|
+
namespaceURI: { get() { return this.nodeType === 1 ? 'http://www.w3.org/1999/xhtml' : null; } },
|
|
28083
|
+
id: { get() { return this.getAttribute?.('id') || ''; }, set(value) { this.setAttribute('id', value); } },
|
|
28084
|
+
className: { get() { return this.getAttribute?.('class') || ''; }, set(value) { this.setAttribute('class', value); } },
|
|
28085
|
+
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; } }; } },
|
|
28086
|
+
innerText: { get() { return this.textContent.replace(/\\s+/g, ' ').trim(); } },
|
|
28087
|
+
attributes: { get() { return Object.entries(this.__data.attrs || {}).map(([name, value]) => ({ name, value })); } },
|
|
28088
|
+
innerHTML: { get() { return this.childNodes.map(serialize).join(''); } },
|
|
28089
|
+
outerHTML: { get() { return serialize(this); } },
|
|
28090
|
+
value: { get() { return this.__value ?? this.getAttribute?.('value') ?? ''; }, set(value) { this.__value = String(value); this.setAttribute?.('value', this.__value); } },
|
|
28091
|
+
selectionStart: { get() { return this.__selectionStart ?? 0; }, set(value) { this.__selectionStart = Number(value); } },
|
|
28092
|
+
selectionEnd: { get() { return this.__selectionEnd ?? this.value.length; }, set(value) { this.__selectionEnd = Number(value); } },
|
|
28093
|
+
checked: { get() { return this.__checked ?? this.hasAttribute?.('checked') ?? false; }, set(value) { this.__checked = Boolean(value); } },
|
|
28094
|
+
disabled: { get() { return this.hasAttribute?.('disabled') ?? false; }, set(value) { value ? this.setAttribute('disabled', '') : this.removeAttribute('disabled'); } },
|
|
28095
|
+
readOnly: { get() { return this.hasAttribute?.('readonly') ?? false; }, set(value) { value ? this.setAttribute('readonly', '') : this.removeAttribute('readonly'); } },
|
|
28096
|
+
type: { get() { return this.getAttribute?.('type') || ''; }, set(value) { this.setAttribute('type', value); } },
|
|
28097
|
+
isContentEditable: { get() { return this.getAttribute?.('contenteditable') === 'true'; } },
|
|
28098
|
+
contentDocument: { get() { return this.__contentDocument || null; } },
|
|
28099
|
+
contentWindow: { get() { return this.__contentDocument?.defaultView || null; } },
|
|
28100
|
+
});
|
|
28101
|
+
VireoNode.prototype.getAttribute = function(name) { const attrs = this.__data.attrs || {}; return Object.hasOwn(attrs, name) ? attrs[name] : null; };
|
|
28102
|
+
VireoNode.prototype.hasAttribute = function(name) { return this.getAttribute(name) !== null; };
|
|
28103
|
+
VireoNode.prototype.setAttribute = function(name, value) { (this.__data.attrs ||= {})[String(name)] = String(value); notify(this, 'attributes'); };
|
|
28104
|
+
VireoNode.prototype.removeAttribute = function(name) { delete (this.__data.attrs ||= {})[String(name)]; notify(this, 'attributes'); };
|
|
28105
|
+
VireoNode.prototype.matches = function(selector) { return matches(this, selector); };
|
|
28106
|
+
VireoNode.prototype.focus = function() { document.activeElement = this; this.dispatchEvent(new Event('focus')); };
|
|
28107
|
+
VireoNode.prototype.blur = function() { if (document.activeElement === this) document.activeElement = document.body; this.dispatchEvent(new Event('blur')); };
|
|
28108
|
+
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 })); };
|
|
28109
|
+
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 }; } }; };
|
|
28110
|
+
VireoNode.prototype.getClientRects = function() { return [this.getBoundingClientRect()]; };
|
|
28111
|
+
VireoNode.prototype.scrollIntoView = function() {};
|
|
28112
|
+
VireoNode.prototype.select = function() { this.selectionStart = 0; this.selectionEnd = this.value.length; };
|
|
28113
|
+
VireoNode.prototype.setSelectionRange = function(start, end) { this.selectionStart = start; this.selectionEnd = end; };
|
|
28114
|
+
VireoNode.prototype.submit = function() { this.dispatchEvent(new Event('submit', { bubbles: true })); };
|
|
28115
|
+
VireoNode.prototype.requestSubmit = function() { this.submit(); };
|
|
28116
|
+
const make = (data, parent = null, path = []) => {
|
|
28117
|
+
const node = data.type === 'element' ? new VireoElement(data, parent) : new VireoNode(data, parent);
|
|
28118
|
+
node.__vireoPath = path;
|
|
28119
|
+
node.childNodes = (data.children || []).map((child, index) => make(child, node, [...path, index]));
|
|
28120
|
+
if (data.contentDocument) node.__pendingContentDocument = data.contentDocument;
|
|
28121
|
+
return node;
|
|
28122
|
+
};
|
|
28123
|
+
const root = make(snapshot.root, null, []);
|
|
28124
|
+
function descendants(node) { return node.childNodes.flatMap(child => [child, ...descendants(child)]); }
|
|
28125
|
+
function matches(node, selector) {
|
|
28126
|
+
if (node.nodeType !== 1) return false;
|
|
28127
|
+
const part = selector.trim();
|
|
28128
|
+
const attr = /\\[([\\w:-]+)(?:=["']?([^\\]"']+)["']?)?\\]/.exec(part);
|
|
28129
|
+
const id = /#([\\w-]+)/.exec(part); const classes = [...part.matchAll(/\\.([\\w-]+)/g)].map(m => m[1]);
|
|
28130
|
+
const tag = /^([a-z][\\w-]*|\\*)/i.exec(part)?.[1];
|
|
28131
|
+
return (!tag || tag === '*' || node.localName === tag.toLowerCase())
|
|
28132
|
+
&& (!id || node.id === id[1])
|
|
28133
|
+
&& classes.every(name => node.className.split(/\\s+/).includes(name))
|
|
28134
|
+
&& (!attr || (node.hasAttribute(attr[1]) && (attr[2] === undefined || node.getAttribute(attr[1]) === attr[2])));
|
|
28135
|
+
}
|
|
28136
|
+
function query(node, selector) {
|
|
28137
|
+
const groups = selector.split(',').map(value => value.trim()).filter(Boolean); const out = [];
|
|
28138
|
+
for (const group of groups) {
|
|
28139
|
+
let scope = [node];
|
|
28140
|
+
for (const part of group.split(/\\s+/)) scope = scope.flatMap(parent => descendants(parent).filter(child => matches(child, part)));
|
|
28141
|
+
for (const match of scope) if (!out.includes(match)) out.push(match);
|
|
28142
|
+
}
|
|
28143
|
+
return out;
|
|
28144
|
+
}
|
|
28145
|
+
function serialize(node) {
|
|
28146
|
+
if (node.nodeType === 3) return escapeText(node.textContent);
|
|
28147
|
+
if (node.nodeType === 9) return node.childNodes.map(serialize).join('');
|
|
28148
|
+
const attrs = Object.entries(node.__data.attrs || {}).map(([k,v]) => ' ' + k + '="' + escapeAttr(v) + '"').join('');
|
|
28149
|
+
return '<' + node.localName + attrs + '>' + node.childNodes.map(serialize).join('') + '</' + node.localName + '>';
|
|
28150
|
+
}
|
|
28151
|
+
const document = root;
|
|
28152
|
+
document.URL = snapshot.url; document.documentURI = snapshot.url; document.readyState = 'complete';
|
|
28153
|
+
document.title = snapshot.title;
|
|
28154
|
+
document.documentElement = document.querySelector('html'); document.head = document.querySelector('head'); document.body = document.querySelector('body');
|
|
28155
|
+
document.getElementById = id => document.querySelector('#' + id);
|
|
28156
|
+
document.createElement = name => make({ type:'element', name:String(name).toLowerCase(), attrs:{}, children:[] });
|
|
28157
|
+
document.createTextNode = text => make({ type:'text', text:String(text) });
|
|
28158
|
+
document.activeElement = document.body;
|
|
28159
|
+
document.hasFocus = () => true;
|
|
28160
|
+
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;
|
|
28161
|
+
document.__nodeByPath = path => path.reduce((node, index) => node?.childNodes[index], document);
|
|
28162
|
+
const listeners = new Map();
|
|
28163
|
+
globalThis.addEventListener = (type, listener) => { const set = listeners.get(type) || new Set(); set.add(listener); listeners.set(type, set); };
|
|
28164
|
+
globalThis.removeEventListener = (type, listener) => listeners.get(type)?.delete(listener);
|
|
28165
|
+
globalThis.dispatchEvent = event => { for (const listener of listeners.get(event.type) || []) listener.call(globalThis, event); return true; };
|
|
28166
|
+
const observers = new Set();
|
|
28167
|
+
const notify = (target, type) => queueMicrotask(() => { for (const observer of observers) observer.callback([{ target, type }], observer); });
|
|
28168
|
+
class MutationObserver { constructor(callback) { this.callback = callback; } observe() { observers.add(this); } disconnect() { observers.delete(this); } takeRecords() { return []; } }
|
|
28169
|
+
class Event { constructor(type, init = {}) { this.type = type; Object.assign(this, init); } }
|
|
28170
|
+
class CustomEvent extends Event { constructor(type, init = {}) { super(type, init); this.detail = init.detail; } }
|
|
28171
|
+
class MouseEvent extends Event {}
|
|
28172
|
+
class KeyboardEvent extends Event {}
|
|
28173
|
+
const requestAnimationFrame = callback => setTimeout(() => callback(Date.now()), 0);
|
|
28174
|
+
const cancelAnimationFrame = id => clearTimeout(id);
|
|
28175
|
+
const getComputedStyle = element => ({ display: element.hasAttribute?.('hidden') ? 'none' : 'block', visibility: 'visible', opacity: '1', pointerEvents: 'auto', getPropertyValue() { return ''; } });
|
|
28176
|
+
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)} } });
|
|
28177
|
+
document.defaultView = globalThis;
|
|
28178
|
+
for (const frame of document.querySelectorAll('iframe')) if (frame.__pendingContentDocument) frame.__contentDocument = buildNestedDocument(frame.__pendingContentDocument);
|
|
28179
|
+
function buildNestedDocument(childSnapshot) {
|
|
28180
|
+
const child = make(childSnapshot.root); child.URL = childSnapshot.url; child.title = childSnapshot.title; child.readyState = 'complete';
|
|
28181
|
+
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;
|
|
28182
|
+
}
|
|
28183
|
+
})()`;
|
|
28184
|
+
}
|
|
27973
28185
|
var DEFERRED = /* @__PURE__ */ Symbol("reply already sent");
|
|
27974
28186
|
|
|
27975
|
-
// src/browser/
|
|
27976
|
-
var
|
|
27977
|
-
|
|
27978
|
-
|
|
27979
|
-
|
|
27980
|
-
|
|
27981
|
-
|
|
27982
|
-
|
|
27983
|
-
|
|
27984
|
-
|
|
27985
|
-
|
|
27986
|
-
|
|
27987
|
-
|
|
27988
|
-
|
|
27989
|
-
|
|
27990
|
-
|
|
27991
|
-
|
|
27992
|
-
|
|
27993
|
-
const server = new CdpServer({
|
|
27994
|
-
send: (message) => {
|
|
27995
|
-
if (output.closed) return;
|
|
27996
|
-
output.write(`${JSON.stringify(message)}\0`);
|
|
27997
|
-
},
|
|
27998
|
-
onClose: () => {
|
|
27999
|
-
closed = true;
|
|
28000
|
-
input.close();
|
|
28187
|
+
// src/browser/search-command.ts
|
|
28188
|
+
var DEFAULT_SEARCH_ENDPOINT = "https://html.duckduckgo.com/html/?q=";
|
|
28189
|
+
var USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
28190
|
+
function decodeEntities(value) {
|
|
28191
|
+
const named = {
|
|
28192
|
+
amp: "&",
|
|
28193
|
+
apos: "'",
|
|
28194
|
+
gt: ">",
|
|
28195
|
+
hellip: "\u2026",
|
|
28196
|
+
lt: "<",
|
|
28197
|
+
nbsp: " ",
|
|
28198
|
+
quot: '"'
|
|
28199
|
+
};
|
|
28200
|
+
return value.replace(/&(#x[\da-f]+|#\d+|[a-z][\da-z]+);/gi, (whole, entity) => {
|
|
28201
|
+
if (entity[0] === "#") {
|
|
28202
|
+
const hex = entity[1]?.toLowerCase() === "x";
|
|
28203
|
+
const code = Number.parseInt(entity.slice(hex ? 2 : 1), hex ? 16 : 10);
|
|
28204
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
|
|
28001
28205
|
}
|
|
28206
|
+
return named[entity.toLowerCase()] ?? whole;
|
|
28002
28207
|
});
|
|
28003
|
-
|
|
28004
|
-
|
|
28005
|
-
|
|
28006
|
-
|
|
28007
|
-
|
|
28008
|
-
const
|
|
28208
|
+
}
|
|
28209
|
+
function plainText(html) {
|
|
28210
|
+
return decodeEntities(html.replace(/<[^>]*>/g, " ")).replace(/\s+/g, " ").trim();
|
|
28211
|
+
}
|
|
28212
|
+
function attribute(tag2, name) {
|
|
28213
|
+
const match2 = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, "i").exec(tag2);
|
|
28214
|
+
return match2 ? decodeEntities(match2[1] ?? match2[2] ?? match2[3] ?? "") : void 0;
|
|
28215
|
+
}
|
|
28216
|
+
function absoluteUrl(raw, base2) {
|
|
28009
28217
|
try {
|
|
28010
|
-
|
|
28011
|
-
|
|
28012
|
-
|
|
28013
|
-
|
|
28014
|
-
|
|
28015
|
-
while ((end = pending.indexOf("\0")) >= 0) {
|
|
28016
|
-
const frame = pending.slice(0, end);
|
|
28017
|
-
pending = pending.slice(end + 1);
|
|
28018
|
-
if (!frame) continue;
|
|
28019
|
-
let message;
|
|
28020
|
-
try {
|
|
28021
|
-
message = JSON.parse(frame);
|
|
28022
|
-
} catch {
|
|
28023
|
-
ctx.warn(`dropping malformed DevTools message: ${frame.slice(0, 80)}`);
|
|
28024
|
-
continue;
|
|
28025
|
-
}
|
|
28026
|
-
void server.dispatch(message);
|
|
28027
|
-
}
|
|
28028
|
-
}
|
|
28029
|
-
} finally {
|
|
28030
|
-
server.dispose();
|
|
28031
|
-
output.end();
|
|
28218
|
+
const url = new URL(raw, base2);
|
|
28219
|
+
if (!/^https?:$/.test(url.protocol)) return void 0;
|
|
28220
|
+
return url.toString();
|
|
28221
|
+
} catch {
|
|
28222
|
+
return void 0;
|
|
28032
28223
|
}
|
|
28033
|
-
return 0;
|
|
28034
28224
|
}
|
|
28035
|
-
function
|
|
28036
|
-
|
|
28037
|
-
|
|
28038
|
-
|
|
28039
|
-
|
|
28040
|
-
|
|
28041
|
-
usage: USAGE,
|
|
28042
|
-
run: run2
|
|
28043
|
-
})
|
|
28044
|
-
];
|
|
28225
|
+
function resultUrl(raw, base2) {
|
|
28226
|
+
const resolved = absoluteUrl(raw, base2);
|
|
28227
|
+
if (!resolved) return void 0;
|
|
28228
|
+
const url = new URL(resolved);
|
|
28229
|
+
const redirected = url.searchParams.get("uddg");
|
|
28230
|
+
return redirected ? absoluteUrl(redirected, base2) : resolved;
|
|
28045
28231
|
}
|
|
28046
|
-
|
|
28047
|
-
|
|
28048
|
-
|
|
28049
|
-
|
|
28232
|
+
function parseSearchResults(html, base2 = "https://html.duckduckgo.com/") {
|
|
28233
|
+
const results = [];
|
|
28234
|
+
const anchors = /<a\b[^>]*class\s*=\s*["'][^"']*\bresult__a\b[^"']*["'][^>]*>[\s\S]*?<\/a>/gi;
|
|
28235
|
+
for (const match2 of html.matchAll(anchors)) {
|
|
28236
|
+
const tag2 = /^<a\b[^>]*>/i.exec(match2[0])?.[0] ?? "";
|
|
28237
|
+
const url = resultUrl(attribute(tag2, "href") ?? "", base2);
|
|
28238
|
+
if (!url) continue;
|
|
28239
|
+
const title = plainText(match2[0]);
|
|
28240
|
+
const tail2 = html.slice((match2.index ?? 0) + match2[0].length, (match2.index ?? 0) + match2[0].length + 4e3);
|
|
28241
|
+
const snippetMatch = /<(?:a|div)\b[^>]*class\s*=\s*["'][^"']*\bresult__snippet\b[^"']*["'][^>]*>([\s\S]*?)<\/(?:a|div)>/i.exec(tail2);
|
|
28242
|
+
results.push({ title, url, snippet: snippetMatch ? plainText(snippetMatch[1]) : "" });
|
|
28243
|
+
}
|
|
28244
|
+
return results;
|
|
28245
|
+
}
|
|
28246
|
+
function extractPage(html, url, status = 200) {
|
|
28247
|
+
const titleMatch = /<title\b[^>]*>([\s\S]*?)<\/title>/i.exec(html);
|
|
28248
|
+
const links = [];
|
|
28249
|
+
for (const match2 of html.matchAll(/<a\b[^>]*>[\s\S]*?<\/a>/gi)) {
|
|
28250
|
+
const tag2 = /^<a\b[^>]*>/i.exec(match2[0])?.[0] ?? "";
|
|
28251
|
+
const href = absoluteUrl(attribute(tag2, "href") ?? "", url);
|
|
28252
|
+
const text2 = plainText(match2[0]);
|
|
28253
|
+
if (href && text2 && !links.some((link) => link.url === href)) links.push({ text: text2, url: href });
|
|
28254
|
+
if (links.length >= 50) break;
|
|
28255
|
+
}
|
|
28256
|
+
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");
|
|
28257
|
+
return {
|
|
28258
|
+
type: "page",
|
|
28259
|
+
url,
|
|
28260
|
+
status,
|
|
28261
|
+
title: titleMatch ? plainText(titleMatch[1]) : "",
|
|
28262
|
+
text: decodeEntities(visible2.replace(/<[^>]*>/g, " ")).replace(/[ \t\f\v]+/g, " ").replace(/ *\n */g, "\n").replace(/\n{3,}/g, "\n\n").trim(),
|
|
28263
|
+
links
|
|
28264
|
+
};
|
|
28265
|
+
}
|
|
28266
|
+
function targetUrl(raw) {
|
|
28267
|
+
try {
|
|
28268
|
+
if (/^https?:\/\//i.test(raw)) return new URL(raw);
|
|
28269
|
+
if (/^(localhost|127\.0\.0\.1|\[::1\])(?::\d+)?(?:\/|$)/i.test(raw)) return new URL(`http://${raw}`);
|
|
28270
|
+
if (/^[\w.-]+\.[a-z]{2,}(?::\d+)?(?:\/|$)/i.test(raw)) return new URL(`https://${raw}`);
|
|
28271
|
+
} catch {
|
|
28272
|
+
}
|
|
28273
|
+
return void 0;
|
|
28274
|
+
}
|
|
28275
|
+
async function get(ctx, url, redirects = 5) {
|
|
28276
|
+
let current2 = url;
|
|
28277
|
+
for (let n = 0; ; n++) {
|
|
28278
|
+
const result = await performRequest(ctx, current2, {
|
|
28279
|
+
method: "GET",
|
|
28280
|
+
headers: { accept: "text/html,application/xhtml+xml,text/plain;q=0.9,*/*;q=0.1", "user-agent": USER_AGENT }
|
|
28281
|
+
});
|
|
28282
|
+
const location2 = result.headers.location;
|
|
28283
|
+
if (result.status < 300 || result.status >= 400 || !location2) return result;
|
|
28284
|
+
if (n >= redirects) throw new Error("too many redirects");
|
|
28285
|
+
current2 = new URL(location2, current2);
|
|
28286
|
+
}
|
|
28287
|
+
}
|
|
28288
|
+
function truncate(text2, max) {
|
|
28289
|
+
return text2.length <= max ? text2 : `${text2.slice(0, Math.max(0, max - 1)).trimEnd()}\u2026`;
|
|
28290
|
+
}
|
|
28291
|
+
var search = defineCommand({
|
|
28292
|
+
name: "search",
|
|
28293
|
+
path: "/usr/bin/search",
|
|
28294
|
+
summary: "search the web or read a URL headlessly",
|
|
28295
|
+
usage: "search [--json] [-n count] [--max-chars count] <query or URL>",
|
|
28296
|
+
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.",
|
|
28297
|
+
async run(ctx) {
|
|
28298
|
+
const args = parseArgs(ctx.args, [
|
|
28299
|
+
{ long: "json" },
|
|
28300
|
+
{ short: "n", long: "count", arg: true, default: "5" },
|
|
28301
|
+
{ long: "max-chars", arg: true, default: "12000" }
|
|
28302
|
+
]);
|
|
28303
|
+
const raw = args.positional.join(" ").trim();
|
|
28304
|
+
if (!raw) return ctx.fail("missing query or URL", 2);
|
|
28305
|
+
const count = Math.max(1, Math.min(20, args.num("count", 5) ?? 5));
|
|
28306
|
+
const maxChars = Math.max(200, Math.min(1e5, args.num("max-chars", 12e3) ?? 12e3));
|
|
28307
|
+
try {
|
|
28308
|
+
const direct = targetUrl(raw);
|
|
28309
|
+
if (direct) {
|
|
28310
|
+
const response2 = await get(ctx, direct);
|
|
28311
|
+
const contentType = response2.headers["content-type"] ?? "";
|
|
28312
|
+
const decoded = new TextDecoder().decode(response2.body);
|
|
28313
|
+
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: [] };
|
|
28314
|
+
page.text = truncate(page.text, maxChars);
|
|
28315
|
+
if (args.has("json")) ctx.line(JSON.stringify(page, null, 2));
|
|
28316
|
+
else {
|
|
28317
|
+
ctx.line(`# ${page.title || page.url}`);
|
|
28318
|
+
ctx.line(`URL: ${page.url}`);
|
|
28319
|
+
ctx.line(`Status: ${page.status}`);
|
|
28320
|
+
ctx.line();
|
|
28321
|
+
ctx.line(page.text);
|
|
28322
|
+
}
|
|
28323
|
+
return response2.status >= 400 ? 22 : 0;
|
|
28324
|
+
}
|
|
28325
|
+
const endpoint = ctx.env.SEARCH_ENDPOINT || DEFAULT_SEARCH_ENDPOINT;
|
|
28326
|
+
const response = await get(ctx, new URL(`${endpoint}${encodeURIComponent(raw)}`));
|
|
28327
|
+
const results = parseSearchResults(new TextDecoder().decode(response.body), response.url).slice(0, count);
|
|
28328
|
+
const output = { type: "search", query: raw, results };
|
|
28329
|
+
if (args.has("json")) ctx.line(JSON.stringify(output, null, 2));
|
|
28330
|
+
else if (!results.length) ctx.line("No search results found.");
|
|
28331
|
+
else results.forEach((result, index) => {
|
|
28332
|
+
ctx.line(`${index + 1}. ${result.title}`);
|
|
28333
|
+
ctx.line(` ${result.url}`);
|
|
28334
|
+
if (result.snippet) ctx.line(` ${result.snippet}`);
|
|
28335
|
+
});
|
|
28336
|
+
return response.status >= 400 ? 22 : 0;
|
|
28337
|
+
} catch (error) {
|
|
28338
|
+
return ctx.fail(error instanceof Error ? error.message : String(error), 1);
|
|
28339
|
+
}
|
|
28340
|
+
}
|
|
28341
|
+
});
|
|
28050
28342
|
|
|
28051
28343
|
// src/apps/registry.ts
|
|
28052
28344
|
var APPS_ROOT = "/opt";
|
|
@@ -28143,8 +28435,242 @@ function validateManifest2(manifest, source) {
|
|
|
28143
28435
|
throw new AppRegistryError(`${source}: a service needs an integer port`);
|
|
28144
28436
|
}
|
|
28145
28437
|
}
|
|
28438
|
+
if (app.browserEngine) {
|
|
28439
|
+
if (app.browserEngine.abi !== "sandboxedjs-browser-engine-v1") {
|
|
28440
|
+
throw new AppRegistryError(`${source}: unsupported browser engine ABI ${JSON.stringify(app.browserEngine.abi)}`);
|
|
28441
|
+
}
|
|
28442
|
+
if (typeof app.browserEngine.wasm !== "string" || !safeRelativePath(app.browserEngine.wasm)) {
|
|
28443
|
+
throw new AppRegistryError(`${source}: browserEngine.wasm must be a safe package-relative path`);
|
|
28444
|
+
}
|
|
28445
|
+
if (typeof app.browserEngine.product !== "string" || !app.browserEngine.product) {
|
|
28446
|
+
throw new AppRegistryError(`${source}: browserEngine.product is required`);
|
|
28447
|
+
}
|
|
28448
|
+
}
|
|
28146
28449
|
return app;
|
|
28147
28450
|
}
|
|
28451
|
+
function safeRelativePath(path) {
|
|
28452
|
+
return path !== "" && !path.startsWith("/") && !path.split("/").some((part) => part === "" || part === "." || part === "..");
|
|
28453
|
+
}
|
|
28454
|
+
|
|
28455
|
+
// src/browser/vireo.ts
|
|
28456
|
+
var VIREO_APP = "vireo";
|
|
28457
|
+
var VireoEngine = class _VireoEngine {
|
|
28458
|
+
constructor(product, wasm) {
|
|
28459
|
+
this.product = product;
|
|
28460
|
+
this.wasm = wasm;
|
|
28461
|
+
}
|
|
28462
|
+
product;
|
|
28463
|
+
wasm;
|
|
28464
|
+
static async load(ctx) {
|
|
28465
|
+
const app = installedVireo(ctx);
|
|
28466
|
+
if (!app?.browserEngine) return null;
|
|
28467
|
+
const manifest = app.browserEngine;
|
|
28468
|
+
const bytes2 = ctx.vfs.readFile(`${app.root}/${manifest.wasm}`, ctx.cred);
|
|
28469
|
+
const loaded = await WebAssembly.instantiate(bytes2, {});
|
|
28470
|
+
const instance = loaded instanceof WebAssembly.Instance ? loaded : loaded.instance;
|
|
28471
|
+
const wasm = instance.exports;
|
|
28472
|
+
for (const name of ["memory", "vireo_alloc", "vireo_load_html", "vireo_snapshot_pointer", "vireo_snapshot_length", "vireo_free_document"]) {
|
|
28473
|
+
if (!(name in wasm)) throw new Error(`Vireo's ${manifest.abi} module does not export ${name}`);
|
|
28474
|
+
}
|
|
28475
|
+
return new _VireoEngine(manifest.product, wasm);
|
|
28476
|
+
}
|
|
28477
|
+
parse(html, url) {
|
|
28478
|
+
const urlBytes = new TextEncoder().encode(url);
|
|
28479
|
+
const htmlPointer = this.copy(html);
|
|
28480
|
+
const urlPointer = this.copy(urlBytes);
|
|
28481
|
+
const handle = this.wasm.vireo_load_html(htmlPointer, html.length, urlPointer, urlBytes.length);
|
|
28482
|
+
if (!handle) throw new Error("Vireo could not parse the document");
|
|
28483
|
+
try {
|
|
28484
|
+
const pointer = this.wasm.vireo_snapshot_pointer(handle);
|
|
28485
|
+
const length = this.wasm.vireo_snapshot_length(handle);
|
|
28486
|
+
if (!pointer || !length) throw new Error("Vireo returned an empty document snapshot");
|
|
28487
|
+
const json = new TextDecoder().decode(new Uint8Array(this.wasm.memory.buffer, pointer, length).slice());
|
|
28488
|
+
return JSON.parse(json);
|
|
28489
|
+
} finally {
|
|
28490
|
+
this.wasm.vireo_free_document(handle);
|
|
28491
|
+
}
|
|
28492
|
+
}
|
|
28493
|
+
copy(bytes2) {
|
|
28494
|
+
if (!bytes2.length) return 0;
|
|
28495
|
+
const pointer = this.wasm.vireo_alloc(bytes2.length);
|
|
28496
|
+
if (!pointer) throw new Error("Vireo ran out of memory");
|
|
28497
|
+
new Uint8Array(this.wasm.memory.buffer, pointer, bytes2.length).set(bytes2);
|
|
28498
|
+
return pointer;
|
|
28499
|
+
}
|
|
28500
|
+
};
|
|
28501
|
+
function installedVireo(ctx) {
|
|
28502
|
+
try {
|
|
28503
|
+
const state = JSON.parse(ctx.vfs.readText(APP_STATE, ctx.cred));
|
|
28504
|
+
return state.apps?.find((app) => app.name === VIREO_APP);
|
|
28505
|
+
} catch {
|
|
28506
|
+
return void 0;
|
|
28507
|
+
}
|
|
28508
|
+
}
|
|
28509
|
+
async function ensureVireo(ctx) {
|
|
28510
|
+
const present = await VireoEngine.load(ctx);
|
|
28511
|
+
if (present) return present;
|
|
28512
|
+
if (ctx.env.SBX_VIREO_AUTOINSTALL === "0") return null;
|
|
28513
|
+
const result = await ctx.kernel.run(["pm", "install", VIREO_APP], {
|
|
28514
|
+
cwd: ctx.cwd,
|
|
28515
|
+
env: ctx.env,
|
|
28516
|
+
cred: ctx.cred,
|
|
28517
|
+
ppid: ctx.proc.pid,
|
|
28518
|
+
timeoutMs: 12e4
|
|
28519
|
+
});
|
|
28520
|
+
if (result.exitCode !== 0) {
|
|
28521
|
+
ctx.warn(`Vireo is not installed and automatic installation failed: ${(result.stderr || result.stdout).trim()}`);
|
|
28522
|
+
return null;
|
|
28523
|
+
}
|
|
28524
|
+
return VireoEngine.load(ctx);
|
|
28525
|
+
}
|
|
28526
|
+
|
|
28527
|
+
// src/browser/chrome-command.ts
|
|
28528
|
+
var USAGE = "chrome --remote-debugging-pipe [chromium flags\u2026]";
|
|
28529
|
+
async function run2(ctx) {
|
|
28530
|
+
if (ctx.args.includes("--version")) {
|
|
28531
|
+
ctx.line(`Chromium ${VIRTUAL_CHROMIUM_VERSION} (SandboxedJs virtual browser)`);
|
|
28532
|
+
return 0;
|
|
28533
|
+
}
|
|
28534
|
+
if (!ctx.args.includes("--remote-debugging-pipe")) {
|
|
28535
|
+
return ctx.fail(
|
|
28536
|
+
"only --remote-debugging-pipe is supported: this is the SandboxedJs virtual browser, driven through the DevTools protocol (Playwright, Puppeteer), not an interactive Chromium"
|
|
28537
|
+
);
|
|
28538
|
+
}
|
|
28539
|
+
const input = ctx.proc.fds.get(3);
|
|
28540
|
+
const output = ctx.proc.fds.get(4);
|
|
28541
|
+
if (!input || !output) {
|
|
28542
|
+
return ctx.fail("--remote-debugging-pipe needs descriptors 3 and 4 open as pipes (stdio: ['ignore','pipe','pipe','pipe','pipe'])");
|
|
28543
|
+
}
|
|
28544
|
+
const vireo = await ensureVireo(ctx);
|
|
28545
|
+
let closed = false;
|
|
28546
|
+
const server = new CdpServer({
|
|
28547
|
+
send: (message) => {
|
|
28548
|
+
if (output.closed) return;
|
|
28549
|
+
output.write(`${JSON.stringify(message)}\0`);
|
|
28550
|
+
},
|
|
28551
|
+
onClose: () => {
|
|
28552
|
+
closed = true;
|
|
28553
|
+
input.close();
|
|
28554
|
+
},
|
|
28555
|
+
...vireo ? {
|
|
28556
|
+
navigate: (rawUrl) => loadDocument(ctx, vireo, rawUrl)
|
|
28557
|
+
} : {}
|
|
28558
|
+
});
|
|
28559
|
+
ctx.stderr.write(`
|
|
28560
|
+
DevTools listening on pipe (${vireo?.product ?? `SandboxedJs virtual browser ${VIRTUAL_CHROMIUM_VERSION}`})
|
|
28561
|
+
`);
|
|
28562
|
+
const decoder9 = new TextDecoder();
|
|
28563
|
+
let pending = "";
|
|
28564
|
+
const aborted = new Promise((resolve3) => ctx.signal.addEventListener("abort", () => resolve3(null), { once: true }));
|
|
28565
|
+
try {
|
|
28566
|
+
while (!closed) {
|
|
28567
|
+
const chunk = await Promise.race([input.read(), aborted]);
|
|
28568
|
+
if (chunk === null) break;
|
|
28569
|
+
pending += decoder9.decode(chunk, { stream: true });
|
|
28570
|
+
let end;
|
|
28571
|
+
while ((end = pending.indexOf("\0")) >= 0) {
|
|
28572
|
+
const frame = pending.slice(0, end);
|
|
28573
|
+
pending = pending.slice(end + 1);
|
|
28574
|
+
if (!frame) continue;
|
|
28575
|
+
let message;
|
|
28576
|
+
try {
|
|
28577
|
+
message = JSON.parse(frame);
|
|
28578
|
+
} catch {
|
|
28579
|
+
ctx.warn(`dropping malformed DevTools message: ${frame.slice(0, 80)}`);
|
|
28580
|
+
continue;
|
|
28581
|
+
}
|
|
28582
|
+
void server.dispatch(message);
|
|
28583
|
+
}
|
|
28584
|
+
}
|
|
28585
|
+
} finally {
|
|
28586
|
+
server.dispose();
|
|
28587
|
+
output.end();
|
|
28588
|
+
}
|
|
28589
|
+
return 0;
|
|
28590
|
+
}
|
|
28591
|
+
async function requestResource(ctx, rawUrl, accept) {
|
|
28592
|
+
let url = new URL(rawUrl);
|
|
28593
|
+
let response;
|
|
28594
|
+
for (let redirects = 0; ; redirects += 1) {
|
|
28595
|
+
response = await performRequest(ctx, url, {
|
|
28596
|
+
method: "GET",
|
|
28597
|
+
headers: { accept, "user-agent": "Mozilla/5.0 (X11; Linux x86_64) Vireo/0.2" }
|
|
28598
|
+
});
|
|
28599
|
+
const location2 = response.headers.location;
|
|
28600
|
+
if (response.status < 300 || response.status >= 400 || !location2) break;
|
|
28601
|
+
if (redirects >= 5) throw new Error("net::ERR_TOO_MANY_REDIRECTS");
|
|
28602
|
+
url = new URL(location2, url);
|
|
28603
|
+
}
|
|
28604
|
+
if (response.status >= 400) throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
28605
|
+
return { response, url: response.url || url.toString() };
|
|
28606
|
+
}
|
|
28607
|
+
async function loadDocument(ctx, engine, rawUrl, depth = 0) {
|
|
28608
|
+
if (rawUrl.startsWith("data:")) {
|
|
28609
|
+
const comma = rawUrl.indexOf(",");
|
|
28610
|
+
if (comma < 0) throw new Error("net::ERR_INVALID_URL");
|
|
28611
|
+
const metadata = rawUrl.slice(5, comma);
|
|
28612
|
+
const raw = rawUrl.slice(comma + 1);
|
|
28613
|
+
const text2 = /;base64(?:;|$)/i.test(metadata) ? new TextDecoder().decode(Uint8Array.from(atob(raw), (character) => character.charCodeAt(0))) : decodeURIComponent(raw);
|
|
28614
|
+
const document3 = engine.parse(new TextEncoder().encode(text2), rawUrl);
|
|
28615
|
+
await loadSubresources(ctx, engine, document3.root, rawUrl, depth);
|
|
28616
|
+
return { url: rawUrl, document: document3 };
|
|
28617
|
+
}
|
|
28618
|
+
const loaded = await requestResource(ctx, rawUrl, "text/html,application/xhtml+xml");
|
|
28619
|
+
const document2 = engine.parse(loaded.response.body, loaded.url);
|
|
28620
|
+
await loadSubresources(ctx, engine, document2.root, loaded.url, depth);
|
|
28621
|
+
return { url: loaded.url, document: document2 };
|
|
28622
|
+
}
|
|
28623
|
+
async function loadSubresources(ctx, engine, node2, baseUrl, depth) {
|
|
28624
|
+
const tasks = [];
|
|
28625
|
+
if (node2.type === "element" && node2.name === "script" && node2.attrs?.src) {
|
|
28626
|
+
tasks.push((async () => {
|
|
28627
|
+
try {
|
|
28628
|
+
const resource = await requestResource(ctx, new URL(node2.attrs.src, baseUrl).toString(), "text/javascript,application/javascript,*/*;q=0.1");
|
|
28629
|
+
node2.resourceText = new TextDecoder().decode(resource.response.body);
|
|
28630
|
+
node2.attrs.src = resource.url;
|
|
28631
|
+
} catch {
|
|
28632
|
+
node2.resourceText = "";
|
|
28633
|
+
}
|
|
28634
|
+
})());
|
|
28635
|
+
}
|
|
28636
|
+
if (node2.type === "element" && node2.name === "link" && /stylesheet/i.test(node2.attrs?.rel ?? "") && node2.attrs?.href) {
|
|
28637
|
+
tasks.push((async () => {
|
|
28638
|
+
try {
|
|
28639
|
+
const resource = await requestResource(ctx, new URL(node2.attrs.href, baseUrl).toString(), "text/css,*/*;q=0.1");
|
|
28640
|
+
node2.resourceText = new TextDecoder().decode(resource.response.body);
|
|
28641
|
+
node2.attrs.href = resource.url;
|
|
28642
|
+
} catch {
|
|
28643
|
+
node2.resourceText = "";
|
|
28644
|
+
}
|
|
28645
|
+
})());
|
|
28646
|
+
}
|
|
28647
|
+
if (depth < 3 && node2.type === "element" && node2.name === "iframe" && node2.attrs?.src) {
|
|
28648
|
+
tasks.push((async () => {
|
|
28649
|
+
try {
|
|
28650
|
+
node2.contentDocument = (await loadDocument(ctx, engine, new URL(node2.attrs.src, baseUrl).toString(), depth + 1)).document;
|
|
28651
|
+
} catch {
|
|
28652
|
+
}
|
|
28653
|
+
})());
|
|
28654
|
+
}
|
|
28655
|
+
for (const child of node2.children ?? []) tasks.push(loadSubresources(ctx, engine, child, baseUrl, depth));
|
|
28656
|
+
await Promise.all(tasks);
|
|
28657
|
+
}
|
|
28658
|
+
function browserCommands() {
|
|
28659
|
+
return [
|
|
28660
|
+
defineCommand({
|
|
28661
|
+
name: "chrome",
|
|
28662
|
+
aliases: ["chromium", "chromium-browser", "google-chrome", "chrome-headless-shell"],
|
|
28663
|
+
summary: "SandboxedJs virtual browser, driven over the DevTools protocol",
|
|
28664
|
+
usage: USAGE,
|
|
28665
|
+
run: run2
|
|
28666
|
+
}),
|
|
28667
|
+
search
|
|
28668
|
+
];
|
|
28669
|
+
}
|
|
28670
|
+
|
|
28671
|
+
// src/apps/pm.ts
|
|
28672
|
+
init_binary();
|
|
28673
|
+
init_zip();
|
|
28148
28674
|
|
|
28149
28675
|
// src/apps/sbjs.ts
|
|
28150
28676
|
init_binary();
|
|
@@ -28332,7 +28858,7 @@ since. It does not say the program is safe to run.`,
|
|
|
28332
28858
|
case "info":
|
|
28333
28859
|
return await info(ctx, rest);
|
|
28334
28860
|
case "search":
|
|
28335
|
-
return await
|
|
28861
|
+
return await search2(ctx, rest);
|
|
28336
28862
|
case "available":
|
|
28337
28863
|
return await available(ctx);
|
|
28338
28864
|
case "trust":
|
|
@@ -28682,7 +29208,7 @@ async function info(ctx, args) {
|
|
|
28682
29208
|
}
|
|
28683
29209
|
return 0;
|
|
28684
29210
|
}
|
|
28685
|
-
async function
|
|
29211
|
+
async function search2(ctx, args) {
|
|
28686
29212
|
const term = (args[0] ?? "").toLowerCase();
|
|
28687
29213
|
const index = await fetchIndex(ctx);
|
|
28688
29214
|
const matches3 = index.apps.filter(
|
|
@@ -30233,9 +30759,9 @@ var vmModule = {
|
|
|
30233
30759
|
measureMemory: async () => ({ total: { jsMemoryEstimate: 0, jsMemoryRange: [0, 0] } })
|
|
30234
30760
|
};
|
|
30235
30761
|
var vm_module_default = vmModule;
|
|
30236
|
-
function define(proto, name,
|
|
30762
|
+
function define(proto, name, get2) {
|
|
30237
30763
|
if (Object.getOwnPropertyDescriptor(proto, name)) return;
|
|
30238
|
-
Object.defineProperty(proto, name, { configurable: true, enumerable: false, get });
|
|
30764
|
+
Object.defineProperty(proto, name, { configurable: true, enumerable: false, get: get2 });
|
|
30239
30765
|
}
|
|
30240
30766
|
var installed = false;
|
|
30241
30767
|
function installWebStreamAdapters(stream) {
|
|
@@ -32142,7 +32668,7 @@ function createHttpModule(router, owner, options = {}, defaultProtocol = "http:"
|
|
|
32142
32668
|
if (callback) req.on("response", callback);
|
|
32143
32669
|
return req;
|
|
32144
32670
|
};
|
|
32145
|
-
const
|
|
32671
|
+
const get2 = (input, second, third) => {
|
|
32146
32672
|
const req = request(input, second, third);
|
|
32147
32673
|
req.end();
|
|
32148
32674
|
return req;
|
|
@@ -32150,7 +32676,7 @@ function createHttpModule(router, owner, options = {}, defaultProtocol = "http:"
|
|
|
32150
32676
|
return {
|
|
32151
32677
|
createServer,
|
|
32152
32678
|
request,
|
|
32153
|
-
get,
|
|
32679
|
+
get: get2,
|
|
32154
32680
|
Server: VirtualHttpServer,
|
|
32155
32681
|
ServerResponse: VirtualServerResponse,
|
|
32156
32682
|
IncomingMessage: VirtualIncomingMessage,
|
|
@@ -39413,7 +39939,7 @@ var str = (s) => {
|
|
|
39413
39939
|
var section = (id, data) => [id, ...leb(data.length), ...data];
|
|
39414
39940
|
function compileInstruction({ op, next }) {
|
|
39415
39941
|
const c = [];
|
|
39416
|
-
const
|
|
39942
|
+
const get2 = (r) => c.push(35, r);
|
|
39417
39943
|
const constant = (n) => c.push(66, ...signed(BigInt.asIntN(64, n)));
|
|
39418
39944
|
if (op.kind === "jump") {
|
|
39419
39945
|
c.push(65, ...signed(BigInt.asIntN(32, BigInt(op.target))));
|
|
@@ -39425,13 +39951,13 @@ function compileInstruction({ op, next }) {
|
|
|
39425
39951
|
} else {
|
|
39426
39952
|
if ("dst" in op) {
|
|
39427
39953
|
if (op.kind !== "mov") {
|
|
39428
|
-
|
|
39954
|
+
get2(op.dst);
|
|
39429
39955
|
if (!op.wide) {
|
|
39430
39956
|
constant(0xffffffffn);
|
|
39431
39957
|
c.push(131);
|
|
39432
39958
|
}
|
|
39433
39959
|
}
|
|
39434
|
-
if (op.src !== void 0)
|
|
39960
|
+
if (op.src !== void 0) get2(op.src);
|
|
39435
39961
|
else constant(op.imm);
|
|
39436
39962
|
if (!op.wide) {
|
|
39437
39963
|
constant(0xffffffffn);
|
|
@@ -39446,7 +39972,7 @@ function compileInstruction({ op, next }) {
|
|
|
39446
39972
|
else {
|
|
39447
39973
|
c.push(36, op.dst);
|
|
39448
39974
|
if (op.kind !== "mov") {
|
|
39449
|
-
|
|
39975
|
+
get2(op.dst);
|
|
39450
39976
|
c.push(80, 36, 16);
|
|
39451
39977
|
}
|
|
39452
39978
|
}
|
|
@@ -39495,7 +40021,7 @@ async function execute2(ctx, source, translated, budget) {
|
|
|
39495
40021
|
const image = { ...source, memory: source.memory.slice() };
|
|
39496
40022
|
const globals = Array.from({ length: 17 }, (_, i) => new WebAssembly.Global({ value: i === 16 ? "i32" : "i64", mutable: true }, i === 16 ? 0 : 0n));
|
|
39497
40023
|
const registers = Object.fromEntries(globals.map((g, i) => [String(i), g]));
|
|
39498
|
-
const
|
|
40024
|
+
const get2 = (r) => globals[r].value;
|
|
39499
40025
|
const set = (r, n) => {
|
|
39500
40026
|
globals[r].value = u64(n);
|
|
39501
40027
|
};
|
|
@@ -39527,15 +40053,15 @@ async function execute2(ctx, source, translated, budget) {
|
|
|
39527
40053
|
if (!op.condition || (op.condition === "zero" ? globals[16].value !== 0 : globals[16].value === 0)) pc = op.target;
|
|
39528
40054
|
} else if ("dst" in op) {
|
|
39529
40055
|
const width = op.wide ? 64 : 32;
|
|
39530
|
-
const a = BigInt.asUintN(width,
|
|
39531
|
-
const b = BigInt.asUintN(width, op.src !== void 0 ?
|
|
40056
|
+
const a = BigInt.asUintN(width, get2(op.dst));
|
|
40057
|
+
const b = BigInt.asUintN(width, op.src !== void 0 ? get2(op.src) : op.imm);
|
|
39532
40058
|
const value = BigInt.asUintN(width, op.kind === "mov" ? b : op.kind === "xor" ? a ^ b : op.kind === "add" ? a + b : a - b);
|
|
39533
40059
|
if (op.kind !== "mov") globals[16].value = Number(value === 0n);
|
|
39534
40060
|
if (op.kind !== "cmp") set(op.dst, value);
|
|
39535
40061
|
}
|
|
39536
40062
|
}
|
|
39537
40063
|
if (op.kind === "syscall") {
|
|
39538
|
-
const number =
|
|
40064
|
+
const number = get2(0), fd = get2(7), address = get2(6), length = get2(2);
|
|
39539
40065
|
set(1, BigInt(next));
|
|
39540
40066
|
set(11, 2n | (globals[16].value ? 64n : 0n));
|
|
39541
40067
|
if (number === 60n || number === 231n) return Number(fd & 255n);
|