@heybox/hb-sdk 0.8.2-alpha.1 → 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.
Files changed (76) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/README.md +5 -1
  3. package/dist/cli-chunks/{build-DFwk1Xza.cjs → build-D2T3ky_9.cjs} +5 -5
  4. package/dist/cli-chunks/{context-DPwjjbIf.cjs → context-DEgmdpSL.cjs} +326 -104
  5. package/dist/cli-chunks/{create-Cbk708wm.cjs → create-DKqQhrG5.cjs} +5 -1
  6. package/dist/cli-chunks/{dev-B1dCAh3V.cjs → dev-BEXM7cmq.cjs} +17 -9
  7. package/dist/cli-chunks/{doctor-KX6cHAbM.cjs → doctor-B7UixGrd.cjs} +1 -1
  8. package/dist/cli-chunks/{index-B5GNdZS6.cjs → index-BtwfuoWI.cjs} +15 -17
  9. package/dist/cli-chunks/{index-D1CfvRqX.cjs → index-C69OtRug.cjs} +2 -2
  10. package/dist/cli-chunks/{index.esm-BvuovqBQ.cjs → index.esm-C86KlVdK.cjs} +11 -10
  11. package/dist/cli-chunks/{login-CqdIa3XD.cjs → login-Bp_HMx-i.cjs} +2 -2
  12. package/dist/cli-chunks/{project-vite-Bw3UJTnC.cjs → project-vite-DZxipd13.cjs} +1 -1
  13. package/dist/cli-chunks/{remote-t0V-fuAW.cjs → remote-B_sVsbQP.cjs} +80 -60
  14. package/dist/cli-chunks/runtime-gate-GR9jZd37.cjs +58 -0
  15. package/dist/cli-chunks/{runtime-gate-BWb8QIcX.cjs → runtime-permission-env-COWketgE.cjs} +4533 -40
  16. package/dist/cli-chunks/{session-DFY2dygN.cjs → session-6-AApcTZ.cjs} +1 -1
  17. package/dist/cli-chunks/{skill-D3xHcK8w.cjs → skill-CPDLk7t7.cjs} +2 -2
  18. package/dist/cli-chunks/{version-DkVq4CXN.cjs → version-hHrSPRwW.cjs} +1 -1
  19. package/dist/cli.cjs +1 -1
  20. package/dist/devtools/browser-dev-host/assets/browser-dev-host-CpwQQhA1.js +101 -0
  21. package/dist/devtools/browser-dev-host/assets/{desktop-app-launch-8J62MOi_.js → desktop-app-launch-BdWBOIvY.js} +1 -1
  22. package/dist/devtools/browser-dev-host/assets/{index-CrsPiYXy.js → index-BQSEynx6.js} +3 -3
  23. package/dist/devtools/browser-dev-host/index.html +2 -2
  24. package/dist/index.cjs.js +946 -42
  25. package/dist/index.esm.js +946 -42
  26. package/dist/miniapp-publish.cjs.js +9410 -122
  27. package/dist/miniapp-publish.esm.js +9406 -123
  28. package/dist/protocol.cjs.js +805 -0
  29. package/dist/protocol.esm.js +805 -0
  30. package/dist/templates/vanilla-vite-js/assets/heybox-placeholder.png +0 -0
  31. package/dist/templates/vanilla-vite-js/package.json.ejs +3 -0
  32. package/dist/vite.cjs.js +843 -507
  33. package/dist/vite.esm.js +844 -508
  34. package/package.json +8 -7
  35. package/skill/SKILL.md +5 -1
  36. package/skill/references/api-root.md +194 -9
  37. package/skill/references/cli.md +3 -3
  38. package/skill/references/recipes.md +43 -4
  39. package/skill/skill.json +5 -5
  40. package/types/core/errors.d.ts +33 -9
  41. package/types/core/handshake-state.d.ts +32 -2
  42. package/types/core/singleton.d.ts +265 -6
  43. package/types/miniapp-manifest/basic-info-browser.d.ts +9 -0
  44. package/types/miniapp-manifest/basic-info-image-header.d.ts +6 -0
  45. package/types/miniapp-manifest/basic-info-node.d.ts +17 -0
  46. package/types/miniapp-manifest/basic-info-placeholder.d.ts +1 -0
  47. package/types/miniapp-manifest/basic-info.d.ts +12 -0
  48. package/types/miniapp-manifest/bindings.d.ts +65 -0
  49. package/types/miniapp-manifest/schema.d.ts +143 -20
  50. package/types/miniapp-publish/index.d.ts +433 -29
  51. package/types/modules/auth/index.d.ts +159 -5
  52. package/types/modules/cloud/index.d.ts +514 -40
  53. package/types/modules/companion/index.d.ts +1518 -17
  54. package/types/modules/device/index.d.ts +140 -9
  55. package/types/modules/environment/index.d.ts +172 -4
  56. package/types/modules/files/types.d.ts +1396 -21
  57. package/types/modules/navigation/app-pages/types.d.ts +81 -24
  58. package/types/modules/navigation/index.d.ts +230 -7
  59. package/types/modules/network/index.d.ts +297 -26
  60. package/types/modules/share/copy-link.d.ts +35 -5
  61. package/types/modules/share/index.d.ts +210 -0
  62. package/types/modules/share/screenshot.d.ts +23 -5
  63. package/types/modules/share/show-share-menu.d.ts +18 -5
  64. package/types/modules/share/types.d.ts +148 -23
  65. package/types/modules/storage/index.d.ts +139 -9
  66. package/types/modules/ui/index.d.ts +249 -18
  67. package/types/modules/user/get-info.d.ts +29 -8
  68. package/types/modules/user/index.d.ts +157 -0
  69. package/types/modules/user/revoke-authorization.d.ts +26 -2
  70. package/types/modules/user/steam-game-list.d.ts +96 -15
  71. package/types/modules/viewport/index.d.ts +158 -22
  72. package/types/protocol/types.d.ts +19 -1
  73. package/types/vite/index.d.ts +159 -1
  74. package/dist/cli-chunks/runtime-permission-env-BgYjqHIE.cjs +0 -3942
  75. package/dist/devtools/browser-dev-host/assets/browser-dev-host-DqPIIvha.js +0 -101
  76. /package/types/{vite → miniapp-manifest}/runtime-gate.d.ts +0 -0
