@heybox/hb-sdk 0.8.2-alpha.2 → 0.8.2-alpha.5
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/CHANGELOG.md +32 -1
- package/README.md +5 -1
- package/dist/cli-chunks/{build-bVThH3Z5.cjs → build-D2T3ky_9.cjs} +5 -5
- package/dist/cli-chunks/{context-DpKqGnjp.cjs → context-DEgmdpSL.cjs} +326 -104
- package/dist/cli-chunks/{create-BWC2343y.cjs → create-DKqQhrG5.cjs} +5 -1
- package/dist/cli-chunks/{dev-t14YpHHY.cjs → dev-BEXM7cmq.cjs} +17 -9
- package/dist/cli-chunks/{doctor-BWkrE0Hj.cjs → doctor-B7UixGrd.cjs} +1 -1
- package/dist/cli-chunks/{index-CDtcZVWv.cjs → index-BtwfuoWI.cjs} +15 -17
- package/dist/cli-chunks/{index-D3FMcOZq.cjs → index-C69OtRug.cjs} +2 -2
- package/dist/cli-chunks/{index.esm-BrJBaJWS.cjs → index.esm-C86KlVdK.cjs} +11 -10
- package/dist/cli-chunks/{login-DRMl7nFH.cjs → login-Bp_HMx-i.cjs} +2 -2
- package/dist/cli-chunks/{project-vite-CXgdzIYw.cjs → project-vite-DZxipd13.cjs} +1 -1
- package/dist/cli-chunks/{remote-ibQybNiM.cjs → remote-B_sVsbQP.cjs} +80 -60
- package/dist/cli-chunks/runtime-gate-GR9jZd37.cjs +58 -0
- package/dist/cli-chunks/{runtime-gate-BWb8QIcX.cjs → runtime-permission-env-COWketgE.cjs} +4533 -40
- package/dist/cli-chunks/{session-CIoBspg-.cjs → session-6-AApcTZ.cjs} +1 -1
- package/dist/cli-chunks/{skill-5labzuWk.cjs → skill-CPDLk7t7.cjs} +2 -2
- package/dist/cli-chunks/{version-QiVEs76a.cjs → version-hHrSPRwW.cjs} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-CpwQQhA1.js +101 -0
- package/dist/devtools/browser-dev-host/assets/{desktop-app-launch-B4KsgItx.js → desktop-app-launch-BdWBOIvY.js} +1 -1
- package/dist/devtools/browser-dev-host/assets/{index-9Nb3XhtI.js → index-BQSEynx6.js} +3 -3
- package/dist/devtools/browser-dev-host/index.html +2 -2
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/miniapp-publish.cjs.js +8782 -0
- package/dist/miniapp-publish.esm.js +8778 -1
- package/dist/templates/vanilla-vite-js/assets/heybox-placeholder.png +0 -0
- package/dist/templates/vanilla-vite-js/package.json.ejs +3 -0
- package/dist/vite.cjs.js +499 -346
- package/dist/vite.esm.js +500 -347
- package/package.json +7 -6
- package/skill/SKILL.md +5 -1
- package/skill/references/api-root.md +5 -4
- package/skill/references/cli.md +3 -3
- package/skill/references/recipes.md +3 -2
- package/skill/skill.json +5 -5
- package/types/miniapp-manifest/basic-info-browser.d.ts +9 -0
- package/types/miniapp-manifest/basic-info-image-header.d.ts +6 -0
- package/types/miniapp-manifest/basic-info-node.d.ts +17 -0
- package/types/miniapp-manifest/basic-info-placeholder.d.ts +1 -0
- package/types/miniapp-manifest/basic-info.d.ts +12 -0
- package/types/miniapp-manifest/schema.d.ts +7 -1
- package/types/miniapp-publish/index.d.ts +8 -0
- package/dist/cli-chunks/runtime-permission-env-DiTeD75Q.cjs +0 -4498
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-DxzOkljJ.js +0 -101
- /package/types/{vite → miniapp-manifest}/runtime-gate.d.ts +0 -0
package/dist/vite.cjs.js
CHANGED
|
@@ -1,336 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var node_fs = require('node:fs');
|
|
4
3
|
var node_crypto = require('node:crypto');
|
|
5
|
-
var
|
|
4
|
+
var node_fs = require('node:fs');
|
|
5
|
+
var promises = require('node:fs/promises');
|
|
6
6
|
var path = require('node:path');
|
|
7
|
+
var sharp = require('sharp');
|
|
8
|
+
var node_os = require('node:os');
|
|
7
9
|
var node_zlib = require('node:zlib');
|
|
8
10
|
var node_async_hooks = require('node:async_hooks');
|
|
9
11
|
|
|
10
12
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
11
|
-
/** 构建时替换为当前发布包的实际版本。 */
|
|
12
|
-
const HB_SDK_VERSION = typeof undefined === 'string'
|
|
13
|
-
? undefined
|
|
14
|
-
: '0.8.2-alpha.2';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* iframe 与调试台页面的开发期 console 捕获转发。
|
|
18
|
-
*
|
|
19
|
-
* 双条件门控(iframe 转发):
|
|
20
|
-
* 1. 构建期:`hb-sdk dev`(vite serve)注入 `false`;`hb-sdk build` /
|
|
21
|
-
* `vite build`(生产)永不注入或替换为 `false`,本模块被死代码消除。
|
|
22
|
-
* 2. 运行期:仅在 `window.name` 为 `__hb_sdk_mini_dev__` 的调试 iframe 中激活,
|
|
23
|
-
* 用户设备上的正式运行环境没有该标记。
|
|
24
|
-
*
|
|
25
|
-
* 无侵入 patch `console.log/debug/info/warn/error`:原样放行(DevTools 不受影响)+
|
|
26
|
-
* 通过 `window.parent.postMessage` 转发给调试台(CSP 的 connect-src 不约束 postMessage,
|
|
27
|
-
* 网络上报由调试台页面代理)。发送全程静默:失败不得回打 console,避免自触发死循环。
|
|
28
|
-
*
|
|
29
|
-
* 激活时机:由 SDK 默认单例初始化时调用(业务 `import '@heybox/hb-sdk'` 即生效);
|
|
30
|
-
* iframe 标记由调试台 `iframe.name` 与 vite dev 注入的无依赖 bootstrap 共同设置。
|
|
31
|
-
* 调试台页面自身(runtime 跑在这里,`[hb-sdk-runtime]` 日志不经过 iframe)另用
|
|
32
|
-
* `installMiniDevConsoleSelfCapture` 接入「日志」页签(source: 'host')。
|
|
33
|
-
*/
|
|
34
|
-
const MINI_DEV_CONSOLE_EVENT_TYPE = 'hb-sdk:mini-dev-console';
|
|
35
|
-
/** 调试台页面与 iframe bootstrap 共用的已安装标记(window/console 上的属性名)。 */
|
|
36
|
-
const MINI_DEV_CONSOLE_INSTALL_FLAG = '__hb_sdk_mini_dev_console_installed__';
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* network.request 诊断日志的纯脱敏工具。
|
|
40
|
-
*
|
|
41
|
-
* @remarks
|
|
42
|
-
* 放在 `core` 而非 `modules/network`:无错误类型依赖,供 SDK 错误文案、
|
|
43
|
-
* modules observability、devtools Browser Dev Host / proxy 共用,并满足 boundary
|
|
44
|
-
*(devtools 不得直接 import runtime capability modules)。
|
|
45
|
-
*/
|
|
46
|
-
/** 敏感字段名:header / query / body key 共用。 */
|
|
47
|
-
const SENSITIVE_KEY_PATTERN = /(authorization|cookie|password|passwd|secret|token|pkey|private[_-]?key|access[_-]?key|client[_-]?secret|api[_-]?key|apikey|session|jwt|refresh[_-]?token|csrf)/i;
|
|
48
|
-
/**
|
|
49
|
-
* 脱敏请求 URL:去掉 userinfo,并将 query / fragment 中的敏感参数值替换为 `[redacted]`。
|
|
50
|
-
*/
|
|
51
|
-
function sanitizeNetworkRequestUrl(url) {
|
|
52
|
-
if (!url) {
|
|
53
|
-
return url;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
const parsed = new URL(url);
|
|
57
|
-
parsed.username = '';
|
|
58
|
-
parsed.password = '';
|
|
59
|
-
redactUrlSearchParams(parsed.searchParams);
|
|
60
|
-
if (parsed.hash && parsed.hash.length > 1) {
|
|
61
|
-
const hashBody = parsed.hash.slice(1);
|
|
62
|
-
if (hashBody.includes('=')) {
|
|
63
|
-
const hashParams = new URLSearchParams(hashBody);
|
|
64
|
-
if (redactUrlSearchParams(hashParams)) {
|
|
65
|
-
parsed.hash = hashParams.toString();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return parsed.toString();
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return redactSensitivePairsInRawText(url);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* 诊断 message 中的 URL 片段做脱敏,避免 error.message 二次泄漏 query secrets。
|
|
77
|
-
*/
|
|
78
|
-
function sanitizeDiagnosticMessage(message) {
|
|
79
|
-
if (!message) {
|
|
80
|
-
return message;
|
|
81
|
-
}
|
|
82
|
-
return message.replace(/https?:\/\/[^\s]+/gi, matched => sanitizeNetworkRequestUrl(matched.replace(/[),.;]+$/g, '')));
|
|
83
|
-
}
|
|
84
|
-
function isSensitiveNetworkKey(key) {
|
|
85
|
-
return SENSITIVE_KEY_PATTERN.test(key.trim());
|
|
86
|
-
}
|
|
87
|
-
function redactUrlSearchParams(params) {
|
|
88
|
-
let changed = false;
|
|
89
|
-
for (const key of [...params.keys()]) {
|
|
90
|
-
if (isSensitiveNetworkKey(key)) {
|
|
91
|
-
params.set(key, '[redacted]');
|
|
92
|
-
changed = true;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return changed;
|
|
96
|
-
}
|
|
97
|
-
function redactSensitivePairsInRawText(text) {
|
|
98
|
-
return text.replace(/([?&#]|^|[?&])([^=&#\s]+)=([^&#\s]*)/g, (full, prefix, key, value) => {
|
|
99
|
-
if (!isSensitiveNetworkKey(key)) {
|
|
100
|
-
return full;
|
|
101
|
-
}
|
|
102
|
-
return `${prefix}${key}=[redacted]`;
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const DEVICE_LOG_PATH = '/__hb_sdk__/device-logs';
|
|
107
|
-
const registryKey = Symbol.for('@heybox/hb-sdk/device-logs');
|
|
108
|
-
const registry = globalThis;
|
|
109
|
-
const stores = (registry[registryKey] ??= new Map());
|
|
110
|
-
function registerDeviceLogs(port, store) {
|
|
111
|
-
stores.set(port, store);
|
|
112
|
-
return () => {
|
|
113
|
-
if (stores.get(port) === store)
|
|
114
|
-
stores.delete(port);
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
class DeviceLogStore {
|
|
118
|
-
sandboxRequests = new Map();
|
|
119
|
-
listSandbox(id, path) {
|
|
120
|
-
const device = this.devices.get(id);
|
|
121
|
-
if (!device || Date.now() - device.lastSeen > 15000)
|
|
122
|
-
return Promise.resolve({ error: '设备已离线' });
|
|
123
|
-
if (path !== '.' && (!path || path.length > 1024 || /[\\\\\u0000-\u001f]/.test(path) || path.split('/').some(part => !part || part === '.' || part === '..')))
|
|
124
|
-
return Promise.resolve({ error: '无效目录路径' });
|
|
125
|
-
if (this.sandboxRequests.size >= 64)
|
|
126
|
-
return Promise.resolve({ error: '查询繁忙,请稍后重试' });
|
|
127
|
-
const requestId = node_crypto.randomUUID();
|
|
128
|
-
return new Promise(resolve => {
|
|
129
|
-
const timer = setTimeout(() => { this.sandboxRequests.delete(requestId); resolve({ error: '设备查询超时' }); }, 10000);
|
|
130
|
-
this.sandboxRequests.set(requestId, { id, path, requestId, resolve: value => { clearTimeout(timer); this.sandboxRequests.delete(requestId); resolve(value); } });
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
uploadToken = node_crypto.randomBytes(32).toString('hex');
|
|
134
|
-
epoch = node_crypto.randomUUID();
|
|
135
|
-
expiresAt = Date.now() + 12 * 60 * 60 * 1000;
|
|
136
|
-
cursor = 0;
|
|
137
|
-
logs = [];
|
|
138
|
-
devices = new Map();
|
|
139
|
-
query(params) {
|
|
140
|
-
const after = Math.max(0, Number(params.get('after')) || 0);
|
|
141
|
-
const limit = Math.min(500, Math.max(1, Number(params.get('limit')) || 100));
|
|
142
|
-
const selected = this.logs.filter((log) => log.cursor > after &&
|
|
143
|
-
(!params.get('device') || log.deviceSessionId === params.get('device')) &&
|
|
144
|
-
(!params.get('level') || log.level === params.get('level')) &&
|
|
145
|
-
(!params.get('source') || log.source === params.get('source')) &&
|
|
146
|
-
(!params.get('callId') || log.callId === params.get('callId')) &&
|
|
147
|
-
(!params.get('id') || String(log.cursor) === params.get('id')) &&
|
|
148
|
-
(!params.get('since') || log.timestamp >= Number(params.get('since'))) &&
|
|
149
|
-
(!params.get('query') || log.message.toLowerCase().includes(params.get('query').toLowerCase())));
|
|
150
|
-
const items = selected.slice(0, limit);
|
|
151
|
-
return {
|
|
152
|
-
schemaVersion: 1,
|
|
153
|
-
epoch: this.epoch,
|
|
154
|
-
items,
|
|
155
|
-
nextCursor: selected.length > limit ? items.at(-1).cursor : this.cursor,
|
|
156
|
-
hasMore: selected.length > limit,
|
|
157
|
-
gap: after > 0 && after < (this.logs[0]?.cursor ?? 1) - 1,
|
|
158
|
-
devices: [...this.devices.values()].map(({ id, name, platform, connectedAt, lastSeen }) => ({
|
|
159
|
-
id,
|
|
160
|
-
name,
|
|
161
|
-
platform,
|
|
162
|
-
connectedAt,
|
|
163
|
-
lastSeen,
|
|
164
|
-
online: Date.now() - lastSeen < 15000,
|
|
165
|
-
})),
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
async upload(req, res) {
|
|
169
|
-
const reply = (status, value) => {
|
|
170
|
-
res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
171
|
-
res.end(JSON.stringify(value));
|
|
172
|
-
};
|
|
173
|
-
if (req.method !== 'POST') {
|
|
174
|
-
reply(405, {});
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
if (Date.now() > this.expiresAt) {
|
|
178
|
-
reply(410, { code: 'EXPIRED' });
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
let text = '';
|
|
182
|
-
try {
|
|
183
|
-
req.setEncoding('utf8');
|
|
184
|
-
for await (const chunk of req) {
|
|
185
|
-
text += chunk;
|
|
186
|
-
if (Buffer.byteLength(text) > 65536) {
|
|
187
|
-
reply(413, {});
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
const body = JSON.parse(text);
|
|
192
|
-
if (body.register === true) {
|
|
193
|
-
if (req.headers['x-hb-log-token'] !== this.uploadToken) {
|
|
194
|
-
reply(403, {});
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
const registrationId = typeof body.registrationId === 'string' && /^[a-f0-9]{32}$/.test(body.registrationId) ? body.registrationId : undefined;
|
|
198
|
-
const existing = registrationId && [...this.devices.values()].find(device => device.registrationId === registrationId);
|
|
199
|
-
if (existing) {
|
|
200
|
-
reply(200, { id: existing.id, token: existing.token });
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (this.devices.size >= 64) {
|
|
204
|
-
reply(429, {});
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const id = node_crypto.randomUUID();
|
|
208
|
-
const platform = ['iOS', 'Android', 'Windows', 'macOS', 'Browser'].includes(body.platform) ? body.platform : 'Browser';
|
|
209
|
-
const device = {
|
|
210
|
-
id,
|
|
211
|
-
platform,
|
|
212
|
-
registrationId,
|
|
213
|
-
connectedAt: Date.now(),
|
|
214
|
-
name: `${platform} · ${this.devices.size + 1}`,
|
|
215
|
-
lastSeen: Date.now(),
|
|
216
|
-
sequence: 0,
|
|
217
|
-
token: node_crypto.randomBytes(32).toString('hex'),
|
|
218
|
-
windowStart: Date.now(),
|
|
219
|
-
count: 0,
|
|
220
|
-
};
|
|
221
|
-
this.devices.set(id, device);
|
|
222
|
-
reply(200, { id, token: device.token });
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
const device = this.devices.get(body.id);
|
|
226
|
-
if (!device || req.headers['x-hb-log-token'] !== device.token) {
|
|
227
|
-
reply(403, {});
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
if (!Array.isArray(body.logs) || body.logs.length > 50) {
|
|
231
|
-
reply(400, {});
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
if (Date.now() - device.windowStart > 1000) {
|
|
235
|
-
device.windowStart = Date.now();
|
|
236
|
-
device.count = 0;
|
|
237
|
-
}
|
|
238
|
-
if (device.count + body.logs.length > 200) {
|
|
239
|
-
reply(429, {});
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
device.count += body.logs.length;
|
|
243
|
-
device.lastSeen = Date.now();
|
|
244
|
-
if (body.sandboxResult && typeof body.sandboxResult === 'object') {
|
|
245
|
-
const result = body.sandboxResult;
|
|
246
|
-
const pending = this.sandboxRequests.get(result.requestId);
|
|
247
|
-
if (pending?.id === device.id) {
|
|
248
|
-
const entries = Array.isArray(result.entries) ? result.entries.slice(0, 200).filter((entry) => entry && ['file', 'directory'].includes(entry.kind) && typeof entry.name === 'string' && entry.name.length <= 255 && !/[\\\\/\u0000-\u001f]/.test(entry.name) && !['.', '..'].includes(entry.name)).map((entry) => ({ name: entry.name, kind: entry.kind })) : [];
|
|
249
|
-
pending.resolve({ deviceSessionId: device.id, requestId: pending.requestId, path: pending.path, timestamp: Date.now(), entries, error: result.error ? '设备不支持或无权读取此目录' : undefined, truncated: result.truncated === true });
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
for (const log of body.logs) {
|
|
253
|
-
if (!log || typeof log !== 'object')
|
|
254
|
-
continue;
|
|
255
|
-
if (!Number.isSafeInteger(log.sequence) ||
|
|
256
|
-
log.sequence <= device.sequence ||
|
|
257
|
-
!Number.isFinite(log.timestamp) ||
|
|
258
|
-
!['log', 'debug', 'info', 'warn', 'error'].includes(log.level) ||
|
|
259
|
-
typeof log.message !== 'string')
|
|
260
|
-
continue;
|
|
261
|
-
device.sequence = log.sequence;
|
|
262
|
-
this.logs.push({
|
|
263
|
-
cursor: ++this.cursor,
|
|
264
|
-
deviceSessionId: device.id,
|
|
265
|
-
sequence: log.sequence,
|
|
266
|
-
timestamp: log.timestamp,
|
|
267
|
-
receivedAt: Date.now(),
|
|
268
|
-
level: log.level,
|
|
269
|
-
source: log.source === 'sdk' ? 'sdk' : 'app',
|
|
270
|
-
message: sanitizeDiagnosticMessage(log.message)
|
|
271
|
-
.replace(/(authorization|cookie|password|secret|token|pkey|api[_-]?key)(["'\s:=]+)([^\s,}]+)/gi, '$1$2[redacted]')
|
|
272
|
-
.slice(0, 8192),
|
|
273
|
-
truncated: log.truncated === true || log.message.length > 8192,
|
|
274
|
-
...(typeof log.method === 'string' ? { method: log.method.slice(0, 100) } : {}),
|
|
275
|
-
...(typeof log.callId === 'string' ? { callId: log.callId.slice(0, 100) } : {}),
|
|
276
|
-
...(typeof log.errorCode === 'string' && /^[A-Z0-9_]{1,100}$/.test(log.errorCode) ? { errorCode: log.errorCode } : {}),
|
|
277
|
-
...(log.outcome === 'success' || log.outcome === 'error' ? { outcome: log.outcome } : {}),
|
|
278
|
-
...(Number.isFinite(log.durationMs) ? { durationMs: Math.max(0, log.durationMs) } : {}),
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
if (this.logs.length > 5000)
|
|
282
|
-
this.logs.splice(0, this.logs.length - 5000);
|
|
283
|
-
reply(200, { sequence: device.sequence, sandboxRequest: [...this.sandboxRequests.values()].find(request => request.id === device.id) ? (() => { const request = [...this.sandboxRequests.values()].find(request => request.id === device.id); return { requestId: request.requestId, path: request.path }; })() : undefined });
|
|
284
|
-
}
|
|
285
|
-
catch {
|
|
286
|
-
reply(400, { code: 'INVALID_LOG_BATCH' });
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
/** 同源开发页面使用有界批量回传;传输失败不会递归写入 console。 */
|
|
291
|
-
function deviceLogBootstrap(token) {
|
|
292
|
-
return `<script>(function(){if(window.__hb_sdk_device_logs_installed__)return;
|
|
293
|
-
window.__hb_sdk_device_logs_installed__=true;
|
|
294
|
-
window.__hb_sdk_mini_dev_console_installed__=true;
|
|
295
|
-
var endpoint='${DEVICE_LOG_PATH}',credential='${token}',id='',queue=[],seq=0,busy=false,dropped=0;
|
|
296
|
-
var registrationId=Array.from(crypto.getRandomValues(new Uint8Array(16)),function(n){return n.toString(16).padStart(2,'0')}).join('');
|
|
297
|
-
var send=window.fetch.bind(window);
|
|
298
|
-
var sandboxResult;
|
|
299
|
-
function format(v){try{return typeof v==='string'?v:v instanceof Error?v.stack||v.message:JSON.stringify(v,function(k,value){return /authorization|cookie|password|secret|token|pkey|api.?key/i.test(k)?'[redacted]':value})}catch(e){return '[无法序列化]'}}function add(level,args,source){if(queue.length>=500){queue.shift();
|
|
300
|
-
dropped++}var message=args.map(format).join(' ');
|
|
301
|
-
message=message.replace(/https?:\\/\\/[^\\s]+/g,function(raw){try{var url=new URL(raw);
|
|
302
|
-
url.username='';
|
|
303
|
-
url.password='';
|
|
304
|
-
url.search='';
|
|
305
|
-
url.hash='';
|
|
306
|
-
return url.href}catch(e){return '[URL]'}});
|
|
307
|
-
message=message.replace(/(authorization|cookie|password|secret|token|pkey|api[_-]?key)([\\"'\\s:=]+)([^\\s,}]+)/gi,'$1$2[redacted]');
|
|
308
|
-
queue.push({truncated:message.length>8192,sequence:++seq,timestamp:Date.now(),level:level,source:source||'app',message:message.slice(0,8192)})}window.__hbSdkDeviceLog=function(event){add(event.outcome==='error'?'error':'info',[event.method+' · '+event.outcome+' · '+event.durationMs+' ms'],'sdk');
|
|
309
|
-
Object.assign(queue[queue.length-1],event)};
|
|
310
|
-
['log','debug','info','warn','error'].forEach(function(level){var original=console[level];
|
|
311
|
-
console[level]=function(){add(level,Array.prototype.slice.call(arguments));
|
|
312
|
-
return original.apply(this,arguments)}});
|
|
313
|
-
window.addEventListener('error',function(e){add('error',[e.message])});
|
|
314
|
-
window.addEventListener('unhandledrejection',function(e){add('error',[e.reason])});
|
|
315
|
-
async function flush(){if(busy)return;
|
|
316
|
-
busy=true;
|
|
317
|
-
try{if(!id){var ua=navigator.userAgent;
|
|
318
|
-
var platform=window.name==='__hb_sdk_mini_dev__'?'Browser':/Android/i.test(ua)?'Android':/iPhone|iPad/i.test(ua)?'iOS':/Windows/i.test(ua)?'Windows':/Mac/i.test(ua)?'macOS':'Browser';
|
|
319
|
-
var r=await send(endpoint,{method:'POST',headers:{'content-type':'application/json','x-hb-log-token':credential},body:JSON.stringify({register:true,platform:platform,registrationId:registrationId}),signal:AbortSignal.timeout(5000)});
|
|
320
|
-
if(!r.ok)return;
|
|
321
|
-
var d=await r.json();
|
|
322
|
-
id=d.id;
|
|
323
|
-
credential=d.token}if(dropped){add('warn',['日志缓冲已丢弃 '+dropped+' 条']);
|
|
324
|
-
dropped=0}var batch=queue.slice(0,50);
|
|
325
|
-
while(batch.length>1&&new TextEncoder().encode(JSON.stringify(batch)).length>48000)batch.pop();
|
|
326
|
-
var result=await send(endpoint,{method:'POST',headers:{'content-type':'application/json','x-hb-log-token':credential},body:JSON.stringify({id:id,logs:batch,sandboxResult:sandboxResult}),signal:AbortSignal.timeout(5000)});
|
|
327
|
-
if(result.ok){var ack=await result.json();sandboxResult=undefined;
|
|
328
|
-
if(ack.sandboxRequest){try{var entries=await window.__hbSdkSandboxList(ack.sandboxRequest.path);sandboxResult={requestId:ack.sandboxRequest.requestId,entries:entries.slice(0,200),truncated:entries.length>200}}catch(e){sandboxResult={requestId:ack.sandboxRequest.requestId,error:true}}}
|
|
329
|
-
queue=queue.filter(function(l){return l.sequence>ack.sequence})}}catch(e){}finally{busy=false}}setInterval(flush,1000);
|
|
330
|
-
flush()})();
|
|
331
|
-
</script>`;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
13
|
var re = {exports: {}};
|
|
335
14
|
|
|
336
15
|
var constants;
|
|
@@ -3114,7 +2793,480 @@ function requireSemver () {
|
|
|
3114
2793
|
return semver;
|
|
3115
2794
|
}
|
|
3116
2795
|
|
|
3117
|
-
var semverExports = requireSemver();
|
|
2796
|
+
var semverExports = requireSemver();
|
|
2797
|
+
|
|
2798
|
+
const BASIC_INFO_MAX_BYTES = 10 * 1024 * 1024;
|
|
2799
|
+
const BASIC_INFO_MAX_PIXELS = 40_000_000;
|
|
2800
|
+
/** 按 NFKC 和 Unicode 空白规范化名称,并校验 1–40 个字符及禁用字符。 */
|
|
2801
|
+
function normalizeBasicInfoName(raw) {
|
|
2802
|
+
if (typeof raw !== 'string')
|
|
2803
|
+
throw new Error('heybox.name 必须是字符串');
|
|
2804
|
+
// 与 Go TrimSpace 一致(JS trim 额外移除 BOM,故使用 Unicode White_Space)。
|
|
2805
|
+
const name = raw.normalize('NFKC').replace(/^\p{White_Space}+|\p{White_Space}+$/gu, '');
|
|
2806
|
+
if (Array.from(name).length < 1 || Array.from(name).length > 40 || /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}<>&"']/u.test(name) || !/[\p{L}\p{N}]/u.test(name)) {
|
|
2807
|
+
throw new Error('名称须包含 1–40 个字符及文字或数字,不能包含控制字符或 HTML 特殊字符');
|
|
2808
|
+
}
|
|
2809
|
+
return name;
|
|
2810
|
+
}
|
|
2811
|
+
function validateBasicInfoPath(raw) {
|
|
2812
|
+
if (typeof raw !== 'string' || !raw || /[\\:%?#\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(raw))
|
|
2813
|
+
throw new Error('资料图片必须是项目内的规范相对路径');
|
|
2814
|
+
const forbidden = new Set(['', '.', '..', '.git', 'node_modules', 'dist', '.hb-sdk', '.vite', '.cache', '.tmp', 'tmp']);
|
|
2815
|
+
if (raw.split('/').some((part) => forbidden.has(part)))
|
|
2816
|
+
throw new Error('资料图片路径包含禁止的目录');
|
|
2817
|
+
if (!/\.(png|jpe?g|gif|webp)$/i.test(raw))
|
|
2818
|
+
throw new Error('资料图片仅支持 JPEG、PNG、GIF、WebP');
|
|
2819
|
+
return raw;
|
|
2820
|
+
}
|
|
2821
|
+
/** 解析项目或构建资料;仅早于 0.8.2 且三项全部缺失时返回 undefined。 */
|
|
2822
|
+
function parseMiniappBasicInfo(value, sdkVersion = '0.8.2') {
|
|
2823
|
+
sdkVersion = sdkVersion.trim();
|
|
2824
|
+
if (!semverExports.valid(sdkVersion) || sdkVersion.startsWith('v') || sdkVersion.includes('+'))
|
|
2825
|
+
throw new Error('manifest.sdkVersion 必须是合法 SemVer');
|
|
2826
|
+
const fields = ['name', 'icon', 'coverImages'];
|
|
2827
|
+
const present = fields.filter((key) => Object.prototype.hasOwnProperty.call(value, key));
|
|
2828
|
+
if (!present.length && semverExports.lt(sdkVersion, '0.8.2'))
|
|
2829
|
+
return undefined;
|
|
2830
|
+
if (present.length !== fields.length)
|
|
2831
|
+
throw new Error('请配置 heybox.name、heybox.icon、heybox.coverImages,或运行 remote bind/dev/remote deploy 迁移已发布资料');
|
|
2832
|
+
const name = normalizeBasicInfoName(value.name);
|
|
2833
|
+
const icon = validateBasicInfoPath(value.icon);
|
|
2834
|
+
if (!Array.isArray(value.coverImages) || value.coverImages.length < 1 || value.coverImages.length > 5)
|
|
2835
|
+
throw new Error('coverImages 必须包含 1–5 张图片');
|
|
2836
|
+
const coverImages = value.coverImages.map(validateBasicInfoPath);
|
|
2837
|
+
if (new Set(coverImages).size !== coverImages.length)
|
|
2838
|
+
throw new Error('封面路径不能重复');
|
|
2839
|
+
return { name, icon, coverImages };
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
async function inspectBasicInfoImage(bytes, sourcePath, kind) {
|
|
2843
|
+
const inspected = await inspectDecodedBasicInfoImage(bytes, kind);
|
|
2844
|
+
const extensions = { jpg: ['jpg', 'jpeg'], png: ['png'], gif: ['gif'], webp: ['webp'] };
|
|
2845
|
+
const ext = sourcePath.split('.').pop().toLowerCase();
|
|
2846
|
+
if (!extensions[inspected.extension]?.includes(ext))
|
|
2847
|
+
throw new Error('资料图片格式与扩展名不一致');
|
|
2848
|
+
return inspected;
|
|
2849
|
+
}
|
|
2850
|
+
async function inspectDecodedBasicInfoImage(bytes, kind) {
|
|
2851
|
+
if (!bytes.length || bytes.length > BASIC_INFO_MAX_BYTES)
|
|
2852
|
+
throw new Error('资料图片不得为空或超过 10 MiB');
|
|
2853
|
+
const decoder = sharp(bytes, { limitInputPixels: BASIC_INFO_MAX_PIXELS, failOn: 'warning' });
|
|
2854
|
+
const metadata = await decoder.metadata();
|
|
2855
|
+
const extensions = { jpeg: 'jpg', png: 'png', gif: 'gif', webp: 'webp' };
|
|
2856
|
+
if (!metadata.format || !extensions[metadata.format])
|
|
2857
|
+
throw new Error('资料图片格式不支持');
|
|
2858
|
+
const width = metadata.width ?? 0;
|
|
2859
|
+
const height = metadata.pageHeight ?? metadata.height ?? 0;
|
|
2860
|
+
if (!width || !height || width * height > BASIC_INFO_MAX_PIXELS)
|
|
2861
|
+
throw new Error('资料图片不得超过 4000 万像素');
|
|
2862
|
+
if (kind === 'icon' && (width !== 200 || height !== 200))
|
|
2863
|
+
throw new Error('icon 必须是 200×200');
|
|
2864
|
+
// 强制解码而非只信任图片头;不将解码结果写回图片。
|
|
2865
|
+
await decoder.stats();
|
|
2866
|
+
return { sha256: node_crypto.createHash('sha256').update(bytes).digest('hex'), extension: extensions[metadata.format] };
|
|
2867
|
+
}
|
|
2868
|
+
async function readBasicInfoSource(projectRoot, relative, kind) {
|
|
2869
|
+
validateBasicInfoPath(relative);
|
|
2870
|
+
const root = await promises.realpath(projectRoot);
|
|
2871
|
+
const target = await promises.realpath(path.join(root, relative));
|
|
2872
|
+
const resolvedRelative = path.relative(root, target).split(path.sep).join('/');
|
|
2873
|
+
validateBasicInfoPath(resolvedRelative);
|
|
2874
|
+
if (resolvedRelative.startsWith('../') || path.isAbsolute(resolvedRelative))
|
|
2875
|
+
throw new Error('资料图片不能逃逸项目目录');
|
|
2876
|
+
const info = await promises.stat(target);
|
|
2877
|
+
if (!info.isFile() || info.size > BASIC_INFO_MAX_BYTES)
|
|
2878
|
+
throw new Error('资料图片必须为不超过 10 MiB 的普通文件');
|
|
2879
|
+
const file = await promises.open(target, node_fs.constants.O_RDONLY | node_fs.constants.O_NOFOLLOW);
|
|
2880
|
+
try {
|
|
2881
|
+
const actual = await file.stat();
|
|
2882
|
+
if (!actual.isFile() || actual.size > BASIC_INFO_MAX_BYTES)
|
|
2883
|
+
throw new Error('资料图片文件已变化');
|
|
2884
|
+
const bytes = await file.readFile();
|
|
2885
|
+
return { bytes, ...(await inspectBasicInfoImage(bytes, relative, kind)) };
|
|
2886
|
+
}
|
|
2887
|
+
finally {
|
|
2888
|
+
await file.close();
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
async function prepareMiniappBasicInfo(projectRoot, outputRoot, sdkVersion) {
|
|
2892
|
+
const pkg = JSON.parse(await promises.readFile(path.join(projectRoot, 'package.json'), 'utf8'));
|
|
2893
|
+
const profile = parseMiniappBasicInfo(pkg.heybox ?? {}, sdkVersion);
|
|
2894
|
+
if (!profile)
|
|
2895
|
+
return undefined;
|
|
2896
|
+
await promises.mkdir(outputRoot, { recursive: true });
|
|
2897
|
+
const output = { ...profile, coverImages: [] };
|
|
2898
|
+
const entries = [
|
|
2899
|
+
{ path: profile.icon, kind: 'icon', label: 'icon' },
|
|
2900
|
+
...profile.coverImages.map((p, i) => ({ path: p, kind: 'cover', label: `cover-${String(i + 1).padStart(2, '0')}` })),
|
|
2901
|
+
];
|
|
2902
|
+
for (const entry of entries) {
|
|
2903
|
+
const sourceReal = await promises.realpath(path.join(projectRoot, entry.path));
|
|
2904
|
+
const outputReal = await promises.realpath(outputRoot);
|
|
2905
|
+
const fromOutput = path.relative(outputReal, sourceReal);
|
|
2906
|
+
if (!fromOutput.startsWith('..') && !path.isAbsolute(fromOutput))
|
|
2907
|
+
throw new Error('资料源文件不能位于构建输出目录');
|
|
2908
|
+
const image = await readBasicInfoSource(projectRoot, entry.path, entry.kind);
|
|
2909
|
+
const relative = `_hb/basic-info/${entry.label}.${image.sha256.slice(0, 12)}.${image.extension}`;
|
|
2910
|
+
const target = path.join(outputRoot, relative);
|
|
2911
|
+
await promises.mkdir(outputRoot, { recursive: true });
|
|
2912
|
+
await ensureBasicInfoDirectory(outputRoot, '_hb/basic-info');
|
|
2913
|
+
// 全字节比较保证截断哈希冲突和 public 保留路径冲突不会静默覆盖。
|
|
2914
|
+
try {
|
|
2915
|
+
await promises.writeFile(target, image.bytes, { flag: 'wx' });
|
|
2916
|
+
}
|
|
2917
|
+
catch (error) {
|
|
2918
|
+
if (error.code !== 'EEXIST' ||
|
|
2919
|
+
!(await readBasicInfoSource(outputRoot, relative, entry.kind).then((existing) => existing.bytes.equals(image.bytes), () => false)))
|
|
2920
|
+
throw new Error('构建资料保留目录冲突,请移除未知文件');
|
|
2921
|
+
}
|
|
2922
|
+
if (entry.kind === 'icon')
|
|
2923
|
+
output.icon = relative;
|
|
2924
|
+
else
|
|
2925
|
+
output.coverImages.push(relative);
|
|
2926
|
+
}
|
|
2927
|
+
return output;
|
|
2928
|
+
}
|
|
2929
|
+
/** 逐级校验真实目录,避免保留目录或迁移目录中的符号链接将写入带到项目外。 */
|
|
2930
|
+
async function ensureBasicInfoDirectory(rootPath, relative) {
|
|
2931
|
+
const root = await promises.realpath(rootPath);
|
|
2932
|
+
let current = root;
|
|
2933
|
+
for (const part of relative.split('/')) {
|
|
2934
|
+
if (!part || part === '.' || part === '..')
|
|
2935
|
+
throw new Error('资料目录不合法');
|
|
2936
|
+
current = path.join(current, part);
|
|
2937
|
+
await promises.mkdir(current).catch((error) => {
|
|
2938
|
+
if (error.code !== 'EEXIST')
|
|
2939
|
+
throw error;
|
|
2940
|
+
});
|
|
2941
|
+
const resolved = await promises.realpath(current);
|
|
2942
|
+
const within = path.relative(root, resolved);
|
|
2943
|
+
if (within.startsWith('..') || path.isAbsolute(within))
|
|
2944
|
+
throw new Error('资料目录不能逃逸项目');
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
/** 构建时替换为当前发布包的实际版本。 */
|
|
2949
|
+
const HB_SDK_VERSION = typeof undefined === 'string'
|
|
2950
|
+
? undefined
|
|
2951
|
+
: '0.8.2-alpha.5';
|
|
2952
|
+
|
|
2953
|
+
/**
|
|
2954
|
+
* iframe 与调试台页面的开发期 console 捕获转发。
|
|
2955
|
+
*
|
|
2956
|
+
* 双条件门控(iframe 转发):
|
|
2957
|
+
* 1. 构建期:`hb-sdk dev`(vite serve)注入 `false`;`hb-sdk build` /
|
|
2958
|
+
* `vite build`(生产)永不注入或替换为 `false`,本模块被死代码消除。
|
|
2959
|
+
* 2. 运行期:仅在 `window.name` 为 `__hb_sdk_mini_dev__` 的调试 iframe 中激活,
|
|
2960
|
+
* 用户设备上的正式运行环境没有该标记。
|
|
2961
|
+
*
|
|
2962
|
+
* 无侵入 patch `console.log/debug/info/warn/error`:原样放行(DevTools 不受影响)+
|
|
2963
|
+
* 通过 `window.parent.postMessage` 转发给调试台(CSP 的 connect-src 不约束 postMessage,
|
|
2964
|
+
* 网络上报由调试台页面代理)。发送全程静默:失败不得回打 console,避免自触发死循环。
|
|
2965
|
+
*
|
|
2966
|
+
* 激活时机:由 SDK 默认单例初始化时调用(业务 `import '@heybox/hb-sdk'` 即生效);
|
|
2967
|
+
* iframe 标记由调试台 `iframe.name` 与 vite dev 注入的无依赖 bootstrap 共同设置。
|
|
2968
|
+
* 调试台页面自身(runtime 跑在这里,`[hb-sdk-runtime]` 日志不经过 iframe)另用
|
|
2969
|
+
* `installMiniDevConsoleSelfCapture` 接入「日志」页签(source: 'host')。
|
|
2970
|
+
*/
|
|
2971
|
+
const MINI_DEV_CONSOLE_EVENT_TYPE = 'hb-sdk:mini-dev-console';
|
|
2972
|
+
/** 调试台页面与 iframe bootstrap 共用的已安装标记(window/console 上的属性名)。 */
|
|
2973
|
+
const MINI_DEV_CONSOLE_INSTALL_FLAG = '__hb_sdk_mini_dev_console_installed__';
|
|
2974
|
+
|
|
2975
|
+
/**
|
|
2976
|
+
* network.request 诊断日志的纯脱敏工具。
|
|
2977
|
+
*
|
|
2978
|
+
* @remarks
|
|
2979
|
+
* 放在 `core` 而非 `modules/network`:无错误类型依赖,供 SDK 错误文案、
|
|
2980
|
+
* modules observability、devtools Browser Dev Host / proxy 共用,并满足 boundary
|
|
2981
|
+
*(devtools 不得直接 import runtime capability modules)。
|
|
2982
|
+
*/
|
|
2983
|
+
/** 敏感字段名:header / query / body key 共用。 */
|
|
2984
|
+
const SENSITIVE_KEY_PATTERN = /(authorization|cookie|password|passwd|secret|token|pkey|private[_-]?key|access[_-]?key|client[_-]?secret|api[_-]?key|apikey|session|jwt|refresh[_-]?token|csrf)/i;
|
|
2985
|
+
/**
|
|
2986
|
+
* 脱敏请求 URL:去掉 userinfo,并将 query / fragment 中的敏感参数值替换为 `[redacted]`。
|
|
2987
|
+
*/
|
|
2988
|
+
function sanitizeNetworkRequestUrl(url) {
|
|
2989
|
+
if (!url) {
|
|
2990
|
+
return url;
|
|
2991
|
+
}
|
|
2992
|
+
try {
|
|
2993
|
+
const parsed = new URL(url);
|
|
2994
|
+
parsed.username = '';
|
|
2995
|
+
parsed.password = '';
|
|
2996
|
+
redactUrlSearchParams(parsed.searchParams);
|
|
2997
|
+
if (parsed.hash && parsed.hash.length > 1) {
|
|
2998
|
+
const hashBody = parsed.hash.slice(1);
|
|
2999
|
+
if (hashBody.includes('=')) {
|
|
3000
|
+
const hashParams = new URLSearchParams(hashBody);
|
|
3001
|
+
if (redactUrlSearchParams(hashParams)) {
|
|
3002
|
+
parsed.hash = hashParams.toString();
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
return parsed.toString();
|
|
3007
|
+
}
|
|
3008
|
+
catch {
|
|
3009
|
+
return redactSensitivePairsInRawText(url);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
/**
|
|
3013
|
+
* 诊断 message 中的 URL 片段做脱敏,避免 error.message 二次泄漏 query secrets。
|
|
3014
|
+
*/
|
|
3015
|
+
function sanitizeDiagnosticMessage(message) {
|
|
3016
|
+
if (!message) {
|
|
3017
|
+
return message;
|
|
3018
|
+
}
|
|
3019
|
+
return message.replace(/https?:\/\/[^\s]+/gi, matched => sanitizeNetworkRequestUrl(matched.replace(/[),.;]+$/g, '')));
|
|
3020
|
+
}
|
|
3021
|
+
function isSensitiveNetworkKey(key) {
|
|
3022
|
+
return SENSITIVE_KEY_PATTERN.test(key.trim());
|
|
3023
|
+
}
|
|
3024
|
+
function redactUrlSearchParams(params) {
|
|
3025
|
+
let changed = false;
|
|
3026
|
+
for (const key of [...params.keys()]) {
|
|
3027
|
+
if (isSensitiveNetworkKey(key)) {
|
|
3028
|
+
params.set(key, '[redacted]');
|
|
3029
|
+
changed = true;
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
return changed;
|
|
3033
|
+
}
|
|
3034
|
+
function redactSensitivePairsInRawText(text) {
|
|
3035
|
+
return text.replace(/([?&#]|^|[?&])([^=&#\s]+)=([^&#\s]*)/g, (full, prefix, key, value) => {
|
|
3036
|
+
if (!isSensitiveNetworkKey(key)) {
|
|
3037
|
+
return full;
|
|
3038
|
+
}
|
|
3039
|
+
return `${prefix}${key}=[redacted]`;
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
const DEVICE_LOG_PATH = '/__hb_sdk__/device-logs';
|
|
3044
|
+
const registryKey = Symbol.for('@heybox/hb-sdk/device-logs');
|
|
3045
|
+
const registry = globalThis;
|
|
3046
|
+
const stores = (registry[registryKey] ??= new Map());
|
|
3047
|
+
function registerDeviceLogs(port, store) {
|
|
3048
|
+
stores.set(port, store);
|
|
3049
|
+
return () => {
|
|
3050
|
+
if (stores.get(port) === store)
|
|
3051
|
+
stores.delete(port);
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
class DeviceLogStore {
|
|
3055
|
+
sandboxRequests = new Map();
|
|
3056
|
+
listSandbox(id, path) {
|
|
3057
|
+
const device = this.devices.get(id);
|
|
3058
|
+
if (!device || Date.now() - device.lastSeen > 15000)
|
|
3059
|
+
return Promise.resolve({ error: '设备已离线' });
|
|
3060
|
+
if (path !== '.' && (!path || path.length > 1024 || /[\\\\\u0000-\u001f]/.test(path) || path.split('/').some(part => !part || part === '.' || part === '..')))
|
|
3061
|
+
return Promise.resolve({ error: '无效目录路径' });
|
|
3062
|
+
if (this.sandboxRequests.size >= 64)
|
|
3063
|
+
return Promise.resolve({ error: '查询繁忙,请稍后重试' });
|
|
3064
|
+
const requestId = node_crypto.randomUUID();
|
|
3065
|
+
return new Promise(resolve => {
|
|
3066
|
+
const timer = setTimeout(() => { this.sandboxRequests.delete(requestId); resolve({ error: '设备查询超时' }); }, 10000);
|
|
3067
|
+
this.sandboxRequests.set(requestId, { id, path, requestId, resolve: value => { clearTimeout(timer); this.sandboxRequests.delete(requestId); resolve(value); } });
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
uploadToken = node_crypto.randomBytes(32).toString('hex');
|
|
3071
|
+
epoch = node_crypto.randomUUID();
|
|
3072
|
+
expiresAt = Date.now() + 12 * 60 * 60 * 1000;
|
|
3073
|
+
cursor = 0;
|
|
3074
|
+
logs = [];
|
|
3075
|
+
devices = new Map();
|
|
3076
|
+
query(params) {
|
|
3077
|
+
const after = Math.max(0, Number(params.get('after')) || 0);
|
|
3078
|
+
const limit = Math.min(500, Math.max(1, Number(params.get('limit')) || 100));
|
|
3079
|
+
const selected = this.logs.filter((log) => log.cursor > after &&
|
|
3080
|
+
(!params.get('device') || log.deviceSessionId === params.get('device')) &&
|
|
3081
|
+
(!params.get('level') || log.level === params.get('level')) &&
|
|
3082
|
+
(!params.get('source') || log.source === params.get('source')) &&
|
|
3083
|
+
(!params.get('callId') || log.callId === params.get('callId')) &&
|
|
3084
|
+
(!params.get('id') || String(log.cursor) === params.get('id')) &&
|
|
3085
|
+
(!params.get('since') || log.timestamp >= Number(params.get('since'))) &&
|
|
3086
|
+
(!params.get('query') || log.message.toLowerCase().includes(params.get('query').toLowerCase())));
|
|
3087
|
+
const items = selected.slice(0, limit);
|
|
3088
|
+
return {
|
|
3089
|
+
schemaVersion: 1,
|
|
3090
|
+
epoch: this.epoch,
|
|
3091
|
+
items,
|
|
3092
|
+
nextCursor: selected.length > limit ? items.at(-1).cursor : this.cursor,
|
|
3093
|
+
hasMore: selected.length > limit,
|
|
3094
|
+
gap: after > 0 && after < (this.logs[0]?.cursor ?? 1) - 1,
|
|
3095
|
+
devices: [...this.devices.values()].map(({ id, name, platform, connectedAt, lastSeen }) => ({
|
|
3096
|
+
id,
|
|
3097
|
+
name,
|
|
3098
|
+
platform,
|
|
3099
|
+
connectedAt,
|
|
3100
|
+
lastSeen,
|
|
3101
|
+
online: Date.now() - lastSeen < 15000,
|
|
3102
|
+
})),
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
async upload(req, res) {
|
|
3106
|
+
const reply = (status, value) => {
|
|
3107
|
+
res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
3108
|
+
res.end(JSON.stringify(value));
|
|
3109
|
+
};
|
|
3110
|
+
if (req.method !== 'POST') {
|
|
3111
|
+
reply(405, {});
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
if (Date.now() > this.expiresAt) {
|
|
3115
|
+
reply(410, { code: 'EXPIRED' });
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
let text = '';
|
|
3119
|
+
try {
|
|
3120
|
+
req.setEncoding('utf8');
|
|
3121
|
+
for await (const chunk of req) {
|
|
3122
|
+
text += chunk;
|
|
3123
|
+
if (Buffer.byteLength(text) > 65536) {
|
|
3124
|
+
reply(413, {});
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
const body = JSON.parse(text);
|
|
3129
|
+
if (body.register === true) {
|
|
3130
|
+
if (req.headers['x-hb-log-token'] !== this.uploadToken) {
|
|
3131
|
+
reply(403, {});
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
const registrationId = typeof body.registrationId === 'string' && /^[a-f0-9]{32}$/.test(body.registrationId) ? body.registrationId : undefined;
|
|
3135
|
+
const existing = registrationId && [...this.devices.values()].find(device => device.registrationId === registrationId);
|
|
3136
|
+
if (existing) {
|
|
3137
|
+
reply(200, { id: existing.id, token: existing.token });
|
|
3138
|
+
return;
|
|
3139
|
+
}
|
|
3140
|
+
if (this.devices.size >= 64) {
|
|
3141
|
+
reply(429, {});
|
|
3142
|
+
return;
|
|
3143
|
+
}
|
|
3144
|
+
const id = node_crypto.randomUUID();
|
|
3145
|
+
const platform = ['iOS', 'Android', 'Windows', 'macOS', 'Browser'].includes(body.platform) ? body.platform : 'Browser';
|
|
3146
|
+
const device = {
|
|
3147
|
+
id,
|
|
3148
|
+
platform,
|
|
3149
|
+
registrationId,
|
|
3150
|
+
connectedAt: Date.now(),
|
|
3151
|
+
name: `${platform} · ${this.devices.size + 1}`,
|
|
3152
|
+
lastSeen: Date.now(),
|
|
3153
|
+
sequence: 0,
|
|
3154
|
+
token: node_crypto.randomBytes(32).toString('hex'),
|
|
3155
|
+
windowStart: Date.now(),
|
|
3156
|
+
count: 0,
|
|
3157
|
+
};
|
|
3158
|
+
this.devices.set(id, device);
|
|
3159
|
+
reply(200, { id, token: device.token });
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
const device = this.devices.get(body.id);
|
|
3163
|
+
if (!device || req.headers['x-hb-log-token'] !== device.token) {
|
|
3164
|
+
reply(403, {});
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
if (!Array.isArray(body.logs) || body.logs.length > 50) {
|
|
3168
|
+
reply(400, {});
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
if (Date.now() - device.windowStart > 1000) {
|
|
3172
|
+
device.windowStart = Date.now();
|
|
3173
|
+
device.count = 0;
|
|
3174
|
+
}
|
|
3175
|
+
if (device.count + body.logs.length > 200) {
|
|
3176
|
+
reply(429, {});
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
device.count += body.logs.length;
|
|
3180
|
+
device.lastSeen = Date.now();
|
|
3181
|
+
if (body.sandboxResult && typeof body.sandboxResult === 'object') {
|
|
3182
|
+
const result = body.sandboxResult;
|
|
3183
|
+
const pending = this.sandboxRequests.get(result.requestId);
|
|
3184
|
+
if (pending?.id === device.id) {
|
|
3185
|
+
const entries = Array.isArray(result.entries) ? result.entries.slice(0, 200).filter((entry) => entry && ['file', 'directory'].includes(entry.kind) && typeof entry.name === 'string' && entry.name.length <= 255 && !/[\\\\/\u0000-\u001f]/.test(entry.name) && !['.', '..'].includes(entry.name)).map((entry) => ({ name: entry.name, kind: entry.kind })) : [];
|
|
3186
|
+
pending.resolve({ deviceSessionId: device.id, requestId: pending.requestId, path: pending.path, timestamp: Date.now(), entries, error: result.error ? '设备不支持或无权读取此目录' : undefined, truncated: result.truncated === true });
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
for (const log of body.logs) {
|
|
3190
|
+
if (!log || typeof log !== 'object')
|
|
3191
|
+
continue;
|
|
3192
|
+
if (!Number.isSafeInteger(log.sequence) ||
|
|
3193
|
+
log.sequence <= device.sequence ||
|
|
3194
|
+
!Number.isFinite(log.timestamp) ||
|
|
3195
|
+
!['log', 'debug', 'info', 'warn', 'error'].includes(log.level) ||
|
|
3196
|
+
typeof log.message !== 'string')
|
|
3197
|
+
continue;
|
|
3198
|
+
device.sequence = log.sequence;
|
|
3199
|
+
this.logs.push({
|
|
3200
|
+
cursor: ++this.cursor,
|
|
3201
|
+
deviceSessionId: device.id,
|
|
3202
|
+
sequence: log.sequence,
|
|
3203
|
+
timestamp: log.timestamp,
|
|
3204
|
+
receivedAt: Date.now(),
|
|
3205
|
+
level: log.level,
|
|
3206
|
+
source: log.source === 'sdk' ? 'sdk' : 'app',
|
|
3207
|
+
message: sanitizeDiagnosticMessage(log.message)
|
|
3208
|
+
.replace(/(authorization|cookie|password|secret|token|pkey|api[_-]?key)(["'\s:=]+)([^\s,}]+)/gi, '$1$2[redacted]')
|
|
3209
|
+
.slice(0, 8192),
|
|
3210
|
+
truncated: log.truncated === true || log.message.length > 8192,
|
|
3211
|
+
...(typeof log.method === 'string' ? { method: log.method.slice(0, 100) } : {}),
|
|
3212
|
+
...(typeof log.callId === 'string' ? { callId: log.callId.slice(0, 100) } : {}),
|
|
3213
|
+
...(typeof log.errorCode === 'string' && /^[A-Z0-9_]{1,100}$/.test(log.errorCode) ? { errorCode: log.errorCode } : {}),
|
|
3214
|
+
...(log.outcome === 'success' || log.outcome === 'error' ? { outcome: log.outcome } : {}),
|
|
3215
|
+
...(Number.isFinite(log.durationMs) ? { durationMs: Math.max(0, log.durationMs) } : {}),
|
|
3216
|
+
});
|
|
3217
|
+
}
|
|
3218
|
+
if (this.logs.length > 5000)
|
|
3219
|
+
this.logs.splice(0, this.logs.length - 5000);
|
|
3220
|
+
reply(200, { sequence: device.sequence, sandboxRequest: [...this.sandboxRequests.values()].find(request => request.id === device.id) ? (() => { const request = [...this.sandboxRequests.values()].find(request => request.id === device.id); return { requestId: request.requestId, path: request.path }; })() : undefined });
|
|
3221
|
+
}
|
|
3222
|
+
catch {
|
|
3223
|
+
reply(400, { code: 'INVALID_LOG_BATCH' });
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
/** 同源开发页面使用有界批量回传;传输失败不会递归写入 console。 */
|
|
3228
|
+
function deviceLogBootstrap(token) {
|
|
3229
|
+
return `<script>(function(){if(window.__hb_sdk_device_logs_installed__)return;
|
|
3230
|
+
window.__hb_sdk_device_logs_installed__=true;
|
|
3231
|
+
window.__hb_sdk_mini_dev_console_installed__=true;
|
|
3232
|
+
var endpoint='${DEVICE_LOG_PATH}',credential='${token}',id='',queue=[],seq=0,busy=false,dropped=0;
|
|
3233
|
+
var registrationId=Array.from(crypto.getRandomValues(new Uint8Array(16)),function(n){return n.toString(16).padStart(2,'0')}).join('');
|
|
3234
|
+
var send=window.fetch.bind(window);
|
|
3235
|
+
var sandboxResult;
|
|
3236
|
+
function format(v){try{return typeof v==='string'?v:v instanceof Error?v.stack||v.message:JSON.stringify(v,function(k,value){return /authorization|cookie|password|secret|token|pkey|api.?key/i.test(k)?'[redacted]':value})}catch(e){return '[无法序列化]'}}function add(level,args,source){if(queue.length>=500){queue.shift();
|
|
3237
|
+
dropped++}var message=args.map(format).join(' ');
|
|
3238
|
+
message=message.replace(/https?:\\/\\/[^\\s]+/g,function(raw){try{var url=new URL(raw);
|
|
3239
|
+
url.username='';
|
|
3240
|
+
url.password='';
|
|
3241
|
+
url.search='';
|
|
3242
|
+
url.hash='';
|
|
3243
|
+
return url.href}catch(e){return '[URL]'}});
|
|
3244
|
+
message=message.replace(/(authorization|cookie|password|secret|token|pkey|api[_-]?key)([\\"'\\s:=]+)([^\\s,}]+)/gi,'$1$2[redacted]');
|
|
3245
|
+
queue.push({truncated:message.length>8192,sequence:++seq,timestamp:Date.now(),level:level,source:source||'app',message:message.slice(0,8192)})}window.__hbSdkDeviceLog=function(event){add(event.outcome==='error'?'error':'info',[event.method+' · '+event.outcome+' · '+event.durationMs+' ms'],'sdk');
|
|
3246
|
+
Object.assign(queue[queue.length-1],event)};
|
|
3247
|
+
['log','debug','info','warn','error'].forEach(function(level){var original=console[level];
|
|
3248
|
+
console[level]=function(){add(level,Array.prototype.slice.call(arguments));
|
|
3249
|
+
return original.apply(this,arguments)}});
|
|
3250
|
+
window.addEventListener('error',function(e){add('error',[e.message])});
|
|
3251
|
+
window.addEventListener('unhandledrejection',function(e){add('error',[e.reason])});
|
|
3252
|
+
async function flush(){if(busy)return;
|
|
3253
|
+
busy=true;
|
|
3254
|
+
try{if(!id){var ua=navigator.userAgent;
|
|
3255
|
+
var platform=window.name==='__hb_sdk_mini_dev__'?'Browser':/Android/i.test(ua)?'Android':/iPhone|iPad/i.test(ua)?'iOS':/Windows/i.test(ua)?'Windows':/Mac/i.test(ua)?'macOS':'Browser';
|
|
3256
|
+
var r=await send(endpoint,{method:'POST',headers:{'content-type':'application/json','x-hb-log-token':credential},body:JSON.stringify({register:true,platform:platform,registrationId:registrationId}),signal:AbortSignal.timeout(5000)});
|
|
3257
|
+
if(!r.ok)return;
|
|
3258
|
+
var d=await r.json();
|
|
3259
|
+
id=d.id;
|
|
3260
|
+
credential=d.token}if(dropped){add('warn',['日志缓冲已丢弃 '+dropped+' 条']);
|
|
3261
|
+
dropped=0}var batch=queue.slice(0,50);
|
|
3262
|
+
while(batch.length>1&&new TextEncoder().encode(JSON.stringify(batch)).length>48000)batch.pop();
|
|
3263
|
+
var result=await send(endpoint,{method:'POST',headers:{'content-type':'application/json','x-hb-log-token':credential},body:JSON.stringify({id:id,logs:batch,sandboxResult:sandboxResult}),signal:AbortSignal.timeout(5000)});
|
|
3264
|
+
if(result.ok){var ack=await result.json();sandboxResult=undefined;
|
|
3265
|
+
if(ack.sandboxRequest){try{var entries=await window.__hbSdkSandboxList(ack.sandboxRequest.path);sandboxResult={requestId:ack.sandboxRequest.requestId,entries:entries.slice(0,200),truncated:entries.length>200}}catch(e){sandboxResult={requestId:ack.sandboxRequest.requestId,error:true}}}
|
|
3266
|
+
queue=queue.filter(function(l){return l.sequence>ack.sequence})}}catch(e){}finally{busy=false}}setInterval(flush,1000);
|
|
3267
|
+
flush()})();
|
|
3268
|
+
</script>`;
|
|
3269
|
+
}
|
|
3118
3270
|
|
|
3119
3271
|
/** 单个小程序版本最多声明的关联游戏数量。 */
|
|
3120
3272
|
const MINIAPP_BIND_APP_IDS_MAX_COUNT = 20;
|
|
@@ -3457,6 +3609,7 @@ function renderMiniappManifest(manifest) {
|
|
|
3457
3609
|
const output = {
|
|
3458
3610
|
version: manifest.version,
|
|
3459
3611
|
sdkVersion: manifest.sdkVersion,
|
|
3612
|
+
...(manifest.name === undefined ? {} : { name: manifest.name, icon: manifest.icon, coverImages: manifest.coverImages }),
|
|
3460
3613
|
platforms,
|
|
3461
3614
|
...(manifest.permissions === undefined ? {} : { permissions: manifest.permissions }),
|
|
3462
3615
|
...(manifest.companions === undefined ? {} : { companions: manifest.companions }),
|
|
@@ -12822,29 +12975,29 @@ function renderRuntimeGateScript() {
|
|
|
12822
12975
|
function removePlatformGateNodes(node) {
|
|
12823
12976
|
if (!('childNodes' in node))
|
|
12824
12977
|
return;
|
|
12825
|
-
node.childNodes = node.childNodes.filter(child => !('tagName' in child) || !hasAttr(child, RUNTIME_GATE_MARKER));
|
|
12978
|
+
node.childNodes = node.childNodes.filter((child) => !('tagName' in child) || !hasAttr(child, RUNTIME_GATE_MARKER));
|
|
12826
12979
|
for (const child of node.childNodes)
|
|
12827
12980
|
removePlatformGateNodes(child);
|
|
12828
12981
|
}
|
|
12829
12982
|
function readAttr(element, name) {
|
|
12830
|
-
return element.attrs.find(attr => attr.name.toLowerCase() === name)?.value;
|
|
12983
|
+
return element.attrs.find((attr) => attr.name.toLowerCase() === name)?.value;
|
|
12831
12984
|
}
|
|
12832
12985
|
function hasAttr(element, name) {
|
|
12833
12986
|
return readAttr(element, name) !== undefined;
|
|
12834
12987
|
}
|
|
12835
12988
|
function setAttr(element, name, value) {
|
|
12836
|
-
const attr = element.attrs.find(item => item.name.toLowerCase() === name);
|
|
12989
|
+
const attr = element.attrs.find((item) => item.name.toLowerCase() === name);
|
|
12837
12990
|
if (attr)
|
|
12838
12991
|
attr.value = value;
|
|
12839
12992
|
else
|
|
12840
12993
|
element.attrs.push({ name, value });
|
|
12841
12994
|
}
|
|
12842
12995
|
function removeAttr(element, name) {
|
|
12843
|
-
element.attrs = element.attrs.filter(attr => attr.name.toLowerCase() !== name);
|
|
12996
|
+
element.attrs = element.attrs.filter((attr) => attr.name.toLowerCase() !== name);
|
|
12844
12997
|
}
|
|
12845
12998
|
function findElement$1(node, tagName) {
|
|
12846
12999
|
let found;
|
|
12847
|
-
walk$1(node, element => {
|
|
13000
|
+
walk$1(node, (element) => {
|
|
12848
13001
|
if (!found && element.tagName === tagName)
|
|
12849
13002
|
found = element;
|
|
12850
13003
|
});
|
|
@@ -12863,10 +13016,7 @@ function walk$1(node, visit) {
|
|
|
12863
13016
|
const FORBIDDEN_RESOURCE_HINTS = new Set(['dns-prefetch', 'preconnect', 'prerender']);
|
|
12864
13017
|
const RELATIVE_URL_BASE = new URL('https://heybox-relative.invalid/');
|
|
12865
13018
|
const OFFICIAL_RESOURCE_ROOT_DOMAINS = ['xiaoheihe.cn', 'max-c.com', 'debugmode.cn', 'maxjia.com'];
|
|
12866
|
-
const OFFICIAL_RESOURCE_SOURCES = OFFICIAL_RESOURCE_ROOT_DOMAINS.flatMap(rootDomain => [
|
|
12867
|
-
`https://${rootDomain}`,
|
|
12868
|
-
`https://*.${rootDomain}`,
|
|
12869
|
-
]);
|
|
13019
|
+
const OFFICIAL_RESOURCE_SOURCES = OFFICIAL_RESOURCE_ROOT_DOMAINS.flatMap((rootDomain) => [`https://${rootDomain}`, `https://*.${rootDomain}`]);
|
|
12870
13020
|
const URL_ATTRIBUTES = {
|
|
12871
13021
|
audio: ['src'],
|
|
12872
13022
|
embed: ['src'],
|
|
@@ -12903,14 +13053,12 @@ function enforceMiniappHtmlPolicy(html, options = {}) {
|
|
|
12903
13053
|
throw new Error('入口 index.html 必须包含 <head>');
|
|
12904
13054
|
}
|
|
12905
13055
|
removePlatformCspMarkers(document);
|
|
12906
|
-
walk(document, element => validateElement(element));
|
|
13056
|
+
walk(document, (element) => validateElement(element));
|
|
12907
13057
|
injectMiniappRuntimeGate(document);
|
|
12908
13058
|
if (options.skipPlatformCsp) {
|
|
12909
13059
|
return serialize(document);
|
|
12910
13060
|
}
|
|
12911
|
-
const content = options.hmrWebSocketUrl
|
|
12912
|
-
? PRODUCTION_CSP.replace("connect-src 'none'", `connect-src ${options.hmrWebSocketUrl}`)
|
|
12913
|
-
: PRODUCTION_CSP;
|
|
13061
|
+
const content = options.hmrWebSocketUrl ? PRODUCTION_CSP.replace("connect-src 'none'", `connect-src ${options.hmrWebSocketUrl}`) : PRODUCTION_CSP;
|
|
12914
13062
|
head.childNodes.unshift({
|
|
12915
13063
|
nodeName: 'meta',
|
|
12916
13064
|
tagName: 'meta',
|
|
@@ -12928,7 +13076,7 @@ function enforceMiniappHtmlPolicy(html, options = {}) {
|
|
|
12928
13076
|
}
|
|
12929
13077
|
function validateElement(element) {
|
|
12930
13078
|
const tagName = element.tagName.toLowerCase();
|
|
12931
|
-
const attrs = new Map(element.attrs.map(attr => [attr.name.toLowerCase(), attr.value]));
|
|
13079
|
+
const attrs = new Map(element.attrs.map((attr) => [attr.name.toLowerCase(), attr.value]));
|
|
12932
13080
|
if (tagName === 'meta' && attrs.get('http-equiv')?.toLowerCase() === 'refresh') {
|
|
12933
13081
|
throw new Error('index.html 不允许 meta refresh');
|
|
12934
13082
|
}
|
|
@@ -12940,7 +13088,7 @@ function validateElement(element) {
|
|
|
12940
13088
|
}
|
|
12941
13089
|
if (tagName === 'link') {
|
|
12942
13090
|
const rels = (attrs.get('rel') ?? '').toLowerCase().split(/\s+/).filter(Boolean);
|
|
12943
|
-
const forbidden = rels.find(rel => FORBIDDEN_RESOURCE_HINTS.has(rel));
|
|
13091
|
+
const forbidden = rels.find((rel) => FORBIDDEN_RESOURCE_HINTS.has(rel));
|
|
12944
13092
|
if (forbidden) {
|
|
12945
13093
|
throw new Error(`index.html 不允许 rel="${forbidden}"`);
|
|
12946
13094
|
}
|
|
@@ -12957,7 +13105,10 @@ function validateElement(element) {
|
|
|
12957
13105
|
for (const attribute of URL_ATTRIBUTES[tagName] ?? []) {
|
|
12958
13106
|
const value = attrs.get(attribute);
|
|
12959
13107
|
if (attribute === 'srcset') {
|
|
12960
|
-
for (const candidate of (value ?? '')
|
|
13108
|
+
for (const candidate of (value ?? '')
|
|
13109
|
+
.split(',')
|
|
13110
|
+
.map((item) => item.trim().split(/\s+/)[0])
|
|
13111
|
+
.filter(Boolean)) {
|
|
12961
13112
|
validateResourceUrl(candidate, `<${tagName} ${attribute}>`, true, supportsOfficialResourceUrl(tagName, attribute));
|
|
12962
13113
|
}
|
|
12963
13114
|
}
|
|
@@ -12969,10 +13120,10 @@ function validateElement(element) {
|
|
|
12969
13120
|
function removePlatformCspMarkers(node) {
|
|
12970
13121
|
if (!('childNodes' in node))
|
|
12971
13122
|
return;
|
|
12972
|
-
node.childNodes = node.childNodes.filter(child => {
|
|
13123
|
+
node.childNodes = node.childNodes.filter((child) => {
|
|
12973
13124
|
if (!('tagName' in child))
|
|
12974
13125
|
return true;
|
|
12975
|
-
return !child.attrs.some(attr => attr.name.toLowerCase() === 'data-heybox-platform-csp');
|
|
13126
|
+
return !child.attrs.some((attr) => attr.name.toLowerCase() === 'data-heybox-platform-csp');
|
|
12976
13127
|
});
|
|
12977
13128
|
for (const child of node.childNodes)
|
|
12978
13129
|
removePlatformCspMarkers(child);
|
|
@@ -13008,7 +13159,7 @@ function isOfficialResourceUrl(value) {
|
|
|
13008
13159
|
const hostname = url.hostname.toLowerCase();
|
|
13009
13160
|
if (hostname.endsWith('.'))
|
|
13010
13161
|
return false;
|
|
13011
|
-
return OFFICIAL_RESOURCE_ROOT_DOMAINS.some(rootDomain => hostname === rootDomain || hostname.endsWith(`.${rootDomain}`));
|
|
13162
|
+
return OFFICIAL_RESOURCE_ROOT_DOMAINS.some((rootDomain) => hostname === rootDomain || hostname.endsWith(`.${rootDomain}`));
|
|
13012
13163
|
}
|
|
13013
13164
|
catch {
|
|
13014
13165
|
return false;
|
|
@@ -13033,7 +13184,7 @@ function isRelativeUrl(value, normalized) {
|
|
|
13033
13184
|
}
|
|
13034
13185
|
function findElement(node, tagName) {
|
|
13035
13186
|
let found;
|
|
13036
|
-
walk(node, element => {
|
|
13187
|
+
walk(node, (element) => {
|
|
13037
13188
|
if (!found && element.tagName === tagName)
|
|
13038
13189
|
found = element;
|
|
13039
13190
|
});
|
|
@@ -13540,10 +13691,12 @@ function miniappManifest() {
|
|
|
13540
13691
|
throw new Error('构建产物必须包含入口 dist/index.html');
|
|
13541
13692
|
}
|
|
13542
13693
|
node_fs.writeFileSync(indexHtmlPath, enforceMiniappHtmlPolicy(node_fs.readFileSync(indexHtmlPath, 'utf8'), { skipPlatformCsp }));
|
|
13694
|
+
const basicInfo = await prepareMiniappBasicInfo(companionConfig.projectRoot, outputRoot, sdkVersion);
|
|
13543
13695
|
const manifestPath = path.join(outputRoot, 'manifest.json');
|
|
13544
13696
|
const companions = prepareMiniappCompanions(companionDeclarations, { root: companionConfig.projectRoot, outputRoot, platforms });
|
|
13545
13697
|
node_fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
13546
13698
|
node_fs.writeFileSync(manifestPath, renderMiniappManifest({
|
|
13699
|
+
...basicInfo,
|
|
13547
13700
|
version,
|
|
13548
13701
|
sdkVersion,
|
|
13549
13702
|
platforms,
|