@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/LICENSE +21 -0
- package/README.md +389 -0
- package/README_CN.md +426 -0
- package/dist/DynaJs.d.ts +20 -0
- package/dist/DynaJs.d.ts.map +1 -0
- package/dist/DynaJs.esm.js +213 -0
- package/dist/DynaJs.js +213 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +14 -0
- package/dist/index.js +37 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.esm.js +1 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.esm.js +61 -0
- package/dist/utils.js +67 -0
- package/package.json +58 -0
package/README_CN.md
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# @ticatec/dyna-js
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/@ticatec%2Fdyna-js)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](http://www.typescriptlang.org/)
|
|
6
|
+
[](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
|
+
```
|
package/dist/DynaJs.d.ts
ADDED
|
@@ -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;
|