package/dist/vite.cjs.js CHANGED
@@ -1,400 +1,79 @@
1
1
  'use strict';
2
2
 
3
- var node_fs = require('node:fs');
4
3
  var node_crypto = require('node:crypto');
5
- var node_os = require('node:os');
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.1';
13
+ var re = {exports: {}};
15
14
 
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__';
15
+ var constants;
16
+ var hasRequiredConstants;
37
17
 
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
- });
18
+ function requireConstants () {
19
+ if (hasRequiredConstants) return constants;
20
+ hasRequiredConstants = 1;
21
+
22
+ // Note: this is the semver.org version of the spec that it implements
23
+ // Not necessarily the package version of this code.
24
+ const SEMVER_SPEC_VERSION = '2.0.0';
25
+
26
+ const MAX_LENGTH = 256;
27
+ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||
28
+ /* istanbul ignore next */ 9007199254740991;
29
+
30
+ // Max safe segment length for coercion.
31
+ const MAX_SAFE_COMPONENT_LENGTH = 16;
32
+
33
+ // Max safe length for a build identifier. The max length minus 6 characters for
34
+ // the shortest version with a build 0.0.0+BUILD.
35
+ const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6;
36
+
37
+ const RELEASE_TYPES = [
38
+ 'major',
39
+ 'premajor',
40
+ 'minor',
41
+ 'preminor',
42
+ 'patch',
43
+ 'prepatch',
44
+ 'prerelease',
45
+ ];
46
+
47
+ constants = {
48
+ MAX_LENGTH,
49
+ MAX_SAFE_COMPONENT_LENGTH,
50
+ MAX_SAFE_BUILD_LENGTH,
51
+ MAX_SAFE_INTEGER,
52
+ RELEASE_TYPES,
53
+ SEMVER_SPEC_VERSION,
54
+ FLAG_INCLUDE_PRERELEASE: 0b001,
55
+ FLAG_LOOSE: 0b010,
56
+ };
57
+ return constants;
104
58
  }
