发出网络请求与跨域处理
Eagle 插件有个其他平台给不了的优势:官方文档明确写了,Eagle Plugin API 不受跨域限制(CORS)的影响,可以访问任何网址。这意味着你不用为了调自己的后端专门去加 CORS 响应头。但「不受 CORS 限制」不等于「随便用」——这一节讲能力,也讲约束。
两种请求方式
fetch:日常首选
fetch 是 Web 标准 API,插件里可以直接用,语法和浏览器里完全一致:
fetch('https://example.com/api/endpoint')
.then(response => response.json())
.then(data => {
// 在这里处理响应
});
异步写起来更顺手:
async function fetchItems() {
const response = await fetch('https://api.example.com/items');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
一定要检查 response.ok。 这是 fetch 最常见的坑:fetch 遇到 404、500 都不会 reject,它会正常返回一个 response,只是 ok 为 false、status 是错误码。不检查就直接 .json(),你会拿到一段 HTML 错误页,然后 JSON.parse 报一个和真实原因完全无关的错误。
https 模块:需要底层控制时
官方给的第二种方案是 Node 原生的 https 模块。文档说明了原因:由于浏览器默认的安全性限制,fetch 有时候会遇到一些限制,这种情况下可以改用 Node.js 原生的网络 API,获得更高的弹性。
const https = require('https');
https.get('https://www.example.com', (res) => {
console.log(`Got response: ${res.statusCode}`);
res.on('data', (d) => {
// 处理响应数据
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
什么时候需要它?几个典型场景:需要自定义 TLS 配置(自签证书)、需要代理、需要细粒度控制 socket、需要访问自签名内网服务。
// 连接本地开发服务器时,可能需要放宽证书校验
const fs = require('fs');
const https = require('https');
const agent = new https.Agent({
rejectUnauthorized: false,
ca: fs.readFileSync('/path/to/dev-cert.pem')
});
https.get('https://localhost:8443/api', { agent }, (res) => {
// ...
});
⚠️ 但要注意:rejectUnauthorized: false 这类写法只应该用于本地开发。发布版本里绝不能出现——审核标准里明确把「使用未加密的 HTTP 传输数据」和「在没有本地整合说明的情况下连接 localhost、回环地址、私人网络」列为高风险项。
完整的请求封装
一个能上生产的请求函数应该处理超时、错误、取消三件事:
const https = require('https');
function request(url, options = {}, timeout = 15000) {
return new Promise((resolve, reject) => {
const req = https.request(url, {
method: options.method || 'GET',
headers: options.headers || {}
}, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
// 防止响应过大占满内存
if (data.length > 10 * 1024 * 1024) {
req.destroy();
reject(new Error('响应体过大'));
}
});
res.on('end', () => resolve({
status: res.statusCode,
data
}));
});
req.setTimeout(timeout, () => {
req.destroy();
reject(new Error(`请求超时: ${url}`));
});
req.on('error', reject);
if (options.body) {
req.write(typeof options.body === 'string'
? options.body
: JSON.stringify(options.body));
}
req.end();
});
}
三个细节值得说明:
- 超时必须显式设置。 没有超时的请求会一直挂着,用户看到的是「点了没反应」。
- 限制响应体大小。 一个返回 500MB 的接口会直接把插件进程撑爆。
- 错误要带上下文。
reject(new Error('请求超时: ' + url))比reject(new Error('timeout'))有用得多。
凭证管理:不要把 Key 写进代码
如果插件需要 API Key,正确做法是让用户自己填,存在本地配置文件里:
const fs = require('fs');
const path = require('path');
eagle.onPluginCreate(async (plugin) => {
const configPath = path.join(plugin.path, 'config.json');
// 不存在就用空模板初始化
if (!fs.existsSync(configPath)) {
fs.writeFileSync(configPath, JSON.stringify({ apiKey: '' }, null, 2));
}
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
console.log('是否已配置:', Boolean(config.apiKey));
});
这个配置文件写在插件目录下,安装包本身不包含任何凭证。审核标准对此有明确要求:安装包不得包含真实凭证或私人密钥,.env、.pem、.key、.p12、.pfx、credentials.json、secrets.json、含 Token 的 .npmrc 都是被点名检查的对象。
⚠️ 还有一条容易忽略:如果敏感数据曾经被打包或公开,不能只删除文件,必须立即停用并更换相关密钥。
跨域:为什么这里不需要处理
在浏览器里发跨域请求要满足两个条件:服务端返回 Access-Control-Allow-Origin,以及请求本身通过预检。这在插件里都不需要——插件进程不受同源策略约束,fetch 到任意站点都能通。
但不受 CORS 限制带来的是责任,不是自由。审核标准会逐条检查你的插件连了哪些外部地址:
- 这个连接用户能合理预期吗?
- 这个外部服务确实是宣称功能所需吗?
- 只传送了完成该功能所需的最少数据吗?
- 商店介绍里说明了会实质影响隐私的外部处理吗?
- 敏感数据有没有被放进 URL、日志或安装包?
具体到「传送用户数据到外部」这一类行为,安全与隐私标准明确列出了不得做的情形:把 Eagle 素材名称、本地路径或文件内容传送到未说明的外部服务。在你把用户的图片发给某个云端 AI 服务之前,必须在商店介绍里说清楚传什么、发到哪。
一个上传素材到云端的例子
async function uploadToCloud(item, apiKey) {
const fs = require('fs');
// 发送前明确告知用户
const confirmed = await eagle.dialog.showMessageBox({
title: '上传确认',
message: `将上传「${item.name}」`,
detail: `文件大小 ${Math.round(item.size / 1024)} KB,将发送至 api.example.com`,
buttons: ['上传', '取消'],
type: 'warning'
});
if (confirmed.response !== 0) return;
const buffer = fs.readFileSync(item.filePath);
const result = await request('https://api.example.com/upload', {
method: 'POST',
headers: {
'Content-Type': item.ext,
'Authorization': `Bearer ${apiKey}`
},
body: buffer
});
if (result.status !== 200) {
await eagle.notification.show({ title: '上传失败', body: `状态码 ${result.status}` });
return;
}
await eagle.notification.show({ title: '上传完成', body: item.name });
}
这个例子里有三处是审核会看的:上传前弹窗说明去向和内容、失败有明确提示、凭证从配置读而不是写死。
开发流程
- 先用
fetch实现,确认接口通不通、返回结构对不对。 - 加上
response.ok检查和错误处理。 - 加超时,测试一下断网时的表现。
- 需要底层控制时再换
https模块。 - 凭证改成用户配置,代码里不出现任何硬编码 Key。
- 列出插件会连的所有外部域名,商店介绍里逐条说明。
- 涉及用户数据的传送,在界面上做明确确认。
常见坑
不检查
response.ok就解析。 404 和 500 都会正常返回response,不检查的话你会拿到 HTML 错误页,然后在一个无关的地方报 JSON 解析错误。API Key 硬编码在代码里。 这是审核的一票否决项。改成用户配置,并确保
.env之类的文件被打包流程排除掉。延伸注意: * 不检查
response.ok就解析。 404 和 500 都会正常返回response,不检查的话你会拿到 HTML 错误页,然后在一个无关的地方报 JSON 解析错误。 * 把凭证放进 URL 查询参数。 URL 会被记录在服务端日志、代理日志和浏览历史里。用请求头传 Token。整理人:WorkBuddy
