@nocobase/flow-engine
Version:
A standalone flow engine for NocoBase, managing workflows, models, and actions.
180 lines (178 loc) • 6.8 kB
JavaScript
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var safeGlobals_exports = {};
__export(safeGlobals_exports, {
createSafeDocument: () => createSafeDocument,
createSafeWindow: () => createSafeWindow
});
module.exports = __toCommonJS(safeGlobals_exports);
function createSafeWindow(extra) {
const getSafeBaseHref = /* @__PURE__ */ __name(() => `${window.location.origin}${window.location.pathname}`, "getSafeBaseHref");
const safeOpen = /* @__PURE__ */ __name((url, target, features) => {
const isSafeUrl = /* @__PURE__ */ __name((u) => {
try {
const parsed = new URL(u, getSafeBaseHref());
const protocol = parsed.protocol.toLowerCase();
if (protocol === "about:") return parsed.href === "about:blank";
return protocol === "http:" || protocol === "https:";
} catch {
return false;
}
}, "isSafeUrl");
if (!isSafeUrl(url)) {
throw new Error("Unsafe URL: window.open only allows http/https/about:blank.");
}
const sanitizedTarget = "_blank";
const enforceFeatures = /* @__PURE__ */ __name((f) => {
const set = /* @__PURE__ */ new Set();
if (f) {
f.split(",").map((s) => s.trim()).filter(Boolean).forEach((part) => {
const key = part.split("=")[0].trim().toLowerCase();
if (key !== "noopener" && key !== "noreferrer") set.add(part);
});
}
set.add("noopener");
set.add("noreferrer");
return Array.from(set).join(",");
}, "enforceFeatures");
const sanitizedFeatures = enforceFeatures(features);
const newWin = window.open.call(window, url, sanitizedTarget, sanitizedFeatures);
if (newWin && "opener" in newWin) {
try {
newWin.opener = null;
} catch {
}
}
return newWin;
}, "safeOpen");
const guardedNavigate = /* @__PURE__ */ __name((rawUrl, opts) => {
const parsed = new URL(rawUrl, getSafeBaseHref());
const protocol = parsed.protocol.toLowerCase();
const isAboutBlank = protocol === "about:" && parsed.href === "about:blank";
const isHttp = protocol === "http:" || protocol === "https:";
if (!isHttp && !isAboutBlank) {
throw new Error("Unsafe URL: only http/https/about:blank are allowed.");
}
if (isAboutBlank) {
return (opts == null ? void 0 : opts.replace) ? window.location.replace("about:blank") : window.location.assign("about:blank");
}
const sameOrigin = parsed.protocol === window.location.protocol && parsed.hostname === window.location.hostname && parsed.port === window.location.port;
if (sameOrigin) {
return (opts == null ? void 0 : opts.replace) ? window.location.replace(parsed.href) : window.location.assign(parsed.href);
}
const win = safeOpen(parsed.href);
if (!win) throw new Error("Popup blocked: cross-origin navigation is opened in a new tab.");
}, "guardedNavigate");
const safeLocation = new Proxy(
{},
{
get(_t, prop) {
switch (prop) {
case "origin":
return window.location.origin;
case "protocol":
return window.location.protocol;
case "host":
return window.location.host;
case "hostname":
return window.location.hostname;
case "port":
return window.location.port;
case "pathname":
return window.location.pathname;
case "assign":
return (u) => guardedNavigate(u, { replace: false });
case "replace":
return (u) => guardedNavigate(u, { replace: true });
case "reload":
throw new Error("Access to location.reload is not allowed.");
case "href":
throw new Error("Reading location.href is not allowed.");
default:
throw new Error(`Access to location property "${prop}" is not allowed.`);
}
},
set(_t, prop, value) {
if (prop === "href") {
guardedNavigate(String(value), { replace: false });
return true;
}
throw new Error("Mutation on location is not allowed.");
}
}
);
const allowedGlobals = {
// 需绑定到原始 window,避免严格模式下触发 Illegal invocation
setTimeout: window.setTimeout.bind(window),
clearTimeout: window.clearTimeout.bind(window),
setInterval: window.setInterval.bind(window),
clearInterval: window.clearInterval.bind(window),
console,
Math,
Date,
// 事件侦听仅绑定到真实 window,便于少量需要的全局监听
addEventListener: addEventListener.bind(window),
// 安全的 window.open 代理
open: safeOpen,
// 安全的 location 代理
location: safeLocation,
...extra || {}
};
return new Proxy(
{},
{
get(_target, prop) {
if (prop in allowedGlobals) return allowedGlobals[prop];
throw new Error(`Access to global property "${prop}" is not allowed.`);
}
}
);
}
__name(createSafeWindow, "createSafeWindow");
function createSafeDocument(extra) {
const allowed = {
createElement: document.createElement.bind(document),
querySelector: document.querySelector.bind(document),
querySelectorAll: document.querySelectorAll.bind(document),
...extra || {}
};
return new Proxy(
{},
{
get(_target, prop) {
if (prop in allowed) return allowed[prop];
throw new Error(`Access to document property "${prop}" is not allowed.`);
}
}
);
}
__name(createSafeDocument, "createSafeDocument");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createSafeDocument,
createSafeWindow
});