@nocobase/flow-engine 2.1.31 → 2.2.0-alpha.10
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/lib/JSRunner.d.ts +1 -0
- package/lib/JSRunner.js +110 -20
- package/lib/components/FlowContextSelector.js +55 -12
- package/lib/components/FormItem.js +11 -7
- package/lib/components/MobilePopup.style.js +16 -5
- package/lib/components/variables/VariableHybridInput.d.ts +9 -0
- package/lib/components/variables/VariableHybridInput.js +146 -17
- package/lib/components/variables/VariableInput.js +19 -7
- package/lib/components/variables/VariableTag.js +48 -36
- package/lib/components/variables/types.d.ts +21 -0
- package/lib/flowContext.js +11 -1
- package/lib/flowI18n.js +3 -3
- package/lib/runjs-context/helpers.js +12 -5
- package/lib/types.d.ts +3 -1
- package/lib/types.js +1 -0
- package/lib/utils/index.d.ts +0 -1
- package/lib/utils/index.js +0 -11
- package/lib/utils/resolveRunJSObjectValues.js +3 -2
- package/lib/utils/runjsModuleLoader.js +0 -30
- package/package.json +4 -4
- package/src/JSRunner.ts +112 -25
- package/src/__tests__/JSRunner.test.ts +4 -5
- package/src/__tests__/flowI18n.test.ts +11 -0
- package/src/components/FlowContextSelector.tsx +66 -11
- package/src/components/FormItem.tsx +12 -7
- package/src/components/MobilePopup.style.ts +22 -6
- package/src/components/__tests__/FormItem.test.tsx +17 -2
- package/src/components/__tests__/MobilePopup.style.test.tsx +103 -0
- package/src/components/variables/VariableHybridInput.tsx +185 -14
- package/src/components/variables/VariableInput.tsx +32 -7
- package/src/components/variables/VariableTag.tsx +51 -37
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +60 -3
- package/src/components/variables/__tests__/VariableHybridInput.test.tsx +212 -0
- package/src/components/variables/__tests__/VariableInput.test.tsx +202 -6
- package/src/components/variables/__tests__/VariableTag.test.tsx +80 -0
- package/src/components/variables/types.ts +21 -0
- package/src/flowContext.ts +11 -1
- package/src/flowI18n.ts +8 -3
- package/src/runjs-context/helpers.ts +12 -6
- package/src/types.ts +2 -0
- package/src/utils/index.ts +0 -9
- package/src/utils/resolveRunJSObjectValues.ts +5 -2
- package/src/utils/runjsModuleLoader.ts +0 -32
- package/lib/utils/safeGlobals.d.ts +0 -28
- package/lib/utils/safeGlobals.js +0 -367
- package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +0 -38
- package/src/utils/__tests__/safeGlobals.test.ts +0 -106
- package/src/utils/safeGlobals.ts +0 -406
package/src/flowContext.ts
CHANGED
|
@@ -3609,7 +3609,17 @@ export class FlowEngineContext extends BaseFlowEngineContext {
|
|
|
3609
3609
|
doc = {};
|
|
3610
3610
|
}
|
|
3611
3611
|
const deprecatedCtx = createRunJSDeprecationProxy(runCtx, { doc });
|
|
3612
|
-
const
|
|
3612
|
+
const browserGlobals: Record<string, any> = {};
|
|
3613
|
+
if (typeof window !== 'undefined') {
|
|
3614
|
+
browserGlobals.window = window;
|
|
3615
|
+
if (typeof navigator !== 'undefined') {
|
|
3616
|
+
browserGlobals.navigator = navigator;
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
if (typeof document !== 'undefined') {
|
|
3620
|
+
browserGlobals.document = document;
|
|
3621
|
+
}
|
|
3622
|
+
const globals: Record<string, any> = { ctx: deprecatedCtx, ...browserGlobals, ...(options?.globals || {}) };
|
|
3613
3623
|
const { timeoutMs } = options || {};
|
|
3614
3624
|
return new JSRunner({ globals, timeoutMs });
|
|
3615
3625
|
});
|
package/src/flowI18n.ts
CHANGED
|
@@ -64,7 +64,9 @@ export class FlowI18n {
|
|
|
64
64
|
* @private
|
|
65
65
|
*/
|
|
66
66
|
private isTemplate(str: string): boolean {
|
|
67
|
-
|
|
67
|
+
// The closing quote is a backreference to the opening one (group 1) so an embedded quote of a different type — e.g.
|
|
68
|
+
// {{t('… "Post-action event" …')}} — does not terminate the key early.
|
|
69
|
+
return /\{\{\s*t\s*\(\s*(["'`])(?:\\.|(?!\1).)*?\1\s*(?:,\s*.*?)?\s*\)\s*\}\}/.test(str);
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
/**
|
|
@@ -72,9 +74,12 @@ export class FlowI18n {
|
|
|
72
74
|
* @private
|
|
73
75
|
*/
|
|
74
76
|
private compileTemplate(template: string): string {
|
|
77
|
+
// `(["'`])` captures the opening quote; the key allows escaped chars (`\\.`) and any char that is not that same
|
|
78
|
+
// quote (`(?!\1).`), and `\1` closes on the matching quote. This keeps embedded quotes of a different type inside
|
|
79
|
+
// the key instead of truncating it at the first quote of any kind.
|
|
75
80
|
return template.replace(
|
|
76
|
-
/\{\{\s*t\s*\(\s*["'`](
|
|
77
|
-
(match, key, optionsStr) => {
|
|
81
|
+
/\{\{\s*t\s*\(\s*(["'`])((?:\\.|(?!\1).)*?)\1\s*(?:,\s*((?:[^{}]|\{[^}]*\})*?))?\s*\)\s*\}\}/g,
|
|
82
|
+
(match, _quote, key, optionsStr) => {
|
|
78
83
|
try {
|
|
79
84
|
let templateOptions = {};
|
|
80
85
|
if (optionsStr) {
|
|
@@ -46,12 +46,17 @@ export function createJSRunnerWithVersion(this: FlowContext, options?: JSRunnerO
|
|
|
46
46
|
doc = {};
|
|
47
47
|
}
|
|
48
48
|
const deprecatedCtx = createRunJSDeprecationProxy(runCtx, { doc });
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (typeof
|
|
53
|
-
|
|
49
|
+
const browserGlobals: Record<string, any> = {};
|
|
50
|
+
if (typeof window !== 'undefined') {
|
|
51
|
+
browserGlobals.window = window;
|
|
52
|
+
if (typeof navigator !== 'undefined') {
|
|
53
|
+
browserGlobals.navigator = navigator;
|
|
54
|
+
}
|
|
54
55
|
}
|
|
56
|
+
if (typeof document !== 'undefined') {
|
|
57
|
+
browserGlobals.document = document;
|
|
58
|
+
}
|
|
59
|
+
const globals: Record<string, any> = { ctx: deprecatedCtx, ...browserGlobals, ...(options?.globals || {}) };
|
|
55
60
|
// 透传 JSRunnerOptions 其余配置(如 timeoutMs)
|
|
56
61
|
const { timeoutMs } = options || {};
|
|
57
62
|
return new JSRunner({ globals, timeoutMs });
|
|
@@ -59,7 +64,8 @@ export function createJSRunnerWithVersion(this: FlowContext, options?: JSRunnerO
|
|
|
59
64
|
|
|
60
65
|
export function getRunJSScenesForModel(modelClass: string, version: RunJSVersion = 'v1'): string[] {
|
|
61
66
|
const meta = RunJSContextRegistry.getMeta(version, modelClass);
|
|
62
|
-
|
|
67
|
+
const scenes = meta?.scenes;
|
|
68
|
+
return Array.isArray(scenes) ? [...scenes] : [];
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
export function getRunJSScenesForContext(ctx: FlowContext, { version = 'v1' as RunJSVersion } = {}): string[] {
|
package/src/types.ts
CHANGED
package/src/utils/index.ts
CHANGED
|
@@ -87,15 +87,6 @@ export {
|
|
|
87
87
|
serializeCtxDateValue,
|
|
88
88
|
} from './dateVariable';
|
|
89
89
|
|
|
90
|
-
// 安全全局对象(window/document)
|
|
91
|
-
export {
|
|
92
|
-
createSafeDocument,
|
|
93
|
-
createSafeWindow,
|
|
94
|
-
createSafeNavigator,
|
|
95
|
-
createSafeRunJSGlobals,
|
|
96
|
-
runjsWithSafeGlobals,
|
|
97
|
-
} from './safeGlobals';
|
|
98
|
-
|
|
99
90
|
// RunJS value helpers
|
|
100
91
|
export { isRunJSValue, normalizeRunJSValue, extractUsedVariablePathsFromRunJS, type RunJSValue } from './runjsValue';
|
|
101
92
|
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { isRunJSValue, normalizeRunJSValue } from './runjsValue';
|
|
11
|
-
import { runjsWithSafeGlobals } from './safeGlobals';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Resolve an object's values, executing any RunJSValue entries via ctx.runjs.
|
|
@@ -29,7 +28,11 @@ export async function resolveRunJSObjectValues(ctx: unknown, raw: unknown): Prom
|
|
|
29
28
|
if (isRunJSValue(value)) {
|
|
30
29
|
const { code, version } = normalizeRunJSValue(value);
|
|
31
30
|
if (!code.trim()) continue;
|
|
32
|
-
const
|
|
31
|
+
const runjsCtx = ctx as
|
|
32
|
+
| { runjs?: (code: string, variables?: Record<string, any>, options?: Record<string, any>) => Promise<any> }
|
|
33
|
+
| undefined
|
|
34
|
+
| null;
|
|
35
|
+
const ret = await runjsCtx?.runjs?.(code, undefined, { version });
|
|
33
36
|
if (!ret?.success) {
|
|
34
37
|
throw new Error(`RunJS execution failed for "${key}"`);
|
|
35
38
|
}
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
import { setRunJSLibOverride } from '../runjsLibs';
|
|
11
11
|
import { resolveModuleUrl } from './resolveModuleUrl';
|
|
12
|
-
import { registerRunJSSafeDocumentGlobals, registerRunJSSafeWindowGlobals } from './safeGlobals';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* RunJS 外部模块加载辅助(浏览器侧)。
|
|
@@ -38,26 +37,6 @@ type ParsedPackageSpecifier = {
|
|
|
38
37
|
subpath?: string;
|
|
39
38
|
};
|
|
40
39
|
|
|
41
|
-
function snapshotOwnKeys(obj: any): string[] {
|
|
42
|
-
try {
|
|
43
|
-
if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) return [];
|
|
44
|
-
return Object.getOwnPropertyNames(obj);
|
|
45
|
-
} catch (_) {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function diffAddedKeys(afterKeys: string[], beforeKeys: string[]): string[] {
|
|
51
|
-
if (!afterKeys.length) return [];
|
|
52
|
-
if (!beforeKeys.length) return [...afterKeys];
|
|
53
|
-
const beforeSet = new Set(beforeKeys);
|
|
54
|
-
const added: string[] = [];
|
|
55
|
-
for (const k of afterKeys) {
|
|
56
|
-
if (!beforeSet.has(k)) added.push(k);
|
|
57
|
-
}
|
|
58
|
-
return added;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
40
|
/**
|
|
62
41
|
* 使用全局 Promise 链实现“互斥锁”:
|
|
63
42
|
* - 锁存放在 `globalThis.__nocobaseRunjsModuleLoadLock`;
|
|
@@ -298,9 +277,6 @@ async function prefetchEsmModule(url: string, options?: { timeoutMs?: number }):
|
|
|
298
277
|
*/
|
|
299
278
|
export async function runjsRequireAsync(requirejs: RequireJsLike, url: string): Promise<any> {
|
|
300
279
|
return await withRunjsModuleLoadLock(async () => {
|
|
301
|
-
const beforeWinKeys = typeof window !== 'undefined' ? snapshotOwnKeys(window) : [];
|
|
302
|
-
const beforeDocKeys = typeof document !== 'undefined' ? snapshotOwnKeys(document) : [];
|
|
303
|
-
|
|
304
280
|
let result: any;
|
|
305
281
|
let error: any;
|
|
306
282
|
try {
|
|
@@ -319,14 +295,6 @@ export async function runjsRequireAsync(requirejs: RequireJsLike, url: string):
|
|
|
319
295
|
});
|
|
320
296
|
} catch (e) {
|
|
321
297
|
error = e;
|
|
322
|
-
} finally {
|
|
323
|
-
const afterWinKeys = typeof window !== 'undefined' ? snapshotOwnKeys(window) : [];
|
|
324
|
-
const afterDocKeys = typeof document !== 'undefined' ? snapshotOwnKeys(document) : [];
|
|
325
|
-
const addedWinKeys = diffAddedKeys(afterWinKeys, beforeWinKeys);
|
|
326
|
-
const addedDocKeys = diffAddedKeys(afterDocKeys, beforeDocKeys);
|
|
327
|
-
// Best-effort: allow RunJS safe window/document to access globals introduced by this module load.
|
|
328
|
-
registerRunJSSafeWindowGlobals(addedWinKeys);
|
|
329
|
-
registerRunJSSafeDocumentGlobals(addedDocKeys);
|
|
330
298
|
}
|
|
331
299
|
|
|
332
300
|
if (error) throw error;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
export declare function registerRunJSSafeWindowGlobals(keys: Iterable<string> | null | undefined): void;
|
|
10
|
-
export declare function registerRunJSSafeDocumentGlobals(keys: Iterable<string> | null | undefined): void;
|
|
11
|
-
export declare function __resetRunJSSafeGlobalsRegistryForTests(): void;
|
|
12
|
-
export declare function createSafeWindow(extra?: Record<string, any>): Record<string, any>;
|
|
13
|
-
export declare function createSafeDocument(extra?: Record<string, any>): Record<string, any>;
|
|
14
|
-
export declare function createSafeNavigator(extra?: Record<string, any>): {};
|
|
15
|
-
/**
|
|
16
|
-
* Create a safe globals object for RunJS execution.
|
|
17
|
-
*
|
|
18
|
-
* - Always tries to provide `navigator`
|
|
19
|
-
* - Best-effort provides `window` and `document` in browser environments
|
|
20
|
-
* - Never throws (so callers can decide how to handle missing globals)
|
|
21
|
-
*/
|
|
22
|
-
export declare function createSafeRunJSGlobals(extraGlobals?: Record<string, any>): Record<string, any>;
|
|
23
|
-
/**
|
|
24
|
-
* Execute RunJS with safe globals (window/document/navigator).
|
|
25
|
-
*
|
|
26
|
-
* Keeps `this` binding by calling `ctx.runjs(...)` instead of passing bare function references.
|
|
27
|
-
*/
|
|
28
|
-
export declare function runjsWithSafeGlobals(ctx: unknown, code: string, options?: any, extraGlobals?: Record<string, any>): Promise<any>;
|
package/lib/utils/safeGlobals.js
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
var __defProp = Object.defineProperty;
|
|
11
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
|
-
var __export = (target, all) => {
|
|
16
|
-
for (var name in all)
|
|
17
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
-
};
|
|
19
|
-
var __copyProps = (to, from, except, desc) => {
|
|
20
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
-
for (let key of __getOwnPropNames(from))
|
|
22
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
-
}
|
|
25
|
-
return to;
|
|
26
|
-
};
|
|
27
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
-
var safeGlobals_exports = {};
|
|
29
|
-
__export(safeGlobals_exports, {
|
|
30
|
-
__resetRunJSSafeGlobalsRegistryForTests: () => __resetRunJSSafeGlobalsRegistryForTests,
|
|
31
|
-
createSafeDocument: () => createSafeDocument,
|
|
32
|
-
createSafeNavigator: () => createSafeNavigator,
|
|
33
|
-
createSafeRunJSGlobals: () => createSafeRunJSGlobals,
|
|
34
|
-
createSafeWindow: () => createSafeWindow,
|
|
35
|
-
registerRunJSSafeDocumentGlobals: () => registerRunJSSafeDocumentGlobals,
|
|
36
|
-
registerRunJSSafeWindowGlobals: () => registerRunJSSafeWindowGlobals,
|
|
37
|
-
runjsWithSafeGlobals: () => runjsWithSafeGlobals
|
|
38
|
-
});
|
|
39
|
-
module.exports = __toCommonJS(safeGlobals_exports);
|
|
40
|
-
function getRunJSSafeGlobalsRegistry() {
|
|
41
|
-
var _a, _b;
|
|
42
|
-
const g = globalThis;
|
|
43
|
-
if (((_a = g.__nocobaseRunJSSafeGlobalsRegistry) == null ? void 0 : _a.windowAllow) && ((_b = g.__nocobaseRunJSSafeGlobalsRegistry) == null ? void 0 : _b.documentAllow)) {
|
|
44
|
-
return g.__nocobaseRunJSSafeGlobalsRegistry;
|
|
45
|
-
}
|
|
46
|
-
const reg = {
|
|
47
|
-
windowAllow: /* @__PURE__ */ new Set(),
|
|
48
|
-
documentAllow: /* @__PURE__ */ new Set()
|
|
49
|
-
};
|
|
50
|
-
g.__nocobaseRunJSSafeGlobalsRegistry = reg;
|
|
51
|
-
return reg;
|
|
52
|
-
}
|
|
53
|
-
__name(getRunJSSafeGlobalsRegistry, "getRunJSSafeGlobalsRegistry");
|
|
54
|
-
function registerRunJSSafeWindowGlobals(keys) {
|
|
55
|
-
if (!keys) return;
|
|
56
|
-
const reg = getRunJSSafeGlobalsRegistry();
|
|
57
|
-
for (const k of keys) {
|
|
58
|
-
if (typeof k !== "string") continue;
|
|
59
|
-
const key = k.trim();
|
|
60
|
-
if (!key) continue;
|
|
61
|
-
reg.windowAllow.add(key);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
__name(registerRunJSSafeWindowGlobals, "registerRunJSSafeWindowGlobals");
|
|
65
|
-
function registerRunJSSafeDocumentGlobals(keys) {
|
|
66
|
-
if (!keys) return;
|
|
67
|
-
const reg = getRunJSSafeGlobalsRegistry();
|
|
68
|
-
for (const k of keys) {
|
|
69
|
-
if (typeof k !== "string") continue;
|
|
70
|
-
const key = k.trim();
|
|
71
|
-
if (!key) continue;
|
|
72
|
-
reg.documentAllow.add(key);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
__name(registerRunJSSafeDocumentGlobals, "registerRunJSSafeDocumentGlobals");
|
|
76
|
-
function __resetRunJSSafeGlobalsRegistryForTests() {
|
|
77
|
-
var _a, _b, _c, _d;
|
|
78
|
-
const g = globalThis;
|
|
79
|
-
if (g.__nocobaseRunJSSafeGlobalsRegistry) {
|
|
80
|
-
try {
|
|
81
|
-
(_b = (_a = g.__nocobaseRunJSSafeGlobalsRegistry.windowAllow) == null ? void 0 : _a.clear) == null ? void 0 : _b.call(_a);
|
|
82
|
-
(_d = (_c = g.__nocobaseRunJSSafeGlobalsRegistry.documentAllow) == null ? void 0 : _c.clear) == null ? void 0 : _d.call(_c);
|
|
83
|
-
} catch {
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
__name(__resetRunJSSafeGlobalsRegistryForTests, "__resetRunJSSafeGlobalsRegistryForTests");
|
|
88
|
-
function isAllowedDynamicWindowKey(key) {
|
|
89
|
-
return getRunJSSafeGlobalsRegistry().windowAllow.has(key);
|
|
90
|
-
}
|
|
91
|
-
__name(isAllowedDynamicWindowKey, "isAllowedDynamicWindowKey");
|
|
92
|
-
function isAllowedDynamicDocumentKey(key) {
|
|
93
|
-
return getRunJSSafeGlobalsRegistry().documentAllow.has(key);
|
|
94
|
-
}
|
|
95
|
-
__name(isAllowedDynamicDocumentKey, "isAllowedDynamicDocumentKey");
|
|
96
|
-
function createSafeWindow(extra) {
|
|
97
|
-
const getSafeBaseHref = /* @__PURE__ */ __name(() => `${window.location.origin}${window.location.pathname}`, "getSafeBaseHref");
|
|
98
|
-
const safeOpen = /* @__PURE__ */ __name((url, target2, features) => {
|
|
99
|
-
const isSafeUrl = /* @__PURE__ */ __name((u) => {
|
|
100
|
-
try {
|
|
101
|
-
const parsed = new URL(u, getSafeBaseHref());
|
|
102
|
-
const protocol = parsed.protocol.toLowerCase();
|
|
103
|
-
if (protocol === "about:") return parsed.href === "about:blank";
|
|
104
|
-
return protocol === "http:" || protocol === "https:";
|
|
105
|
-
} catch {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}, "isSafeUrl");
|
|
109
|
-
if (!isSafeUrl(url)) {
|
|
110
|
-
throw new Error("Unsafe URL: window.open only allows http/https/about:blank.");
|
|
111
|
-
}
|
|
112
|
-
const sanitizedTarget = "_blank";
|
|
113
|
-
const enforceFeatures = /* @__PURE__ */ __name((f) => {
|
|
114
|
-
const set = /* @__PURE__ */ new Set();
|
|
115
|
-
if (f) {
|
|
116
|
-
f.split(",").map((s) => s.trim()).filter(Boolean).forEach((part) => {
|
|
117
|
-
const key = part.split("=")[0].trim().toLowerCase();
|
|
118
|
-
if (key !== "noopener" && key !== "noreferrer") set.add(part);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
set.add("noopener");
|
|
122
|
-
set.add("noreferrer");
|
|
123
|
-
return Array.from(set).join(",");
|
|
124
|
-
}, "enforceFeatures");
|
|
125
|
-
const sanitizedFeatures = enforceFeatures(features);
|
|
126
|
-
const newWin = window.open.call(window, url, sanitizedTarget, sanitizedFeatures);
|
|
127
|
-
if (newWin && "opener" in newWin) {
|
|
128
|
-
try {
|
|
129
|
-
newWin.opener = null;
|
|
130
|
-
} catch {
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return newWin;
|
|
134
|
-
}, "safeOpen");
|
|
135
|
-
const guardedNavigate = /* @__PURE__ */ __name((rawUrl, opts) => {
|
|
136
|
-
const parsed = new URL(rawUrl, getSafeBaseHref());
|
|
137
|
-
const protocol = parsed.protocol.toLowerCase();
|
|
138
|
-
const isAboutBlank = protocol === "about:" && parsed.href === "about:blank";
|
|
139
|
-
const isHttp = protocol === "http:" || protocol === "https:";
|
|
140
|
-
if (!isHttp && !isAboutBlank) {
|
|
141
|
-
throw new Error("Unsafe URL: only http/https/about:blank are allowed.");
|
|
142
|
-
}
|
|
143
|
-
if (isAboutBlank) {
|
|
144
|
-
return (opts == null ? void 0 : opts.replace) ? window.location.replace("about:blank") : window.location.assign("about:blank");
|
|
145
|
-
}
|
|
146
|
-
const sameOrigin = parsed.protocol === window.location.protocol && parsed.hostname === window.location.hostname && parsed.port === window.location.port;
|
|
147
|
-
if (sameOrigin) {
|
|
148
|
-
return (opts == null ? void 0 : opts.replace) ? window.location.replace(parsed.href) : window.location.assign(parsed.href);
|
|
149
|
-
}
|
|
150
|
-
const win = safeOpen(parsed.href);
|
|
151
|
-
if (!win) throw new Error("Popup blocked: cross-origin navigation is opened in a new tab.");
|
|
152
|
-
}, "guardedNavigate");
|
|
153
|
-
const safeLocation = new Proxy(
|
|
154
|
-
{},
|
|
155
|
-
{
|
|
156
|
-
get(_t, prop) {
|
|
157
|
-
switch (prop) {
|
|
158
|
-
case "origin":
|
|
159
|
-
return window.location.origin;
|
|
160
|
-
case "protocol":
|
|
161
|
-
return window.location.protocol;
|
|
162
|
-
case "host":
|
|
163
|
-
return window.location.host;
|
|
164
|
-
case "hostname":
|
|
165
|
-
return window.location.hostname;
|
|
166
|
-
case "port":
|
|
167
|
-
return window.location.port;
|
|
168
|
-
case "pathname":
|
|
169
|
-
return window.location.pathname;
|
|
170
|
-
case "assign":
|
|
171
|
-
return (u) => guardedNavigate(u, { replace: false });
|
|
172
|
-
case "replace":
|
|
173
|
-
return (u) => guardedNavigate(u, { replace: true });
|
|
174
|
-
case "reload":
|
|
175
|
-
return window.location.reload.bind(window.location);
|
|
176
|
-
case "href":
|
|
177
|
-
throw new Error("Reading location.href is not allowed.");
|
|
178
|
-
default:
|
|
179
|
-
throw new Error(`Access to location property "${prop}" is not allowed.`);
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
set(_t, prop, value) {
|
|
183
|
-
if (prop === "href") {
|
|
184
|
-
guardedNavigate(String(value), { replace: false });
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
throw new Error("Mutation on location is not allowed.");
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
);
|
|
191
|
-
const allowedGlobals = {
|
|
192
|
-
// 需绑定到原始 window,避免严格模式下触发 Illegal invocation
|
|
193
|
-
setTimeout: window.setTimeout.bind(window),
|
|
194
|
-
clearTimeout: window.clearTimeout.bind(window),
|
|
195
|
-
setInterval: window.setInterval.bind(window),
|
|
196
|
-
clearInterval: window.clearInterval.bind(window),
|
|
197
|
-
console,
|
|
198
|
-
Math,
|
|
199
|
-
Date,
|
|
200
|
-
FormData,
|
|
201
|
-
...typeof Blob !== "undefined" ? { Blob } : {},
|
|
202
|
-
...typeof URL !== "undefined" ? { URL } : {},
|
|
203
|
-
// 事件侦听仅绑定到真实 window,便于少量需要的全局监听
|
|
204
|
-
addEventListener: addEventListener.bind(window),
|
|
205
|
-
// 安全的 window.open 代理
|
|
206
|
-
open: safeOpen,
|
|
207
|
-
// 安全的 location 代理
|
|
208
|
-
location: safeLocation,
|
|
209
|
-
...extra || {}
|
|
210
|
-
};
|
|
211
|
-
const target = /* @__PURE__ */ Object.create(null);
|
|
212
|
-
return new Proxy(target, {
|
|
213
|
-
get(t, prop) {
|
|
214
|
-
if (typeof prop !== "string") {
|
|
215
|
-
return Reflect.get(t, prop);
|
|
216
|
-
}
|
|
217
|
-
if (prop in allowedGlobals) return allowedGlobals[prop];
|
|
218
|
-
if (Object.prototype.hasOwnProperty.call(t, prop)) return t[prop];
|
|
219
|
-
if (isAllowedDynamicWindowKey(prop)) {
|
|
220
|
-
const v = window[prop];
|
|
221
|
-
if (typeof v === "function") return v.bind(window);
|
|
222
|
-
return v;
|
|
223
|
-
}
|
|
224
|
-
throw new Error(`Access to global property "${prop}" is not allowed.`);
|
|
225
|
-
},
|
|
226
|
-
set(t, prop, value) {
|
|
227
|
-
if (typeof prop !== "string") {
|
|
228
|
-
Reflect.set(t, prop, value);
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
231
|
-
if (prop in allowedGlobals) {
|
|
232
|
-
throw new Error(`Mutation of global property "${prop}" is not allowed.`);
|
|
233
|
-
}
|
|
234
|
-
t[prop] = value;
|
|
235
|
-
return true;
|
|
236
|
-
},
|
|
237
|
-
has(t, prop) {
|
|
238
|
-
if (typeof prop !== "string") return Reflect.has(t, prop);
|
|
239
|
-
if (prop in allowedGlobals) return true;
|
|
240
|
-
if (Object.prototype.hasOwnProperty.call(t, prop)) return true;
|
|
241
|
-
if (isAllowedDynamicWindowKey(prop)) return true;
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
__name(createSafeWindow, "createSafeWindow");
|
|
247
|
-
function createSafeDocument(extra) {
|
|
248
|
-
const allowed = {
|
|
249
|
-
createElement: document.createElement.bind(document),
|
|
250
|
-
querySelector: document.querySelector.bind(document),
|
|
251
|
-
querySelectorAll: document.querySelectorAll.bind(document),
|
|
252
|
-
...extra || {}
|
|
253
|
-
};
|
|
254
|
-
const target = /* @__PURE__ */ Object.create(null);
|
|
255
|
-
return new Proxy(target, {
|
|
256
|
-
get(t, prop) {
|
|
257
|
-
if (typeof prop !== "string") {
|
|
258
|
-
return Reflect.get(t, prop);
|
|
259
|
-
}
|
|
260
|
-
if (prop in allowed) return allowed[prop];
|
|
261
|
-
if (Object.prototype.hasOwnProperty.call(t, prop)) return t[prop];
|
|
262
|
-
if (isAllowedDynamicDocumentKey(prop)) {
|
|
263
|
-
const v = document[prop];
|
|
264
|
-
if (typeof v === "function") return v.bind(document);
|
|
265
|
-
return v;
|
|
266
|
-
}
|
|
267
|
-
throw new Error(`Access to document property "${prop}" is not allowed.`);
|
|
268
|
-
},
|
|
269
|
-
set(t, prop, value) {
|
|
270
|
-
if (typeof prop !== "string") {
|
|
271
|
-
Reflect.set(t, prop, value);
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
274
|
-
if (prop in allowed) {
|
|
275
|
-
throw new Error(`Mutation of document property "${prop}" is not allowed.`);
|
|
276
|
-
}
|
|
277
|
-
t[prop] = value;
|
|
278
|
-
return true;
|
|
279
|
-
},
|
|
280
|
-
has(t, prop) {
|
|
281
|
-
if (typeof prop !== "string") return Reflect.has(t, prop);
|
|
282
|
-
if (prop in allowed) return true;
|
|
283
|
-
if (Object.prototype.hasOwnProperty.call(t, prop)) return true;
|
|
284
|
-
if (isAllowedDynamicDocumentKey(prop)) return true;
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
__name(createSafeDocument, "createSafeDocument");
|
|
290
|
-
function createSafeNavigator(extra) {
|
|
291
|
-
var _a;
|
|
292
|
-
const nav = typeof window !== "undefined" && window.navigator || void 0;
|
|
293
|
-
const clipboard = {};
|
|
294
|
-
const writeText = (_a = nav == null ? void 0 : nav.clipboard) == null ? void 0 : _a.writeText;
|
|
295
|
-
if (typeof writeText === "function") {
|
|
296
|
-
clipboard.writeText = writeText.bind(nav.clipboard);
|
|
297
|
-
}
|
|
298
|
-
const allowed = {
|
|
299
|
-
clipboard
|
|
300
|
-
};
|
|
301
|
-
Object.defineProperty(allowed, "onLine", {
|
|
302
|
-
get: /* @__PURE__ */ __name(() => !!(nav == null ? void 0 : nav.onLine), "get"),
|
|
303
|
-
enumerable: true,
|
|
304
|
-
configurable: false
|
|
305
|
-
});
|
|
306
|
-
Object.defineProperty(allowed, "language", {
|
|
307
|
-
get: /* @__PURE__ */ __name(() => nav == null ? void 0 : nav.language, "get"),
|
|
308
|
-
enumerable: true,
|
|
309
|
-
configurable: false
|
|
310
|
-
});
|
|
311
|
-
Object.defineProperty(allowed, "languages", {
|
|
312
|
-
get: /* @__PURE__ */ __name(() => (nav == null ? void 0 : nav.languages) ? [...nav.languages] : void 0, "get"),
|
|
313
|
-
enumerable: true,
|
|
314
|
-
configurable: false
|
|
315
|
-
});
|
|
316
|
-
Object.assign(allowed, extra || {});
|
|
317
|
-
return new Proxy(
|
|
318
|
-
{},
|
|
319
|
-
{
|
|
320
|
-
get(_t, prop) {
|
|
321
|
-
if (prop in allowed) return allowed[prop];
|
|
322
|
-
throw new Error(`Access to navigator property "${String(prop)}" is not allowed.`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
__name(createSafeNavigator, "createSafeNavigator");
|
|
328
|
-
function createSafeRunJSGlobals(extraGlobals) {
|
|
329
|
-
const globals = {};
|
|
330
|
-
try {
|
|
331
|
-
const navigator = createSafeNavigator();
|
|
332
|
-
globals.navigator = navigator;
|
|
333
|
-
try {
|
|
334
|
-
globals.window = createSafeWindow({ navigator });
|
|
335
|
-
} catch {
|
|
336
|
-
}
|
|
337
|
-
} catch {
|
|
338
|
-
}
|
|
339
|
-
try {
|
|
340
|
-
globals.document = createSafeDocument();
|
|
341
|
-
} catch {
|
|
342
|
-
}
|
|
343
|
-
return extraGlobals ? { ...globals, ...extraGlobals } : globals;
|
|
344
|
-
}
|
|
345
|
-
__name(createSafeRunJSGlobals, "createSafeRunJSGlobals");
|
|
346
|
-
async function runjsWithSafeGlobals(ctx, code, options, extraGlobals) {
|
|
347
|
-
if (!ctx || typeof ctx !== "object" && typeof ctx !== "function") return void 0;
|
|
348
|
-
const runjs = ctx.runjs;
|
|
349
|
-
if (typeof runjs !== "function") return void 0;
|
|
350
|
-
return ctx.runjs(
|
|
351
|
-
code,
|
|
352
|
-
createSafeRunJSGlobals(extraGlobals),
|
|
353
|
-
options
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
__name(runjsWithSafeGlobals, "runjsWithSafeGlobals");
|
|
357
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
358
|
-
0 && (module.exports = {
|
|
359
|
-
__resetRunJSSafeGlobalsRegistryForTests,
|
|
360
|
-
createSafeDocument,
|
|
361
|
-
createSafeNavigator,
|
|
362
|
-
createSafeRunJSGlobals,
|
|
363
|
-
createSafeWindow,
|
|
364
|
-
registerRunJSSafeDocumentGlobals,
|
|
365
|
-
registerRunJSSafeWindowGlobals,
|
|
366
|
-
runjsWithSafeGlobals
|
|
367
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
11
|
-
import { runjsRequireAsync } from '../runjsModuleLoader';
|
|
12
|
-
import { __resetRunJSSafeGlobalsRegistryForTests, createSafeWindow } from '../safeGlobals';
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
__resetRunJSSafeGlobalsRegistryForTests();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('runjsRequireAsync auto whitelist', () => {
|
|
19
|
-
it('should allow safeWindow to access globals introduced during requireAsync', async () => {
|
|
20
|
-
const key = '__nb_require_async_added_global__';
|
|
21
|
-
delete (window as any)[key];
|
|
22
|
-
|
|
23
|
-
const safeWin: any = createSafeWindow();
|
|
24
|
-
expect(() => safeWin[key]).toThrow(/not allowed/);
|
|
25
|
-
|
|
26
|
-
const requirejs: any = (deps: string[], onLoad: (...args: any[]) => void) => {
|
|
27
|
-
// Simulate a remote library attaching itself to the real window.
|
|
28
|
-
(window as any)[key] = { ok: true };
|
|
29
|
-
onLoad(undefined);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
await runjsRequireAsync(requirejs, 'https://example.com/fake-lib.js');
|
|
33
|
-
|
|
34
|
-
expect(safeWin[key]).toEqual({ ok: true });
|
|
35
|
-
|
|
36
|
-
delete (window as any)[key];
|
|
37
|
-
});
|
|
38
|
-
});
|