@heybox/hb-sdk 0.8.1-alpha.1 → 0.8.1-alpha.11
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 +69 -0
- package/README.md +80 -7
- package/dist/cli-chunks/{build-kMLIWqWY.cjs → build-DXpmT9rR.cjs} +16 -10
- package/dist/cli-chunks/{context-CzFFRcL4.cjs → context-cpQKdAv9.cjs} +17 -3
- package/dist/cli-chunks/{create-0ow6gA9w.cjs → create-BBUaKfey.cjs} +1 -1
- package/dist/cli-chunks/{dev-CPBPdpyu.cjs → dev-jz2O-8Sf.cjs} +633 -147
- package/dist/cli-chunks/{doctor-DQ8ctFKC.cjs → doctor-8tT7UEPZ.cjs} +1 -1
- package/dist/cli-chunks/{index-RDcIrvwi.cjs → index-BV_ZCDU1.cjs} +2 -2
- package/dist/cli-chunks/{index-Buner4kE.cjs → index-IeNOfmd0.cjs} +87 -15
- package/dist/cli-chunks/{index.esm-qwrdRuy-.cjs → index.esm-DiC4hnWh.cjs} +9 -8
- package/dist/cli-chunks/{login-DoduAO_f.cjs → login-DipFJGVa.cjs} +2 -2
- package/dist/cli-chunks/{project-vite-B-sK-qup.cjs → project-vite-DLIFCiZD.cjs} +1 -1
- package/dist/cli-chunks/{remote-CF_SRmnj.cjs → remote-DHlEJtjc.cjs} +41 -28
- package/dist/cli-chunks/{runtime-gate-FseEYZze.cjs → runtime-gate-BWb8QIcX.cjs} +0 -107
- package/dist/cli-chunks/{runtime-permission-env-DdQtxdut.cjs → runtime-permission-env-B0s4KP_U.cjs} +318 -23
- package/dist/cli-chunks/{session-DHbw2Xgh.cjs → session-CUPzN2aa.cjs} +1 -1
- package/dist/cli-chunks/{skill-6IVv12JW.cjs → skill-DhgXHAjs.cjs} +83 -27
- package/dist/cli-chunks/{version-TXwWxKvv.cjs → version-DOBbqU-f.cjs} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-C7lX5xaK.js +101 -0
- package/dist/devtools/browser-dev-host/assets/desktop-app-launch-DLQ5YSCQ.js +6 -0
- package/dist/devtools/browser-dev-host/assets/index-DXd5IZBz.css +1 -0
- package/dist/devtools/browser-dev-host/assets/index-DsL03GcE.js +567 -0
- package/dist/devtools/browser-dev-host/index.html +3 -3
- package/dist/index.cjs.js +1970 -1567
- package/dist/index.esm.js +1969 -1568
- package/dist/miniapp-publish.cjs.js +61 -0
- package/dist/miniapp-publish.esm.js +59 -1
- package/dist/protocol.cjs.js +64 -13
- package/dist/protocol.esm.js +64 -13
- package/dist/templates/vanilla-vite-js/README.md.ejs +1 -1
- package/dist/templates/vanilla-vite-js/package.json.ejs +1 -0
- package/dist/templates/vanilla-vite-js/vite.config.js +1 -1
- package/dist/vite.cjs.js +1333 -18
- package/dist/vite.esm.js +1334 -20
- package/package.json +9 -9
- package/skill/SKILL.md +11 -3
- package/skill/references/api-protocol.md +8 -4
- package/skill/references/api-root.md +328 -210
- package/skill/references/cli.md +11 -3
- package/skill/references/examples.md +13 -1
- package/skill/references/recipes.md +162 -0
- package/skill/references/safety-boundaries.md +9 -3
- package/skill/skill.json +5 -5
- package/types/core/client.d.ts +9 -1
- package/types/core/sdk.d.ts +6 -0
- package/types/core/singleton.d.ts +6 -0
- package/types/devtools/device-logs.d.ts +48 -0
- package/types/index.d.ts +5 -1
- package/types/miniapp-manifest/companion-directory.d.ts +2 -0
- package/types/miniapp-manifest/companion-executable.d.ts +4 -0
- package/types/miniapp-manifest/companion-types.d.ts +38 -0
- package/types/miniapp-manifest/companions.d.ts +17 -0
- package/types/miniapp-manifest/index.d.ts +2 -0
- package/types/miniapp-manifest/node.d.ts +9 -0
- package/types/miniapp-manifest/permissions.d.ts +1 -1
- package/types/miniapp-manifest/schema.d.ts +14 -1
- package/types/miniapp-publish/index.d.ts +17 -2
- package/types/modules/companion/index.d.ts +57 -0
- package/types/modules/environment/index.d.ts +70 -0
- package/types/modules/network/index.d.ts +1 -4
- package/types/modules/network/observability.d.ts +0 -1
- package/types/protocol/dev-session.d.ts +8 -1
- package/types/protocol.d.ts +1 -1
- package/types/vite/index.d.ts +11 -4
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-TzYf9L6C.js +0 -99
- package/dist/devtools/browser-dev-host/assets/index-C5MZZDa5.js +0 -567
- package/dist/devtools/browser-dev-host/assets/index-P-ra4m1y.css +0 -1
- package/dist/devtools/browser-dev-host/assets/workbench-state-BwV7bm4n.js +0 -5
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var node_crypto = require('node:crypto');
|
|
3
4
|
var fs = require('node:fs');
|
|
4
5
|
var os = require('node:os');
|
|
5
6
|
var path = require('node:path');
|
|
6
7
|
var net = require('node:net');
|
|
7
|
-
var node_crypto = require('node:crypto');
|
|
8
8
|
var promises = require('node:dns/promises');
|
|
9
9
|
var node_http = require('node:http');
|
|
10
10
|
var node_module = require('node:module');
|
|
11
|
-
var context = require('./context-
|
|
12
|
-
var runtimePermissionEnv = require('./runtime-permission-env-
|
|
11
|
+
var context = require('./context-cpQKdAv9.cjs');
|
|
12
|
+
var runtimePermissionEnv = require('./runtime-permission-env-B0s4KP_U.cjs');
|
|
13
13
|
var undici = require('undici');
|
|
14
14
|
var fs$1 = require('node:fs/promises');
|
|
15
|
-
var index = require('./index-
|
|
15
|
+
var index = require('./index-IeNOfmd0.cjs');
|
|
16
16
|
var require$$0$1 = require('crypto');
|
|
17
17
|
var require$$0 = require('buffer');
|
|
18
|
-
var session = require('./session-
|
|
18
|
+
var session = require('./session-CUPzN2aa.cjs');
|
|
19
19
|
var browser = require('./browser-RAy8e8cV.cjs');
|
|
20
|
-
var projectVite = require('./project-vite-
|
|
21
|
-
var version = require('./version-
|
|
20
|
+
var projectVite = require('./project-vite-DLIFCiZD.cjs');
|
|
21
|
+
var version = require('./version-DOBbqU-f.cjs');
|
|
22
|
+
require('node:zlib');
|
|
22
23
|
|
|
23
24
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
24
25
|
class Locked extends Error {
|
|
@@ -183,6 +184,82 @@ async function getPorts(options) {
|
|
|
183
184
|
throw new Error('No available ports found');
|
|
184
185
|
}
|
|
185
186
|
|
|
187
|
+
/**
|
|
188
|
+
* network.request 诊断日志的纯脱敏工具。
|
|
189
|
+
*
|
|
190
|
+
* @remarks
|
|
191
|
+
* 放在 `core` 而非 `modules/network`:无错误类型依赖,供 SDK 错误文案、
|
|
192
|
+
* modules observability、devtools Browser Dev Host / proxy 共用,并满足 boundary
|
|
193
|
+
*(devtools 不得直接 import runtime capability modules)。
|
|
194
|
+
*/
|
|
195
|
+
/** 敏感字段名:header / query / body key 共用。 */
|
|
196
|
+
const SENSITIVE_KEY_PATTERN$1 = /(authorization|cookie|password|passwd|secret|token|pkey|private[_-]?key|access[_-]?key|client[_-]?secret|api[_-]?key|apikey|session|jwt|refresh[_-]?token|csrf)/i;
|
|
197
|
+
/**
|
|
198
|
+
* 脱敏请求 URL:去掉 userinfo,并将 query / fragment 中的敏感参数值替换为 `[redacted]`。
|
|
199
|
+
*/
|
|
200
|
+
function sanitizeNetworkRequestUrl(url) {
|
|
201
|
+
if (!url) {
|
|
202
|
+
return url;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const parsed = new URL(url);
|
|
206
|
+
parsed.username = '';
|
|
207
|
+
parsed.password = '';
|
|
208
|
+
redactUrlSearchParams(parsed.searchParams);
|
|
209
|
+
if (parsed.hash && parsed.hash.length > 1) {
|
|
210
|
+
const hashBody = parsed.hash.slice(1);
|
|
211
|
+
if (hashBody.includes('=')) {
|
|
212
|
+
const hashParams = new URLSearchParams(hashBody);
|
|
213
|
+
if (redactUrlSearchParams(hashParams)) {
|
|
214
|
+
parsed.hash = hashParams.toString();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return parsed.toString();
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return redactSensitivePairsInRawText(url);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 诊断 message 中的 URL 片段做脱敏,避免 error.message 二次泄漏 query secrets。
|
|
226
|
+
*/
|
|
227
|
+
function sanitizeDiagnosticMessage(message) {
|
|
228
|
+
if (!message) {
|
|
229
|
+
return message;
|
|
230
|
+
}
|
|
231
|
+
return message.replace(/https?:\/\/[^\s]+/gi, matched => sanitizeNetworkRequestUrl(matched.replace(/[),.;]+$/g, '')));
|
|
232
|
+
}
|
|
233
|
+
function isSensitiveNetworkKey(key) {
|
|
234
|
+
return SENSITIVE_KEY_PATTERN$1.test(key.trim());
|
|
235
|
+
}
|
|
236
|
+
function redactUrlSearchParams(params) {
|
|
237
|
+
let changed = false;
|
|
238
|
+
for (const key of [...params.keys()]) {
|
|
239
|
+
if (isSensitiveNetworkKey(key)) {
|
|
240
|
+
params.set(key, '[redacted]');
|
|
241
|
+
changed = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return changed;
|
|
245
|
+
}
|
|
246
|
+
function redactSensitivePairsInRawText(text) {
|
|
247
|
+
return text.replace(/([?&#]|^|[?&])([^=&#\s]+)=([^&#\s]*)/g, (full, prefix, key, value) => {
|
|
248
|
+
if (!isSensitiveNetworkKey(key)) {
|
|
249
|
+
return full;
|
|
250
|
+
}
|
|
251
|
+
return `${prefix}${key}=[redacted]`;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const DEVICE_LOG_PATH = '/__hb_sdk__/device-logs';
|
|
256
|
+
const registryKey = Symbol.for('@heybox/hb-sdk/device-logs');
|
|
257
|
+
const registry = globalThis;
|
|
258
|
+
const stores = (registry[registryKey] ??= new Map());
|
|
259
|
+
function getDeviceLogs(appUrl) {
|
|
260
|
+
return stores.get(Number(new URL(appUrl).port));
|
|
261
|
+
}
|
|
262
|
+
|
|
186
263
|
/** Dev Shell 读取局域网 Dev Session endpoint 的 query 参数。 */
|
|
187
264
|
const MINI_PROGRAM_DEV_CONTEXT_QUERY_PARAM = 'dev_context_url';
|
|
188
265
|
/** Browser Dev Host 创建和读取 Dev Session 的固定路径。 */
|
|
@@ -546,78 +623,100 @@ function readErrorMessage(error) {
|
|
|
546
623
|
return error instanceof Error ? error.message : String(error);
|
|
547
624
|
}
|
|
548
625
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
626
|
+
const PATHNAME_MAX_DECODE_ROUNDS = 4;
|
|
627
|
+
const OFFICIAL_ROOT_DOMAINS = ['xiaoheihe.cn', 'max-c.com', 'debugmode.cn', 'maxjia.com'];
|
|
628
|
+
const DENIED_REQUEST_HEADERS = new Set([
|
|
629
|
+
'connection',
|
|
630
|
+
'content-length',
|
|
631
|
+
'host',
|
|
632
|
+
'keep-alive',
|
|
633
|
+
'proxy-connection',
|
|
634
|
+
'te',
|
|
635
|
+
'trailer',
|
|
636
|
+
'transfer-encoding',
|
|
637
|
+
'upgrade',
|
|
638
|
+
]);
|
|
639
|
+
/** 任意 origin 都不得通过公开 network.request 访问的 Runtime 私有路径。 */
|
|
640
|
+
const PUBLIC_NETWORK_PROTECTED_PATH_PREFIXES = ['/user_miniprogram/runtime/auth', '/user_miniprogram/openapi/v1'];
|
|
641
|
+
/** 官方 origin 上会签发、恢复或校验账号 credential 的私有路径。 */
|
|
642
|
+
const PUBLIC_NETWORK_OFFICIAL_ACCOUNT_SENSITIVE_PATH_PREFIXES = [
|
|
643
|
+
'/account/restore_login',
|
|
644
|
+
'/account/check_login',
|
|
645
|
+
'/account/login',
|
|
646
|
+
'/account/get_login_code',
|
|
647
|
+
'/account/login_code',
|
|
648
|
+
'/account/get_qrcode_url',
|
|
649
|
+
'/account/qr_state',
|
|
650
|
+
'/account/google_2fa_verify',
|
|
651
|
+
'/account/open/login',
|
|
652
|
+
'/account/captcha',
|
|
653
|
+
'/account/get_verify_code',
|
|
654
|
+
'/account/send_verify_code',
|
|
655
|
+
'/account/verify_code',
|
|
656
|
+
];
|
|
657
|
+
/** 判断公开网络能力是否命中 Runtime 或官方账号敏感路径。 */
|
|
658
|
+
function isProtectedMiniProgramNetworkPath(pathname, official) {
|
|
659
|
+
const normalized = normalizePublicNetworkPathname(pathname);
|
|
660
|
+
if (normalized === null)
|
|
661
|
+
return true;
|
|
662
|
+
if (matchesPathPrefix(normalized, PUBLIC_NETWORK_PROTECTED_PATH_PREFIXES))
|
|
663
|
+
return true;
|
|
664
|
+
return official && matchesPathPrefix(normalized, PUBLIC_NETWORK_OFFICIAL_ACCOUNT_SENSITIVE_PATH_PREFIXES);
|
|
665
|
+
}
|
|
666
|
+
/** 使用 Runtime 唯一的官方域名分类规则判断请求目标。 */
|
|
667
|
+
function isOfficialMiniProgramNetworkUrl(url) {
|
|
668
|
+
const hostname = url.hostname.toLowerCase().replace(/\.$/, '');
|
|
669
|
+
return OFFICIAL_ROOT_DOMAINS.some((rootDomain) => hostname === rootDomain || hostname.endsWith(`.${rootDomain}`));
|
|
670
|
+
}
|
|
671
|
+
/** 判断公开网络能力是否禁止调用方设置该请求头。 */
|
|
672
|
+
function isForbiddenMiniProgramNetworkRequestHeader(name) {
|
|
673
|
+
const normalized = name.trim().toLowerCase();
|
|
674
|
+
return DENIED_REQUEST_HEADERS.has(normalized) || /^(?:x-heybox|x-xhh)-(?:access-token|auth|authorization|pkey|session|token)$/.test(normalized);
|
|
675
|
+
}
|
|
676
|
+
function normalizePublicNetworkPathname(pathname) {
|
|
677
|
+
let decoded = pathname;
|
|
678
|
+
for (let round = 0; round < PATHNAME_MAX_DECODE_ROUNDS; round += 1) {
|
|
679
|
+
let next;
|
|
680
|
+
try {
|
|
681
|
+
next = decodeURIComponent(decoded);
|
|
682
|
+
}
|
|
683
|
+
catch {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
if (next === decoded)
|
|
687
|
+
return normalizeDecodedPathname(decoded);
|
|
688
|
+
decoded = next;
|
|
565
689
|
}
|
|
566
690
|
try {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
parsed.password = '';
|
|
570
|
-
redactUrlSearchParams(parsed.searchParams);
|
|
571
|
-
if (parsed.hash && parsed.hash.length > 1) {
|
|
572
|
-
const hashBody = parsed.hash.slice(1);
|
|
573
|
-
if (hashBody.includes('=')) {
|
|
574
|
-
const hashParams = new URLSearchParams(hashBody);
|
|
575
|
-
if (redactUrlSearchParams(hashParams)) {
|
|
576
|
-
parsed.hash = hashParams.toString();
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return parsed.toString();
|
|
691
|
+
if (decodeURIComponent(decoded) !== decoded)
|
|
692
|
+
return null;
|
|
581
693
|
}
|
|
582
694
|
catch {
|
|
583
|
-
return
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* 诊断 message 中的 URL 片段做脱敏,避免 error.message 二次泄漏 query secrets。
|
|
588
|
-
*/
|
|
589
|
-
function sanitizeDiagnosticMessage(message) {
|
|
590
|
-
if (!message) {
|
|
591
|
-
return message;
|
|
695
|
+
return null;
|
|
592
696
|
}
|
|
593
|
-
return
|
|
697
|
+
return normalizeDecodedPathname(decoded);
|
|
594
698
|
}
|
|
595
|
-
function
|
|
596
|
-
return
|
|
699
|
+
function matchesPathPrefix(pathname, prefixes) {
|
|
700
|
+
return prefixes.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
|
|
597
701
|
}
|
|
598
|
-
function
|
|
599
|
-
|
|
600
|
-
for (const
|
|
601
|
-
if (
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
702
|
+
function normalizeDecodedPathname(pathname) {
|
|
703
|
+
const segments = [];
|
|
704
|
+
for (const segment of pathname.replace(/\\/g, '/').split('/')) {
|
|
705
|
+
if (!segment || segment === '.')
|
|
706
|
+
continue;
|
|
707
|
+
if (segment === '..')
|
|
708
|
+
segments.pop();
|
|
709
|
+
else
|
|
710
|
+
segments.push(segment);
|
|
605
711
|
}
|
|
606
|
-
return
|
|
607
|
-
}
|
|
608
|
-
function redactSensitivePairsInRawText(text) {
|
|
609
|
-
return text.replace(/([?&#]|^|[?&])([^=&#\s]+)=([^&#\s]*)/g, (full, prefix, key, value) => {
|
|
610
|
-
if (!isSensitiveNetworkKey(key)) {
|
|
611
|
-
return full;
|
|
612
|
-
}
|
|
613
|
-
return `${prefix}${key}=[redacted]`;
|
|
614
|
-
});
|
|
712
|
+
return `/${segments.join('/')}`;
|
|
615
713
|
}
|
|
616
714
|
|
|
617
715
|
const MAX_BROWSER_DEV_HOST_NETWORK_REQUEST_BODY = 2 * 1024 * 1024;
|
|
618
716
|
const MAX_BROWSER_DEV_HOST_NETWORK_RESPONSE_BODY = 5 * 1024 * 1024;
|
|
619
717
|
const DENIED_NETWORK_TARGETS = createDeniedNetworkTargets();
|
|
620
|
-
const
|
|
718
|
+
const CREDENTIAL_RESPONSE_HEADER_RE = /^(?:authentication-info|authorization|cookie|cookie2|proxy-authenticate|proxy-authentication-info|proxy-authorization|set-cookie|set-cookie2|www-authenticate|pkey|x-pkey|x-access-token|x-auth-token|x-api-key)$/i;
|
|
719
|
+
const HOST_CONTROLLED_REQUEST_HEADERS = new Set(['origin', 'referer', 'user-agent']);
|
|
621
720
|
async function handleBrowserDevHostNetworkProxy(request, response, fetchImpl, lookupHostname, options) {
|
|
622
721
|
const cors = options.cors === true;
|
|
623
722
|
if (request.method === 'OPTIONS') {
|
|
@@ -636,7 +735,7 @@ async function handleBrowserDevHostNetworkProxy(request, response, fetchImpl, lo
|
|
|
636
735
|
payload = await readJsonRequestBody(request);
|
|
637
736
|
const requestAbort = createBrowserDevHostRequestAbortScope(request, response);
|
|
638
737
|
try {
|
|
639
|
-
const result = await requestBrowserDevHostNetwork(payload, fetchImpl, lookupHostname, options.allowedHttpOrigins, requestAbort.signal);
|
|
738
|
+
const result = await requestBrowserDevHostNetwork(payload, fetchImpl, lookupHostname, options.allowedHttpOrigins, requestAbort.signal, options.permission);
|
|
640
739
|
if (typeof result.status === 'number' && (result.status < 200 || result.status >= 300)) {
|
|
641
740
|
logBrowserDevHostNetworkProxyDiagnostic('http_status', payload, result);
|
|
642
741
|
}
|
|
@@ -692,11 +791,12 @@ function writeNetworkJsonResponse(response, status, body, cors) {
|
|
|
692
791
|
writeJsonResponse(response, status, body);
|
|
693
792
|
}
|
|
694
793
|
const BROWSER_DEV_HOST_NETWORK_MULTIPART_UNSUPPORTED_MESSAGE = 'network.request 不支持 multipart/form-data。App Host 客户端代发仅支持 application/x-www-form-urlencoded(form)与 application/json;请将 data 编码为 form 字符串(例如 new URLSearchParams({...}).toString())并设置 Content-Type: application/x-www-form-urlencoded。文件上传请使用专用上传能力,勿手写 multipart boundary。';
|
|
695
|
-
async function requestBrowserDevHostNetwork(payload, fetchImpl, lookupHostname, allowedHttpOrigins, requestSignal) {
|
|
794
|
+
async function requestBrowserDevHostNetwork(payload, fetchImpl, lookupHostname, allowedHttpOrigins, requestSignal, permission) {
|
|
696
795
|
if (!isRecord$2(payload) || typeof payload.url !== 'string' || !payload.url.trim()) {
|
|
697
796
|
throw createBrowserDevHostError(400, 'INVALID_NETWORK_REQUEST', 'network.request url 必须是非空字符串');
|
|
698
797
|
}
|
|
699
798
|
const url = createBrowserDevHostNetworkUrl(payload.url, payload.params);
|
|
799
|
+
assertMiniProgramNetworkPolicy(url, permission);
|
|
700
800
|
const method = typeof payload.method === 'string' && payload.method.trim() ? payload.method.trim().toUpperCase() : 'GET';
|
|
701
801
|
const headers = normalizeBrowserDevHostNetworkHeaders(payload.headers);
|
|
702
802
|
assertSupportedBrowserDevHostNetworkRequestShape(headers, payload.data);
|
|
@@ -729,7 +829,7 @@ async function requestBrowserDevHostNetwork(payload, fetchImpl, lookupHostname,
|
|
|
729
829
|
data: contentType.includes('application/json') ? safeJsonParse(text) : text,
|
|
730
830
|
status: response.status,
|
|
731
831
|
statusText: response.statusText,
|
|
732
|
-
headers: Object.fromEntries([...response.headers.entries()].filter(([name]) => !
|
|
832
|
+
headers: Object.fromEntries([...response.headers.entries()].filter(([name]) => !isCredentialResponseHeader(name))),
|
|
733
833
|
};
|
|
734
834
|
}
|
|
735
835
|
catch (error) {
|
|
@@ -824,9 +924,9 @@ function isDeniedNetworkAddress(address, family) {
|
|
|
824
924
|
}
|
|
825
925
|
return true;
|
|
826
926
|
}
|
|
827
|
-
function
|
|
927
|
+
function isCredentialResponseHeader(name) {
|
|
828
928
|
const normalized = name.trim().toLowerCase();
|
|
829
|
-
return
|
|
929
|
+
return CREDENTIAL_RESPONSE_HEADER_RE.test(normalized) || normalized.startsWith('x-heybox-') || normalized.startsWith('x-xhh-');
|
|
830
930
|
}
|
|
831
931
|
function createPinnedNetworkDispatcher(pinnedTargets) {
|
|
832
932
|
const connect = undici.buildConnector({});
|
|
@@ -899,31 +999,29 @@ function normalizeBrowserDevHostNetworkHeaders(headers) {
|
|
|
899
999
|
return normalized;
|
|
900
1000
|
}
|
|
901
1001
|
Object.entries(headers).forEach(([key, value]) => {
|
|
902
|
-
if (
|
|
1002
|
+
if (isForbiddenMiniProgramNetworkRequestHeader(key)) {
|
|
903
1003
|
throw createBrowserDevHostError(400, 'INVALID_NETWORK_REQUEST', `network.request 不允许设置敏感请求头: ${key}`);
|
|
904
1004
|
}
|
|
905
|
-
if (typeof value === 'string' && !
|
|
1005
|
+
if (typeof value === 'string' && !isHostManagedRequestHeader(key)) {
|
|
906
1006
|
normalized.set(key, value);
|
|
907
1007
|
}
|
|
908
1008
|
});
|
|
909
1009
|
return normalized;
|
|
910
1010
|
}
|
|
911
|
-
function
|
|
912
|
-
|
|
1011
|
+
function isHostManagedRequestHeader(name) {
|
|
1012
|
+
const normalized = name.trim().toLowerCase();
|
|
1013
|
+
return HOST_CONTROLLED_REQUEST_HEADERS.has(normalized);
|
|
913
1014
|
}
|
|
914
|
-
function
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
'
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
'
|
|
923
|
-
|
|
924
|
-
'transfer-encoding',
|
|
925
|
-
'upgrade',
|
|
926
|
-
].includes(key.toLowerCase());
|
|
1015
|
+
function assertMiniProgramNetworkPolicy(url, permission) {
|
|
1016
|
+
if (!permission)
|
|
1017
|
+
return;
|
|
1018
|
+
const official = isOfficialMiniProgramNetworkUrl(url);
|
|
1019
|
+
if (official && !permission.useOfficialDomain) {
|
|
1020
|
+
throw createBrowserDevHostError(403, 'PERMISSION_DENIED', '当前小程序暂不支持访问官方域名');
|
|
1021
|
+
}
|
|
1022
|
+
if (isProtectedMiniProgramNetworkPath(url.pathname, official)) {
|
|
1023
|
+
throw createBrowserDevHostError(403, 'PERMISSION_DENIED', 'network.request 不允许访问 Runtime 或账号敏感接口');
|
|
1024
|
+
}
|
|
927
1025
|
}
|
|
928
1026
|
/**
|
|
929
1027
|
* 与 App Host 对齐:Mock 不再放行 multipart,避免“本地成功、上线失败”。
|
|
@@ -1062,7 +1160,8 @@ function createDevSessionRoutes(options) {
|
|
|
1062
1160
|
}
|
|
1063
1161
|
try {
|
|
1064
1162
|
const payload = await readJsonRequestBody(request);
|
|
1065
|
-
const
|
|
1163
|
+
const runtimePermissions = context.createMobileDevSessionRuntimePermissions(await options.readDevContext());
|
|
1164
|
+
const validated = validateDevSessionCreatePayload(payload, options.allowedMiniUrlOrigins, runtimePermissions);
|
|
1066
1165
|
reapExpiredDevSessions(devSessions, expiredDevSessions, options.now());
|
|
1067
1166
|
const replacedTokens = findDevSessionTokensForOrigin(devSessions, validated.miniUrlOrigin);
|
|
1068
1167
|
if (devSessions.size - replacedTokens.length >= MAX_ACTIVE_DEV_SESSIONS) {
|
|
@@ -1081,6 +1180,7 @@ function createDevSessionRoutes(options) {
|
|
|
1081
1180
|
expiresAt,
|
|
1082
1181
|
miniUrlOrigin: validated.miniUrlOrigin,
|
|
1083
1182
|
networkRequestEnabled: validated.networkRequestEnabled,
|
|
1183
|
+
useOfficialDomain: validated.useOfficialDomain,
|
|
1084
1184
|
serialized: JSON.stringify(snapshot),
|
|
1085
1185
|
});
|
|
1086
1186
|
try {
|
|
@@ -1129,7 +1229,7 @@ function createDevSessionRoutes(options) {
|
|
|
1129
1229
|
},
|
|
1130
1230
|
};
|
|
1131
1231
|
}
|
|
1132
|
-
function validateDevSessionCreatePayload(payload, allowedMiniUrlOrigins) {
|
|
1232
|
+
function validateDevSessionCreatePayload(payload, allowedMiniUrlOrigins, runtimePermissions) {
|
|
1133
1233
|
if (!isRecord$2(payload) || Object.keys(payload).some((key) => !['mini_url_origin', 'runtime_permissions'].includes(key))) {
|
|
1134
1234
|
throw new TypeError('Dev Session body is invalid');
|
|
1135
1235
|
}
|
|
@@ -1149,18 +1249,16 @@ function validateDevSessionCreatePayload(payload, allowedMiniUrlOrigins) {
|
|
|
1149
1249
|
!allowedMiniUrlOrigins.has(miniUrlOrigin.origin)) {
|
|
1150
1250
|
throw new TypeError('mini_url_origin must be an allowed LAN origin');
|
|
1151
1251
|
}
|
|
1152
|
-
const parsedPermissions = runtimePermissionEnv.parseMiniProgramRuntimePermissions(
|
|
1252
|
+
const parsedPermissions = runtimePermissionEnv.parseMiniProgramRuntimePermissions(runtimePermissions);
|
|
1153
1253
|
if (!parsedPermissions.valid) {
|
|
1154
1254
|
throw new TypeError('runtime_permissions is invalid');
|
|
1155
1255
|
}
|
|
1156
1256
|
const networkPermission = parsedPermissions.permissions.network;
|
|
1157
|
-
if (networkPermission?.config.useOfficialDomain !== false) {
|
|
1158
|
-
throw new TypeError('Dev Session does not allow official domains');
|
|
1159
|
-
}
|
|
1160
1257
|
return {
|
|
1161
1258
|
miniUrlOrigin: miniUrlOrigin.origin,
|
|
1162
|
-
networkRequestEnabled: networkPermission
|
|
1163
|
-
|
|
1259
|
+
networkRequestEnabled: networkPermission?.status === 'enabled',
|
|
1260
|
+
useOfficialDomain: networkPermission?.status === 'enabled' && networkPermission.config.useOfficialDomain === true,
|
|
1261
|
+
runtimePermissions: JSON.parse(JSON.stringify(runtimePermissions)),
|
|
1164
1262
|
};
|
|
1165
1263
|
}
|
|
1166
1264
|
async function handleTokenNetworkProxy(request, response, token, runtime) {
|
|
@@ -1190,6 +1288,7 @@ async function handleTokenNetworkProxy(request, response, token, runtime) {
|
|
|
1190
1288
|
await handleBrowserDevHostNetworkProxy(request, response, runtime.fetchImpl, runtime.lookupHostname, {
|
|
1191
1289
|
allowedHttpOrigins: new Set([resolved.session.miniUrlOrigin]),
|
|
1192
1290
|
cors: true,
|
|
1291
|
+
permission: { useOfficialDomain: resolved.session.useOfficialDomain },
|
|
1193
1292
|
});
|
|
1194
1293
|
}
|
|
1195
1294
|
function resolveDevSession(token, devSessions, expiredDevSessions, now) {
|
|
@@ -1851,8 +1950,8 @@ const DEFAULT_CHANNEL_TRANSPORT = {
|
|
|
1851
1950
|
};
|
|
1852
1951
|
const IPV4_LOOPBACK_ADDRESSES = new net.BlockList();
|
|
1853
1952
|
IPV4_LOOPBACK_ADDRESSES.addSubnet('127.0.0.0', 8, 'ipv4');
|
|
1854
|
-
const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-
|
|
1855
|
-
const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-
|
|
1953
|
+
const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-jz2O-8Sf.cjs', document.baseURI).href)))('ws');
|
|
1954
|
+
const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-jz2O-8Sf.cjs', document.baseURI).href)))('parse5');
|
|
1856
1955
|
const BROWSER_DEV_HOST_ROOT_CANDIDATES = [
|
|
1857
1956
|
path.resolve(__dirname, 'devtools/browser-dev-host'),
|
|
1858
1957
|
path.resolve(__dirname, '../devtools/browser-dev-host'),
|
|
@@ -1883,9 +1982,11 @@ async function startBrowserDevHostServer(options) {
|
|
|
1883
1982
|
lanAddresses: options.lanAddresses ?? [],
|
|
1884
1983
|
loginAvailable: options.loginService !== undefined,
|
|
1885
1984
|
miniProgram: options.miniProgram,
|
|
1985
|
+
miniProgramSupportsDesktop: options.miniProgramSupportsDesktop,
|
|
1886
1986
|
mobileLaunchRedirect: initialMobileLaunchRedirect,
|
|
1887
1987
|
nativeAppLaunchUnavailableReason: options.nativeAppLaunchUnavailableReason,
|
|
1888
1988
|
runtimePermissions: options.runtimePermissions,
|
|
1989
|
+
devReadiness: options.devReadiness,
|
|
1889
1990
|
};
|
|
1890
1991
|
let bootstrapJson = serializeBrowserDevHostBootstrap(bootstrap);
|
|
1891
1992
|
const serverRuntime = createBrowserDevHostServer(root, {
|
|
@@ -1906,6 +2007,8 @@ async function startBrowserDevHostServer(options) {
|
|
|
1906
2007
|
loginAvailable: options.loginService !== undefined,
|
|
1907
2008
|
cliAuth: options.cliAuth,
|
|
1908
2009
|
readDevContext: () => bootstrap.devContext ?? { source: 'anonymous', runtimePermissions: bootstrap.runtimePermissions },
|
|
2010
|
+
readMobileDevSessionContext: options.readMobileDevSessionContext ??
|
|
2011
|
+
(() => Promise.resolve(bootstrap.devContext ?? { source: 'anonymous', runtimePermissions: bootstrap.runtimePermissions })),
|
|
1909
2012
|
readLanAddresses: () => bootstrap.lanAddresses,
|
|
1910
2013
|
mobileLaunchRedirectRegistry,
|
|
1911
2014
|
onMobileDevSessionCreated: options.onMobileDevSessionCreated,
|
|
@@ -1950,6 +2053,8 @@ async function startBrowserDevHostServer(options) {
|
|
|
1950
2053
|
return closePromise;
|
|
1951
2054
|
},
|
|
1952
2055
|
updateBootstrap: (next) => {
|
|
2056
|
+
if (next.devReadiness !== undefined)
|
|
2057
|
+
bootstrap.devReadiness = next.devReadiness;
|
|
1953
2058
|
if (next.devContext !== undefined)
|
|
1954
2059
|
bootstrap.devContext = next.devContext;
|
|
1955
2060
|
if (next.lanAddresses !== undefined) {
|
|
@@ -1966,6 +2071,8 @@ async function startBrowserDevHostServer(options) {
|
|
|
1966
2071
|
}
|
|
1967
2072
|
if (next.miniProgram !== undefined)
|
|
1968
2073
|
bootstrap.miniProgram = next.miniProgram;
|
|
2074
|
+
if (next.miniProgramSupportsDesktop !== undefined)
|
|
2075
|
+
bootstrap.miniProgramSupportsDesktop = next.miniProgramSupportsDesktop;
|
|
1969
2076
|
bootstrapJson = serializeBrowserDevHostBootstrap(bootstrap);
|
|
1970
2077
|
},
|
|
1971
2078
|
networkUrls: createBrowserDevHostNetworkUrls({
|
|
@@ -2073,6 +2180,7 @@ function createBrowserDevHostServer(root, runtime) {
|
|
|
2073
2180
|
fetchImpl: runtime.fetchImpl,
|
|
2074
2181
|
lookupHostname: runtime.lookupHostname,
|
|
2075
2182
|
now: runtime.now,
|
|
2183
|
+
readDevContext: runtime.readMobileDevSessionContext,
|
|
2076
2184
|
onCreated: ({ expiresAt, miniUrlOrigin, token }, request) => {
|
|
2077
2185
|
const origin = new URL(readRequestOrigin(request));
|
|
2078
2186
|
const hostname = origin.hostname.startsWith('[') && origin.hostname.endsWith(']') ? origin.hostname.slice(1, -1) : origin.hostname;
|
|
@@ -2172,6 +2280,44 @@ function createBrowserDevHostServer(root, runtime) {
|
|
|
2172
2280
|
writeSerializedJsonResponse(response, 200, runtime.readBootstrapJson());
|
|
2173
2281
|
return;
|
|
2174
2282
|
}
|
|
2283
|
+
if (pathname === '/__hb_sdk__/sandbox') {
|
|
2284
|
+
if (!isValidSensitiveRequest(request, loopbackPeer, runtime.sessionController.csrfToken)) {
|
|
2285
|
+
writeForbidden(response);
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
if (request.method !== 'POST') {
|
|
2289
|
+
writeMethodNotAllowed(response, 'POST');
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
try {
|
|
2293
|
+
const input = await readJsonRequestBody(request);
|
|
2294
|
+
if (!isRecord$2(input) || typeof input.device !== 'string' || typeof input.path !== 'string') {
|
|
2295
|
+
writeBadRequest(response);
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
const store = getDeviceLogs(runtime.appUrl);
|
|
2299
|
+
writeJsonResponse(response, 200, store ? await store.listSandbox(input.device, input.path) : { error: '设备服务不可用' });
|
|
2300
|
+
}
|
|
2301
|
+
catch {
|
|
2302
|
+
writeBadRequest(response);
|
|
2303
|
+
}
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
if (pathname === DEVICE_LOG_PATH) {
|
|
2307
|
+
const origin = request.headers.origin;
|
|
2308
|
+
if (!loopbackPeer || !hasNumericLoopbackHost(request) ||
|
|
2309
|
+
(origin && origin !== `http://${request.headers.host}`)) {
|
|
2310
|
+
writeForbidden(response);
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
if (request.method !== 'GET') {
|
|
2314
|
+
writeMethodNotAllowed(response, 'GET');
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
2317
|
+
const store = getDeviceLogs(runtime.appUrl);
|
|
2318
|
+
writeJsonResponse(response, 200, store ? store.query(requestUrl.searchParams) : { epoch: '', items: [], devices: [], nextCursor: 0, hasMore: false, gap: false });
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2175
2321
|
if (pathname === '/session/status') {
|
|
2176
2322
|
if (!loopbackPeer || !hasNumericLoopbackHost(request)) {
|
|
2177
2323
|
writeForbidden(response);
|
|
@@ -10902,7 +11048,7 @@ function assertNodeRuntimeCapabilities(ctx, settings) {
|
|
|
10902
11048
|
function createNodeRealtimeDriverRegistry() {
|
|
10903
11049
|
const registry = createRuntimeRealtimeDriverRegistry();
|
|
10904
11050
|
registry.registerLazy('node-websocket', async () => {
|
|
10905
|
-
const mod = await Promise.resolve().then(function () { return require('./index.esm-
|
|
11051
|
+
const mod = await Promise.resolve().then(function () { return require('./index.esm-DiC4hnWh.cjs'); });
|
|
10906
11052
|
return mod.createNodeWebSocketRuntimeDriver();
|
|
10907
11053
|
});
|
|
10908
11054
|
return registry;
|
|
@@ -13785,9 +13931,10 @@ function readHeyboxWebV4BrowserFingerprint() {
|
|
|
13785
13931
|
if (typeof document === 'undefined' || typeof navigator === 'undefined') {
|
|
13786
13932
|
return '';
|
|
13787
13933
|
}
|
|
13934
|
+
let webgl = null;
|
|
13788
13935
|
try {
|
|
13789
13936
|
const canvas = document.createElement('canvas');
|
|
13790
|
-
|
|
13937
|
+
webgl = canvas.getContext('webgl');
|
|
13791
13938
|
const debugInfo = webgl?.getExtension('WEBGL_debug_renderer_info');
|
|
13792
13939
|
let renderer = debugInfo ? String(webgl?.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) ?? '') : '';
|
|
13793
13940
|
if (!renderer) {
|
|
@@ -13806,6 +13953,15 @@ function readHeyboxWebV4BrowserFingerprint() {
|
|
|
13806
13953
|
catch {
|
|
13807
13954
|
return `${navigator.userAgent ?? ''}${navigator.platform ?? ''}${navigator.language ?? ''}`;
|
|
13808
13955
|
}
|
|
13956
|
+
finally {
|
|
13957
|
+
// 签名请求会反复读取种子,及时释放临时上下文,避免耗尽浏览器的 WebGL 配额。
|
|
13958
|
+
try {
|
|
13959
|
+
webgl?.getExtension('WEBGL_lose_context')?.loseContext();
|
|
13960
|
+
}
|
|
13961
|
+
catch {
|
|
13962
|
+
// 浏览器可能已丢失上下文;清理失败不改变本次签名种子或原降级语义。
|
|
13963
|
+
}
|
|
13964
|
+
}
|
|
13809
13965
|
}
|
|
13810
13966
|
/**
|
|
13811
13967
|
* 根据 pathname、时间和 nonce 计算 Web V4 hkey。
|
|
@@ -14862,7 +15018,7 @@ function containsSecret(value, secret) {
|
|
|
14862
15018
|
|
|
14863
15019
|
const LAUNCH_CORS_HEADERS = {
|
|
14864
15020
|
'access-control-allow-methods': 'GET',
|
|
14865
|
-
'access-control-allow-origin': '
|
|
15021
|
+
'access-control-allow-origin': 'https://www.xiaoheihe.cn',
|
|
14866
15022
|
'access-control-allow-private-network': 'true',
|
|
14867
15023
|
'cache-control': 'no-store',
|
|
14868
15024
|
};
|
|
@@ -14877,6 +15033,10 @@ function tryHandleMiniProgramDevLaunchRequest(request, response, readSource) {
|
|
|
14877
15033
|
if (requestUrl.pathname !== MINI_PROGRAM_DEV_LAUNCH_PATH || requestUrl.search !== '') {
|
|
14878
15034
|
return false;
|
|
14879
15035
|
}
|
|
15036
|
+
if (!isAllowedLaunchOrigin(request.headers.origin)) {
|
|
15037
|
+
writeLaunchResponse(response, 403);
|
|
15038
|
+
return true;
|
|
15039
|
+
}
|
|
14880
15040
|
if (request.method !== 'GET' && request.method !== 'OPTIONS') {
|
|
14881
15041
|
response.writeHead(405, { allow: 'GET', ...LAUNCH_CORS_HEADERS });
|
|
14882
15042
|
response.end();
|
|
@@ -14911,6 +15071,16 @@ function createLaunchSnapshot(source, requestOrigin) {
|
|
|
14911
15071
|
const localDevLaunchToken = source.localDevLaunchToken?.trim();
|
|
14912
15072
|
if (localDevLaunchToken)
|
|
14913
15073
|
snapshot.local_dev_launch_token = localDevLaunchToken;
|
|
15074
|
+
if (source.devSessionId)
|
|
15075
|
+
snapshot.dev_session_id = source.devSessionId;
|
|
15076
|
+
if (source.expiresAt)
|
|
15077
|
+
snapshot.expires_at = source.expiresAt;
|
|
15078
|
+
const companionManifestSha256 = source.companionManifestSha256?.trim();
|
|
15079
|
+
if (companionManifestSha256)
|
|
15080
|
+
snapshot.companion_manifest_sha256 = companionManifestSha256;
|
|
15081
|
+
const companionArtifactAccessToken = source.companionArtifactAccessToken?.trim();
|
|
15082
|
+
if (companionArtifactAccessToken)
|
|
15083
|
+
snapshot.companion_artifact_access_token = companionArtifactAccessToken;
|
|
14914
15084
|
const storedContextUrl = source.devContextUrlsByMiniUrlOrigin.get(requestOrigin.origin);
|
|
14915
15085
|
if (storedContextUrl) {
|
|
14916
15086
|
snapshot.dev_context_url = projectStoredDevContextUrl(storedContextUrl, requestOrigin.hostname);
|
|
@@ -14954,6 +15124,9 @@ function readNumericRequestOrigin(request) {
|
|
|
14954
15124
|
function isAllowedLaunchPeer(address) {
|
|
14955
15125
|
return isLoopbackOrPrivateLanAddress(address);
|
|
14956
15126
|
}
|
|
15127
|
+
function isAllowedLaunchOrigin(origin) {
|
|
15128
|
+
return origin === undefined || origin === 'https://www.xiaoheihe.cn';
|
|
15129
|
+
}
|
|
14957
15130
|
function writeLaunchResponse(response, status, body) {
|
|
14958
15131
|
response.writeHead(status, {
|
|
14959
15132
|
...LAUNCH_CORS_HEADERS,
|
|
@@ -14970,6 +15143,10 @@ const LOCAL_DEV_URL_HOST = '127.0.0.1';
|
|
|
14970
15143
|
// 与 Heybox H5 Vite 插件共享,让 hb-sdk 统一管理启动输出。
|
|
14971
15144
|
const MANAGED_DEV_OUTPUT_ENV = 'HEYBOX_DEV_SERVER_MANAGED_OUTPUT';
|
|
14972
15145
|
const VITE_LOG_LEVEL = 'warn';
|
|
15146
|
+
const COMPANION_DEV_MANIFEST_PATH = '/__hb_sdk__/companion/manifest.json';
|
|
15147
|
+
const COMPANION_DEV_MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
15148
|
+
const COMPANION_ARTIFACT_ACCESS_TOKEN_ENV = 'HB_SDK_COMPANION_ARTIFACT_ACCESS_TOKEN';
|
|
15149
|
+
const COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER = 'x-hb-sdk-companion-dev-token';
|
|
14973
15150
|
async function runDevCommand(options, runtime = {}) {
|
|
14974
15151
|
const logger = runtime.logger ?? index.createCliLogger();
|
|
14975
15152
|
const fetchImpl = runtime.fetchImpl ?? fetch;
|
|
@@ -14991,6 +15168,9 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
14991
15168
|
port: appPort,
|
|
14992
15169
|
};
|
|
14993
15170
|
const projectRoot = projectVite.findProjectRoot(runtime.cwd ?? process.cwd());
|
|
15171
|
+
const companionArtifactAccessToken = (runtime.randomBytes ?? node_crypto.randomBytes)(32).toString('base64url');
|
|
15172
|
+
const devSessionId = node_crypto.randomBytes(16).toString('hex');
|
|
15173
|
+
const miniProgramPlatforms = runtimePermissionEnv.readMiniappPlatformsFromPackageJson(projectRoot);
|
|
14994
15174
|
const { context: devLaunchContext, miniProgram } = await loadDevLaunchContext({
|
|
14995
15175
|
env: runtime.env,
|
|
14996
15176
|
fetchImpl,
|
|
@@ -14998,22 +15178,59 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
14998
15178
|
projectRoot,
|
|
14999
15179
|
timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
|
|
15000
15180
|
});
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
const appServer = await runDevStartupTask(logger, '正在创建 Vite 开发服务器', () => runtimePermissionEnv.withMiniappPlatformCspEnv(context.shouldSkipMiniappPlatformCsp(devLaunchContext.runtimePermissions), () => withManagedDevOutputEnv(() => vite.createServer(createViteServerOptions(projectRoot, resolvedOptions)), runtime.env), runtime.env), '已创建 Vite 开发服务器');
|
|
15181
|
+
let currentDevContext = devLaunchContext;
|
|
15182
|
+
let launchRuntimePermissions = context.createBrowserDevRuntimePermissions(devLaunchContext.runtimePermissions, context.createBrowserDevPermissionState(devLaunchContext));
|
|
15004
15183
|
const mobileDevContextUrlsByOrigin = new Map();
|
|
15184
|
+
let currentCompanionManifestSha256;
|
|
15005
15185
|
let currentLocalDevLaunchToken = devLaunchContext.localDevLaunchToken;
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15186
|
+
let currentExpiresAt;
|
|
15187
|
+
let tokenSession;
|
|
15188
|
+
const readLaunchSource = () => ({
|
|
15189
|
+
companionArtifactAccessToken,
|
|
15190
|
+
companionManifestSha256: currentCompanionManifestSha256,
|
|
15191
|
+
devContextUrlsByMiniUrlOrigin: mobileDevContextUrlsByOrigin,
|
|
15192
|
+
localDevLaunchToken: currentLocalDevLaunchToken,
|
|
15193
|
+
devSessionId,
|
|
15194
|
+
expiresAt: currentExpiresAt,
|
|
15195
|
+
miniProgramId: devLaunchContext.miniProgramId,
|
|
15196
|
+
runtimePermissions: launchRuntimePermissions,
|
|
15197
|
+
sdkVersion: version.HB_SDK_VERSION,
|
|
15016
15198
|
});
|
|
15199
|
+
const launchPlugin = createMiniProgramDevLaunchVitePlugin(readLaunchSource, async (request, response) => {
|
|
15200
|
+
if (request.url !== MINI_PROGRAM_DEV_LAUNCH_PATH || request.headers[COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER] === undefined)
|
|
15201
|
+
return false;
|
|
15202
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
15203
|
+
const token = request.headers[COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER];
|
|
15204
|
+
const remote = request.socket.remoteAddress;
|
|
15205
|
+
if (typeof token !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(token) ||
|
|
15206
|
+
!node_crypto.timingSafeEqual(Buffer.from(token), Buffer.from(companionArtifactAccessToken)) ||
|
|
15207
|
+
!['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(remote ?? '') ||
|
|
15208
|
+
!/^(127\.0\.0\.1|\[::1\]):\d{1,5}$/.test(request.headers.host ?? '')) {
|
|
15209
|
+
response.writeHead(403).end();
|
|
15210
|
+
return true;
|
|
15211
|
+
}
|
|
15212
|
+
if (request.method !== 'GET') {
|
|
15213
|
+
response.writeHead(405, { Allow: 'GET' }).end();
|
|
15214
|
+
return true;
|
|
15215
|
+
}
|
|
15216
|
+
try {
|
|
15217
|
+
const issued = await tokenSession?.getTicket(currentCompanionManifestSha256);
|
|
15218
|
+
if (!issued)
|
|
15219
|
+
response.writeHead(503).end();
|
|
15220
|
+
else
|
|
15221
|
+
response.writeHead(200, { 'Content-Type': 'application/json', 'X-Content-Type-Options': 'nosniff' }).end(JSON.stringify(createLaunchSnapshot({
|
|
15222
|
+
...readLaunchSource(), localDevLaunchToken: issued.token, expiresAt: issued.expiresAt,
|
|
15223
|
+
}, new URL(`http://${request.headers.host}`))));
|
|
15224
|
+
}
|
|
15225
|
+
catch (error) {
|
|
15226
|
+
const status = error instanceof index.CliError && (error.httpStatus === 401 || error.httpStatus === 403) ? error.httpStatus : 503;
|
|
15227
|
+
logger.debug(`本地开发会话续票失败:${index.readRedactedErrorMessage(error, { verbose: true })}`);
|
|
15228
|
+
response.writeHead(status).end();
|
|
15229
|
+
}
|
|
15230
|
+
return true;
|
|
15231
|
+
});
|
|
15232
|
+
const vite = await runDevStartupTask(logger, '正在加载项目 Vite', () => projectVite.loadProjectVite(projectRoot), '已加载项目 Vite');
|
|
15233
|
+
const appServer = await runDevStartupTask(logger, '正在创建 Vite 开发服务器', () => runtimePermissionEnv.withMiniappPlatformCspEnv(context.shouldSkipMiniappPlatformCsp(devLaunchContext.runtimePermissions), () => withCompanionArtifactAccessTokenEnv(companionArtifactAccessToken, () => withManagedDevOutputEnv(() => vite.createServer(createViteServerOptions(projectRoot, resolvedOptions, launchPlugin)), runtime.env), runtime.env), runtime.env), '已创建 Vite 开发服务器');
|
|
15017
15234
|
await runDevStartupTask(logger, '正在启动 Vite 开发服务器', () => appServer.listen(appPort), 'Vite 开发服务器已启动');
|
|
15018
15235
|
const appUrl = await runDevStartupTask(logger, '正在探测小程序入口', () => resolveViteAppUrl(appServer, resolvedOptions, fetchImpl), '已确定小程序入口');
|
|
15019
15236
|
const buildLanAddresses = () => createLanAddressCandidates({
|
|
@@ -15032,7 +15249,7 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15032
15249
|
try {
|
|
15033
15250
|
browserDevHost = await runDevStartupTask(logger, '正在启动浏览器调试页服务', () => startBrowserDevHostServer({
|
|
15034
15251
|
appUrl,
|
|
15035
|
-
devContext:
|
|
15252
|
+
devContext: currentDevContext,
|
|
15036
15253
|
fetchImpl,
|
|
15037
15254
|
getPort: getPortImpl,
|
|
15038
15255
|
defaultLanAddressId: lanAddresses[0]?.id,
|
|
@@ -15040,6 +15257,8 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15040
15257
|
lookupHostname: runtime.lookupHostname,
|
|
15041
15258
|
loginService: runtime.loginService,
|
|
15042
15259
|
miniProgram,
|
|
15260
|
+
// 只在项目声明了桌面平台时向调试台暴露「在桌面端打开」入口。
|
|
15261
|
+
miniProgramSupportsDesktop: runtimePermissionEnv.hasMiniappDesktopPlatform(miniProgramPlatforms),
|
|
15043
15262
|
cliAuth: runtime.cliAuth ?? createBrowserDevHostCliAuth({ env: runtime.env, fetchImpl }),
|
|
15044
15263
|
onMobileDevSessionCreated: ({ devContextUrl, miniUrlOrigin }) => {
|
|
15045
15264
|
mobileDevContextUrlsByOrigin.set(miniUrlOrigin, devContextUrl);
|
|
@@ -15047,6 +15266,30 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15047
15266
|
lanAddresses: buildLanAddresses(),
|
|
15048
15267
|
});
|
|
15049
15268
|
},
|
|
15269
|
+
readMobileDevSessionContext: async () => {
|
|
15270
|
+
try {
|
|
15271
|
+
const refreshed = await loadDevLaunchContext({
|
|
15272
|
+
env: runtime.env,
|
|
15273
|
+
fetchImpl,
|
|
15274
|
+
logger,
|
|
15275
|
+
projectRoot,
|
|
15276
|
+
timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
|
|
15277
|
+
});
|
|
15278
|
+
currentDevContext = withCurrentLocalDevLaunchToken(refreshed.context, currentLocalDevLaunchToken);
|
|
15279
|
+
launchRuntimePermissions = context.createMobileDevSessionRuntimePermissions(currentDevContext);
|
|
15280
|
+
browserDevHost.updateBootstrap({
|
|
15281
|
+
devContext: currentDevContext,
|
|
15282
|
+
miniProgram: refreshed.miniProgram,
|
|
15283
|
+
});
|
|
15284
|
+
return currentDevContext;
|
|
15285
|
+
}
|
|
15286
|
+
catch (error) {
|
|
15287
|
+
currentDevContext = { source: 'anonymous' };
|
|
15288
|
+
launchRuntimePermissions = { schema_version: 1, entries: [] };
|
|
15289
|
+
browserDevHost.updateBootstrap({ devContext: currentDevContext });
|
|
15290
|
+
throw error;
|
|
15291
|
+
}
|
|
15292
|
+
},
|
|
15050
15293
|
port: options.browserDevHostPort ?? DEFAULT_BROWSER_DEV_HOST_PORT,
|
|
15051
15294
|
root: runtime.browserDevHostRoot,
|
|
15052
15295
|
rootCandidates: runtime.browserDevHostRootCandidates,
|
|
@@ -15059,39 +15302,70 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15059
15302
|
}
|
|
15060
15303
|
closers.push(() => browserDevHost.close());
|
|
15061
15304
|
if (devLaunchContext.source === 'remote' && devLaunchContext.miniProgramId) {
|
|
15062
|
-
|
|
15305
|
+
tokenSession = startLocalDevLaunchTokenSession({
|
|
15063
15306
|
env: runtime.env,
|
|
15064
15307
|
fetchImpl,
|
|
15065
15308
|
logger,
|
|
15309
|
+
manifestUrl: new URL(COMPANION_DEV_MANIFEST_PATH, appUrl).toString(),
|
|
15310
|
+
companionArtifactAccessToken,
|
|
15311
|
+
companionApproved: hasCompanionApproval(devLaunchContext.runtimePermissions),
|
|
15066
15312
|
miniProgramId: devLaunchContext.miniProgramId,
|
|
15067
|
-
|
|
15313
|
+
refreshPermissions: async () => {
|
|
15314
|
+
const refreshed = await loadDevLaunchContext({ env: runtime.env, fetchImpl, logger, projectRoot,
|
|
15315
|
+
timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS });
|
|
15316
|
+
if (refreshed.context.miniProgramId !== devLaunchContext.miniProgramId)
|
|
15317
|
+
throw new Error('项目绑定已变更,请重新启动调试');
|
|
15318
|
+
devLaunchContext.runtimePermissions = refreshed.context.runtimePermissions;
|
|
15319
|
+
currentDevContext = withCurrentLocalDevLaunchToken(devLaunchContext, currentLocalDevLaunchToken);
|
|
15320
|
+
launchRuntimePermissions = context.createBrowserDevRuntimePermissions(devLaunchContext.runtimePermissions, context.createBrowserDevPermissionState(devLaunchContext));
|
|
15321
|
+
browserDevHost.updateBootstrap({ devContext: currentDevContext });
|
|
15322
|
+
return hasCompanionApproval(devLaunchContext.runtimePermissions);
|
|
15323
|
+
},
|
|
15324
|
+
onToken: (localDevLaunchToken, companionManifestSha256, expiresAt) => {
|
|
15325
|
+
currentExpiresAt = expiresAt;
|
|
15326
|
+
currentCompanionManifestSha256 = companionManifestSha256;
|
|
15068
15327
|
currentLocalDevLaunchToken = localDevLaunchToken;
|
|
15069
|
-
|
|
15070
|
-
source: 'remote',
|
|
15071
|
-
miniProgramId: devLaunchContext.miniProgramId,
|
|
15072
|
-
...(localDevLaunchToken ? { localDevLaunchToken } : {}),
|
|
15073
|
-
runtimePermissions: devLaunchContext.runtimePermissions,
|
|
15074
|
-
};
|
|
15328
|
+
currentDevContext = withCurrentLocalDevLaunchToken(currentDevContext, localDevLaunchToken);
|
|
15075
15329
|
browserDevHost.updateBootstrap({
|
|
15076
|
-
devContext:
|
|
15330
|
+
devContext: currentDevContext,
|
|
15077
15331
|
lanAddresses: buildLanAddresses(),
|
|
15078
15332
|
});
|
|
15079
15333
|
},
|
|
15334
|
+
onReadiness: devReadiness => browserDevHost.updateBootstrap({ devReadiness }),
|
|
15080
15335
|
projectRoot,
|
|
15081
15336
|
timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
|
|
15082
15337
|
});
|
|
15083
15338
|
closers.push(tokenSession.close);
|
|
15084
15339
|
}
|
|
15085
|
-
|
|
15340
|
+
else {
|
|
15341
|
+
const reason = '登录或小程序绑定不可用,请完成 CLI 登录和项目绑定';
|
|
15342
|
+
browserDevHost.updateBootstrap({
|
|
15343
|
+
devReadiness: {
|
|
15344
|
+
browser: { state: 'ready' },
|
|
15345
|
+
mobile: { state: 'blocked', reason },
|
|
15346
|
+
companion: { state: 'blocked', reason },
|
|
15347
|
+
},
|
|
15348
|
+
});
|
|
15349
|
+
}
|
|
15350
|
+
logger.success('Web / Browser Fake 调试已就绪');
|
|
15086
15351
|
logger.info(`调试页:${browserDevHost.url}`);
|
|
15087
15352
|
logger.info(`小程序地址:${appUrl}`);
|
|
15088
|
-
logger.info('
|
|
15353
|
+
logger.info('手机与 PC Companion 的就绪状态请查看调试页');
|
|
15089
15354
|
if (options.open !== false) {
|
|
15090
15355
|
logger.debug('正在打开浏览器调试页');
|
|
15091
15356
|
void openUrl(browserDevHost.url);
|
|
15092
15357
|
}
|
|
15093
15358
|
installShutdownHandlers(closers, runtime.process ?? process);
|
|
15094
15359
|
}
|
|
15360
|
+
function withCurrentLocalDevLaunchToken(context, localDevLaunchToken) {
|
|
15361
|
+
return {
|
|
15362
|
+
source: context.source,
|
|
15363
|
+
...(context.miniProgramId ? { miniProgramId: context.miniProgramId } : {}),
|
|
15364
|
+
...(localDevLaunchToken ? { localDevLaunchToken } : {}),
|
|
15365
|
+
...(context.runtimePermissions !== undefined ? { runtimePermissions: context.runtimePermissions } : {}),
|
|
15366
|
+
...(context.warning ? { warning: context.warning } : {}),
|
|
15367
|
+
};
|
|
15368
|
+
}
|
|
15095
15369
|
function runDevStartupTask(logger, text, action, successText) {
|
|
15096
15370
|
if (!logger.verbose) {
|
|
15097
15371
|
return action();
|
|
@@ -15165,10 +15439,26 @@ async function loadRemoteDevContextWithTimeout(options) {
|
|
|
15165
15439
|
}
|
|
15166
15440
|
}
|
|
15167
15441
|
}
|
|
15168
|
-
function
|
|
15442
|
+
function createMiniProgramDevLaunchVitePlugin(getSnapshot, handleSession) {
|
|
15443
|
+
return {
|
|
15444
|
+
name: 'heybox-mini-program-dev-launch',
|
|
15445
|
+
enforce: 'pre',
|
|
15446
|
+
configureServer(server) {
|
|
15447
|
+
server.middlewares.use(async (request, response, next) => {
|
|
15448
|
+
if (handleSession && await handleSession(request, response))
|
|
15449
|
+
return;
|
|
15450
|
+
if (!tryHandleMiniProgramDevLaunchRequest(request, response, getSnapshot))
|
|
15451
|
+
next();
|
|
15452
|
+
});
|
|
15453
|
+
},
|
|
15454
|
+
};
|
|
15455
|
+
}
|
|
15456
|
+
function createViteServerOptions(projectRoot, options, launchPlugin) {
|
|
15169
15457
|
const configFile = resolveProjectViteConfig(projectRoot);
|
|
15170
15458
|
const server = {
|
|
15171
|
-
|
|
15459
|
+
// launch.json 由 mini-program-dev-launch plugin 自己处理 PNA/CORS 预检;
|
|
15460
|
+
// Vite 通用 CORS 会抢先返回 `*`,缺少 Access-Control-Allow-Private-Network。
|
|
15461
|
+
cors: false,
|
|
15172
15462
|
hmr: {
|
|
15173
15463
|
clientPort: options.port ?? DEFAULT_APP_PORT,
|
|
15174
15464
|
},
|
|
@@ -15180,11 +15470,13 @@ function createViteServerOptions(projectRoot, options) {
|
|
|
15180
15470
|
return {
|
|
15181
15471
|
configFile,
|
|
15182
15472
|
logLevel: VITE_LOG_LEVEL,
|
|
15473
|
+
plugins: [launchPlugin],
|
|
15183
15474
|
server,
|
|
15184
15475
|
};
|
|
15185
15476
|
}
|
|
15186
15477
|
return {
|
|
15187
15478
|
logLevel: VITE_LOG_LEVEL,
|
|
15479
|
+
plugins: [launchPlugin],
|
|
15188
15480
|
root: projectRoot,
|
|
15189
15481
|
server,
|
|
15190
15482
|
};
|
|
@@ -15298,6 +15590,19 @@ async function withManagedDevOutputEnv(action, env = process.env) {
|
|
|
15298
15590
|
}
|
|
15299
15591
|
}
|
|
15300
15592
|
}
|
|
15593
|
+
async function withCompanionArtifactAccessTokenEnv(token, action, env = process.env) {
|
|
15594
|
+
const previous = env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV];
|
|
15595
|
+
env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV] = token;
|
|
15596
|
+
try {
|
|
15597
|
+
return await action();
|
|
15598
|
+
}
|
|
15599
|
+
finally {
|
|
15600
|
+
if (previous === undefined)
|
|
15601
|
+
delete env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV];
|
|
15602
|
+
else
|
|
15603
|
+
env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV] = previous;
|
|
15604
|
+
}
|
|
15605
|
+
}
|
|
15301
15606
|
function installShutdownHandlers(closers, processLike) {
|
|
15302
15607
|
const shutdown = async () => {
|
|
15303
15608
|
await closeAll(closers);
|
|
@@ -15318,28 +15623,94 @@ function startLocalDevLaunchTokenSession(options) {
|
|
|
15318
15623
|
let expirationTimer;
|
|
15319
15624
|
let hasReportedTokenFailure = false;
|
|
15320
15625
|
let refreshTimer;
|
|
15626
|
+
let companionApproved = options.companionApproved;
|
|
15627
|
+
let permissionsRefreshAt = Date.now() + 30_000;
|
|
15628
|
+
let publishedToken;
|
|
15629
|
+
let publishedDigest;
|
|
15630
|
+
let ticket;
|
|
15631
|
+
let pending;
|
|
15632
|
+
const getTicket = async (companionManifestSha256) => {
|
|
15633
|
+
if (closed)
|
|
15634
|
+
return undefined;
|
|
15635
|
+
if (ticket && (companionManifestSha256 === undefined || ticket.companionManifestSha256 === companionManifestSha256) && ticket.refreshAt > Date.now())
|
|
15636
|
+
return ticket;
|
|
15637
|
+
return issueTicket(companionManifestSha256);
|
|
15638
|
+
};
|
|
15639
|
+
const issueTicket = (companionManifestSha256) => {
|
|
15640
|
+
if (pending && pending.digest === companionManifestSha256)
|
|
15641
|
+
return pending.promise;
|
|
15642
|
+
const promise = issueLocalDevLaunchToken({
|
|
15643
|
+
...options,
|
|
15644
|
+
...(companionManifestSha256 ? { companionManifestSha256 } : {}),
|
|
15645
|
+
})
|
|
15646
|
+
.then(issued => {
|
|
15647
|
+
ticket = issued;
|
|
15648
|
+
return issued;
|
|
15649
|
+
}).finally(() => { if (pending?.promise === promise)
|
|
15650
|
+
pending = undefined; });
|
|
15651
|
+
pending = { digest: companionManifestSha256, promise };
|
|
15652
|
+
return promise;
|
|
15653
|
+
};
|
|
15321
15654
|
const refresh = async () => {
|
|
15322
15655
|
try {
|
|
15323
|
-
|
|
15656
|
+
if (options.refreshPermissions && Date.now() >= permissionsRefreshAt) {
|
|
15657
|
+
companionApproved = await options.refreshPermissions();
|
|
15658
|
+
permissionsRefreshAt = Date.now() + 30_000;
|
|
15659
|
+
}
|
|
15660
|
+
let manifest;
|
|
15661
|
+
let snapshotError;
|
|
15662
|
+
try {
|
|
15663
|
+
manifest = await loadRemoteDevContextWithTimeout({
|
|
15664
|
+
fetchImpl: options.fetchImpl,
|
|
15665
|
+
timeoutMs: options.timeoutMs,
|
|
15666
|
+
load: fetchImpl => readCompanionManifest(options.manifestUrl, options.companionArtifactAccessToken, fetchImpl),
|
|
15667
|
+
});
|
|
15668
|
+
}
|
|
15669
|
+
catch (error) {
|
|
15670
|
+
snapshotError = error;
|
|
15671
|
+
}
|
|
15672
|
+
const digest = manifest?.digest;
|
|
15673
|
+
const issued = await getTicket(digest);
|
|
15674
|
+
if (!issued)
|
|
15675
|
+
return;
|
|
15324
15676
|
if (closed)
|
|
15325
15677
|
return;
|
|
15326
|
-
|
|
15678
|
+
if (publishedToken !== issued.token || publishedDigest !== digest) {
|
|
15679
|
+
publishedToken = issued.token;
|
|
15680
|
+
publishedDigest = digest;
|
|
15681
|
+
options.onToken(issued.token, digest, issued.expiresAt);
|
|
15682
|
+
}
|
|
15683
|
+
options.onReadiness({
|
|
15684
|
+
browser: { state: 'ready' },
|
|
15685
|
+
mobile: { state: 'ready' },
|
|
15686
|
+
companion: snapshotError
|
|
15687
|
+
? { state: snapshotError instanceof Error && snapshotError.message.includes('artifact-updating') ? 'pending' : 'blocked', reason: safeCompanionFailureReason(snapshotError) }
|
|
15688
|
+
: manifest?.artifacts.length
|
|
15689
|
+
? companionApproved
|
|
15690
|
+
? { state: 'ready', digest, artifacts: manifest.artifacts, reason: '本地产物和启动票已就绪,PC 授权将在打开后验证' }
|
|
15691
|
+
: { state: 'blocked', reason: 'Companion 未获 COA 批准或本地权限声明未开启' }
|
|
15692
|
+
: { state: 'not-configured', reason: '未配置本地 Companion 产物' },
|
|
15693
|
+
});
|
|
15694
|
+
if (snapshotError)
|
|
15695
|
+
options.logger.debug(`PC Companion 快照校验失败:${index.readRedactedErrorMessage(snapshotError, { verbose: true })}`);
|
|
15327
15696
|
const ttl = Math.max(0, issued.expiresAt - Date.now());
|
|
15328
15697
|
scheduleExpiration(ttl);
|
|
15329
|
-
schedule(Math.max(1_000, Math.floor(ttl * 0.8)));
|
|
15698
|
+
schedule(Math.min(2_000, Math.max(1_000, Math.floor(ttl * 0.8))));
|
|
15330
15699
|
if (hasReportedTokenFailure) {
|
|
15331
15700
|
hasReportedTokenFailure = false;
|
|
15332
|
-
options.logger.success('
|
|
15701
|
+
options.logger.success('本地调试凭证已恢复');
|
|
15333
15702
|
}
|
|
15334
15703
|
}
|
|
15335
15704
|
catch (error) {
|
|
15336
15705
|
if (closed)
|
|
15337
15706
|
return;
|
|
15338
15707
|
if (!hasReportedTokenFailure) {
|
|
15339
|
-
options.logger.warn('
|
|
15708
|
+
options.logger.warn('本地调试凭证获取失败,将自动重试;Web / Browser Fake 仍可使用');
|
|
15340
15709
|
}
|
|
15341
15710
|
hasReportedTokenFailure = true;
|
|
15342
|
-
options.
|
|
15711
|
+
options.onReadiness({ browser: { state: 'ready' }, mobile: { state: 'blocked', reason: '调试凭证暂不可用' },
|
|
15712
|
+
companion: { state: 'blocked', reason: safeCompanionFailureReason(error) } });
|
|
15713
|
+
options.logger.debug(`本地调试凭证获取失败,详细原因:${index.readRedactedErrorMessage(error, { verbose: true })}`);
|
|
15343
15714
|
schedule(options.timeoutMs);
|
|
15344
15715
|
}
|
|
15345
15716
|
};
|
|
@@ -15353,13 +15724,20 @@ function startLocalDevLaunchTokenSession(options) {
|
|
|
15353
15724
|
if (expirationTimer)
|
|
15354
15725
|
clearTimeout(expirationTimer);
|
|
15355
15726
|
expirationTimer = setTimeout(() => {
|
|
15356
|
-
if (!closed)
|
|
15357
|
-
options.onToken(undefined);
|
|
15727
|
+
if (!closed) {
|
|
15728
|
+
options.onToken(undefined, undefined);
|
|
15729
|
+
publishedToken = undefined;
|
|
15730
|
+
options.onReadiness({ browser: { state: 'ready' }, mobile: { state: 'blocked', reason: '调试凭证已过期' },
|
|
15731
|
+
companion: { state: 'blocked', reason: '调试凭证已过期' } });
|
|
15732
|
+
}
|
|
15358
15733
|
}, Math.max(1, ttl));
|
|
15359
15734
|
expirationTimer.unref?.();
|
|
15360
15735
|
};
|
|
15361
|
-
|
|
15736
|
+
options.onReadiness({ browser: { state: 'ready' }, mobile: { state: 'pending' }, companion: { state: 'pending' } });
|
|
15737
|
+
const ready = refresh();
|
|
15362
15738
|
return {
|
|
15739
|
+
getTicket,
|
|
15740
|
+
ready,
|
|
15363
15741
|
close: () => {
|
|
15364
15742
|
closed = true;
|
|
15365
15743
|
if (expirationTimer)
|
|
@@ -15373,22 +15751,128 @@ async function issueLocalDevLaunchToken(options) {
|
|
|
15373
15751
|
const issued = await loadRemoteDevContextWithTimeout({
|
|
15374
15752
|
fetchImpl: options.fetchImpl,
|
|
15375
15753
|
timeoutMs: options.timeoutMs,
|
|
15376
|
-
load: (fetchImpl) =>
|
|
15377
|
-
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
|
|
15754
|
+
load: async (fetchImpl) => {
|
|
15755
|
+
const result = await context.createLocalDevLaunchToken({
|
|
15756
|
+
cwd: options.projectRoot,
|
|
15757
|
+
env: options.env,
|
|
15758
|
+
fetchImpl,
|
|
15759
|
+
miniProgramId: options.miniProgramId,
|
|
15760
|
+
...(options.companionManifestSha256
|
|
15761
|
+
? { companionManifestSha256: options.companionManifestSha256 }
|
|
15762
|
+
: {}),
|
|
15763
|
+
});
|
|
15764
|
+
return { result };
|
|
15765
|
+
},
|
|
15382
15766
|
});
|
|
15383
|
-
const token = typeof issued.token === 'string' ? issued.token.trim() : '';
|
|
15767
|
+
const token = typeof issued.result.token === 'string' ? issued.result.token.trim() : '';
|
|
15384
15768
|
if (!token) {
|
|
15385
15769
|
throw new Error('响应缺少 token');
|
|
15386
15770
|
}
|
|
15387
|
-
const expiresAt = normalizeExpiresAt(issued.expires_at);
|
|
15771
|
+
const expiresAt = normalizeExpiresAt(issued.result.expires_at);
|
|
15388
15772
|
if (!expiresAt || expiresAt <= Date.now()) {
|
|
15389
15773
|
throw new Error('响应缺少有效 expires_at');
|
|
15390
15774
|
}
|
|
15391
|
-
return { expiresAt, token
|
|
15775
|
+
return { ...(options.companionManifestSha256 ? { companionManifestSha256: options.companionManifestSha256 } : {}), expiresAt, token,
|
|
15776
|
+
refreshAt: Date.now() + Math.max(1, (expiresAt - Date.now()) * 0.8) };
|
|
15777
|
+
}
|
|
15778
|
+
function safeCompanionFailureReason(error) {
|
|
15779
|
+
const message = index.readRedactedErrorMessage(error, { verbose: true });
|
|
15780
|
+
if (/artifact-updating/.test(message))
|
|
15781
|
+
return '本地 Companion 产物准备中';
|
|
15782
|
+
if (/artifact-missing/.test(message))
|
|
15783
|
+
return '本地 Companion 产物目录不存在,请先构建产物';
|
|
15784
|
+
if (/snapshot-limit/.test(message))
|
|
15785
|
+
return '本地快照数量或大小已达上限,请结束会话后重启 dev';
|
|
15786
|
+
if (/invalid-manifest/.test(message))
|
|
15787
|
+
return '本地 Manifest 或权限配置无效';
|
|
15788
|
+
if (/invalid-artifact/.test(message))
|
|
15789
|
+
return '本地产物或原生入口校验失败';
|
|
15790
|
+
if (/404|not found/i.test(message))
|
|
15791
|
+
return '调试接口尚未部署或当前登录环境不匹配';
|
|
15792
|
+
if (/403|forbidden|permission|授权|权限/i.test(message))
|
|
15793
|
+
return 'Companion 授权未通过,请检查 COA 批准与开发者身份';
|
|
15794
|
+
if (/401|登录/i.test(message))
|
|
15795
|
+
return 'CLI 登录态已失效,请重新登录';
|
|
15796
|
+
if (/manifest|快照|ZIP|ENOENT/i.test(message))
|
|
15797
|
+
return '本地 Companion ZIP 或 Manifest 校验失败,请检查产物与配置';
|
|
15798
|
+
return 'PC Companion 暂不可用,请检查网络、登录环境和调试接口';
|
|
15799
|
+
}
|
|
15800
|
+
function hasCompanionApproval(value) {
|
|
15801
|
+
if (!value || typeof value !== 'object' || !('entries' in value) || !Array.isArray(value.entries))
|
|
15802
|
+
return false;
|
|
15803
|
+
return value.entries.some(entry => entry?.key === 'companion' && entry?.status === 'enabled');
|
|
15804
|
+
}
|
|
15805
|
+
async function readCompanionManifest(manifestUrl, companionArtifactAccessToken, fetchImpl) {
|
|
15806
|
+
try {
|
|
15807
|
+
const response = await fetchImpl(manifestUrl, {
|
|
15808
|
+
cache: 'no-store',
|
|
15809
|
+
credentials: 'omit',
|
|
15810
|
+
headers: {
|
|
15811
|
+
accept: 'application/json',
|
|
15812
|
+
[COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER]: companionArtifactAccessToken,
|
|
15813
|
+
},
|
|
15814
|
+
method: 'GET',
|
|
15815
|
+
redirect: 'error',
|
|
15816
|
+
referrerPolicy: 'no-referrer',
|
|
15817
|
+
});
|
|
15818
|
+
if (response.status === 404) {
|
|
15819
|
+
return undefined;
|
|
15820
|
+
}
|
|
15821
|
+
if (!response.ok) {
|
|
15822
|
+
const code = response.headers.get('x-hb-sdk-companion-error');
|
|
15823
|
+
const safeCode = ['artifact-updating', 'artifact-missing', 'snapshot-limit', 'invalid-manifest', 'invalid-artifact'].includes(code ?? '') ? code : '';
|
|
15824
|
+
throw new Error(`Companion manifest request failed with status ${response.status} ${safeCode}`);
|
|
15825
|
+
}
|
|
15826
|
+
if (!response.headers.get('content-type')?.toLowerCase().includes('application/json')) {
|
|
15827
|
+
throw new Error('Companion manifest response is not JSON');
|
|
15828
|
+
}
|
|
15829
|
+
const contentLength = Number(response.headers.get('content-length'));
|
|
15830
|
+
if (Number.isFinite(contentLength) && contentLength > COMPANION_DEV_MANIFEST_MAX_BYTES) {
|
|
15831
|
+
throw new Error('Companion manifest response is too large');
|
|
15832
|
+
}
|
|
15833
|
+
const bytes = await readBoundedCompanionManifestBytes(response, COMPANION_DEV_MANIFEST_MAX_BYTES);
|
|
15834
|
+
const value = JSON.parse(bytes.toString('utf8'));
|
|
15835
|
+
const artifacts = Object.entries(value.companions ?? {}).flatMap(([alias, definition]) => {
|
|
15836
|
+
if (!/^[a-z][a-z0-9-]{0,31}$/.test(alias) || !definition || typeof definition !== 'object') {
|
|
15837
|
+
throw new Error('Companion invalid-manifest');
|
|
15838
|
+
}
|
|
15839
|
+
return Object.keys(definition.targets ?? {}).map(target => {
|
|
15840
|
+
if (target !== 'windows-x64' && target !== 'macos-arm64')
|
|
15841
|
+
throw new Error('Companion invalid-manifest');
|
|
15842
|
+
return { alias, target };
|
|
15843
|
+
});
|
|
15844
|
+
});
|
|
15845
|
+
return { digest: node_crypto.createHash('sha256').update(bytes).digest('hex'), artifacts };
|
|
15846
|
+
}
|
|
15847
|
+
catch (error) {
|
|
15848
|
+
if (error instanceof Error)
|
|
15849
|
+
throw error;
|
|
15850
|
+
throw new Error('Companion manifest request failed');
|
|
15851
|
+
}
|
|
15852
|
+
}
|
|
15853
|
+
async function readBoundedCompanionManifestBytes(response, maxBytes) {
|
|
15854
|
+
if (!response.body)
|
|
15855
|
+
return Buffer.alloc(0);
|
|
15856
|
+
const reader = response.body.getReader();
|
|
15857
|
+
const chunks = [];
|
|
15858
|
+
let total = 0;
|
|
15859
|
+
try {
|
|
15860
|
+
for (;;) {
|
|
15861
|
+
const { done, value } = await reader.read();
|
|
15862
|
+
if (done)
|
|
15863
|
+
break;
|
|
15864
|
+
total += value.byteLength;
|
|
15865
|
+
if (total > maxBytes) {
|
|
15866
|
+
await reader.cancel('Companion manifest response is too large');
|
|
15867
|
+
throw new Error('Companion manifest response is too large');
|
|
15868
|
+
}
|
|
15869
|
+
chunks.push(value);
|
|
15870
|
+
}
|
|
15871
|
+
}
|
|
15872
|
+
finally {
|
|
15873
|
+
reader.releaseLock();
|
|
15874
|
+
}
|
|
15875
|
+
return Buffer.concat(chunks.map(chunk => Buffer.from(chunk)), total);
|
|
15392
15876
|
}
|
|
15393
15877
|
function normalizeExpiresAt(value) {
|
|
15394
15878
|
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
@@ -15501,8 +15985,10 @@ function isPrivateIPv4(address) {
|
|
|
15501
15985
|
|
|
15502
15986
|
var dev = /*#__PURE__*/Object.freeze({
|
|
15503
15987
|
__proto__: null,
|
|
15988
|
+
createMiniProgramDevLaunchVitePlugin: createMiniProgramDevLaunchVitePlugin,
|
|
15504
15989
|
normalizeExpiresAt: normalizeExpiresAt,
|
|
15505
|
-
runDevCommand: runDevCommand
|
|
15990
|
+
runDevCommand: runDevCommand,
|
|
15991
|
+
startLocalDevLaunchTokenSession: startLocalDevLaunchTokenSession
|
|
15506
15992
|
});
|
|
15507
15993
|
|
|
15508
15994
|
exports.RuntimeError = RuntimeError;
|