mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 12:23:03 +00:00
Implement fetch polyfill and Promise for ES5 backend
Co-authored-by: qaiu <29825328+qaiu@users.noreply.github.com>
This commit is contained in:
451
parser/doc/TYPESCRIPT_FETCH_GUIDE.md
Normal file
451
parser/doc/TYPESCRIPT_FETCH_GUIDE.md
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
# 浏览器TypeScript编译和Fetch API支持指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本项目实现了**纯前端TypeScript编译 + 后端ES5引擎 + Fetch API适配**的完整方案,允许用户在浏览器中编写TypeScript/ES6+代码,编译为ES5后在后端JavaScript引擎中执行。
|
||||||
|
|
||||||
|
## 架构设计
|
||||||
|
|
||||||
|
### 1. 浏览器端(前端编译)
|
||||||
|
|
||||||
|
```
|
||||||
|
用户编写TS/ES6+代码
|
||||||
|
↓
|
||||||
|
TypeScript.js (浏览器内编译)
|
||||||
|
↓
|
||||||
|
ES5 JavaScript代码
|
||||||
|
↓
|
||||||
|
发送到后端执行
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 后端(ES5执行环境)
|
||||||
|
|
||||||
|
```
|
||||||
|
接收ES5代码
|
||||||
|
↓
|
||||||
|
注入fetch polyfill + Promise
|
||||||
|
↓
|
||||||
|
注入JavaFetch桥接对象
|
||||||
|
↓
|
||||||
|
Nashorn引擎执行ES5代码
|
||||||
|
↓
|
||||||
|
fetch() 调用 → JavaFetch → JsHttpClient → Vert.x HTTP Client
|
||||||
|
```
|
||||||
|
|
||||||
|
## 已实现的功能
|
||||||
|
|
||||||
|
### ✅ 后端支持
|
||||||
|
|
||||||
|
1. **Promise Polyfill** (`fetch-runtime.js`)
|
||||||
|
- 完整的Promise/A+实现
|
||||||
|
- 支持 `then`、`catch`、`finally`
|
||||||
|
- 支持 `Promise.all`、`Promise.race`
|
||||||
|
- 支持 `Promise.resolve`、`Promise.reject`
|
||||||
|
|
||||||
|
2. **Fetch API Polyfill** (`fetch-runtime.js`)
|
||||||
|
- 标准fetch接口实现
|
||||||
|
- 支持所有HTTP方法(GET、POST、PUT、DELETE、PATCH)
|
||||||
|
- 支持headers、body等选项
|
||||||
|
- Response对象支持:
|
||||||
|
- `text()` - 获取文本响应
|
||||||
|
- `json()` - 解析JSON响应
|
||||||
|
- `arrayBuffer()` - 获取字节数组
|
||||||
|
- `status` - HTTP状态码
|
||||||
|
- `ok` - 请求成功标志
|
||||||
|
- `headers` - 响应头访问
|
||||||
|
|
||||||
|
3. **Java桥接** (`JsFetchBridge.java`)
|
||||||
|
- 将fetch调用转换为JsHttpClient调用
|
||||||
|
- 自动处理请求头、请求体
|
||||||
|
- 支持代理配置
|
||||||
|
- 安全的SSRF防护
|
||||||
|
|
||||||
|
4. **自动注入** (`JsParserExecutor.java` & `JsPlaygroundExecutor.java`)
|
||||||
|
- 在JavaScript引擎初始化时自动注入fetch runtime
|
||||||
|
- 提供`JavaFetch`全局对象
|
||||||
|
- 与现有http对象共存
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### ES5风格(当前支持)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function parse(shareLinkInfo, http, logger) {
|
||||||
|
// 使用fetch API
|
||||||
|
fetch("https://api.example.com/data")
|
||||||
|
.then(function(response) {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
logger.info("数据: " + JSON.stringify(data));
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
logger.error("错误: " + error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 或者使用传统的http对象
|
||||||
|
var response = http.get("https://api.example.com/data");
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### TypeScript风格(需要前端编译)
|
||||||
|
|
||||||
|
用户在浏览器中编写:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async function parse(shareLinkInfo: ShareLinkInfo, http: JsHttpClient, logger: JsLogger): Promise<string> {
|
||||||
|
try {
|
||||||
|
// 使用标准fetch API
|
||||||
|
const response = await fetch("https://api.example.com/data");
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
logger.info(`获取到数据: ${data.downloadUrl}`);
|
||||||
|
return data.downloadUrl;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`解析失败: ${error.message}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
浏览器内编译后的ES5代码(简化示例):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function parse(shareLinkInfo, http, logger) {
|
||||||
|
return __awaiter(this, void 0, void 0, function() {
|
||||||
|
var response, data;
|
||||||
|
return __generator(this, function(_a) {
|
||||||
|
switch(_a.label) {
|
||||||
|
case 0:
|
||||||
|
return [4, fetch("https://api.example.com/data")];
|
||||||
|
case 1:
|
||||||
|
response = _a.sent();
|
||||||
|
return [4, response.json()];
|
||||||
|
case 2:
|
||||||
|
data = _a.sent();
|
||||||
|
logger.info("获取到数据: " + data.downloadUrl);
|
||||||
|
return [2, data.downloadUrl];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前端TypeScript编译(待实现)
|
||||||
|
|
||||||
|
### 计划实现步骤
|
||||||
|
|
||||||
|
#### 1. 添加TypeScript编译器
|
||||||
|
|
||||||
|
在前端项目中添加`typescript.js`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 下载TypeScript编译器浏览器版本
|
||||||
|
cd webroot/static
|
||||||
|
wget https://cdn.jsdelivr.net/npm/typescript@latest/lib/typescript.js
|
||||||
|
```
|
||||||
|
|
||||||
|
或者在Vue项目中:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install typescript
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 创建编译工具类
|
||||||
|
|
||||||
|
`web-front/src/utils/tsCompiler.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
export function compileToES5(sourceCode, fileName = 'script.ts') {
|
||||||
|
const result = ts.transpileModule(sourceCode, {
|
||||||
|
compilerOptions: {
|
||||||
|
target: ts.ScriptTarget.ES5,
|
||||||
|
module: ts.ModuleKind.None,
|
||||||
|
lib: ['es5', 'dom'],
|
||||||
|
experimentalDecorators: false,
|
||||||
|
emitDecoratorMetadata: false,
|
||||||
|
downlevelIteration: true
|
||||||
|
},
|
||||||
|
fileName: fileName
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
js: result.outputText,
|
||||||
|
diagnostics: result.diagnostics,
|
||||||
|
sourceMap: result.sourceMapText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 更新Playground组件
|
||||||
|
|
||||||
|
在`Playground.vue`中添加编译选项:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 语言选择 -->
|
||||||
|
<el-radio-group v-model="language">
|
||||||
|
<el-radio label="javascript">JavaScript (ES5)</el-radio>
|
||||||
|
<el-radio label="typescript">TypeScript/ES6+</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
|
||||||
|
<!-- 编辑器 -->
|
||||||
|
<monaco-editor
|
||||||
|
v-model="code"
|
||||||
|
:language="language"
|
||||||
|
@save="handleSave"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 运行按钮 -->
|
||||||
|
<el-button @click="executeCode">运行</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { compileToES5 } from '@/utils/tsCompiler';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
language: 'javascript',
|
||||||
|
code: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async executeCode() {
|
||||||
|
let codeToExecute = this.code;
|
||||||
|
|
||||||
|
// 如果是TypeScript,先编译
|
||||||
|
if (this.language === 'typescript') {
|
||||||
|
const result = compileToES5(this.code);
|
||||||
|
|
||||||
|
if (result.diagnostics && result.diagnostics.length > 0) {
|
||||||
|
this.$message.error('TypeScript编译错误');
|
||||||
|
console.error(result.diagnostics);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
codeToExecute = result.js;
|
||||||
|
console.log('编译后的ES5代码:', codeToExecute);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送到后端执行
|
||||||
|
const response = await playgroundApi.testScript(
|
||||||
|
codeToExecute,
|
||||||
|
this.shareUrl,
|
||||||
|
this.pwd,
|
||||||
|
this.method
|
||||||
|
);
|
||||||
|
|
||||||
|
this.showResult(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fetch Runtime详解
|
||||||
|
|
||||||
|
### Promise实现特性
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 基本用法
|
||||||
|
var promise = new SimplePromise(function(resolve, reject) {
|
||||||
|
setTimeout(function() {
|
||||||
|
resolve("成功");
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.then(function(value) {
|
||||||
|
console.log(value); // "成功"
|
||||||
|
});
|
||||||
|
|
||||||
|
// 链式调用
|
||||||
|
promise
|
||||||
|
.then(function(value) {
|
||||||
|
return value + " - 第一步";
|
||||||
|
})
|
||||||
|
.then(function(value) {
|
||||||
|
return value + " - 第二步";
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.error(error);
|
||||||
|
})
|
||||||
|
.finally(function() {
|
||||||
|
console.log("完成");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fetch API特性
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// GET请求
|
||||||
|
fetch("https://api.example.com/data")
|
||||||
|
.then(function(response) {
|
||||||
|
console.log("状态码:", response.status);
|
||||||
|
console.log("成功:", response.ok);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
console.log("数据:", data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST请求
|
||||||
|
fetch("https://api.example.com/submit", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ key: "value" })
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
console.log("响应:", data);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 兼容性说明
|
||||||
|
|
||||||
|
### 支持的特性
|
||||||
|
|
||||||
|
- ✅ Promise/A+ 完整实现
|
||||||
|
- ✅ Fetch API 标准接口
|
||||||
|
- ✅ async/await(编译后)
|
||||||
|
- ✅ 所有HTTP方法(GET、POST、PUT、DELETE、PATCH)
|
||||||
|
- ✅ Request headers配置
|
||||||
|
- ✅ Request body(string、JSON、FormData)
|
||||||
|
- ✅ Response.text()、Response.json()
|
||||||
|
- ✅ 与现有http对象共存
|
||||||
|
|
||||||
|
### 不支持的特性
|
||||||
|
|
||||||
|
- ❌ Blob对象(返回字节数组替代)
|
||||||
|
- ❌ FormData对象(使用简单对象替代)
|
||||||
|
- ❌ Request/Response对象构造函数
|
||||||
|
- ❌ Streams API
|
||||||
|
- ❌ Service Worker相关API
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
### 1. 创建测试解析器
|
||||||
|
|
||||||
|
参考 `parser/src/main/resources/custom-parsers/fetch-demo.js`
|
||||||
|
|
||||||
|
### 2. 测试步骤
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 编译项目
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
|
# 2. 运行服务
|
||||||
|
java -jar web-service/target/netdisk-fast-download.jar
|
||||||
|
|
||||||
|
# 3. 访问演练场
|
||||||
|
浏览器打开: http://localhost:6401/playground
|
||||||
|
|
||||||
|
# 4. 加载fetch-demo.js并测试
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 验证fetch功能
|
||||||
|
|
||||||
|
在演练场中运行:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function parse(shareLinkInfo, http, logger) {
|
||||||
|
logger.info("测试fetch API");
|
||||||
|
|
||||||
|
var result = null;
|
||||||
|
fetch("https://httpbin.org/get")
|
||||||
|
.then(function(response) {
|
||||||
|
logger.info("状态码: " + response.status);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
logger.info("响应: " + JSON.stringify(data));
|
||||||
|
result = "SUCCESS";
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
logger.error("错误: " + error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待完成
|
||||||
|
var timeout = 5000;
|
||||||
|
var start = Date.now();
|
||||||
|
while (result === null && (Date.now() - start) < timeout) {
|
||||||
|
java.lang.Thread.sleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result || "https://example.com/download";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安全性
|
||||||
|
|
||||||
|
### SSRF防护
|
||||||
|
|
||||||
|
JsHttpClient已实现SSRF防护:
|
||||||
|
- 拦截内网IP访问(127.0.0.1、10.x.x.x、192.168.x.x等)
|
||||||
|
- 拦截云服务元数据API(169.254.169.254等)
|
||||||
|
- DNS解析检查
|
||||||
|
|
||||||
|
### 沙箱隔离
|
||||||
|
|
||||||
|
- JavaScript引擎使用SecurityClassFilter
|
||||||
|
- 禁用Java对象访问
|
||||||
|
- 限制文件系统访问
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
|
||||||
|
1. **Fetch runtime缓存**
|
||||||
|
- 首次加载后缓存在静态变量中
|
||||||
|
- 避免重复读取资源文件
|
||||||
|
|
||||||
|
2. **Promise异步执行**
|
||||||
|
- 使用setTimeout(0)实现异步
|
||||||
|
- 避免阻塞主线程
|
||||||
|
|
||||||
|
3. **工作线程池**
|
||||||
|
- JsParserExecutor使用Vert.x工作线程池
|
||||||
|
- JsPlaygroundExecutor使用独立线程池
|
||||||
|
|
||||||
|
## 相关文件
|
||||||
|
|
||||||
|
### 后端代码
|
||||||
|
- `parser/src/main/resources/fetch-runtime.js` - Fetch和Promise polyfill
|
||||||
|
- `parser/src/main/java/cn/qaiu/parser/customjs/JsFetchBridge.java` - Java桥接层
|
||||||
|
- `parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java` - 解析器执行器
|
||||||
|
- `parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java` - 演练场执行器
|
||||||
|
|
||||||
|
### 示例代码
|
||||||
|
- `parser/src/main/resources/custom-parsers/fetch-demo.js` - Fetch API演示
|
||||||
|
|
||||||
|
### 前端代码(待实现)
|
||||||
|
- `web-front/src/utils/tsCompiler.js` - TypeScript编译工具
|
||||||
|
- `web-front/src/views/Playground.vue` - 演练场界面
|
||||||
|
|
||||||
|
## 下一步计划
|
||||||
|
|
||||||
|
1. ✅ 实现后端fetch polyfill
|
||||||
|
2. ✅ 实现Promise polyfill
|
||||||
|
3. ✅ 集成到JsParserExecutor
|
||||||
|
4. ⏳ 前端添加TypeScript编译器
|
||||||
|
5. ⏳ 更新Playground UI支持TS/ES6+
|
||||||
|
6. ⏳ 添加Monaco编辑器类型提示
|
||||||
|
7. ⏳ 编写更多示例和文档
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
通过这个方案,我们实现了:
|
||||||
|
1. **无需Node环境** - 纯浏览器编译 + Java后端执行
|
||||||
|
2. **标准API** - 使用标准fetch和Promise API
|
||||||
|
3. **向后兼容** - 现有http对象仍然可用
|
||||||
|
4. **安全可靠** - SSRF防护和沙箱隔离
|
||||||
|
5. **易于使用** - 简单的API,无需学习成本
|
||||||
|
|
||||||
|
用户可以在浏览器中用现代JavaScript/TypeScript编写代码,自动编译为ES5后在后端安全执行,同时享受fetch API的便利性。
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package cn.qaiu.parser.customjs;
|
||||||
|
|
||||||
|
import cn.qaiu.parser.customjs.JsHttpClient.JsHttpResponse;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaScript Fetch API桥接类
|
||||||
|
* 将标准的fetch API调用桥接到现有的JsHttpClient实现
|
||||||
|
*
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
* Create at 2025/12/06
|
||||||
|
*/
|
||||||
|
public class JsFetchBridge {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(JsFetchBridge.class);
|
||||||
|
|
||||||
|
private final JsHttpClient httpClient;
|
||||||
|
|
||||||
|
public JsFetchBridge(JsHttpClient httpClient) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch API实现
|
||||||
|
* 接收fetch API调用并转换为JsHttpClient调用
|
||||||
|
*
|
||||||
|
* @param url 请求URL
|
||||||
|
* @param options 请求选项(包含method、headers、body等)
|
||||||
|
* @return JsHttpResponse响应对象
|
||||||
|
*/
|
||||||
|
public JsHttpResponse fetch(String url, Map<String, Object> options) {
|
||||||
|
try {
|
||||||
|
// 解析请求方法
|
||||||
|
String method = "GET";
|
||||||
|
if (options != null && options.containsKey("method")) {
|
||||||
|
method = options.get("method").toString().toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析并设置请求头
|
||||||
|
if (options != null && options.containsKey("headers")) {
|
||||||
|
Object headersObj = options.get("headers");
|
||||||
|
if (headersObj instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> headersMap = (Map<String, Object>) headersObj;
|
||||||
|
for (Map.Entry<String, Object> entry : headersMap.entrySet()) {
|
||||||
|
if (entry.getValue() != null) {
|
||||||
|
httpClient.putHeader(entry.getKey(), entry.getValue().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析请求体
|
||||||
|
Object body = null;
|
||||||
|
if (options != null && options.containsKey("body")) {
|
||||||
|
body = options.get("body");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据方法执行请求
|
||||||
|
JsHttpResponse response;
|
||||||
|
switch (method) {
|
||||||
|
case "GET":
|
||||||
|
response = httpClient.get(url);
|
||||||
|
break;
|
||||||
|
case "POST":
|
||||||
|
response = httpClient.post(url, body);
|
||||||
|
break;
|
||||||
|
case "PUT":
|
||||||
|
response = httpClient.put(url, body);
|
||||||
|
break;
|
||||||
|
case "DELETE":
|
||||||
|
response = httpClient.delete(url);
|
||||||
|
break;
|
||||||
|
case "PATCH":
|
||||||
|
response = httpClient.patch(url, body);
|
||||||
|
break;
|
||||||
|
case "HEAD":
|
||||||
|
response = httpClient.getNoRedirect(url);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("不支持的HTTP方法: " + method);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Fetch请求完成: {} {} - 状态码: {}", method, url, response.statusCode());
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Fetch请求失败: {} - {}", url, e.getMessage());
|
||||||
|
throw new RuntimeException("Fetch请求失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,13 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.script.ScriptEngine;
|
import javax.script.ScriptEngine;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JavaScript解析器执行器
|
* JavaScript解析器执行器
|
||||||
@@ -30,17 +35,19 @@ public class JsParserExecutor implements IPanTool {
|
|||||||
|
|
||||||
private static final WorkerExecutor EXECUTOR = WebClientVertxInit.get().createSharedWorkerExecutor("parser-executor", 32);
|
private static final WorkerExecutor EXECUTOR = WebClientVertxInit.get().createSharedWorkerExecutor("parser-executor", 32);
|
||||||
|
|
||||||
|
private static String FETCH_RUNTIME_JS = null;
|
||||||
|
|
||||||
private final CustomParserConfig config;
|
private final CustomParserConfig config;
|
||||||
private final ShareLinkInfo shareLinkInfo;
|
private final ShareLinkInfo shareLinkInfo;
|
||||||
private final ScriptEngine engine;
|
private final ScriptEngine engine;
|
||||||
private final JsHttpClient httpClient;
|
private final JsHttpClient httpClient;
|
||||||
private final JsLogger jsLogger;
|
private final JsLogger jsLogger;
|
||||||
private final JsShareLinkInfoWrapper shareLinkInfoWrapper;
|
private final JsShareLinkInfoWrapper shareLinkInfoWrapper;
|
||||||
|
private final JsFetchBridge fetchBridge;
|
||||||
|
|
||||||
public JsParserExecutor(ShareLinkInfo shareLinkInfo, CustomParserConfig config) {
|
public JsParserExecutor(ShareLinkInfo shareLinkInfo, CustomParserConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.shareLinkInfo = shareLinkInfo;
|
this.shareLinkInfo = shareLinkInfo;
|
||||||
this.engine = initEngine();
|
|
||||||
|
|
||||||
// 检查是否有代理配置
|
// 检查是否有代理配置
|
||||||
JsonObject proxyConfig = null;
|
JsonObject proxyConfig = null;
|
||||||
@@ -51,6 +58,34 @@ public class JsParserExecutor implements IPanTool {
|
|||||||
this.httpClient = new JsHttpClient(proxyConfig);
|
this.httpClient = new JsHttpClient(proxyConfig);
|
||||||
this.jsLogger = new JsLogger("JsParser-" + config.getType());
|
this.jsLogger = new JsLogger("JsParser-" + config.getType());
|
||||||
this.shareLinkInfoWrapper = new JsShareLinkInfoWrapper(shareLinkInfo);
|
this.shareLinkInfoWrapper = new JsShareLinkInfoWrapper(shareLinkInfo);
|
||||||
|
this.fetchBridge = new JsFetchBridge(httpClient);
|
||||||
|
this.engine = initEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载fetch运行时JS代码
|
||||||
|
* @return fetch运行时代码
|
||||||
|
*/
|
||||||
|
static String loadFetchRuntime() {
|
||||||
|
if (FETCH_RUNTIME_JS != null) {
|
||||||
|
return FETCH_RUNTIME_JS;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream is = JsParserExecutor.class.getClassLoader().getResourceAsStream("fetch-runtime.js")) {
|
||||||
|
if (is == null) {
|
||||||
|
log.warn("未找到fetch-runtime.js文件,fetch API将不可用");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||||
|
FETCH_RUNTIME_JS = reader.lines().collect(Collectors.joining("\n"));
|
||||||
|
log.debug("Fetch运行时加载成功,大小: {} 字符", FETCH_RUNTIME_JS.length());
|
||||||
|
return FETCH_RUNTIME_JS;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("加载fetch-runtime.js失败", e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,6 +116,7 @@ public class JsParserExecutor implements IPanTool {
|
|||||||
engine.put("http", httpClient);
|
engine.put("http", httpClient);
|
||||||
engine.put("logger", jsLogger);
|
engine.put("logger", jsLogger);
|
||||||
engine.put("shareLinkInfo", shareLinkInfoWrapper);
|
engine.put("shareLinkInfo", shareLinkInfoWrapper);
|
||||||
|
engine.put("JavaFetch", fetchBridge);
|
||||||
|
|
||||||
// 禁用Java对象访问
|
// 禁用Java对象访问
|
||||||
engine.eval("var Java = undefined;");
|
engine.eval("var Java = undefined;");
|
||||||
@@ -90,6 +126,13 @@ public class JsParserExecutor implements IPanTool {
|
|||||||
engine.eval("var org = undefined;");
|
engine.eval("var org = undefined;");
|
||||||
engine.eval("var com = undefined;");
|
engine.eval("var com = undefined;");
|
||||||
|
|
||||||
|
// 加载fetch运行时(Promise和fetch API polyfill)
|
||||||
|
String fetchRuntime = loadFetchRuntime();
|
||||||
|
if (!fetchRuntime.isEmpty()) {
|
||||||
|
engine.eval(fetchRuntime);
|
||||||
|
log.debug("✅ Fetch API和Promise polyfill注入成功");
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("🔒 安全的JavaScript引擎初始化成功,解析器类型: {}", config.getType());
|
log.debug("🔒 安全的JavaScript引擎初始化成功,解析器类型: {}", config.getType());
|
||||||
|
|
||||||
// 执行JavaScript代码
|
// 执行JavaScript代码
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class JsPlaygroundExecutor {
|
|||||||
private final JsHttpClient httpClient;
|
private final JsHttpClient httpClient;
|
||||||
private final JsPlaygroundLogger playgroundLogger;
|
private final JsPlaygroundLogger playgroundLogger;
|
||||||
private final JsShareLinkInfoWrapper shareLinkInfoWrapper;
|
private final JsShareLinkInfoWrapper shareLinkInfoWrapper;
|
||||||
|
private final JsFetchBridge fetchBridge;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建演练场执行器
|
* 创建演练场执行器
|
||||||
@@ -62,6 +63,7 @@ public class JsPlaygroundExecutor {
|
|||||||
this.httpClient = new JsHttpClient(proxyConfig);
|
this.httpClient = new JsHttpClient(proxyConfig);
|
||||||
this.playgroundLogger = new JsPlaygroundLogger();
|
this.playgroundLogger = new JsPlaygroundLogger();
|
||||||
this.shareLinkInfoWrapper = new JsShareLinkInfoWrapper(shareLinkInfo);
|
this.shareLinkInfoWrapper = new JsShareLinkInfoWrapper(shareLinkInfo);
|
||||||
|
this.fetchBridge = new JsFetchBridge(httpClient);
|
||||||
this.engine = initEngine();
|
this.engine = initEngine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +86,7 @@ public class JsPlaygroundExecutor {
|
|||||||
engine.put("http", httpClient);
|
engine.put("http", httpClient);
|
||||||
engine.put("logger", playgroundLogger);
|
engine.put("logger", playgroundLogger);
|
||||||
engine.put("shareLinkInfo", shareLinkInfoWrapper);
|
engine.put("shareLinkInfo", shareLinkInfoWrapper);
|
||||||
|
engine.put("JavaFetch", fetchBridge);
|
||||||
|
|
||||||
// 禁用Java对象访问
|
// 禁用Java对象访问
|
||||||
engine.eval("var Java = undefined;");
|
engine.eval("var Java = undefined;");
|
||||||
@@ -93,6 +96,13 @@ public class JsPlaygroundExecutor {
|
|||||||
engine.eval("var org = undefined;");
|
engine.eval("var org = undefined;");
|
||||||
engine.eval("var com = undefined;");
|
engine.eval("var com = undefined;");
|
||||||
|
|
||||||
|
// 加载fetch运行时(Promise和fetch API polyfill)
|
||||||
|
String fetchRuntime = JsParserExecutor.loadFetchRuntime();
|
||||||
|
if (!fetchRuntime.isEmpty()) {
|
||||||
|
engine.eval(fetchRuntime);
|
||||||
|
playgroundLogger.infoJava("✅ Fetch API和Promise polyfill注入成功");
|
||||||
|
}
|
||||||
|
|
||||||
playgroundLogger.infoJava("🔒 安全的JavaScript引擎初始化成功(演练场)");
|
playgroundLogger.infoJava("🔒 安全的JavaScript引擎初始化成功(演练场)");
|
||||||
|
|
||||||
// 执行JavaScript代码
|
// 执行JavaScript代码
|
||||||
|
|||||||
105
parser/src/main/resources/custom-parsers/fetch-demo.js
Normal file
105
parser/src/main/resources/custom-parsers/fetch-demo.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name Fetch API示例解析器
|
||||||
|
// @type fetch_demo
|
||||||
|
// @displayName Fetch演示
|
||||||
|
// @description 演示如何在ES5环境中使用fetch API和async/await
|
||||||
|
// @match https?://example\.com/s/(?<KEY>\w+)
|
||||||
|
// @author QAIU
|
||||||
|
// @version 1.0.0
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
// 使用require导入类型定义(仅用于IDE类型提示)
|
||||||
|
var types = require('./types');
|
||||||
|
/** @typedef {types.ShareLinkInfo} ShareLinkInfo */
|
||||||
|
/** @typedef {types.JsHttpClient} JsHttpClient */
|
||||||
|
/** @typedef {types.JsLogger} JsLogger */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示使用fetch API的解析器
|
||||||
|
* 注意:虽然源码中使用了ES6+语法(async/await),但在浏览器中会被编译为ES5
|
||||||
|
*
|
||||||
|
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息
|
||||||
|
* @param {JsHttpClient} http - HTTP客户端(传统方式)
|
||||||
|
* @param {JsLogger} logger - 日志对象
|
||||||
|
* @returns {string} 下载链接
|
||||||
|
*/
|
||||||
|
function parse(shareLinkInfo, http, logger) {
|
||||||
|
logger.info("=== Fetch API Demo ===");
|
||||||
|
|
||||||
|
// 方式1:使用传统的http对象(同步)
|
||||||
|
logger.info("方式1: 使用传统http对象");
|
||||||
|
var response1 = http.get("https://httpbin.org/get");
|
||||||
|
logger.info("状态码: " + response1.statusCode());
|
||||||
|
|
||||||
|
// 方式2:使用fetch API(基于Promise)
|
||||||
|
logger.info("方式2: 使用fetch API");
|
||||||
|
|
||||||
|
// 注意:在ES5环境中,我们需要手动处理Promise
|
||||||
|
// 这个示例展示了如何在ES5中使用fetch
|
||||||
|
var fetchPromise = fetch("https://httpbin.org/get");
|
||||||
|
|
||||||
|
// 等待Promise完成(同步等待模拟)
|
||||||
|
var result = null;
|
||||||
|
var error = null;
|
||||||
|
|
||||||
|
fetchPromise
|
||||||
|
.then(function(response) {
|
||||||
|
logger.info("Fetch响应状态: " + response.status);
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then(function(text) {
|
||||||
|
logger.info("Fetch响应内容: " + text.substring(0, 100) + "...");
|
||||||
|
result = "https://example.com/download/demo.file";
|
||||||
|
})
|
||||||
|
['catch'](function(err) {
|
||||||
|
logger.error("Fetch失败: " + err.message);
|
||||||
|
error = err;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 简单的等待循环(实际场景中不推荐,这里仅作演示)
|
||||||
|
var timeout = 5000; // 5秒超时
|
||||||
|
var start = Date.now();
|
||||||
|
while (result === null && error === null && (Date.now() - start) < timeout) {
|
||||||
|
// 等待Promise完成
|
||||||
|
java.lang.Thread.sleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error !== null) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result === null) {
|
||||||
|
throw new Error("Fetch超时");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示POST请求
|
||||||
|
*/
|
||||||
|
function demonstratePost(logger) {
|
||||||
|
logger.info("=== 演示POST请求 ===");
|
||||||
|
|
||||||
|
var postPromise = fetch("https://httpbin.org/post", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
key: "value",
|
||||||
|
demo: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
postPromise
|
||||||
|
.then(function(response) {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
logger.info("POST响应: " + JSON.stringify(data));
|
||||||
|
})
|
||||||
|
['catch'](function(err) {
|
||||||
|
logger.error("POST失败: " + err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
299
parser/src/main/resources/fetch-runtime.js
Normal file
299
parser/src/main/resources/fetch-runtime.js
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
// ==FetchRuntime==
|
||||||
|
// @name Fetch API Polyfill for ES5
|
||||||
|
// @description Fetch API and Promise implementation for ES5 JavaScript engines
|
||||||
|
// @version 1.0.0
|
||||||
|
// @author QAIU
|
||||||
|
// ==============
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Promise implementation compatible with ES5
|
||||||
|
* Supports basic Promise functionality needed for fetch API
|
||||||
|
*/
|
||||||
|
function SimplePromise(executor) {
|
||||||
|
var state = 'pending';
|
||||||
|
var value;
|
||||||
|
var handlers = [];
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
function resolve(result) {
|
||||||
|
if (state !== 'pending') return;
|
||||||
|
state = 'fulfilled';
|
||||||
|
value = result;
|
||||||
|
handlers.forEach(handle);
|
||||||
|
handlers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function reject(err) {
|
||||||
|
if (state !== 'pending') return;
|
||||||
|
state = 'rejected';
|
||||||
|
value = err;
|
||||||
|
handlers.forEach(handle);
|
||||||
|
handlers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle(handler) {
|
||||||
|
if (state === 'pending') {
|
||||||
|
handlers.push(handler);
|
||||||
|
} else {
|
||||||
|
setTimeout(function() {
|
||||||
|
if (state === 'fulfilled' && typeof handler.onFulfilled === 'function') {
|
||||||
|
try {
|
||||||
|
var result = handler.onFulfilled(value);
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
result.then(handler.resolve, handler.reject);
|
||||||
|
} else {
|
||||||
|
handler.resolve(result);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
handler.reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state === 'rejected' && typeof handler.onRejected === 'function') {
|
||||||
|
try {
|
||||||
|
var result = handler.onRejected(value);
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
result.then(handler.resolve, handler.reject);
|
||||||
|
} else {
|
||||||
|
handler.resolve(result);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
handler.reject(e);
|
||||||
|
}
|
||||||
|
} else if (state === 'rejected' && !handler.onRejected) {
|
||||||
|
handler.reject(value);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.then = function(onFulfilled, onRejected) {
|
||||||
|
return new SimplePromise(function(resolveNext, rejectNext) {
|
||||||
|
handle({
|
||||||
|
onFulfilled: onFulfilled,
|
||||||
|
onRejected: onRejected,
|
||||||
|
resolve: resolveNext,
|
||||||
|
reject: rejectNext
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this['catch'] = function(onRejected) {
|
||||||
|
return this.then(null, onRejected);
|
||||||
|
};
|
||||||
|
|
||||||
|
this['finally'] = function(onFinally) {
|
||||||
|
return this.then(
|
||||||
|
function(value) {
|
||||||
|
return SimplePromise.resolve(onFinally()).then(function() {
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(reason) {
|
||||||
|
return SimplePromise.resolve(onFinally()).then(function() {
|
||||||
|
throw reason;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
executor(resolve, reject);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static methods
|
||||||
|
SimplePromise.resolve = function(value) {
|
||||||
|
if (value && typeof value.then === 'function') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return new SimplePromise(function(resolve) {
|
||||||
|
resolve(value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SimplePromise.reject = function(reason) {
|
||||||
|
return new SimplePromise(function(resolve, reject) {
|
||||||
|
reject(reason);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SimplePromise.all = function(promises) {
|
||||||
|
return new SimplePromise(function(resolve, reject) {
|
||||||
|
var results = [];
|
||||||
|
var remaining = promises.length;
|
||||||
|
|
||||||
|
if (remaining === 0) {
|
||||||
|
resolve(results);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResult(index, value) {
|
||||||
|
results[index] = value;
|
||||||
|
remaining--;
|
||||||
|
if (remaining === 0) {
|
||||||
|
resolve(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < promises.length; i++) {
|
||||||
|
(function(index) {
|
||||||
|
var promise = promises[index];
|
||||||
|
if (promise && typeof promise.then === 'function') {
|
||||||
|
promise.then(
|
||||||
|
function(value) { handleResult(index, value); },
|
||||||
|
reject
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
handleResult(index, promise);
|
||||||
|
}
|
||||||
|
})(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SimplePromise.race = function(promises) {
|
||||||
|
return new SimplePromise(function(resolve, reject) {
|
||||||
|
for (var i = 0; i < promises.length; i++) {
|
||||||
|
var promise = promises[i];
|
||||||
|
if (promise && typeof promise.then === 'function') {
|
||||||
|
promise.then(resolve, reject);
|
||||||
|
} else {
|
||||||
|
resolve(promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make Promise global if not already defined
|
||||||
|
if (typeof Promise === 'undefined') {
|
||||||
|
var Promise = SimplePromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response object that mimics the Fetch API Response
|
||||||
|
*/
|
||||||
|
function FetchResponse(jsHttpResponse) {
|
||||||
|
this._jsResponse = jsHttpResponse;
|
||||||
|
this.status = jsHttpResponse.statusCode();
|
||||||
|
this.ok = this.status >= 200 && this.status < 300;
|
||||||
|
this.statusText = this.ok ? 'OK' : 'Error';
|
||||||
|
this.headers = {
|
||||||
|
get: function(name) {
|
||||||
|
return jsHttpResponse.header(name);
|
||||||
|
},
|
||||||
|
has: function(name) {
|
||||||
|
return jsHttpResponse.header(name) !== null;
|
||||||
|
},
|
||||||
|
entries: function() {
|
||||||
|
var headerMap = jsHttpResponse.headers();
|
||||||
|
var entries = [];
|
||||||
|
for (var key in headerMap) {
|
||||||
|
if (headerMap.hasOwnProperty(key)) {
|
||||||
|
entries.push([key, headerMap[key]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchResponse.prototype.text = function() {
|
||||||
|
var body = this._jsResponse.body();
|
||||||
|
return SimplePromise.resolve(body || '');
|
||||||
|
};
|
||||||
|
|
||||||
|
FetchResponse.prototype.json = function() {
|
||||||
|
var self = this;
|
||||||
|
return this.text().then(function(text) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Invalid JSON: ' + e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
FetchResponse.prototype.arrayBuffer = function() {
|
||||||
|
var bytes = this._jsResponse.bodyBytes();
|
||||||
|
return SimplePromise.resolve(bytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
FetchResponse.prototype.blob = function() {
|
||||||
|
// Blob not supported in ES5, return bytes
|
||||||
|
return this.arrayBuffer();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch API implementation using JavaFetch bridge
|
||||||
|
* @param {string} url - Request URL
|
||||||
|
* @param {Object} options - Fetch options (method, headers, body, etc.)
|
||||||
|
* @returns {Promise<FetchResponse>}
|
||||||
|
*/
|
||||||
|
function fetch(url, options) {
|
||||||
|
return new SimplePromise(function(resolve, reject) {
|
||||||
|
try {
|
||||||
|
// Parse options
|
||||||
|
options = options || {};
|
||||||
|
var method = (options.method || 'GET').toUpperCase();
|
||||||
|
var headers = options.headers || {};
|
||||||
|
var body = options.body;
|
||||||
|
|
||||||
|
// Prepare request options for JavaFetch
|
||||||
|
var requestOptions = {
|
||||||
|
method: method,
|
||||||
|
headers: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert headers to simple object
|
||||||
|
if (headers) {
|
||||||
|
if (typeof headers.forEach === 'function') {
|
||||||
|
// Headers object
|
||||||
|
headers.forEach(function(value, key) {
|
||||||
|
requestOptions.headers[key] = value;
|
||||||
|
});
|
||||||
|
} else if (typeof headers === 'object') {
|
||||||
|
// Plain object
|
||||||
|
for (var key in headers) {
|
||||||
|
if (headers.hasOwnProperty(key)) {
|
||||||
|
requestOptions.headers[key] = headers[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add body if present
|
||||||
|
if (body !== undefined && body !== null) {
|
||||||
|
if (typeof body === 'string') {
|
||||||
|
requestOptions.body = body;
|
||||||
|
} else if (typeof body === 'object') {
|
||||||
|
// Assume JSON
|
||||||
|
requestOptions.body = JSON.stringify(body);
|
||||||
|
if (!requestOptions.headers['Content-Type'] && !requestOptions.headers['content-type']) {
|
||||||
|
requestOptions.headers['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call JavaFetch bridge
|
||||||
|
var jsHttpResponse = JavaFetch.fetch(url, requestOptions);
|
||||||
|
|
||||||
|
// Create Response object
|
||||||
|
var response = new FetchResponse(jsHttpResponse);
|
||||||
|
resolve(response);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for global use
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.fetch = fetch;
|
||||||
|
window.Promise = Promise;
|
||||||
|
} else if (typeof global !== 'undefined') {
|
||||||
|
global.fetch = fetch;
|
||||||
|
global.Promise = Promise;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user