openvibers/source.openvibe.games Commits
Add js/core/module.js + addon.js (force; bare 'core' gitignore rule)
Changes
nnew file mode 100644@@@ -0,0 +1,86 @@++// OpenVibe JS addon system — GMod-style addons for Source, in JavaScript.++//++// Discovers addons under addons/<name>/ and loads them through require() so an++// addon can pull in npm packages (js/node_modules or addons/node_modules) and++// its own relative files. Each addon may ship:++// addons/<name>/addon.json { "name", "entry": { "shared","server","client" } }++// addons/<name>/shared.js loaded in both realms first++// addons/<name>/server.js loaded only on the server realm++// addons/<name>/client.js loaded only on the client realm++//++// Realm is decided by OV.isServer(). Loading is idempotent per realm.++(function () {++ if (globalThis.Addon && globalThis.Addon.__openvibe) return;++++ var OV = globalThis.OV;++ var require = globalThis.require;++++ function log(m) { if (OV && OV.log) OV.log("[addon] " + m); }++ function warn(m) { if (OV && OV.warn) OV.warn("[addon] " + m); }++++ var loaded = Object.create(null); // path -> true++++ // path is a mod-root-relative file path (no leading slash), e.g.++ // "addons/foo/server.js" — the file bridge is mod-sandboxed and rejects++ // leading-slash/parent paths, so we keep specifiers slash-free and load++ // them via require.load (mod-root loader).++ function tryRequire(path, label) {++ if (loaded[path]) return true;++ if (!OV.fileExists(path)) return false;++ try {++ require.load(path);++ loaded[path] = true;++ log(label + " -> " + path);++ return true;++ } catch (e) {++ warn("failed " + path + ": " + (e && e.message ? e.message : e));++ return false;++ }++ }++++ function loadOne(name, isServer) {++ var dir = "addons/" + name;++ var manifestPath = dir + "/addon.json";++ var entry = { shared: "shared.js", server: "server.js", client: "client.js" };++++ if (OV.fileExists(manifestPath)) {++ try {++ var m = JSON.parse(OV.readFile(manifestPath));++ if (m && m.entry) {++ entry.shared = m.entry.shared || entry.shared;++ entry.server = m.entry.server || entry.server;++ entry.client = m.entry.client || entry.client;++ }++ log("addon '" + (m && m.name ? m.name : name) + "'");++ } catch (e) {++ warn("bad addon.json in " + name + ": " + (e && e.message));++ }++ }++++ tryRequire(dir + "/" + entry.shared, name + ":shared");++ if (isServer) tryRequire(dir + "/" + entry.server, name + ":server");++ else tryRequire(dir + "/" + entry.client, name + ":client");++ }++++ var Addon = {++ __openvibe: true,++ list: function () {++ if (!OV || !OV.listDir) return [];++ return OV.listDir("addons", "*");++ },++ loadAll: function () {++ if (!OV || !OV.listDir) { warn("file bridge unavailable"); return; }++ var isServer = OV.isServer ? OV.isServer() : true;++ var names = this.list();++ log("scanning " + names.length + " addon folder(s), realm=" + (isServer ? "server" : "client"));++ for (var i = 0; i < names.length; i++) {++ // Skip node_modules and dotfiles.++ if (names[i] === "node_modules" || names[i].charAt(0) === ".") continue;++ loadOne(names[i], isServer);++ }++ }++ };++++ globalThis.Addon = Addon;++ if (OV && OV.log) OV.log("addon system ready");++})();nnew file mode 100644@@@ -0,0 +1,175 @@++// OpenVibe JS module system — CommonJS require() over the OV.readFile bridge.++//++// Gives the embedded QuickJS runtime a Node-style require() with:++// - relative resolution ("./x", "../x")++// - bare-specifier resolution via node_modules (npm packages)++// - extensions: exact, .js, .json, /index.js, package.json "main"++// - a module cache (require twice -> same exports)++// - hotloading (require.reload(id) drops the cache entry and re-evaluates)++//++// Everything is read through OV.readFile (mod-sandboxed), so only pure-JS++// packages work — there is no Node native-addon or node: builtin support in++// QuickJS. Safe browser/node-agnostic npm packages run fine.++(function () {++ if (globalThis.require && globalThis.require.__openvibe) return;++++ var OV = globalThis.OV;++ function readFile(p) { return OV && OV.readFile ? OV.readFile(p) : null; }++ function fileExists(p) { return OV && OV.fileExists ? OV.fileExists(p) : false; }++++ // Roots searched for bare specifiers (npm-style). Mod-relative.++ var NODE_MODULES_ROOTS = ["js/node_modules", "addons/node_modules"];++++ var cache = Object.create(null);++++ function dirname(p) {++ var i = p.lastIndexOf("/");++ return i <= 0 ? "" : p.slice(0, i);++ }++ function join(a, b) {++ if (!a) return b;++ if (b.charAt(0) === "/") b = b.slice(1);++ return a.replace(/\/+$/, "") + "/" + b;++ }++ // Collapse "a/./b" and "a/../b" without touching the filesystem.++ function normalize(p) {++ var parts = p.split("/");++ var out = [];++ for (var i = 0; i < parts.length; i++) {++ var s = parts[i];++ if (s === "" || s === ".") continue;++ if (s === "..") { out.pop(); continue; }++ out.push(s);++ }++ return out.join("/");++ }++++ function tryFile(p) { return p && fileExists(p) ? p : null; }++++ function hasExtension(p) {++ var slash = p.lastIndexOf("/");++ return p.slice(slash + 1).indexOf(".") >= 0;++ }++++ // Resolve a base path to a concrete file. The exact path is only accepted when++ // it carries an extension — otherwise "foo" (a directory) would be mistaken for++ // a file, since fileExists() can be true for directories. Extension-less++ // specifiers fall through to .js/.json and then directory resolution.++ function resolveAsFile(p) {++ if (hasExtension(p) && tryFile(p)) return p;++ return tryFile(p + ".js") || tryFile(p + ".json") || null;++ }++++ function resolveAsDirectory(p) {++ var pkgPath = join(p, "package.json");++ if (fileExists(pkgPath)) {++ try {++ var pkg = JSON.parse(readFile(pkgPath));++ var main = pkg && (pkg.main || (pkg.exports && (typeof pkg.exports === "string" ? pkg.exports : pkg.exports["."])));++ if (main) {++ var mainPath = normalize(join(p, main));++ var r = resolveAsFile(mainPath) || resolveAsFile(join(mainPath, "index"));++ if (r) return r;++ }++ } catch (e) { /* fall through to index */ }++ }++ return resolveAsFile(join(p, "index"));++ }++++ function resolveBare(name) {++ // name may be "pkg" or "pkg/sub/path"++ for (var i = 0; i < NODE_MODULES_ROOTS.length; i++) {++ var base = join(NODE_MODULES_ROOTS[i], name);++ var r = resolveAsFile(base) || resolveAsDirectory(base);++ if (r) return r;++ }++ return null;++ }++++ function resolve(request, fromDir) {++ if (request.charAt(0) === ".") {++ var abs = normalize(join(fromDir || "js", request));++ return resolveAsFile(abs) || resolveAsDirectory(abs);++ }++ if (request.charAt(0) === "/") {++ var absr = normalize(request);++ return resolveAsFile(absr) || resolveAsDirectory(absr);++ }++ return resolveBare(request);++ }++++ function loadModule(filename, parentRequire) {++ if (cache[filename]) return cache[filename].exports;++++ var module = { id: filename, exports: {}, loaded: false, filename: filename };++ cache[filename] = module;++++ var code = readFile(filename);++ if (code == null) {++ delete cache[filename];++ throw new Error("Cannot read module file: " + filename);++ }++++ if (/\.json$/.test(filename)) {++ module.exports = JSON.parse(code);++ module.loaded = true;++ return module.exports;++ }++++ var dir = dirname(filename);++ var localRequire = makeRequire(dir);++++ // Wrap like Node's module wrapper so top-level var/const stay module-scoped.++ var wrapper;++ try {++ wrapper = new Function(++ "exports", "require", "module", "__filename", "__dirname",++ code + "\n//# sourceURL=" + filename++ );++ } catch (e) {++ delete cache[filename];++ throw new Error("Syntax error in " + filename + ": " + (e && e.message));++ }++++ try {++ wrapper.call(module.exports, module.exports, localRequire, module, filename, dir);++ } catch (e) {++ delete cache[filename];++ throw e;++ }++++ module.loaded = true;++ return module.exports;++ }++++ function makeRequire(fromDir) {++ function req(request) {++ var filename = resolve(request, fromDir);++ if (!filename) throw new Error("Cannot find module '" + request + "' from '" + (fromDir || "js") + "'");++ return loadModule(filename, req);++ }++ req.resolve = function (request) { return resolve(request, fromDir); };++ // Load a file by mod-root-relative path (no ./ or node_modules semantics).++ // Used by the addon loader, which knows exact mod-relative file paths.++ req.load = function (modPath) {++ var norm = normalize(modPath);++ var filename = resolveAsFile(norm) || resolveAsDirectory(norm);++ if (!filename) throw new Error("Cannot find module file '" + modPath + "'");++ return loadModule(filename, req);++ };++ req.cache = cache;++ req.reload = function (request) {++ var filename = resolve(request, fromDir);++ if (filename && cache[filename]) delete cache[filename];++ return req(request);++ };++ req.__openvibe = true;++ return req;++ }++++ // Global require resolves relative specifiers against "js/".++ globalThis.require = makeRequire("js");++ globalThis.module = { exports: {} };++++ if (OV && OV.log) OV.log("module system ready (require/ npm node_modules)");++})();
new file mode 100644
new file mode 100644
@@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
+// OpenVibe JS addon system — GMod-style addons for Source, in JavaScript.
+//
+// Discovers addons under addons/<name>/ and loads them through require() so an
+// addon can pull in npm packages (js/node_modules or addons/node_modules) and
+// its own relative files. Each addon may ship:
+// addons/<name>/addon.json { "name", "entry": { "shared","server","client" } }
+// addons/<name>/shared.js loaded in both realms first
+// addons/<name>/server.js loaded only on the server realm
+// addons/<name>/client.js loaded only on the client realm
+//
+// Realm is decided by OV.isServer(). Loading is idempotent per realm.
+(function () {
+ if (globalThis.Addon && globalThis.Addon.__openvibe) return;
+
+ var OV = globalThis.OV;
+ var require = globalThis.require;
+
+ function log(m) { if (OV && OV.log) OV.log("[addon] " + m); }
+ function warn(m) { if (OV && OV.warn) OV.warn("[addon] " + m); }
+
+ var loaded = Object.create(null); // path -> true
+
+ // path is a mod-root-relative file path (no leading slash), e.g.
+ // "addons/foo/server.js" — the file bridge is mod-sandboxed and rejects
+ // leading-slash/parent paths, so we keep specifiers slash-free and load
+ // them via require.load (mod-root loader).
+ function tryRequire(path, label) {
+ if (loaded[path]) return true;
+ if (!OV.fileExists(path)) return false;
+ try {
+ require.load(path);
+ loaded[path] = true;
+ log(label + " -> " + path);
+ return true;
+ } catch (e) {
+ warn("failed " + path + ": " + (e && e.message ? e.message : e));
+ return false;
+ }
+ }
+
+ function loadOne(name, isServer) {
+ var dir = "addons/" + name;
+ var manifestPath = dir + "/addon.json";
+ var entry = { shared: "shared.js", server: "server.js", client: "client.js" };
+
+ if (OV.fileExists(manifestPath)) {
+ try {
+ var m = JSON.parse(OV.readFile(manifestPath));
+ if (m && m.entry) {
+ entry.shared = m.entry.shared || entry.shared;
+ entry.server = m.entry.server || entry.server;
+ entry.client = m.entry.client || entry.client;
+ }
+ log("addon '" + (m && m.name ? m.name : name) + "'");
+ } catch (e) {
+ warn("bad addon.json in " + name + ": " + (e && e.message));
+ }
+ }
+
+ tryRequire(dir + "/" + entry.shared, name + ":shared");
+ if (isServer) tryRequire(dir + "/" + entry.server, name + ":server");
+ else tryRequire(dir + "/" + entry.client, name + ":client");
+ }
+
+ var Addon = {
+ __openvibe: true,
+ list: function () {
+ if (!OV || !OV.listDir) return [];
+ return OV.listDir("addons", "*");
+ },
+ loadAll: function () {
+ if (!OV || !OV.listDir) { warn("file bridge unavailable"); return; }
+ var isServer = OV.isServer ? OV.isServer() : true;
+ var names = this.list();
+ log("scanning " + names.length + " addon folder(s), realm=" + (isServer ? "server" : "client"));
+ for (var i = 0; i < names.length; i++) {
+ // Skip node_modules and dotfiles.
+ if (names[i] === "node_modules" || names[i].charAt(0) === ".") continue;
+ loadOne(names[i], isServer);
+ }
+ }
+ };
+
+ globalThis.Addon = Addon;
+ if (OV && OV.log) OV.log("addon system ready");
+})();
new file mode 100644
new file mode 100644
@@ -0,0 +1,175 @@
@@ -0,0 +1,175 @@
+// OpenVibe JS module system — CommonJS require() over the OV.readFile bridge.
+//
+// Gives the embedded QuickJS runtime a Node-style require() with:
+// - relative resolution ("./x", "../x")
+// - bare-specifier resolution via node_modules (npm packages)
+// - extensions: exact, .js, .json, /index.js, package.json "main"
+// - a module cache (require twice -> same exports)
+// - hotloading (require.reload(id) drops the cache entry and re-evaluates)
+//
+// Everything is read through OV.readFile (mod-sandboxed), so only pure-JS
+// packages work — there is no Node native-addon or node: builtin support in
+// QuickJS. Safe browser/node-agnostic npm packages run fine.
+(function () {
+ if (globalThis.require && globalThis.require.__openvibe) return;
+
+ var OV = globalThis.OV;
+ function readFile(p) { return OV && OV.readFile ? OV.readFile(p) : null; }
+ function fileExists(p) { return OV && OV.fileExists ? OV.fileExists(p) : false; }
+
+ // Roots searched for bare specifiers (npm-style). Mod-relative.
+ var NODE_MODULES_ROOTS = ["js/node_modules", "addons/node_modules"];
+
+ var cache = Object.create(null);
+
+ function dirname(p) {
+ var i = p.lastIndexOf("/");
+ return i <= 0 ? "" : p.slice(0, i);
+ }
+ function join(a, b) {
+ if (!a) return b;
+ if (b.charAt(0) === "/") b = b.slice(1);
+ return a.replace(/\/+$/, "") + "/" + b;
+ }
+ // Collapse "a/./b" and "a/../b" without touching the filesystem.
+ function normalize(p) {
+ var parts = p.split("/");
+ var out = [];
+ for (var i = 0; i < parts.length; i++) {
+ var s = parts[i];
+ if (s === "" || s === ".") continue;
+ if (s === "..") { out.pop(); continue; }
+ out.push(s);
+ }
+ return out.join("/");
+ }
+
+ function tryFile(p) { return p && fileExists(p) ? p : null; }
+
+ function hasExtension(p) {
+ var slash = p.lastIndexOf("/");
+ return p.slice(slash + 1).indexOf(".") >= 0;
+ }
+
+ // Resolve a base path to a concrete file. The exact path is only accepted when
+ // it carries an extension — otherwise "foo" (a directory) would be mistaken for
+ // a file, since fileExists() can be true for directories. Extension-less
+ // specifiers fall through to .js/.json and then directory resolution.
+ function resolveAsFile(p) {
+ if (hasExtension(p) && tryFile(p)) return p;
+ return tryFile(p + ".js") || tryFile(p + ".json") || null;
+ }
+
+ function resolveAsDirectory(p) {
+ var pkgPath = join(p, "package.json");
+ if (fileExists(pkgPath)) {
+ try {
+ var pkg = JSON.parse(readFile(pkgPath));
+ var main = pkg && (pkg.main || (pkg.exports && (typeof pkg.exports === "string" ? pkg.exports : pkg.exports["."])));
+ if (main) {
+ var mainPath = normalize(join(p, main));
+ var r = resolveAsFile(mainPath) || resolveAsFile(join(mainPath, "index"));
+ if (r) return r;
+ }
+ } catch (e) { /* fall through to index */ }
+ }
+ return resolveAsFile(join(p, "index"));
+ }
+
+ function resolveBare(name) {
+ // name may be "pkg" or "pkg/sub/path"
+ for (var i = 0; i < NODE_MODULES_ROOTS.length; i++) {
+ var base = join(NODE_MODULES_ROOTS[i], name);
+ var r = resolveAsFile(base) || resolveAsDirectory(base);
+ if (r) return r;
+ }
+ return null;
+ }
+
+ function resolve(request, fromDir) {
+ if (request.charAt(0) === ".") {
+ var abs = normalize(join(fromDir || "js", request));
+ return resolveAsFile(abs) || resolveAsDirectory(abs);
+ }
+ if (request.charAt(0) === "/") {
+ var absr = normalize(request);
+ return resolveAsFile(absr) || resolveAsDirectory(absr);
+ }
+ return resolveBare(request);
+ }
+
+ function loadModule(filename, parentRequire) {
+ if (cache[filename]) return cache[filename].exports;
+
+ var module = { id: filename, exports: {}, loaded: false, filename: filename };
+ cache[filename] = module;
+
+ var code = readFile(filename);
+ if (code == null) {
+ delete cache[filename];
+ throw new Error("Cannot read module file: " + filename);
+ }
+
+ if (/\.json$/.test(filename)) {
+ module.exports = JSON.parse(code);
+ module.loaded = true;
+ return module.exports;
+ }
+
+ var dir = dirname(filename);
+ var localRequire = makeRequire(dir);
+
+ // Wrap like Node's module wrapper so top-level var/const stay module-scoped.
+ var wrapper;
+ try {
+ wrapper = new Function(
+ "exports", "require", "module", "__filename", "__dirname",
+ code + "\n//# sourceURL=" + filename
+ );
+ } catch (e) {
+ delete cache[filename];
+ throw new Error("Syntax error in " + filename + ": " + (e && e.message));
+ }
+
+ try {
+ wrapper.call(module.exports, module.exports, localRequire, module, filename, dir);
+ } catch (e) {
+ delete cache[filename];
+ throw e;
+ }
+
+ module.loaded = true;
+ return module.exports;
+ }
+
+ function makeRequire(fromDir) {
+ function req(request) {
+ var filename = resolve(request, fromDir);
+ if (!filename) throw new Error("Cannot find module '" + request + "' from '" + (fromDir || "js") + "'");
+ return loadModule(filename, req);
+ }
+ req.resolve = function (request) { return resolve(request, fromDir); };
+ // Load a file by mod-root-relative path (no ./ or node_modules semantics).
+ // Used by the addon loader, which knows exact mod-relative file paths.
+ req.load = function (modPath) {
+ var norm = normalize(modPath);
+ var filename = resolveAsFile(norm) || resolveAsDirectory(norm);
+ if (!filename) throw new Error("Cannot find module file '" + modPath + "'");
+ return loadModule(filename, req);
+ };
+ req.cache = cache;
+ req.reload = function (request) {
+ var filename = resolve(request, fromDir);
+ if (filename && cache[filename]) delete cache[filename];
+ return req(request);
+ };
+ req.__openvibe = true;
+ return req;
+ }
+
+ // Global require resolves relative specifiers against "js/".
+ globalThis.require = makeRequire("js");
+ globalThis.module = { exports: {} };
+
+ if (OV && OV.log) OV.log("module system ready (require/ npm node_modules)");
+})();