105
59
 
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
- var re = {exports: {}};
335
-
336
- var constants;
337
- var hasRequiredConstants;
338
-
339
- function requireConstants () {
340
- if (hasRequiredConstants) return constants;
341
- hasRequiredConstants = 1;
342
-
343
- // Note: this is the semver.org version of the spec that it implements
344
- // Not necessarily the package version of this code.
345
- const SEMVER_SPEC_VERSION = '2.0.0';
346
-
347
- const MAX_LENGTH = 256;
348
- const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||
349
- /* istanbul ignore next */ 9007199254740991;
350
-
351
- // Max safe segment length for coercion.
352
- const MAX_SAFE_COMPONENT_LENGTH = 16;
353
-
354
- // Max safe length for a build identifier. The max length minus 6 characters for
355
- // the shortest version with a build 0.0.0+BUILD.
356
- const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6;
357
-
358
- const RELEASE_TYPES = [
359
- 'major',
360
- 'premajor',
361
- 'minor',
362
- 'preminor',
363
- 'patch',
364
- 'prepatch',
365
- 'prerelease',
366
- ];
367
-
368
- constants = {
369
- MAX_LENGTH,
370
- MAX_SAFE_COMPONENT_LENGTH,
371
- MAX_SAFE_BUILD_LENGTH,
372
- MAX_SAFE_INTEGER,
373
- RELEASE_TYPES,
374
- SEMVER_SPEC_VERSION,
375
- FLAG_INCLUDE_PRERELEASE: 0b001,
376
- FLAG_LOOSE: 0b010,
377
- };
378
- return constants;
379
- }
380
-
381
- var debug_1;
382
- var hasRequiredDebug;
383
-
384
- function requireDebug () {
385
- if (hasRequiredDebug) return debug_1;
386
- hasRequiredDebug = 1;
387
-
388
- const debug = (
389
- typeof process === 'object' &&
390
- process.env &&
391
- process.env.NODE_DEBUG &&
392
- /\bsemver\b/i.test(process.env.NODE_DEBUG)
393
- ) ? (...args) => console.error('SEMVER', ...args)
394
- : () => {};
395
-
396
- debug_1 = debug;
397
- return debug_1;
60
+ var debug_1;
61
+ var hasRequiredDebug;
62
+
63
+ function requireDebug () {
64
+ if (hasRequiredDebug) return debug_1;
65
+ hasRequiredDebug = 1;
66
+
67
+ const debug = (
68
+ typeof process === 'object' &&
69
+ process.env &&
70
+ process.env.NODE_DEBUG &&
71
+ /\bsemver\b/i.test(process.env.NODE_DEBUG)
72
+ ) ? (...args) => console.error('SEMVER', ...args)
73
+ : () => {};
74
+
75
+ debug_1 = debug;
76
+ return debug_1;
398
77
  }
399
78
 
400
79
  var hasRequiredRe;
@@ -3012,111 +2691,635 @@ function requireSubset () {
3012
2691
  subset_1 = subset;
3013
2692
  return subset_1;
3014
2693
  }
3015
-
3016
- var semver;
3017
- var hasRequiredSemver;
3018
-
3019
- function requireSemver () {
3020
- if (hasRequiredSemver) return semver;
3021
- hasRequiredSemver = 1;
3022
-
3023
- // just pre-load all the stuff that index.js lazily exports
3024
- const internalRe = requireRe();
3025
- const constants = requireConstants();
3026
- const SemVer = requireSemver$1();
3027
- const identifiers = requireIdentifiers();
3028
- const parse = requireParse();
3029
- const valid = requireValid$1();
3030
- const clean = requireClean();
3031
- const inc = requireInc();
3032
- const diff = requireDiff();
3033
- const major = requireMajor();
3034
- const minor = requireMinor();
3035
- const patch = requirePatch();
3036
- const prerelease = requirePrerelease();
3037
- const compare = requireCompare();
3038
- const rcompare = requireRcompare();
3039
- const compareLoose = requireCompareLoose();
3040
- const compareBuild = requireCompareBuild();
3041
- const sort = requireSort();
3042
- const rsort = requireRsort();
3043
- const gt = requireGt();
3044
- const lt = requireLt();
3045
- const eq = requireEq();
3046
- const neq = requireNeq();
3047
- const gte = requireGte();
3048
- const lte = requireLte();
3049
- const cmp = requireCmp();
3050
- const coerce = requireCoerce();
3051
- const truncate = requireTruncate();
3052
- const Comparator = requireComparator();
3053
- const Range = requireRange();
3054
- const satisfies = requireSatisfies();
3055
- const toComparators = requireToComparators();
3056
- const maxSatisfying = requireMaxSatisfying();
3057
- const minSatisfying = requireMinSatisfying();
3058
- const minVersion = requireMinVersion();
3059
- const validRange = requireValid();
3060
- const outside = requireOutside();
3061
- const gtr = requireGtr();
3062
- const ltr = requireLtr();
3063
- const intersects = requireIntersects();
3064
- const simplifyRange = requireSimplify();
3065
- const subset = requireSubset();
3066
- semver = {
3067
- parse,
3068
- valid,
3069
- clean,
3070
- inc,
3071
- diff,
3072
- major,
3073
- minor,
3074
- patch,
3075
- prerelease,
3076
- compare,
3077
- rcompare,
3078
- compareLoose,
3079
- compareBuild,
3080
- sort,
3081
- rsort,
3082
- gt,
3083
- lt,
3084
- eq,
3085
- neq,
3086
- gte,
3087
- lte,
3088
- cmp,
3089
- coerce,
3090
- truncate,
3091
- Comparator,
3092
- Range,
3093
- satisfies,
3094
- toComparators,
3095
- maxSatisfying,
3096
- minSatisfying,
3097
- minVersion,
3098
- validRange,
3099
- outside,
3100
- gtr,
3101
- ltr,
3102
- intersects,
3103
- simplifyRange,
3104
- subset,
3105
- SemVer,
3106
- re: internalRe.re,
3107
- src: internalRe.src,
3108
- tokens: internalRe.t,
3109
- SEMVER_SPEC_VERSION: constants.SEMVER_SPEC_VERSION,
3110
- RELEASE_TYPES: constants.RELEASE_TYPES,
3111
- compareIdentifiers: identifiers.compareIdentifiers,
3112
- rcompareIdentifiers: identifiers.rcompareIdentifiers,
3113
- };
3114
- return semver;
2694
+
2695
+ var semver;
2696
+ var hasRequiredSemver;
2697
+
2698
+ function requireSemver () {
2699
+ if (hasRequiredSemver) return semver;
2700
+ hasRequiredSemver = 1;
2701
+
2702
+ // just pre-load all the stuff that index.js lazily exports
2703
+ const internalRe = requireRe();
2704
+ const constants = requireConstants();
2705
+ const SemVer = requireSemver$1();
2706
+ const identifiers = requireIdentifiers();
2707
+ const parse = requireParse();
2708
+ const valid = requireValid$1();
2709
+ const clean = requireClean();
2710
+ const inc = requireInc();
2711
+ const diff = requireDiff();
2712
+ const major = requireMajor();
2713
+ const minor = requireMinor();
2714
+ const patch = requirePatch();
2715
+ const prerelease = requirePrerelease();
2716
+ const compare = requireCompare();
2717
+ const rcompare = requireRcompare();
2718
+ const compareLoose = requireCompareLoose();
2719
+ const compareBuild = requireCompareBuild();
2720
+ const sort = requireSort();
2721
+ const rsort = requireRsort();
2722
+ const gt = requireGt();
2723
+ const lt = requireLt();
2724
+ const eq = requireEq();
2725
+ const neq = requireNeq();
2726
+ const gte = requireGte();
2727
+ const lte = requireLte();
2728
+ const cmp = requireCmp();
2729
+ const coerce = requireCoerce();
2730
+ const truncate = requireTruncate();
2731
+ const Comparator = requireComparator();
2732
+ const Range = requireRange();
2733
+ const satisfies = requireSatisfies();
2734
+ const toComparators = requireToComparators();
2735
+ const maxSatisfying = requireMaxSatisfying();
2736
+ const minSatisfying = requireMinSatisfying();
2737
+ const minVersion = requireMinVersion();
2738
+ const validRange = requireValid();
2739
+ const outside = requireOutside();
2740
+ const gtr = requireGtr();
2741
+ const ltr = requireLtr();
2742
+ const intersects = requireIntersects();
2743
+ const simplifyRange = requireSimplify();
2744
+ const subset = requireSubset();
2745
+ semver = {
2746
+ parse,
2747
+ valid,
2748
+ clean,
2749
+ inc,
2750
+ diff,
2751
+ major,
2752
+ minor,
2753
+ patch,
2754
+ prerelease,
2755
+ compare,
2756
+ rcompare,
2757
+ compareLoose,
2758
+ compareBuild,
2759
+ sort,
2760
+ rsort,
2761
+ gt,
2762
+ lt,
2763
+ eq,
2764
+ neq,
2765
+ gte,
2766
+ lte,
2767
+ cmp,
2768
+ coerce,
2769
+ truncate,
2770
+ Comparator,
2771
+ Range,
2772
+ satisfies,
2773
+ toComparators,
2774
+ maxSatisfying,
2775
+ minSatisfying,
2776
+ minVersion,
2777
+ validRange,
2778
+ outside,
2779
+ gtr,
2780
+ ltr,
2781
+ intersects,
2782
+ simplifyRange,
2783
+ subset,
2784
+ SemVer,
2785
+ re: internalRe.re,
2786
+ src: internalRe.src,
2787
+ tokens: internalRe.t,
2788
+ SEMVER_SPEC_VERSION: constants.SEMVER_SPEC_VERSION,
2789
+ RELEASE_TYPES: constants.RELEASE_TYPES,
2790
+ compareIdentifiers: identifiers.compareIdentifiers,
2791
+ rcompareIdentifiers: identifiers.rcompareIdentifiers,
2792
+ };
2793
+ return semver;
2794
+ }
2795
+
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>`;
3115
3269
  }
3116
3270
 
3117
- var semverExports = requireSemver();
3118
-
3271
+ /** 单个小程序版本最多声明的关联游戏数量。 */
3119
3272
  const MINIAPP_BIND_APP_IDS_MAX_COUNT = 20;
3273
+ /**
3274
+ * 校验并规范化小程序关联的游戏 App ID。
3275
+ *
3276
+ * @param value - 不可信声明。`undefined` 和 `[]` 均返回空绑定;其他值必须是最多 20 项的
3277
+ * 字符串数组,不接受 `null`、单个字符串、number 数组或类数组对象。
3278
+ * @param options - 可选诊断来源;`sourceLabel` 默认 `package.json#heybox.bindAppIds`,并原样
3279
+ * 作为所有错误消息的前缀。
3280
+ * @returns 新建、按 appid 数值升序排列且经 `Object.freeze` 冻结的 `readonly string[]`。
3281
+ * 输入数组及其顺序不会被修改;空输入也返回新的冻结空数组。
3282
+ * @throws `Error` `${sourceLabel} 必须是字符串数组`:`value` 不是数组。
3283
+ * @throws `Error` `${sourceLabel} 最多包含 20 个游戏 appid`:输入数组超过 20 项;数量检查
3284
+ * 先于逐项格式检查。
3285
+ * @throws `Error` `${sourceLabel} 包含非法游戏 appid:<value>`:任一项不是 string,或不匹配
3286
+ * `/^[1-9]\d*$/`。因此空串、首尾空白、`0`、前导零、正负号、小数、指数和非十进制字符
3287
+ * 都不会被自动修正。
3288
+ * @throws `Error` `${sourceLabel} 包含超出正 int32 范围的游戏 appid:<value>`:十进制值
3289
+ * 大于 `2147483647` 或不能成为安全整数。
3290
+ * @throws `Error` `${sourceLabel} 包含重复游戏 appid:<value>`:规范字符串在同一数组重复。
3291
+ *
3292
+ * @remarks
3293
+ * 合法范围是闭区间 `1..2147483647`,每项在输出中仍保持十进制 string。排序按数值而不是
3294
+ * 字典序执行,所以 `['3513350', '730', '10']` 输出为 `['10', '730', '3513350']`。
3295
+ * 函数先复制后排序并冻结结果,不排序或冻结调用方传入的数组。
3296
+ *
3297
+ * 这是同步、纯离线的格式规范化函数,不访问网络,也不确认 appid 是否真实存在、可绑定或
3298
+ * 属于当前开发者。`miniappManifest()` 用它校验 `package.json#heybox.bindAppIds` 并把非空结果
3299
+ * 写入 Manifest;远端 deploy 预检和提审才校验游戏可用性。缺失和空数组在普通构建中都表示
3300
+ * 无绑定,函数本身不处理 `remote deploy --from-version` 的历史快照继承语义。
3301
+ *
3302
+ * `bindAppIds` 是版本发布元数据,不是 Runtime 权限,也不会因调用
3303
+ * [navigation.openAppPage](/reference/sdk/navigation/openAppPage) 而变化。后者的 `appId` 是一次
3304
+ * 页面导航参数,允许 string 或 number、只要求字符串化后非空,不适用本函数的数组、int32、
3305
+ * 前导零、去重、20 项或排序规则。
3306
+ *
3307
+ * @example
3308
+ * ```ts
3309
+ * import { parseMiniappBindAppIds } from '@heybox/hb-sdk/miniapp-publish'
3310
+ *
3311
+ * const declared = ['3513350', '730', '10']
3312
+ * const appIds = parseMiniappBindAppIds(declared)
3313
+ *
3314
+ * console.log(appIds) // ['10', '730', '3513350']
3315
+ * console.log(declared) // ['3513350', '730', '10'],输入顺序不变
3316
+ * console.log(Object.isFrozen(appIds)) // true
3317
+ * ```
3318
+ *
3319
+ * @see [miniappManifest](/reference/symbols/vite/functions/miniappManifest)
3320
+ * @see [navigation.openAppPage](/reference/sdk/navigation/openAppPage)
3321
+ * @see [CLI 与远端发布](/guide/cli#生产构建)
3322
+ */
3120
3323
  function parseMiniappBindAppIds(value, options = {}) {
3121
3324
  const sourceLabel = options.sourceLabel ?? 'package.json#heybox.bindAppIds';
3122
3325
  if (value === undefined)
@@ -3145,8 +3348,25 @@ function parseMiniappBindAppIds(value, options = {}) {
3145
3348
  return Object.freeze(appIds);
3146
3349
  }
3147
3350
 
3351
+ /**
3352
+ * 所有小程序 iframe bridge envelope 共用的固定 namespace。
3353
+ *
3354
+ * @remarks 该值用于排除无关 `postMessage` 数据,不负责验证消息来源;接收方仍须校验 source 和 nonce,
3355
+ * 并在 origin 可以固定且协议模式要求时校验 origin。
3356
+ */
3148
3357
  const MINI_PROGRAM_MESSAGE_NAMESPACE = 'heybox:miniprogram';
3358
+ /**
3359
+ * 新消息默认发送的当前 bridge wire 版本。
3360
+ *
3361
+ * @remarks 接收 guard 为握手协商继续接受 v1 与 v2 envelope;cancel 与 operation progress 等 v2-only 消息仍必须使用 v2。
3362
+ */
3149
3363
  const MINI_PROGRAM_MESSAGE_VERSION = 2;
3364
+ /**
3365
+ * Runtime 在 iframe URL 中传递 bridge nonce 的 query 参数名。
3366
+ *
3367
+ * @remarks nonce 用于把消息关联到当前 iframe 实例,不是独立的身份认证凭据;接收方还必须校验 source,
3368
+ * 并在 origin 可以固定且协议模式要求时校验 origin。
3369
+ */
3150
3370
  const MINI_PROGRAM_BRIDGE_NONCE_PARAM = 'hb_mini_bridge_nonce';
3151
3371
  /** Companion alias 的跨构建、Runtime 与 Host 规范。 */
3152
3372
  const COMPANION_ALIAS_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
@@ -3362,6 +3582,11 @@ function isRecord$3(value) {
3362
3582
  return typeof value === 'object' && value !== null && !Array.isArray(value);
3363
3583
  }
3364
3584
 
3585
+ /**
3586
+ * `package.json#heybox.platforms` 接受的平台值,顺序同时是 Manifest 的规范化输出顺序。
3587
+ *
3588
+ * @remarks 项目仍应只声明实际完成适配并经过验收的平台;模板默认值不是自动兼容承诺。
3589
+ */
3365
3590
  const MINIAPP_PLATFORM_VALUES = ['android', 'ios', 'ohos', 'windows', 'macos', 'linux'];
3366
3591
  /** 窗口尺寸字段的脏值兜底上限;真正的上限是宿主侧显示器 workArea。 */
3367
3592
  const MINIAPP_WINDOW_SIZE_MAX = 16_384;
@@ -3384,6 +3609,7 @@ function renderMiniappManifest(manifest) {
3384
3609
  const output = {
3385
3610
  version: manifest.version,
3386
3611
  sdkVersion: manifest.sdkVersion,
3612
+ ...(manifest.name === undefined ? {} : { name: manifest.name, icon: manifest.icon, coverImages: manifest.coverImages }),
3387
3613
  platforms,
3388
3614
  ...(manifest.permissions === undefined ? {} : { permissions: manifest.permissions }),
3389
3615
  ...(manifest.companions === undefined ? {} : { companions: manifest.companions }),
@@ -12749,29 +12975,29 @@ function renderRuntimeGateScript() {
12749
12975
  function removePlatformGateNodes(node) {
12750
12976
  if (!('childNodes' in node))
12751
12977
  return;
12752
- 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));
12753
12979
  for (const child of node.childNodes)
12754
12980
  removePlatformGateNodes(child);
12755
12981
  }
12756
12982
  function readAttr(element, name) {
12757
- return element.attrs.find(attr => attr.name.toLowerCase() === name)?.value;
12983
+ return element.attrs.find((attr) => attr.name.toLowerCase() === name)?.value;
12758
12984
  }
12759
12985
  function hasAttr(element, name) {
12760
12986
  return readAttr(element, name) !== undefined;
12761
12987
  }
12762
12988
  function setAttr(element, name, value) {
12763
- const attr = element.attrs.find(item => item.name.toLowerCase() === name);
12989
+ const attr = element.attrs.find((item) => item.name.toLowerCase() === name);
12764
12990
  if (attr)
12765
12991
  attr.value = value;
12766
12992
  else
12767
12993
  element.attrs.push({ name, value });
12768
12994
  }
12769
12995
  function removeAttr(element, name) {
12770
- element.attrs = element.attrs.filter(attr => attr.name.toLowerCase() !== name);
12996
+ element.attrs = element.attrs.filter((attr) => attr.name.toLowerCase() !== name);
12771
12997
  }
12772
12998
  function findElement$1(node, tagName) {
12773
12999
  let found;
12774
- walk$1(node, element => {
13000
+ walk$1(node, (element) => {
12775
13001
  if (!found && element.tagName === tagName)
12776
13002
  found = element;
12777
13003
  });
@@ -12790,10 +13016,7 @@ function walk$1(node, visit) {
12790
13016
  const FORBIDDEN_RESOURCE_HINTS = new Set(['dns-prefetch', 'preconnect', 'prerender']);
12791
13017
  const RELATIVE_URL_BASE = new URL('https://heybox-relative.invalid/');
12792
13018
  const OFFICIAL_RESOURCE_ROOT_DOMAINS = ['xiaoheihe.cn', 'max-c.com', 'debugmode.cn', 'maxjia.com'];
12793
- const OFFICIAL_RESOURCE_SOURCES = OFFICIAL_RESOURCE_ROOT_DOMAINS.flatMap(rootDomain => [
12794
- `https://${rootDomain}`,
12795
- `https://*.${rootDomain}`,
12796
- ]);
13019
+ const OFFICIAL_RESOURCE_SOURCES = OFFICIAL_RESOURCE_ROOT_DOMAINS.flatMap((rootDomain) => [`https://${rootDomain}`, `https://*.${rootDomain}`]);
12797
13020
  const URL_ATTRIBUTES = {
12798
13021
  audio: ['src'],
12799
13022
  embed: ['src'],
@@ -12830,14 +13053,12 @@ function enforceMiniappHtmlPolicy(html, options = {}) {
12830
13053
  throw new Error('入口 index.html 必须包含 <head>');
12831
13054
  }
12832
13055
  removePlatformCspMarkers(document);
12833
- walk(document, element => validateElement(element));
13056
+ walk(document, (element) => validateElement(element));
12834
13057
  injectMiniappRuntimeGate(document);
12835
13058
  if (options.skipPlatformCsp) {
12836
13059
  return serialize(document);
12837
13060
  }
12838
- const content = options.hmrWebSocketUrl
12839
- ? PRODUCTION_CSP.replace("connect-src 'none'", `connect-src ${options.hmrWebSocketUrl}`)
12840
- : PRODUCTION_CSP;
13061
+ const content = options.hmrWebSocketUrl ? PRODUCTION_CSP.replace("connect-src 'none'", `connect-src ${options.hmrWebSocketUrl}`) : PRODUCTION_CSP;
12841
13062
  head.childNodes.unshift({
12842
13063
  nodeName: 'meta',
12843
13064
  tagName: 'meta',
@@ -12855,7 +13076,7 @@ function enforceMiniappHtmlPolicy(html, options = {}) {
12855
13076
  }
12856
13077
  function validateElement(element) {
12857
13078
  const tagName = element.tagName.toLowerCase();
12858
- 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]));
12859
13080
  if (tagName === 'meta' && attrs.get('http-equiv')?.toLowerCase() === 'refresh') {
12860
13081
  throw new Error('index.html 不允许 meta refresh');
12861
13082
  }
@@ -12867,7 +13088,7 @@ function validateElement(element) {
12867
13088
  }
12868
13089
  if (tagName === 'link') {
12869
13090
  const rels = (attrs.get('rel') ?? '').toLowerCase().split(/\s+/).filter(Boolean);
12870
- const forbidden = rels.find(rel => FORBIDDEN_RESOURCE_HINTS.has(rel));
13091
+ const forbidden = rels.find((rel) => FORBIDDEN_RESOURCE_HINTS.has(rel));
12871
13092
  if (forbidden) {
12872
13093
  throw new Error(`index.html 不允许 rel="${forbidden}"`);
12873
13094
  }
@@ -12884,7 +13105,10 @@ function validateElement(element) {
12884
13105
  for (const attribute of URL_ATTRIBUTES[tagName] ?? []) {
12885
13106
  const value = attrs.get(attribute);
12886
13107
  if (attribute === 'srcset') {
12887
- for (const candidate of (value ?? '').split(',').map(item => item.trim().split(/\s+/)[0]).filter(Boolean)) {
13108
+ for (const candidate of (value ?? '')
13109
+ .split(',')
13110
+ .map((item) => item.trim().split(/\s+/)[0])
13111
+ .filter(Boolean)) {
12888
13112
  validateResourceUrl(candidate, `<${tagName} ${attribute}>`, true, supportsOfficialResourceUrl(tagName, attribute));
12889
13113
  }
12890
13114
  }
@@ -12896,10 +13120,10 @@ function validateElement(element) {
12896
13120
  function removePlatformCspMarkers(node) {
12897
13121
  if (!('childNodes' in node))
12898
13122
  return;
12899
- node.childNodes = node.childNodes.filter(child => {
13123
+ node.childNodes = node.childNodes.filter((child) => {
12900
13124
  if (!('tagName' in child))
12901
13125
  return true;
12902
- 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');
12903
13127
  });
12904
13128
  for (const child of node.childNodes)
12905
13129
  removePlatformCspMarkers(child);
@@ -12935,7 +13159,7 @@ function isOfficialResourceUrl(value) {
12935
13159
  const hostname = url.hostname.toLowerCase();
12936
13160
  if (hostname.endsWith('.'))
12937
13161
  return false;
12938
- 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}`));
12939
13163
  }
12940
13164
  catch {
12941
13165
  return false;
@@ -12960,7 +13184,7 @@ function isRelativeUrl(value, normalized) {
12960
13184
  }
12961
13185
  function findElement(node, tagName) {
12962
13186
  let found;
12963
- walk(node, element => {
13187
+ walk(node, (element) => {
12964
13188
  if (!found && element.tagName === tagName)
12965
13189
  found = element;
12966
13190
  });
@@ -13025,7 +13249,19 @@ const DEV_ERROR_MESSAGE = 'Companion 开发资源暂时不可用';
13025
13249
  /** 与 Companion manifest 一致的单响应上限,避免本地调试入口被大体积声明撑爆。 */
13026
13250
  const DEV_MINI_PROGRAM_MANIFEST_MAX_BYTES = 1024 * 1024;
13027
13251
  const DEV_COMPANION_PATH_PATTERN = /^\/__hb_sdk__\/companions\/([a-z][a-z0-9-]{0,31})\/(windows-x64|macos-arm64)\.zip$/;
13252
+ /**
13253
+ * `hb-sdk dev` 为本地 Companion 产物访问注入的进程环境变量名。
13254
+ *
13255
+ * @remarks
13256
+ * 该变量由 CLI 管理,不是项目配置项。应用代码不应读取、设置、记录或转发它的值。
13257
+ */
13028
13258
  const HB_SDK_COMPANION_ARTIFACT_ACCESS_TOKEN_ENV = 'HB_SDK_COMPANION_ARTIFACT_ACCESS_TOKEN';
13259
+ /**
13260
+ * `hb-sdk dev` 为本地小程序 Manifest 访问注入的进程环境变量名。
13261
+ *
13262
+ * @remarks
13263
+ * 该变量由 CLI 管理,不是项目配置项。应用代码不应读取、设置、记录或转发它的值。
13264
+ */
13029
13265
  const HB_SDK_MINI_PROGRAM_MANIFEST_ACCESS_TOKEN_ENV = 'HB_SDK_MINI_PROGRAM_MANIFEST_ACCESS_TOKEN';
13030
13266
  const devSnapshotStoreKey = Symbol.for('@heybox/hb-sdk/dev-companion-snapshots');
13031
13267
  const devSnapshotRegistry = globalThis;
@@ -13039,6 +13275,104 @@ if (!devSnapshotRegistry[devSnapshotStoreKey]) {
13039
13275
  });
13040
13276
  }
13041
13277
  const devSnapshotStores = devSnapshotRegistry[devSnapshotStoreKey];
13278
+ /**
13279
+ * 创建工坊小程序使用的 Vite 插件。
13280
+ *
13281
+ * @returns 可直接加入 Vite `plugins` 数组的
13282
+ * [MiniappManifestPlugin](/reference/symbols/vite/interfaces/MiniappManifestPlugin)。插件固定名为
13283
+ * `heybox-miniapp-manifest`,并提供 `config`、`configResolved`、`configureServer`、`buildEnd`、
13284
+ * `transformIndexHtml`、`closeBundle` hooks;这些 hooks 由 Vite 调用,应用代码不应手动调用。
13285
+ * @throws `Error` `config` 阶段发现 Vite `base` 不是 `''` 或 `'./'`,或无法解析当前 SDK 版本时抛出。
13286
+ * @throws `Error` `configResolved` 阶段发现已解析插件列表中 `miniappManifest()` 数量不是一个时抛出。
13287
+ * @throws `Error` HTML 缺少 `<head>`,包含 `meta refresh`、`<base>`、禁止的 resource hint,或资源 URL
13288
+ * 不符合小程序 HTML 策略时,`transformIndexHtml` / 生产产物复检抛出。
13289
+ * @throws `Error` 生产构建无法读取或解析最近的 `package.json`,`version`、`heybox.platforms`、
13290
+ * `heybox.permissions`、`heybox.window`、`heybox.bindAppIds` 或 `heybox.companions` 声明非法时抛出。
13291
+ * @throws `Error` 生产 `build.outDir` 缺少根 `index.html`、递归发现额外 HTML,或 Companion 权限、
13292
+ * 目标平台、来源目录、归档结构、入口二进制不符合约束时抛出;更早的 Rollup/Vite 错误会原样优先重抛。
13293
+ *
13294
+ * @remarks
13295
+ * 该函数必须从 `@heybox/hb-sdk/vite` 导入,在 `vite.config.ts` 中无参调用且只注册一次;它不是
13296
+ * iframe 业务代码 API,也没有 options 对象。小程序声明统一来自从 Vite `root` 向上找到的最近一个
13297
+ * `package.json`,插件只读取和规范化,不会改写源文件:
13298
+ *
13299
+ * - `version`:发布版本;trim 后必须是完整 SemVer,可含 prerelease,不允许 build metadata 或模板值
13300
+ * `0.0.0`;
13301
+ * - `heybox.platforms`:项目实际承诺适配的非空、无重复平台 allowlist;输出按平台词表规范排序;
13302
+ * - `heybox.permissions`:严格权限对象;未知权限/字段或错误类型会失败,`enabled: false` 项不写入
13303
+ * Manifest。旧版本缺失声明时可能警告,新硬切版本会失败;
13304
+ * - `heybox.window`:可选的新 PC 开窗初值、最小尺寸和 `resizable` 声明;省略时不写该字段;
13305
+ * - `heybox.bindAppIds`:可选的最多 20 个正 int32 字符串 App ID;校验、去重并按数值排序,空数组
13306
+ * 在本地产物中等同不写;
13307
+ * - `heybox.companions`:可选的受管桌面程序声明;`source` 相对上述 `package.json` 所在目录解析,
13308
+ * 必须同时声明启用的 `companion` 权限和匹配的 Windows/macOS 目标平台。
13309
+ *
13310
+ * 产物中的 `sdkVersion` 来自当前实际安装的 `@heybox/hb-sdk`,不是项目可配置字段。Vite options、页面
13311
+ * 代码和普通环境变量不能覆盖上述 package 声明;插件也不会联网确认 App ID、平台批准或远端版本状态。
13312
+ *
13313
+ * Hook 行为如下:
13314
+ *
13315
+ * - `config`:省略 `base` 时补为 `'./'`,显式值只接受 `''` / `'./'`;注入 SDK 构建版本,且仅
13316
+ * `vite serve` 注入开发日志编译常量;
13317
+ * - `configResolved`:确认插件唯一性,记录真实 `root`、`build.outDir`、`serve|build` 命令和精确 HMR
13318
+ * WebSocket 地址;直接 `vite build` 使用该实际 `outDir`,`hb-sdk build` 则由 CLI 固定为 `dist/`;
13319
+ * - `configureServer`:为 `hb-sdk dev` 注册设备日志、本地小程序 Manifest 与 Companion Manifest/ZIP
13320
+ * 中间件;敏感路由只接受数值 loopback socket/Host、`GET|HEAD` 和 CLI 注入的独立短期令牌,快照在
13321
+ * 配置重载时按 digest 保留,并在服务真正关闭时清理;
13322
+ * - `transformIndexHtml`:校验 HTML URL/标签策略,注入 Runtime Gate;默认注入平台 CSP,serve 时
13323
+ * `connect-src` 只开放解析出的 HMR WebSocket,并额外注入调试日志 bootstrap;
13324
+ * - `buildEnd`:记住更早的 Rollup/Vite 错误,避免 `closeBundle` 的二次产物错误遮盖根因;
13325
+ * - `closeBundle`:serve 时直接完成;build 时复检最终 HTML,要求整个输出树只有根 `index.html`,再于
13326
+ * `build.outDir` 写入 `manifest.json`,并把 Companion 目录打包成
13327
+ * `companions/<alias>/<target>.zip`,记录 size、SHA-256、固定入口与 args。
13328
+ *
13329
+ * HTML 策略会隔离业务 script/modulepreload,直到父 Runtime Gate 以 namespace、版本、nonce、source
13330
+ * 以及 referrer 可确定时的 origin 完成探测;失败时显示只能在小黑盒 App 内打开的安全页。策略拒绝自动跳转、`<base>`、危险
13331
+ * resource hints 和不受支持的外部 URL,并按标签只允许相对 URL、明确允许的 `data:` / `blob:` 或
13332
+ * 小黑盒官方 HTTPS 资源。开发者自行提供的 CSP 会保留;插件管理的 CSP marker 可重复转换而不会叠加。
13333
+ *
13334
+ * 平台 CSP 默认始终保留,直接 `vite build` / `hb-sdk build` 不会因项目声明
13335
+ * `heybox.permissions.network.useOfficialDomain` 而跳过。只有官方 `hb-sdk dev` 或
13336
+ * `hb-sdk remote deploy` 将当前版本的 `network` 声明与远端批准结果求交并确认最终启用后,才会
13337
+ * 建立 verified 上下文供插件读取内部
13338
+ * `HB_SDK_RUNTIME_PERMISSION_CONTEXT` + `HB_SDK_RUNTIME_USE_OFFICIAL_DOMAIN` 组合跳过平台 CSP;单独设置
13339
+ * 任一值无效。0.8 兼容期对完全缺失权限声明的项目仍按迁移规则处理;`useOfficialDomain` 不参与
13340
+ * CSP 跳过决定。Runtime Gate、HTML 校验和 Manifest 生成始终保留。
13341
+ *
13342
+ * `HB_SDK_COMPANION_ARTIFACT_ACCESS_TOKEN`、`HB_SDK_MINI_PROGRAM_MANIFEST_ACCESS_TOKEN`、
13343
+ * `HB_SDK_SKIP_COMPANIONS` 以及上述 CSP 上下文均是 CLI/发布链路内部开关,不是项目配置项。它们在
13344
+ * 插件创建时捕获;开发服务配置重载只会从同一服务的内存快照恢复 CLI 令牌。业务代码和 Vite 配置
13345
+ * 不应手工设置、读取、记录或转发令牌值。正式声明必须继续写在 `package.json#heybox`,内部环境变量
13346
+ * 不能替代声明校验。
13347
+ *
13348
+ * `manifest.json` 是 release pipeline 元数据,不是部署后供页面 fetch 的 CDN 资源。发布链路使用
13349
+ * [isValidVersion](/reference/symbols/miniapp-publish/functions/isValidVersion) 复核版本、
13350
+ * [resolveMiniappManifestPlatforms](/reference/symbols/miniapp-publish/functions/resolveMiniappManifestPlatforms)
13351
+ * 解析平台硬切规则、
13352
+ * [parseMiniappBindAppIds](/reference/symbols/miniapp-publish/functions/parseMiniappBindAppIds) 复核游戏绑定,
13353
+ * 并通过 [shouldUploadDistFile](/reference/symbols/miniapp-publish/functions/shouldUploadDistFile) 将根
13354
+ * `manifest.json` 排除出普通 CDN 文件。Web/Companion 上传预算由
13355
+ * [validateUploadTotalSize](/reference/symbols/miniapp-publish/functions/validateUploadTotalSize) 等发布 helper
13356
+ * 独立校验,不能用插件成功代替 deploy 预检。
13357
+ *
13358
+ * @example
13359
+ * ```ts
13360
+ * import vue from '@vitejs/plugin-vue'
13361
+ * import { miniappManifest } from '@heybox/hb-sdk/vite'
13362
+ * import { defineConfig } from 'vite'
13363
+ *
13364
+ * export default defineConfig({
13365
+ * // base 省略时 miniappManifest() 会补为 './'
13366
+ * plugins: [vue(), miniappManifest()],
13367
+ * })
13368
+ * ```
13369
+ *
13370
+ * @see [MiniappManifestPlugin](/reference/symbols/vite/interfaces/MiniappManifestPlugin) 返回插件及各 hook 类型。
13371
+ * @see [安装](/guide/installation) Vite 接入、相对资源路径和构建命令。
13372
+ * @see [权限声明](/guide/permissions) `heybox.permissions` 严格 schema 与迁移规则。
13373
+ * @see [窗口尺寸与可缩放性](/guide/window-size) `heybox.window` 字段与 Host 消费语义。
13374
+ * @see [受管桌面程序](/guide/companion) Companion 声明、归档和本地联调边界。
13375
+ */
13042
13376
  function miniappManifest() {
13043
13377
  const includeCompanions = process.env.HB_SDK_SKIP_COMPANIONS !== '1';
13044
13378
  const deviceLogs = new DeviceLogStore();
@@ -13357,10 +13691,12 @@ function miniappManifest() {
13357
13691
  throw new Error('构建产物必须包含入口 dist/index.html');
13358
13692
  }
13359
13693
  node_fs.writeFileSync(indexHtmlPath, enforceMiniappHtmlPolicy(node_fs.readFileSync(indexHtmlPath, 'utf8'), { skipPlatformCsp }));
13694
+ const basicInfo = await prepareMiniappBasicInfo(companionConfig.projectRoot, outputRoot, sdkVersion);
13360
13695
  const manifestPath = path.join(outputRoot, 'manifest.json');
13361
13696
  const companions = prepareMiniappCompanions(companionDeclarations, { root: companionConfig.projectRoot, outputRoot, platforms });
13362
13697
  node_fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
13363
13698
  node_fs.writeFileSync(manifestPath, renderMiniappManifest({
13699
+ ...basicInfo,
13364
13700
  version,
13365
13701
  sdkVersion,
13366
13702
  platforms,