151 lines
4.7 KiB
JavaScript
151 lines
4.7 KiB
JavaScript
/**
|
|
* Capability origin allowlist. Shared by the MV3 service worker (importScripts)
|
|
* and Node unit tests (module.exports).
|
|
*
|
|
* Default allow: the examples server origin(s). There is no "allow all" mode —
|
|
* an empty user list still includes the examples origin.
|
|
*/
|
|
(function (root, factory) {
|
|
if (typeof module === 'object' && module.exports) {
|
|
module.exports = factory();
|
|
} else {
|
|
root.BridgeSwarmOriginAllowlist = factory();
|
|
}
|
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
const DEFAULT_EXAMPLES_PORT = 4173;
|
|
const CAP_PACK_PREFIX =
|
|
/^(media|fs|sqlite|net|qvac|agent)\.[a-zA-Z0-9_-]+$/;
|
|
|
|
function defaultOrigins(examplesPort) {
|
|
const port = Number(examplesPort) > 0 ? Number(examplesPort) : DEFAULT_EXAMPLES_PORT;
|
|
return [
|
|
'http://127.0.0.1:' + port,
|
|
'http://localhost:' + port,
|
|
'http://[::1]:' + port,
|
|
];
|
|
}
|
|
|
|
function originFromUrl(raw) {
|
|
if (!raw || typeof raw !== 'string') return '';
|
|
try {
|
|
const u = new URL(raw);
|
|
if (u.protocol === 'http:' || u.protocol === 'https:') return u.origin;
|
|
if (u.protocol === 'chrome-extension:' || u.protocol === 'moz-extension:') {
|
|
return u.origin;
|
|
}
|
|
if (u.protocol === 'file:') return 'file://';
|
|
return u.origin || '';
|
|
} catch (_) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function normalizeOrigin(value) {
|
|
if (!value || typeof value !== 'string') return '';
|
|
const trimmed = value.trim().replace(/\/+$/, '');
|
|
if (!trimmed) return '';
|
|
if (trimmed === 'file://') return 'file://';
|
|
try {
|
|
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) {
|
|
return originFromUrl(trimmed) || trimmed;
|
|
}
|
|
return originFromUrl('https://' + trimmed) || trimmed;
|
|
} catch (_) {
|
|
return trimmed;
|
|
}
|
|
}
|
|
|
|
function parseOriginList(raw) {
|
|
if (Array.isArray(raw)) {
|
|
return raw.map(normalizeOrigin).filter(Boolean);
|
|
}
|
|
if (typeof raw !== 'string') return [];
|
|
return raw
|
|
.split(/\r?\n/)
|
|
.map(normalizeOrigin)
|
|
.filter(Boolean);
|
|
}
|
|
|
|
function allowedOrigins(settings) {
|
|
const s = settings || {};
|
|
const port = s.examplesServerPort;
|
|
const extras = parseOriginList(s.capabilityOrigins);
|
|
const seen = new Set();
|
|
const out = [];
|
|
for (const o of defaultOrigins(port).concat(extras)) {
|
|
if (!seen.has(o)) {
|
|
seen.add(o);
|
|
out.push(o);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function isExtensionOrigin(origin, extensionOrigin) {
|
|
if (!origin) return false;
|
|
if (extensionOrigin && origin === extensionOrigin) return true;
|
|
return origin.startsWith('chrome-extension://') || origin.startsWith('moz-extension://');
|
|
}
|
|
|
|
function isOriginAllowed(origin, settings, opts) {
|
|
const o = normalizeOrigin(origin);
|
|
if (!o) return false;
|
|
if (isExtensionOrigin(o, opts && opts.extensionOrigin)) return true;
|
|
const list = allowedOrigins(settings);
|
|
if (list.indexOf(o) !== -1) return true;
|
|
return false;
|
|
}
|
|
|
|
function isAlwaysApproveOrigin(origin, settings) {
|
|
const o = normalizeOrigin(origin);
|
|
if (!o) return false;
|
|
const list = parseOriginList(settings && settings.agentAlwaysApproveOrigins);
|
|
return list.indexOf(o) !== -1;
|
|
}
|
|
|
|
function isCapabilityType(type) {
|
|
if (type === 'capability') return true;
|
|
return typeof type === 'string' && CAP_PACK_PREFIX.test(type);
|
|
}
|
|
|
|
const QVAC_META_CMDS = { detect: 1, status: 1, catalog: 1, openaiStatus: 1 };
|
|
const AGENT_META_CMDS = { status: 1, setGrants: 1, list: 1 };
|
|
|
|
function qvacPackAndCmd(msg) {
|
|
if (!msg || !msg.type) return null;
|
|
if (msg.type === 'capability' && msg.payload) {
|
|
return { pack: msg.payload.pack, cmd: msg.payload.cmd };
|
|
}
|
|
const m = typeof msg.type === 'string' && msg.type.match(/^(qvac|agent)\.([a-zA-Z0-9_-]+)$/);
|
|
if (m) return { pack: m[1], cmd: m[2] };
|
|
return null;
|
|
}
|
|
|
|
/** True when QVAC/agent inference must be blocked (user has not enabled QVAC).
|
|
* qvac.setEnabled is never allowed from pages (background send bypasses this gate). */
|
|
function isQvacDisabled(msg, settings) {
|
|
const pc = qvacPackAndCmd(msg);
|
|
if (pc && pc.pack === 'qvac' && pc.cmd === 'setEnabled') return true;
|
|
if (settings && settings.qvacEnabled === true) return false;
|
|
if (!pc || (pc.pack !== 'qvac' && pc.pack !== 'agent')) return false;
|
|
if (pc.pack === 'qvac' && QVAC_META_CMDS[pc.cmd]) return false;
|
|
if (pc.pack === 'agent' && AGENT_META_CMDS[pc.cmd]) return false;
|
|
return true;
|
|
}
|
|
|
|
return {
|
|
DEFAULT_EXAMPLES_PORT,
|
|
defaultOrigins,
|
|
originFromUrl,
|
|
normalizeOrigin,
|
|
parseOriginList,
|
|
allowedOrigins,
|
|
isExtensionOrigin,
|
|
isOriginAllowed,
|
|
isAlwaysApproveOrigin,
|
|
isCapabilityType,
|
|
qvacPackAndCmd,
|
|
isQvacDisabled,
|
|
};
|
|
});
|