@ticatec/dyna-js 0.0.2

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_CN.md ADDED
@@ -0,0 +1,426 @@
1
+ # @ticatec/dyna-js
2
+
3
+ [![npm version](https://badge.fury.io/js/@ticatec%2Fdyna-js.svg)](https://badge.fury.io/js/@ticatec%2Fdyna-js)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
6
+ [![Node.js](https://img.shields.io/badge/Node.js-14%2B-green.svg)](https://nodejs.org/)
7
+
8
+ [English Documentation](README.md)
9
+
10
+ 一个使用 `new Function()` 进行安全动态代码执行的 TypeScript 库,同时支持 Node.js 和浏览器环境。
11
+
12
+ ## 功能特性
13
+
14
+ ✅ **通用兼容性** - 同时支持 Node.js 和浏览器环境
15
+ ✅ **TypeScript 支持** - 完整的类型安全和类型定义
16
+ ✅ **单例模式** - 一次初始化,全局使用
17
+ ✅ **模块导入** - 为动态代码预定义类和函数
18
+ ✅ **基于代理的沙盒** - 使用代理机制的安全变量访问控制
19
+ ✅ **可配置安全性** - 对允许的 API 和操作进行细粒度控制
20
+ ✅ **表单类创建** - 专门用于创建动态表单类的方法
21
+ ✅ **多种构建格式** - 支持 CommonJS 和 ESM
22
+ ✅ **性能监控** - 内置执行时间跟踪
23
+
24
+ ## 安装
25
+
26
+ ```bash
27
+ npm install @ticatec/dyna-js
28
+ ```
29
+
30
+ ## 快速开始
31
+
32
+ ### 1. 一次性初始化(应用启动时)
33
+
34
+ ```typescript
35
+ import { initializeDynaJs } from '@ticatec/dyna-js';
36
+
37
+ // 使用您的类和函数进行初始化
38
+ initializeDynaJs({
39
+ defaultImports: {
40
+ FlexiForm: FlexiFormClass,
41
+ FlexiCard: FlexiCardClass,
42
+ Dialog: DialogClass,
43
+ MessageBox: MessageBoxClass,
44
+ FlexiContext: FlexiContextClass,
45
+ ModuleLoader: ModuleLoaderClass
46
+ },
47
+ defaultInjectedKeys: ['Dialog', 'MessageBox', 'Indicator', 'Toast'],
48
+ useProxyByDefault: true,
49
+ allowBrowserAPIs: false, // 默认安全
50
+ validateCode: true
51
+ });
52
+ ```
53
+
54
+ ### 2. 在任意位置使用
55
+
56
+ ```typescript
57
+ import { getDynaJs } from '@ticatec/dyna-js';
58
+
59
+ // 获取已初始化的加载器
60
+ const loader = getDynaJs();
61
+
62
+ // 创建表单类
63
+ const MyFormClass = loader.createFormClass(`
64
+ class CustomForm extends FlexiForm {
65
+ constructor() {
66
+ super();
67
+ this.dialog = Dialog; // 自动注入
68
+ }
69
+
70
+ show() {
71
+ MessageBox.info('表单已就绪!'); // 自动注入
72
+ }
73
+
74
+ render() {
75
+ return new FlexiCard({
76
+ title: '动态表单',
77
+ content: '动态创建的内容!'
78
+ });
79
+ }
80
+ }
81
+ return CustomForm;
82
+ `);
83
+
84
+ // 实例化并使用
85
+ const form = new MyFormClass();
86
+ form.show();
87
+ ```
88
+
89
+ ## API 参考
90
+
91
+ ### 核心方法
92
+
93
+ #### `createFormClass<T>(code: string, context?: object, injectedKeys?: string[]): T`
94
+
95
+ 使用基于代理的沙盒执行创建表单类。
96
+
97
+ ```typescript
98
+ const FormClass = loader.createFormClass(`
99
+ class MyForm extends FlexiForm {
100
+ constructor() {
101
+ super();
102
+ this.setupDialog();
103
+ }
104
+
105
+ setupDialog() {
106
+ this.dialog = new Dialog({
107
+ title: '动态对话框'
108
+ });
109
+ }
110
+ }
111
+ return MyForm;
112
+ `, {
113
+ customData: '附加上下文'
114
+ }, ['extraKey']);
115
+ ```
116
+
117
+ #### `executeSync<T>(code: string, options?: ExecutionOptions): ExecutionResult<T>`
118
+
119
+ 同步执行代码并返回结果和计时信息。
120
+
121
+ ```typescript
122
+ const result = loader.executeSync(`
123
+ const form = new FlexiForm();
124
+ form.setTitle('动态表单');
125
+ return form;
126
+ `);
127
+
128
+ console.log(result.result); // FlexiForm 实例
129
+ console.log(result.executionTime); // 执行时间(毫秒)
130
+ ```
131
+
132
+ #### `execute<T>(code: string, options?: ExecutionOptions): Promise<ExecutionResult<T>>`
133
+
134
+ 异步执行代码,支持超时控制。
135
+
136
+ ```typescript
137
+ const result = await loader.execute(`
138
+ return new Promise(resolve => {
139
+ const form = new FlexiForm();
140
+ resolve(form);
141
+ });
142
+ `, {
143
+ timeout: 3000,
144
+ context: { customVar: 'value' }
145
+ });
146
+ ```
147
+
148
+ #### `executeWithImports<T>(code: string, imports: object, options?: ExecutionOptions): ExecutionResult<T>`
149
+
150
+ 使用额外的临时导入执行代码。
151
+
152
+ ```typescript
153
+ const result = loader.executeWithImports(`
154
+ return new CustomComponent({
155
+ message: '来自动态代码的问候!'
156
+ });
157
+ `, {
158
+ CustomComponent: MyCustomComponent,
159
+ utils: myUtilsLibrary
160
+ });
161
+ ```
162
+
163
+ ### 配置选项
164
+
165
+ ```typescript
166
+ interface DynaJsConfig {
167
+ defaultTimeout?: number; // 默认:5000ms
168
+ defaultStrict?: boolean; // 默认:true
169
+ allowedGlobals?: string[]; // 允许的全局变量白名单
170
+ blockedGlobals?: string[]; // 阻止的变量黑名单
171
+ defaultImports?: object; // 预导入的类/函数
172
+ defaultInjectedKeys?: string[]; // 自动注入的变量名
173
+ useProxyByDefault?: boolean; // 默认:true
174
+ allowTimers?: boolean; // 允许 setTimeout/setInterval(默认:false)
175
+ allowDynamicImports?: boolean; // 允许 import()/require()(默认:false)
176
+ validateCode?: boolean; // 启用代码验证(默认:true)
177
+ allowBrowserAPIs?: boolean; // 允许 window/document 访问(默认:false)
178
+ allowNodeAPIs?: boolean; // 允许 process/require 访问(默认:false)
179
+ }
180
+ ```
181
+
182
+ ## 安全配置
183
+
184
+ ### 🔒 **严格模式(推荐,默认)**
185
+
186
+ ```typescript
187
+ initializeDynaJs({
188
+ defaultImports: { FlexiForm, Dialog },
189
+ allowBrowserAPIs: false, // 阻止 window、document、localStorage
190
+ allowNodeAPIs: false, // 阻止 process、require
191
+ allowTimers: false, // 阻止 setTimeout/setInterval
192
+ validateCode: true // 启用代码模式验证
193
+ });
194
+
195
+ // ❌ 这些将被阻止:
196
+ // window.location.href = 'malicious-site.com'
197
+ // localStorage.clear()
198
+ // setTimeout(maliciousFunction, 1000)
199
+ ```
200
+
201
+ ### 🟡 **宽松模式**
202
+
203
+ ```typescript
204
+ initializeDynaJs({
205
+ defaultImports: { FlexiForm, Dialog },
206
+ allowBrowserAPIs: true, // ✅ 允许浏览器 API
207
+ allowTimers: true, // ✅ 允许定时器
208
+ allowDynamicImports: true, // ✅ 允许动态导入
209
+ validateCode: false // 禁用验证
210
+ });
211
+
212
+ // ✅ 现在允许:
213
+ // document.getElementById('myDiv')
214
+ // localStorage.getItem('data')
215
+ // setTimeout(() => {}, 1000)
216
+ ```
217
+
218
+ ### 🎯 **平衡模式(推荐用于表单创建)**
219
+
220
+ ```typescript
221
+ initializeDynaJs({
222
+ defaultImports: {
223
+ FlexiForm, Dialog, MessageBox,
224
+ // 提供安全的 DOM 访问
225
+ safeDOM: {
226
+ getElementById: (id) => document.getElementById(id),
227
+ createElement: (tag) => document.createElement(tag)
228
+ }
229
+ },
230
+ allowBrowserAPIs: false, // 仍然安全
231
+ allowTimers: false, // 表单不需要定时器
232
+ validateCode: true, // 保持验证
233
+ defaultInjectedKeys: ['window'] // 只注入 window 引用
234
+ });
235
+ ```
236
+
237
+ ## 高级示例
238
+
239
+ ### 创建动态表单组件
240
+
241
+ ```typescript
242
+ const DynamicFormBuilder = loader.createFormClass(`
243
+ class FormBuilder extends FlexiForm {
244
+ constructor(config) {
245
+ super();
246
+ this.config = config;
247
+ this.components = [];
248
+ }
249
+
250
+ addField(fieldConfig) {
251
+ const field = new FlexiCard({
252
+ title: fieldConfig.label,
253
+ content: this.createInput(fieldConfig.type)
254
+ });
255
+ this.components.push(field);
256
+ return this;
257
+ }
258
+
259
+ createInput(type) {
260
+ switch(type) {
261
+ case 'text':
262
+ return '<input type="text" />';
263
+ case 'number':
264
+ return '<input type="number" />';
265
+ default:
266
+ return '<input type="text" />';
267
+ }
268
+ }
269
+
270
+ build() {
271
+ return this.components;
272
+ }
273
+
274
+ show() {
275
+ const dialog = new Dialog({
276
+ title: this.config.title,
277
+ content: this.render()
278
+ });
279
+ dialog.show();
280
+ }
281
+
282
+ render() {
283
+ return this.components.map(c => c.render()).join('');
284
+ }
285
+ }
286
+
287
+ return FormBuilder;
288
+ `);
289
+
290
+ // 使用动态创建的表单构建器
291
+ const formBuilder = new DynamicFormBuilder({
292
+ title: '动态联系表单'
293
+ });
294
+
295
+ formBuilder
296
+ .addField({ label: '姓名', type: 'text' })
297
+ .addField({ label: '年龄', type: 'number' })
298
+ .show();
299
+ ```
300
+
301
+ ### 函数创建
302
+
303
+ ```typescript
304
+ const dynamicValidator = loader.createFunction(`
305
+ return function validateForm(formData) {
306
+ const errors = [];
307
+
308
+ if (!formData.name) {
309
+ errors.push('姓名是必填项');
310
+ }
311
+
312
+ if (!formData.email || !formData.email.includes('@')) {
313
+ errors.push('需要有效的邮箱地址');
314
+ }
315
+
316
+ return {
317
+ isValid: errors.length === 0,
318
+ errors: errors
319
+ };
320
+ };
321
+ `, []);
322
+
323
+ // 使用动态函数
324
+ const validation = dynamicValidator({
325
+ name: 'John',
326
+ email: 'john@example.com'
327
+ });
328
+ ```
329
+
330
+ ## 错误处理
331
+
332
+ ```typescript
333
+ try {
334
+ const result = loader.executeSync(`
335
+ // 一些可能失败的动态代码
336
+ return new NonExistentClass();
337
+ `);
338
+ } catch (error) {
339
+ console.error('执行失败:', error.message);
340
+ // 错误包含执行时间和详细的错误信息
341
+ }
342
+ ```
343
+
344
+ ## 浏览器支持
345
+
346
+ - Chrome 51+
347
+ - Firefox 40+
348
+ - Safari 10+
349
+ - Edge 14+
350
+ - Node.js 14+
351
+
352
+ ## TypeScript 支持
353
+
354
+ 具有完整类型定义的 TypeScript 支持:
355
+
356
+ ```typescript
357
+ import {
358
+ DynaJs,
359
+ ExecutionResult,
360
+ ExecutionOptions,
361
+ ModuleImports
362
+ } from '@ticatec/dyna-js';
363
+ ```
364
+
365
+ ## 贡献
366
+
367
+ 1. Fork 仓库
368
+ 2. 创建您的功能分支 (`git checkout -b feature/amazing-feature`)
369
+ 3. 提交您的更改 (`git commit -m 'Add some amazing feature'`)
370
+ 4. 推送到分支 (`git push origin feature/amazing-feature`)
371
+ 5. 打开一个 Pull Request
372
+
373
+ ## 许可证
374
+
375
+ MIT 许可证 - 详情请查看 [LICENSE](LICENSE) 文件。
376
+
377
+ ## 安全考虑
378
+
379
+ ⚠️ **重要安全注意事项:**
380
+
381
+ 1. **代码验证**:在生产环境中始终保持 `validateCode: true`
382
+ 2. **API 限制**:启用 `allowBrowserAPIs` 或 `allowNodeAPIs` 时要小心
383
+ 3. **输入清理**:验证来自外部源的所有动态代码输入
384
+ 4. **超时设置**:设置适当的超时以防止无限循环
385
+ 5. **最小权限原则**:只导入所需的最少函数和类
386
+
387
+ ## 支持
388
+
389
+ 如有问题和功能请求,请使用 [GitHub Issues](https://github.com/ticatec-auckland/common-web-library/issues) 页面。
390
+
391
+ ---
392
+
393
+ ## 使用场景示例
394
+
395
+ ### 动态表单创建
396
+ ```typescript
397
+ // 适合创建各种动态表单组件
398
+ const ContactForm = loader.createFormClass(`...`);
399
+ const SurveyForm = loader.createFormClass(`...`);
400
+ const RegistrationForm = loader.createFormClass(`...`);
401
+ ```
402
+
403
+ ### 业务规则执行
404
+ ```typescript
405
+ // 动态业务逻辑
406
+ const businessRule = loader.executeSync(`
407
+ return function(data) {
408
+ // 复杂的业务逻辑
409
+ return processBusinessRule(data);
410
+ };
411
+ `);
412
+ ```
413
+
414
+ ### 模板渲染
415
+ ```typescript
416
+ // 动态模板处理
417
+ const templateEngine = loader.createFormClass(`
418
+ class TemplateEngine {
419
+ render(template, data) {
420
+ // 模板渲染逻辑
421
+ return processTemplate(template, data);
422
+ }
423
+ }
424
+ return TemplateEngine;
425
+ `);
426
+ ```
@@ -0,0 +1,20 @@
1
+ import { ExecutionContext, ExecutionOptions, ExecutionResult, DynaJsConfig, ModuleImports } from './types';
2
+ export default class DynaJs {
3
+ private config;
4
+ constructor(config?: DynaJsConfig);
5
+ execute<T = any>(code: string, options?: ExecutionOptions): Promise<ExecutionResult<T>>;
6
+ executeSync<T = any>(code: string, options?: ExecutionOptions): ExecutionResult<T>;
7
+ private executeWithTimeout;
8
+ private executeCode;
9
+ createFunction<T extends (...args: any[]) => any>(code: string, paramNames?: string[], options?: ExecutionOptions): T;
10
+ executeWithImports<T = any>(code: string, imports: ModuleImports, options?: ExecutionOptions): ExecutionResult<T>;
11
+ executeWithImportsAsync<T = any>(code: string, imports: ModuleImports, options?: ExecutionOptions): Promise<ExecutionResult<T>>;
12
+ createFormClass<T = any>(code: string, context?: ExecutionContext, injectedKeys?: string[]): T;
13
+ private executeWithProxy;
14
+ static instance: DynaJs | null;
15
+ static initialize(config: DynaJsConfig): DynaJs;
16
+ static getInstance(): DynaJs;
17
+ static reset(): void;
18
+ private prepareContext;
19
+ }
20
+ //# sourceMappingURL=DynaJs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DynaJs.d.ts","sourceRoot":"","sources":["../src/DynaJs.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,aAAa,EACd,MAAM,SAAS,CAAC;AAGjB,MAAM,CAAC,OAAO,OAAO,MAAM;IACzB,OAAO,CAAC,MAAM,CAAyB;gBAE3B,MAAM,GAAE,YAAiB;IAiB/B,OAAO,CAAC,CAAC,GAAG,GAAG,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IA4B9B,WAAW,CAAC,CAAC,GAAG,GAAG,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,gBAAqB,GAC7B,eAAe,CAAC,CAAC,CAAC;YA4BP,kBAAkB;IAsBhC,OAAO,CAAC,WAAW;IAenB,cAAc,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAC9C,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,MAAM,EAAO,EACzB,OAAO,GAAE,gBAAqB,GAC7B,CAAC;IAuBJ,kBAAkB,CAAC,CAAC,GAAG,GAAG,EACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE,gBAAqB,GAC7B,eAAe,CAAC,CAAC,CAAC;IAOf,uBAAuB,CAAC,CAAC,GAAG,GAAG,EACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAO9B,eAAe,CAAC,CAAC,GAAG,GAAG,EACrB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,gBAAqB,EAC9B,YAAY,GAAE,MAAM,EAAO,GAC1B,CAAC;IAeJ,OAAO,CAAC,gBAAgB;IA2CxB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEtC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM;IAO/C,MAAM,CAAC,WAAW,IAAI,MAAM;IAO5B,MAAM,CAAC,KAAK,IAAI,IAAI;IAIpB,OAAO,CAAC,cAAc;CAwBvB"}
@@ -0,0 +1,213 @@
1
+ import { createSafeContext, validateCode } from './utils';
2
+ class DynaJs {
3
+ constructor(config = {}) {
4
+ this.config = {
5
+ defaultTimeout: config.defaultTimeout ?? 5000,
6
+ defaultStrict: config.defaultStrict ?? true,
7
+ allowedGlobals: config.allowedGlobals ?? [],
8
+ blockedGlobals: config.blockedGlobals ?? [],
9
+ defaultImports: config.defaultImports ?? {},
10
+ defaultInjectedKeys: config.defaultInjectedKeys ?? [],
11
+ useProxyByDefault: config.useProxyByDefault ?? true,
12
+ allowTimers: config.allowTimers ?? false,
13
+ allowDynamicImports: config.allowDynamicImports ?? false,
14
+ validateCode: config.validateCode ?? true,
15
+ allowBrowserAPIs: config.allowBrowserAPIs ?? false,
16
+ allowNodeAPIs: config.allowNodeAPIs ?? false
17
+ };
18
+ }
19
+ async execute(code, options = {}) {
20
+ const startTime = performance.now();
21
+ try {
22
+ if (this.config.validateCode) {
23
+ validateCode(code, {
24
+ allowTimers: this.config.allowTimers,
25
+ allowDynamicImports: this.config.allowDynamicImports
26
+ });
27
+ }
28
+ const context = this.prepareContext(options.context, options.imports);
29
+ const timeout = options.timeout ?? this.config.defaultTimeout;
30
+ const strict = options.strict ?? this.config.defaultStrict;
31
+ const result = await this.executeWithTimeout(code, context, timeout, strict);
32
+ const executionTime = performance.now() - startTime;
33
+ return {
34
+ result: result,
35
+ executionTime
36
+ };
37
+ }
38
+ catch (error) {
39
+ const executionTime = performance.now() - startTime;
40
+ throw new Error(`Script execution failed after ${executionTime.toFixed(2)}ms: ${error instanceof Error ? error.message : String(error)}`);
41
+ }
42
+ }
43
+ executeSync(code, options = {}) {
44
+ const startTime = performance.now();
45
+ try {
46
+ if (this.config.validateCode) {
47
+ validateCode(code, {
48
+ allowTimers: this.config.allowTimers,
49
+ allowDynamicImports: this.config.allowDynamicImports
50
+ });
51
+ }
52
+ const context = this.prepareContext(options.context, options.imports);
53
+ const strict = options.strict ?? this.config.defaultStrict;
54
+ const result = this.executeCode(code, context, strict);
55
+ const executionTime = performance.now() - startTime;
56
+ return {
57
+ result: result,
58
+ executionTime
59
+ };
60
+ }
61
+ catch (error) {
62
+ const executionTime = performance.now() - startTime;
63
+ throw new Error(`Script execution failed after ${executionTime.toFixed(2)}ms: ${error instanceof Error ? error.message : String(error)}`);
64
+ }
65
+ }
66
+ async executeWithTimeout(code, context, timeout, strict) {
67
+ return new Promise((resolve, reject) => {
68
+ const timer = setTimeout(() => {
69
+ reject(new Error(`Script execution timed out after ${timeout}ms`));
70
+ }, timeout);
71
+ try {
72
+ const result = this.executeCode(code, context, strict);
73
+ clearTimeout(timer);
74
+ resolve(result);
75
+ }
76
+ catch (error) {
77
+ clearTimeout(timer);
78
+ reject(error);
79
+ }
80
+ });
81
+ }
82
+ executeCode(code, context, strict) {
83
+ const contextKeys = Object.keys(context);
84
+ const contextValues = Object.values(context);
85
+ const strictMode = strict ? '"use strict";' : '';
86
+ const wrappedCode = `${strictMode}\nreturn (function() {\n${code}\n})();`;
87
+ try {
88
+ const func = new Function(...contextKeys, wrappedCode);
89
+ return func.apply(null, contextValues);
90
+ }
91
+ catch (error) {
92
+ throw new Error(`Code execution error: ${error instanceof Error ? error.message : String(error)}`);
93
+ }
94
+ }
95
+ createFunction(code, paramNames = [], options = {}) {
96
+ const context = this.prepareContext(options.context, options.imports);
97
+ const strict = options.strict ?? this.config.defaultStrict;
98
+ const strictMode = strict ? '"use strict";' : '';
99
+ const wrappedCode = `${strictMode}\n${code}`;
100
+ try {
101
+ const contextKeys = Object.keys(context);
102
+ const contextValues = Object.values(context);
103
+ const allParams = [...contextKeys, ...paramNames];
104
+ const func = new Function(...allParams, wrappedCode);
105
+ return ((...args) => {
106
+ const allArgs = [...contextValues, ...args];
107
+ return func.apply(null, allArgs);
108
+ });
109
+ }
110
+ catch (error) {
111
+ throw new Error(`Function creation error: ${error instanceof Error ? error.message : String(error)}`);
112
+ }
113
+ }
114
+ executeWithImports(code, imports, options = {}) {
115
+ return this.executeSync(code, {
116
+ ...options,
117
+ imports: { ...this.config.defaultImports, ...imports }
118
+ });
119
+ }
120
+ async executeWithImportsAsync(code, imports, options = {}) {
121
+ return this.execute(code, {
122
+ ...options,
123
+ imports: { ...this.config.defaultImports, ...imports }
124
+ });
125
+ }
126
+ createFormClass(code, context = {}, injectedKeys = []) {
127
+ const useProxy = this.config.useProxyByDefault;
128
+ const mergedContext = { ...context, ...this.config.defaultImports };
129
+ const mergedKeys = [...this.config.defaultInjectedKeys, ...injectedKeys];
130
+ if (useProxy) {
131
+ return this.executeWithProxy(code, mergedContext, mergedKeys);
132
+ }
133
+ else {
134
+ return this.executeSync(code, {
135
+ context: mergedContext,
136
+ imports: this.config.defaultImports
137
+ }).result;
138
+ }
139
+ }
140
+ executeWithProxy(code, context, injectedKeys) {
141
+ const injected = {};
142
+ for (const key of injectedKeys) {
143
+ if (context.window && key in context.window) {
144
+ injected[key] = context.window[key];
145
+ }
146
+ else if (key in context) {
147
+ injected[key] = context[key];
148
+ }
149
+ }
150
+ const sandboxContext = { ...context, ...injected };
151
+ const sandbox = new Proxy(sandboxContext, {
152
+ has(target, key) {
153
+ if (key in target)
154
+ return true;
155
+ if (context.window && key in context.window)
156
+ return true;
157
+ return false;
158
+ },
159
+ get(target, key) {
160
+ if (key in target)
161
+ return target[key];
162
+ if (context.window && key in context.window)
163
+ return context.window[key];
164
+ console.warn(`DynaJs: ${String(key)} not found`);
165
+ return undefined;
166
+ }
167
+ });
168
+ const wrappedCode = `
169
+ with (sandbox) {
170
+ ${Object.keys(sandbox).map(k => `const ${k} = sandbox.${k};`).join('\n')}
171
+
172
+ ${code}
173
+ }
174
+ `;
175
+ const fn = new Function('sandbox', wrappedCode);
176
+ return fn(sandbox);
177
+ }
178
+ static initialize(config) {
179
+ if (DynaJs.instance == null) {
180
+ DynaJs.instance = new DynaJs(config);
181
+ }
182
+ return DynaJs.instance;
183
+ }
184
+ static getInstance() {
185
+ if (DynaJs.instance == null) {
186
+ throw new Error("DynaJs hasn't been initialized. Call DynaJs.initialize() first.");
187
+ }
188
+ return DynaJs.instance;
189
+ }
190
+ static reset() {
191
+ DynaJs.instance = null;
192
+ }
193
+ prepareContext(userContext = {}, imports = {}) {
194
+ let context = createSafeContext(userContext, {
195
+ allowBrowserAPIs: this.config.allowBrowserAPIs,
196
+ allowNodeAPIs: this.config.allowNodeAPIs,
197
+ blockedKeys: this.config.blockedGlobals
198
+ });
199
+ context = { ...context, ...this.config.defaultImports, ...imports };
200
+ if (this.config.allowedGlobals.length > 0) {
201
+ const allowedContext = {};
202
+ for (const key of this.config.allowedGlobals) {
203
+ if (key in context) {
204
+ allowedContext[key] = context[key];
205
+ }
206
+ }
207
+ context = allowedContext;
208
+ }
209
+ return context;
210
+ }
211
+ }
212
+ DynaJs.instance = null;
213
+ export default DynaJs;