@heybox/hb-sdk 0.8.1-alpha.3 → 0.8.1-alpha.7
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 +40 -0
- package/README.md +47 -3
- package/dist/cli-chunks/{build-B5Sbi8Sq.cjs → build-C8oN_Ms9.cjs} +7 -5
- package/dist/cli-chunks/{context-DuxaBL3Y.cjs → context-DZkqvg9S.cjs} +8 -3
- package/dist/cli-chunks/{create-CIfZrg3F.cjs → create-CToSHRfO.cjs} +1 -1
- package/dist/cli-chunks/{dev-CFubP5kW.cjs → dev-CTA0lsLD.cjs} +479 -140
- package/dist/cli-chunks/{doctor-IPKn3PBn.cjs → doctor-DRa_kjP1.cjs} +1 -1
- package/dist/cli-chunks/{index-KHhdZ8eZ.cjs → index-Bkl5YmtN.cjs} +2 -2
- package/dist/cli-chunks/{index-DCJN-V1y.cjs → index-zNpZDWhc.cjs} +87 -15
- package/dist/cli-chunks/{index.esm-8hgZlNF8.cjs → index.esm-CGoUW47e.cjs} +9 -8
- package/dist/cli-chunks/{login-CoB9jXxC.cjs → login-CCEPk2Ok.cjs} +2 -2
- package/dist/cli-chunks/{project-vite-DMxdOWsU.cjs → project-vite-couFw5bL.cjs} +1 -1
- package/dist/cli-chunks/{remote-D4ZcngLn.cjs → remote-DQ70o-Km.cjs} +33 -24
- package/dist/cli-chunks/{runtime-permission-env-XAZFauvZ.cjs → runtime-permission-env-BN43V7d0.cjs} +181 -16
- package/dist/cli-chunks/{session-BnoHxzFq.cjs → session-C1R_39si.cjs} +1 -1
- package/dist/cli-chunks/{skill-CDEJtATn.cjs → skill-CVXOE5jH.cjs} +83 -27
- package/dist/cli-chunks/{version-BGzCIQhE.cjs → version-DbDVL6vm.cjs} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-CH6L1ZJI.js +101 -0
- package/dist/devtools/browser-dev-host/assets/desktop-app-launch-DnPRjCa3.js +6 -0
- package/dist/devtools/browser-dev-host/assets/index-D-aNERAr.css +1 -0
- package/dist/devtools/browser-dev-host/assets/index-DocojUxT.js +567 -0
- package/dist/devtools/browser-dev-host/index.html +3 -3
- package/dist/index.cjs.js +1893 -1571
- package/dist/index.esm.js +1893 -1572
- 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/vite.cjs.js +1316 -10
- package/dist/vite.esm.js +1317 -12
- package/package.json +7 -7
- package/skill/SKILL.md +8 -3
- package/skill/references/api-protocol.md +3 -3
- package/skill/references/api-root.md +224 -208
- package/skill/references/cli.md +7 -1
- package/skill/references/examples.md +4 -0
- package/skill/references/recipes.md +161 -0
- package/skill/references/safety-boundaries.md +8 -2
- package/skill/skill.json +5 -5
- package/types/core/client.d.ts +2 -0
- package/types/core/sdk.d.ts +3 -0
- package/types/core/singleton.d.ts +3 -0
- package/types/devtools/device-logs.d.ts +48 -0
- package/types/index.d.ts +3 -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 +7 -0
- package/types/miniapp-manifest/permissions.d.ts +1 -1
- package/types/miniapp-manifest/schema.d.ts +4 -1
- package/types/miniapp-publish/index.d.ts +17 -2
- package/types/modules/companion/index.d.ts +57 -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 +6 -0
- package/types/vite/index.d.ts +10 -0
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-DzEst9n7.js +0 -99
- package/dist/devtools/browser-dev-host/assets/index-B-NHlLsr.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-DZkqvg9S.cjs');
|
|
12
|
+
var runtimePermissionEnv = require('./runtime-permission-env-BN43V7d0.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-zNpZDWhc.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-C1R_39si.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-couFw5bL.cjs');
|
|
21
|
+
var version = require('./version-DbDVL6vm.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,15 @@ function readErrorMessage(error) {
|
|
|
546
623
|
return error instanceof Error ? error.message : String(error);
|
|
547
624
|
}
|
|
548
625
|
|
|
549
|
-
/**
|
|
550
|
-
* network.request 诊断日志的纯脱敏工具。
|
|
551
|
-
*
|
|
552
|
-
* @remarks
|
|
553
|
-
* 放在 `core` 而非 `modules/network`:无错误类型依赖,供 SDK 错误文案、
|
|
554
|
-
* modules observability、devtools Browser Dev Host / proxy 共用,并满足 boundary
|
|
555
|
-
*(devtools 不得直接 import runtime capability modules)。
|
|
556
|
-
*/
|
|
557
|
-
/** 敏感字段名:header / query / body key 共用。 */
|
|
558
|
-
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;
|
|
559
|
-
/**
|
|
560
|
-
* 脱敏请求 URL:去掉 userinfo,并将 query / fragment 中的敏感参数值替换为 `[redacted]`。
|
|
561
|
-
*/
|
|
562
|
-
function sanitizeNetworkRequestUrl(url) {
|
|
563
|
-
if (!url) {
|
|
564
|
-
return url;
|
|
565
|
-
}
|
|
566
|
-
try {
|
|
567
|
-
const parsed = new URL(url);
|
|
568
|
-
parsed.username = '';
|
|
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();
|
|
581
|
-
}
|
|
582
|
-
catch {
|
|
583
|
-
return redactSensitivePairsInRawText(url);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* 诊断 message 中的 URL 片段做脱敏,避免 error.message 二次泄漏 query secrets。
|
|
588
|
-
*/
|
|
589
|
-
function sanitizeDiagnosticMessage(message) {
|
|
590
|
-
if (!message) {
|
|
591
|
-
return message;
|
|
592
|
-
}
|
|
593
|
-
return message.replace(/https?:\/\/[^\s]+/gi, matched => sanitizeNetworkRequestUrl(matched.replace(/[),.;]+$/g, '')));
|
|
594
|
-
}
|
|
595
|
-
function isSensitiveNetworkKey(key) {
|
|
596
|
-
return SENSITIVE_KEY_PATTERN$1.test(key.trim());
|
|
597
|
-
}
|
|
598
|
-
function redactUrlSearchParams(params) {
|
|
599
|
-
let changed = false;
|
|
600
|
-
for (const key of [...params.keys()]) {
|
|
601
|
-
if (isSensitiveNetworkKey(key)) {
|
|
602
|
-
params.set(key, '[redacted]');
|
|
603
|
-
changed = true;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
return changed;
|
|
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
|
-
});
|
|
615
|
-
}
|
|
616
|
-
|
|
617
626
|
const MAX_BROWSER_DEV_HOST_NETWORK_REQUEST_BODY = 2 * 1024 * 1024;
|
|
618
627
|
const MAX_BROWSER_DEV_HOST_NETWORK_RESPONSE_BODY = 5 * 1024 * 1024;
|
|
619
628
|
const DENIED_NETWORK_TARGETS = createDeniedNetworkTargets();
|
|
620
|
-
const
|
|
629
|
+
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;
|
|
630
|
+
const HOST_CONTROLLED_REQUEST_HEADERS = new Set(['origin', 'referer', 'user-agent']);
|
|
631
|
+
const TRANSPORT_CONTROLLED_REQUEST_HEADERS = new Set([
|
|
632
|
+
'connection', 'content-length', 'host', 'keep-alive', 'proxy-connection',
|
|
633
|
+
'te', 'trailer', 'transfer-encoding', 'upgrade',
|
|
634
|
+
]);
|
|
621
635
|
async function handleBrowserDevHostNetworkProxy(request, response, fetchImpl, lookupHostname, options) {
|
|
622
636
|
const cors = options.cors === true;
|
|
623
637
|
if (request.method === 'OPTIONS') {
|
|
@@ -729,7 +743,7 @@ async function requestBrowserDevHostNetwork(payload, fetchImpl, lookupHostname,
|
|
|
729
743
|
data: contentType.includes('application/json') ? safeJsonParse(text) : text,
|
|
730
744
|
status: response.status,
|
|
731
745
|
statusText: response.statusText,
|
|
732
|
-
headers: Object.fromEntries([...response.headers.entries()].filter(([name]) => !
|
|
746
|
+
headers: Object.fromEntries([...response.headers.entries()].filter(([name]) => !isCredentialResponseHeader(name))),
|
|
733
747
|
};
|
|
734
748
|
}
|
|
735
749
|
catch (error) {
|
|
@@ -824,9 +838,9 @@ function isDeniedNetworkAddress(address, family) {
|
|
|
824
838
|
}
|
|
825
839
|
return true;
|
|
826
840
|
}
|
|
827
|
-
function
|
|
841
|
+
function isCredentialResponseHeader(name) {
|
|
828
842
|
const normalized = name.trim().toLowerCase();
|
|
829
|
-
return
|
|
843
|
+
return CREDENTIAL_RESPONSE_HEADER_RE.test(normalized) || normalized.startsWith('x-heybox-') || normalized.startsWith('x-xhh-');
|
|
830
844
|
}
|
|
831
845
|
function createPinnedNetworkDispatcher(pinnedTargets) {
|
|
832
846
|
const connect = undici.buildConnector({});
|
|
@@ -899,31 +913,18 @@ function normalizeBrowserDevHostNetworkHeaders(headers) {
|
|
|
899
913
|
return normalized;
|
|
900
914
|
}
|
|
901
915
|
Object.entries(headers).forEach(([key, value]) => {
|
|
902
|
-
if (
|
|
903
|
-
throw createBrowserDevHostError(400, 'INVALID_NETWORK_REQUEST', `network.request
|
|
916
|
+
if (TRANSPORT_CONTROLLED_REQUEST_HEADERS.has(key.trim().toLowerCase())) {
|
|
917
|
+
throw createBrowserDevHostError(400, 'INVALID_NETWORK_REQUEST', `network.request 请求头由 Host 传输层管理: ${key}`);
|
|
904
918
|
}
|
|
905
|
-
if (typeof value === 'string' && !
|
|
919
|
+
if (typeof value === 'string' && !isHostManagedRequestHeader(key)) {
|
|
906
920
|
normalized.set(key, value);
|
|
907
921
|
}
|
|
908
922
|
});
|
|
909
923
|
return normalized;
|
|
910
924
|
}
|
|
911
|
-
function
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
function isBlockedBrowserDevHostNetworkHeader(key) {
|
|
915
|
-
return [
|
|
916
|
-
'connection',
|
|
917
|
-
'content-length',
|
|
918
|
-
'host',
|
|
919
|
-
'keep-alive',
|
|
920
|
-
'proxy-authenticate',
|
|
921
|
-
'proxy-authorization',
|
|
922
|
-
'te',
|
|
923
|
-
'trailer',
|
|
924
|
-
'transfer-encoding',
|
|
925
|
-
'upgrade',
|
|
926
|
-
].includes(key.toLowerCase());
|
|
925
|
+
function isHostManagedRequestHeader(name) {
|
|
926
|
+
const normalized = name.trim().toLowerCase();
|
|
927
|
+
return HOST_CONTROLLED_REQUEST_HEADERS.has(normalized);
|
|
927
928
|
}
|
|
928
929
|
/**
|
|
929
930
|
* 与 App Host 对齐:Mock 不再放行 multipart,避免“本地成功、上线失败”。
|
|
@@ -1851,8 +1852,8 @@ const DEFAULT_CHANNEL_TRANSPORT = {
|
|
|
1851
1852
|
};
|
|
1852
1853
|
const IPV4_LOOPBACK_ADDRESSES = new net.BlockList();
|
|
1853
1854
|
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-
|
|
1855
|
+
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-CTA0lsLD.cjs', document.baseURI).href)))('ws');
|
|
1856
|
+
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-CTA0lsLD.cjs', document.baseURI).href)))('parse5');
|
|
1856
1857
|
const BROWSER_DEV_HOST_ROOT_CANDIDATES = [
|
|
1857
1858
|
path.resolve(__dirname, 'devtools/browser-dev-host'),
|
|
1858
1859
|
path.resolve(__dirname, '../devtools/browser-dev-host'),
|
|
@@ -1886,6 +1887,7 @@ async function startBrowserDevHostServer(options) {
|
|
|
1886
1887
|
mobileLaunchRedirect: initialMobileLaunchRedirect,
|
|
1887
1888
|
nativeAppLaunchUnavailableReason: options.nativeAppLaunchUnavailableReason,
|
|
1888
1889
|
runtimePermissions: options.runtimePermissions,
|
|
1890
|
+
devReadiness: options.devReadiness,
|
|
1889
1891
|
};
|
|
1890
1892
|
let bootstrapJson = serializeBrowserDevHostBootstrap(bootstrap);
|
|
1891
1893
|
const serverRuntime = createBrowserDevHostServer(root, {
|
|
@@ -1950,6 +1952,8 @@ async function startBrowserDevHostServer(options) {
|
|
|
1950
1952
|
return closePromise;
|
|
1951
1953
|
},
|
|
1952
1954
|
updateBootstrap: (next) => {
|
|
1955
|
+
if (next.devReadiness !== undefined)
|
|
1956
|
+
bootstrap.devReadiness = next.devReadiness;
|
|
1953
1957
|
if (next.devContext !== undefined)
|
|
1954
1958
|
bootstrap.devContext = next.devContext;
|
|
1955
1959
|
if (next.lanAddresses !== undefined) {
|
|
@@ -2172,6 +2176,44 @@ function createBrowserDevHostServer(root, runtime) {
|
|
|
2172
2176
|
writeSerializedJsonResponse(response, 200, runtime.readBootstrapJson());
|
|
2173
2177
|
return;
|
|
2174
2178
|
}
|
|
2179
|
+
if (pathname === '/__hb_sdk__/sandbox') {
|
|
2180
|
+
if (!isValidSensitiveRequest(request, loopbackPeer, runtime.sessionController.csrfToken)) {
|
|
2181
|
+
writeForbidden(response);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
if (request.method !== 'POST') {
|
|
2185
|
+
writeMethodNotAllowed(response, 'POST');
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
try {
|
|
2189
|
+
const input = await readJsonRequestBody(request);
|
|
2190
|
+
if (!isRecord$2(input) || typeof input.device !== 'string' || typeof input.path !== 'string') {
|
|
2191
|
+
writeBadRequest(response);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
const store = getDeviceLogs(runtime.appUrl);
|
|
2195
|
+
writeJsonResponse(response, 200, store ? await store.listSandbox(input.device, input.path) : { error: '设备服务不可用' });
|
|
2196
|
+
}
|
|
2197
|
+
catch {
|
|
2198
|
+
writeBadRequest(response);
|
|
2199
|
+
}
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
if (pathname === DEVICE_LOG_PATH) {
|
|
2203
|
+
const origin = request.headers.origin;
|
|
2204
|
+
if (!loopbackPeer || !hasNumericLoopbackHost(request) ||
|
|
2205
|
+
(origin && origin !== `http://${request.headers.host}`)) {
|
|
2206
|
+
writeForbidden(response);
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
if (request.method !== 'GET') {
|
|
2210
|
+
writeMethodNotAllowed(response, 'GET');
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
const store = getDeviceLogs(runtime.appUrl);
|
|
2214
|
+
writeJsonResponse(response, 200, store ? store.query(requestUrl.searchParams) : { epoch: '', items: [], devices: [], nextCursor: 0, hasMore: false, gap: false });
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2175
2217
|
if (pathname === '/session/status') {
|
|
2176
2218
|
if (!loopbackPeer || !hasNumericLoopbackHost(request)) {
|
|
2177
2219
|
writeForbidden(response);
|
|
@@ -10902,7 +10944,7 @@ function assertNodeRuntimeCapabilities(ctx, settings) {
|
|
|
10902
10944
|
function createNodeRealtimeDriverRegistry() {
|
|
10903
10945
|
const registry = createRuntimeRealtimeDriverRegistry();
|
|
10904
10946
|
registry.registerLazy('node-websocket', async () => {
|
|
10905
|
-
const mod = await Promise.resolve().then(function () { return require('./index.esm-
|
|
10947
|
+
const mod = await Promise.resolve().then(function () { return require('./index.esm-CGoUW47e.cjs'); });
|
|
10906
10948
|
return mod.createNodeWebSocketRuntimeDriver();
|
|
10907
10949
|
});
|
|
10908
10950
|
return registry;
|
|
@@ -14862,7 +14904,7 @@ function containsSecret(value, secret) {
|
|
|
14862
14904
|
|
|
14863
14905
|
const LAUNCH_CORS_HEADERS = {
|
|
14864
14906
|
'access-control-allow-methods': 'GET',
|
|
14865
|
-
'access-control-allow-origin': '
|
|
14907
|
+
'access-control-allow-origin': 'https://www.xiaoheihe.cn',
|
|
14866
14908
|
'access-control-allow-private-network': 'true',
|
|
14867
14909
|
'cache-control': 'no-store',
|
|
14868
14910
|
};
|
|
@@ -14877,6 +14919,10 @@ function tryHandleMiniProgramDevLaunchRequest(request, response, readSource) {
|
|
|
14877
14919
|
if (requestUrl.pathname !== MINI_PROGRAM_DEV_LAUNCH_PATH || requestUrl.search !== '') {
|
|
14878
14920
|
return false;
|
|
14879
14921
|
}
|
|
14922
|
+
if (!isAllowedLaunchOrigin(request.headers.origin)) {
|
|
14923
|
+
writeLaunchResponse(response, 403);
|
|
14924
|
+
return true;
|
|
14925
|
+
}
|
|
14880
14926
|
if (request.method !== 'GET' && request.method !== 'OPTIONS') {
|
|
14881
14927
|
response.writeHead(405, { allow: 'GET', ...LAUNCH_CORS_HEADERS });
|
|
14882
14928
|
response.end();
|
|
@@ -14911,6 +14957,16 @@ function createLaunchSnapshot(source, requestOrigin) {
|
|
|
14911
14957
|
const localDevLaunchToken = source.localDevLaunchToken?.trim();
|
|
14912
14958
|
if (localDevLaunchToken)
|
|
14913
14959
|
snapshot.local_dev_launch_token = localDevLaunchToken;
|
|
14960
|
+
if (source.devSessionId)
|
|
14961
|
+
snapshot.dev_session_id = source.devSessionId;
|
|
14962
|
+
if (source.expiresAt)
|
|
14963
|
+
snapshot.expires_at = source.expiresAt;
|
|
14964
|
+
const companionManifestSha256 = source.companionManifestSha256?.trim();
|
|
14965
|
+
if (companionManifestSha256)
|
|
14966
|
+
snapshot.companion_manifest_sha256 = companionManifestSha256;
|
|
14967
|
+
const companionArtifactAccessToken = source.companionArtifactAccessToken?.trim();
|
|
14968
|
+
if (companionArtifactAccessToken)
|
|
14969
|
+
snapshot.companion_artifact_access_token = companionArtifactAccessToken;
|
|
14914
14970
|
const storedContextUrl = source.devContextUrlsByMiniUrlOrigin.get(requestOrigin.origin);
|
|
14915
14971
|
if (storedContextUrl) {
|
|
14916
14972
|
snapshot.dev_context_url = projectStoredDevContextUrl(storedContextUrl, requestOrigin.hostname);
|
|
@@ -14954,6 +15010,9 @@ function readNumericRequestOrigin(request) {
|
|
|
14954
15010
|
function isAllowedLaunchPeer(address) {
|
|
14955
15011
|
return isLoopbackOrPrivateLanAddress(address);
|
|
14956
15012
|
}
|
|
15013
|
+
function isAllowedLaunchOrigin(origin) {
|
|
15014
|
+
return origin === undefined || origin === 'https://www.xiaoheihe.cn';
|
|
15015
|
+
}
|
|
14957
15016
|
function writeLaunchResponse(response, status, body) {
|
|
14958
15017
|
response.writeHead(status, {
|
|
14959
15018
|
...LAUNCH_CORS_HEADERS,
|
|
@@ -14970,6 +15029,10 @@ const LOCAL_DEV_URL_HOST = '127.0.0.1';
|
|
|
14970
15029
|
// 与 Heybox H5 Vite 插件共享,让 hb-sdk 统一管理启动输出。
|
|
14971
15030
|
const MANAGED_DEV_OUTPUT_ENV = 'HEYBOX_DEV_SERVER_MANAGED_OUTPUT';
|
|
14972
15031
|
const VITE_LOG_LEVEL = 'warn';
|
|
15032
|
+
const COMPANION_DEV_MANIFEST_PATH = '/__hb_sdk__/companion/manifest.json';
|
|
15033
|
+
const COMPANION_DEV_MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
15034
|
+
const COMPANION_ARTIFACT_ACCESS_TOKEN_ENV = 'HB_SDK_COMPANION_ARTIFACT_ACCESS_TOKEN';
|
|
15035
|
+
const COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER = 'x-hb-sdk-companion-dev-token';
|
|
14973
15036
|
async function runDevCommand(options, runtime = {}) {
|
|
14974
15037
|
const logger = runtime.logger ?? index.createCliLogger();
|
|
14975
15038
|
const fetchImpl = runtime.fetchImpl ?? fetch;
|
|
@@ -14991,6 +15054,8 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
14991
15054
|
port: appPort,
|
|
14992
15055
|
};
|
|
14993
15056
|
const projectRoot = projectVite.findProjectRoot(runtime.cwd ?? process.cwd());
|
|
15057
|
+
const companionArtifactAccessToken = (runtime.randomBytes ?? node_crypto.randomBytes)(32).toString('base64url');
|
|
15058
|
+
const devSessionId = node_crypto.randomBytes(16).toString('hex');
|
|
14994
15059
|
runtimePermissionEnv.readMiniappPlatformsFromPackageJson(projectRoot);
|
|
14995
15060
|
const { context: devLaunchContext, miniProgram } = await loadDevLaunchContext({
|
|
14996
15061
|
env: runtime.env,
|
|
@@ -14999,22 +15064,58 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
14999
15064
|
projectRoot,
|
|
15000
15065
|
timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
|
|
15001
15066
|
});
|
|
15002
|
-
|
|
15003
|
-
const vite = await runDevStartupTask(logger, '正在加载项目 Vite', () => projectVite.loadProjectVite(projectRoot), '已加载项目 Vite');
|
|
15004
|
-
const appServer = await runDevStartupTask(logger, '正在创建 Vite 开发服务器', () => runtimePermissionEnv.withMiniappPlatformCspEnv(context.shouldSkipMiniappPlatformCsp(devLaunchContext.runtimePermissions), () => withManagedDevOutputEnv(() => vite.createServer(createViteServerOptions(projectRoot, resolvedOptions)), runtime.env), runtime.env), '已创建 Vite 开发服务器');
|
|
15067
|
+
let launchRuntimePermissions = context.createBrowserDevRuntimePermissions(devLaunchContext.runtimePermissions, context.createBrowserDevPermissionState(devLaunchContext));
|
|
15005
15068
|
const mobileDevContextUrlsByOrigin = new Map();
|
|
15069
|
+
let currentCompanionManifestSha256;
|
|
15006
15070
|
let currentLocalDevLaunchToken = devLaunchContext.localDevLaunchToken;
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15071
|
+
let currentExpiresAt;
|
|
15072
|
+
let tokenSession;
|
|
15073
|
+
const readLaunchSource = () => ({
|
|
15074
|
+
companionArtifactAccessToken,
|
|
15075
|
+
companionManifestSha256: currentCompanionManifestSha256,
|
|
15076
|
+
devContextUrlsByMiniUrlOrigin: mobileDevContextUrlsByOrigin,
|
|
15077
|
+
localDevLaunchToken: currentLocalDevLaunchToken,
|
|
15078
|
+
devSessionId,
|
|
15079
|
+
expiresAt: currentExpiresAt,
|
|
15080
|
+
miniProgramId: devLaunchContext.miniProgramId,
|
|
15081
|
+
runtimePermissions: launchRuntimePermissions,
|
|
15082
|
+
sdkVersion: version.HB_SDK_VERSION,
|
|
15083
|
+
});
|
|
15084
|
+
const launchPlugin = createMiniProgramDevLaunchVitePlugin(readLaunchSource, async (request, response) => {
|
|
15085
|
+
if (request.url !== MINI_PROGRAM_DEV_LAUNCH_PATH || request.headers[COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER] === undefined)
|
|
15086
|
+
return false;
|
|
15087
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
15088
|
+
const token = request.headers[COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER];
|
|
15089
|
+
const remote = request.socket.remoteAddress;
|
|
15090
|
+
if (typeof token !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(token) ||
|
|
15091
|
+
!node_crypto.timingSafeEqual(Buffer.from(token), Buffer.from(companionArtifactAccessToken)) ||
|
|
15092
|
+
!['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(remote ?? '') ||
|
|
15093
|
+
!/^(127\.0\.0\.1|\[::1\]):\d{1,5}$/.test(request.headers.host ?? '')) {
|
|
15094
|
+
response.writeHead(403).end();
|
|
15095
|
+
return true;
|
|
15096
|
+
}
|
|
15097
|
+
if (request.method !== 'GET') {
|
|
15098
|
+
response.writeHead(405, { Allow: 'GET' }).end();
|
|
15099
|
+
return true;
|
|
15100
|
+
}
|
|
15101
|
+
try {
|
|
15102
|
+
const issued = await tokenSession?.getTicket(currentCompanionManifestSha256);
|
|
15103
|
+
if (!issued)
|
|
15104
|
+
response.writeHead(503).end();
|
|
15105
|
+
else
|
|
15106
|
+
response.writeHead(200, { 'Content-Type': 'application/json', 'X-Content-Type-Options': 'nosniff' }).end(JSON.stringify(createLaunchSnapshot({
|
|
15107
|
+
...readLaunchSource(), localDevLaunchToken: issued.token, expiresAt: issued.expiresAt,
|
|
15108
|
+
}, new URL(`http://${request.headers.host}`))));
|
|
15109
|
+
}
|
|
15110
|
+
catch (error) {
|
|
15111
|
+
const status = error instanceof index.CliError && (error.httpStatus === 401 || error.httpStatus === 403) ? error.httpStatus : 503;
|
|
15112
|
+
logger.debug(`本地开发会话续票失败:${index.readRedactedErrorMessage(error, { verbose: true })}`);
|
|
15113
|
+
response.writeHead(status).end();
|
|
15114
|
+
}
|
|
15115
|
+
return true;
|
|
15017
15116
|
});
|
|
15117
|
+
const vite = await runDevStartupTask(logger, '正在加载项目 Vite', () => projectVite.loadProjectVite(projectRoot), '已加载项目 Vite');
|
|
15118
|
+
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 开发服务器');
|
|
15018
15119
|
await runDevStartupTask(logger, '正在启动 Vite 开发服务器', () => appServer.listen(appPort), 'Vite 开发服务器已启动');
|
|
15019
15120
|
const appUrl = await runDevStartupTask(logger, '正在探测小程序入口', () => resolveViteAppUrl(appServer, resolvedOptions, fetchImpl), '已确定小程序入口');
|
|
15020
15121
|
const buildLanAddresses = () => createLanAddressCandidates({
|
|
@@ -15060,12 +15161,27 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15060
15161
|
}
|
|
15061
15162
|
closers.push(() => browserDevHost.close());
|
|
15062
15163
|
if (devLaunchContext.source === 'remote' && devLaunchContext.miniProgramId) {
|
|
15063
|
-
|
|
15164
|
+
tokenSession = startLocalDevLaunchTokenSession({
|
|
15064
15165
|
env: runtime.env,
|
|
15065
15166
|
fetchImpl,
|
|
15066
15167
|
logger,
|
|
15168
|
+
manifestUrl: new URL(COMPANION_DEV_MANIFEST_PATH, appUrl).toString(),
|
|
15169
|
+
companionArtifactAccessToken,
|
|
15170
|
+
companionApproved: hasCompanionApproval(devLaunchContext.runtimePermissions),
|
|
15067
15171
|
miniProgramId: devLaunchContext.miniProgramId,
|
|
15068
|
-
|
|
15172
|
+
refreshPermissions: async () => {
|
|
15173
|
+
const refreshed = await loadDevLaunchContext({ env: runtime.env, fetchImpl, logger, projectRoot,
|
|
15174
|
+
timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS });
|
|
15175
|
+
if (refreshed.context.miniProgramId !== devLaunchContext.miniProgramId)
|
|
15176
|
+
throw new Error('项目绑定已变更,请重新启动调试');
|
|
15177
|
+
devLaunchContext.runtimePermissions = refreshed.context.runtimePermissions;
|
|
15178
|
+
launchRuntimePermissions = context.createBrowserDevRuntimePermissions(devLaunchContext.runtimePermissions, context.createBrowserDevPermissionState(devLaunchContext));
|
|
15179
|
+
browserDevHost.updateBootstrap({ devContext: { ...devLaunchContext, localDevLaunchToken: currentLocalDevLaunchToken } });
|
|
15180
|
+
return hasCompanionApproval(devLaunchContext.runtimePermissions);
|
|
15181
|
+
},
|
|
15182
|
+
onToken: (localDevLaunchToken, companionManifestSha256, expiresAt) => {
|
|
15183
|
+
currentExpiresAt = expiresAt;
|
|
15184
|
+
currentCompanionManifestSha256 = companionManifestSha256;
|
|
15069
15185
|
currentLocalDevLaunchToken = localDevLaunchToken;
|
|
15070
15186
|
const tokenContext = {
|
|
15071
15187
|
source: 'remote',
|
|
@@ -15078,15 +15194,26 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15078
15194
|
lanAddresses: buildLanAddresses(),
|
|
15079
15195
|
});
|
|
15080
15196
|
},
|
|
15197
|
+
onReadiness: devReadiness => browserDevHost.updateBootstrap({ devReadiness }),
|
|
15081
15198
|
projectRoot,
|
|
15082
15199
|
timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
|
|
15083
15200
|
});
|
|
15084
15201
|
closers.push(tokenSession.close);
|
|
15085
15202
|
}
|
|
15086
|
-
|
|
15203
|
+
else {
|
|
15204
|
+
const reason = '登录或小程序绑定不可用,请完成 CLI 登录和项目绑定';
|
|
15205
|
+
browserDevHost.updateBootstrap({
|
|
15206
|
+
devReadiness: {
|
|
15207
|
+
browser: { state: 'ready' },
|
|
15208
|
+
mobile: { state: 'blocked', reason },
|
|
15209
|
+
companion: { state: 'blocked', reason },
|
|
15210
|
+
},
|
|
15211
|
+
});
|
|
15212
|
+
}
|
|
15213
|
+
logger.success('Web / Browser Fake 调试已就绪');
|
|
15087
15214
|
logger.info(`调试页:${browserDevHost.url}`);
|
|
15088
15215
|
logger.info(`小程序地址:${appUrl}`);
|
|
15089
|
-
logger.info('
|
|
15216
|
+
logger.info('手机与 PC Companion 的就绪状态请查看调试页');
|
|
15090
15217
|
if (options.open !== false) {
|
|
15091
15218
|
logger.debug('正在打开浏览器调试页');
|
|
15092
15219
|
void openUrl(browserDevHost.url);
|
|
@@ -15166,10 +15293,26 @@ async function loadRemoteDevContextWithTimeout(options) {
|
|
|
15166
15293
|
}
|
|
15167
15294
|
}
|
|
15168
15295
|
}
|
|
15169
|
-
function
|
|
15296
|
+
function createMiniProgramDevLaunchVitePlugin(getSnapshot, handleSession) {
|
|
15297
|
+
return {
|
|
15298
|
+
name: 'heybox-mini-program-dev-launch',
|
|
15299
|
+
enforce: 'pre',
|
|
15300
|
+
configureServer(server) {
|
|
15301
|
+
server.middlewares.use(async (request, response, next) => {
|
|
15302
|
+
if (handleSession && await handleSession(request, response))
|
|
15303
|
+
return;
|
|
15304
|
+
if (!tryHandleMiniProgramDevLaunchRequest(request, response, getSnapshot))
|
|
15305
|
+
next();
|
|
15306
|
+
});
|
|
15307
|
+
},
|
|
15308
|
+
};
|
|
15309
|
+
}
|
|
15310
|
+
function createViteServerOptions(projectRoot, options, launchPlugin) {
|
|
15170
15311
|
const configFile = resolveProjectViteConfig(projectRoot);
|
|
15171
15312
|
const server = {
|
|
15172
|
-
|
|
15313
|
+
// launch.json 由 mini-program-dev-launch plugin 自己处理 PNA/CORS 预检;
|
|
15314
|
+
// Vite 通用 CORS 会抢先返回 `*`,缺少 Access-Control-Allow-Private-Network。
|
|
15315
|
+
cors: false,
|
|
15173
15316
|
hmr: {
|
|
15174
15317
|
clientPort: options.port ?? DEFAULT_APP_PORT,
|
|
15175
15318
|
},
|
|
@@ -15181,11 +15324,13 @@ function createViteServerOptions(projectRoot, options) {
|
|
|
15181
15324
|
return {
|
|
15182
15325
|
configFile,
|
|
15183
15326
|
logLevel: VITE_LOG_LEVEL,
|
|
15327
|
+
plugins: [launchPlugin],
|
|
15184
15328
|
server,
|
|
15185
15329
|
};
|
|
15186
15330
|
}
|
|
15187
15331
|
return {
|
|
15188
15332
|
logLevel: VITE_LOG_LEVEL,
|
|
15333
|
+
plugins: [launchPlugin],
|
|
15189
15334
|
root: projectRoot,
|
|
15190
15335
|
server,
|
|
15191
15336
|
};
|
|
@@ -15299,6 +15444,19 @@ async function withManagedDevOutputEnv(action, env = process.env) {
|
|
|
15299
15444
|
}
|
|
15300
15445
|
}
|
|
15301
15446
|
}
|
|
15447
|
+
async function withCompanionArtifactAccessTokenEnv(token, action, env = process.env) {
|
|
15448
|
+
const previous = env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV];
|
|
15449
|
+
env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV] = token;
|
|
15450
|
+
try {
|
|
15451
|
+
return await action();
|
|
15452
|
+
}
|
|
15453
|
+
finally {
|
|
15454
|
+
if (previous === undefined)
|
|
15455
|
+
delete env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV];
|
|
15456
|
+
else
|
|
15457
|
+
env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV] = previous;
|
|
15458
|
+
}
|
|
15459
|
+
}
|
|
15302
15460
|
function installShutdownHandlers(closers, processLike) {
|
|
15303
15461
|
const shutdown = async () => {
|
|
15304
15462
|
await closeAll(closers);
|
|
@@ -15319,28 +15477,94 @@ function startLocalDevLaunchTokenSession(options) {
|
|
|
15319
15477
|
let expirationTimer;
|
|
15320
15478
|
let hasReportedTokenFailure = false;
|
|
15321
15479
|
let refreshTimer;
|
|
15480
|
+
let companionApproved = options.companionApproved;
|
|
15481
|
+
let permissionsRefreshAt = Date.now() + 30_000;
|
|
15482
|
+
let publishedToken;
|
|
15483
|
+
let publishedDigest;
|
|
15484
|
+
let ticket;
|
|
15485
|
+
let pending;
|
|
15486
|
+
const getTicket = async (companionManifestSha256) => {
|
|
15487
|
+
if (closed)
|
|
15488
|
+
return undefined;
|
|
15489
|
+
if (ticket && (companionManifestSha256 === undefined || ticket.companionManifestSha256 === companionManifestSha256) && ticket.refreshAt > Date.now())
|
|
15490
|
+
return ticket;
|
|
15491
|
+
return issueTicket(companionManifestSha256);
|
|
15492
|
+
};
|
|
15493
|
+
const issueTicket = (companionManifestSha256) => {
|
|
15494
|
+
if (pending && pending.digest === companionManifestSha256)
|
|
15495
|
+
return pending.promise;
|
|
15496
|
+
const promise = issueLocalDevLaunchToken({
|
|
15497
|
+
...options,
|
|
15498
|
+
...(companionManifestSha256 ? { companionManifestSha256 } : {}),
|
|
15499
|
+
})
|
|
15500
|
+
.then(issued => {
|
|
15501
|
+
ticket = issued;
|
|
15502
|
+
return issued;
|
|
15503
|
+
}).finally(() => { if (pending?.promise === promise)
|
|
15504
|
+
pending = undefined; });
|
|
15505
|
+
pending = { digest: companionManifestSha256, promise };
|
|
15506
|
+
return promise;
|
|
15507
|
+
};
|
|
15322
15508
|
const refresh = async () => {
|
|
15323
15509
|
try {
|
|
15324
|
-
|
|
15510
|
+
if (options.refreshPermissions && Date.now() >= permissionsRefreshAt) {
|
|
15511
|
+
companionApproved = await options.refreshPermissions();
|
|
15512
|
+
permissionsRefreshAt = Date.now() + 30_000;
|
|
15513
|
+
}
|
|
15514
|
+
let manifest;
|
|
15515
|
+
let snapshotError;
|
|
15516
|
+
try {
|
|
15517
|
+
manifest = await loadRemoteDevContextWithTimeout({
|
|
15518
|
+
fetchImpl: options.fetchImpl,
|
|
15519
|
+
timeoutMs: options.timeoutMs,
|
|
15520
|
+
load: fetchImpl => readCompanionManifest(options.manifestUrl, options.companionArtifactAccessToken, fetchImpl),
|
|
15521
|
+
});
|
|
15522
|
+
}
|
|
15523
|
+
catch (error) {
|
|
15524
|
+
snapshotError = error;
|
|
15525
|
+
}
|
|
15526
|
+
const digest = manifest?.digest;
|
|
15527
|
+
const issued = await getTicket(digest);
|
|
15528
|
+
if (!issued)
|
|
15529
|
+
return;
|
|
15325
15530
|
if (closed)
|
|
15326
15531
|
return;
|
|
15327
|
-
|
|
15532
|
+
if (publishedToken !== issued.token || publishedDigest !== digest) {
|
|
15533
|
+
publishedToken = issued.token;
|
|
15534
|
+
publishedDigest = digest;
|
|
15535
|
+
options.onToken(issued.token, digest, issued.expiresAt);
|
|
15536
|
+
}
|
|
15537
|
+
options.onReadiness({
|
|
15538
|
+
browser: { state: 'ready' },
|
|
15539
|
+
mobile: { state: 'ready' },
|
|
15540
|
+
companion: snapshotError
|
|
15541
|
+
? { state: snapshotError instanceof Error && snapshotError.message.includes('artifact-updating') ? 'pending' : 'blocked', reason: safeCompanionFailureReason(snapshotError) }
|
|
15542
|
+
: manifest?.artifacts.length
|
|
15543
|
+
? companionApproved
|
|
15544
|
+
? { state: 'ready', digest, artifacts: manifest.artifacts, reason: '本地产物和启动票已就绪,PC 授权将在打开后验证' }
|
|
15545
|
+
: { state: 'blocked', reason: 'Companion 未获 COA 批准或本地权限声明未开启' }
|
|
15546
|
+
: { state: 'not-configured', reason: '未配置本地 Companion 产物' },
|
|
15547
|
+
});
|
|
15548
|
+
if (snapshotError)
|
|
15549
|
+
options.logger.debug(`PC Companion 快照校验失败:${index.readRedactedErrorMessage(snapshotError, { verbose: true })}`);
|
|
15328
15550
|
const ttl = Math.max(0, issued.expiresAt - Date.now());
|
|
15329
15551
|
scheduleExpiration(ttl);
|
|
15330
|
-
schedule(Math.max(1_000, Math.floor(ttl * 0.8)));
|
|
15552
|
+
schedule(Math.min(2_000, Math.max(1_000, Math.floor(ttl * 0.8))));
|
|
15331
15553
|
if (hasReportedTokenFailure) {
|
|
15332
15554
|
hasReportedTokenFailure = false;
|
|
15333
|
-
options.logger.success('
|
|
15555
|
+
options.logger.success('本地调试凭证已恢复');
|
|
15334
15556
|
}
|
|
15335
15557
|
}
|
|
15336
15558
|
catch (error) {
|
|
15337
15559
|
if (closed)
|
|
15338
15560
|
return;
|
|
15339
15561
|
if (!hasReportedTokenFailure) {
|
|
15340
|
-
options.logger.warn('
|
|
15562
|
+
options.logger.warn('本地调试凭证获取失败,将自动重试;Web / Browser Fake 仍可使用');
|
|
15341
15563
|
}
|
|
15342
15564
|
hasReportedTokenFailure = true;
|
|
15343
|
-
options.
|
|
15565
|
+
options.onReadiness({ browser: { state: 'ready' }, mobile: { state: 'blocked', reason: '调试凭证暂不可用' },
|
|
15566
|
+
companion: { state: 'blocked', reason: safeCompanionFailureReason(error) } });
|
|
15567
|
+
options.logger.debug(`本地调试凭证获取失败,详细原因:${index.readRedactedErrorMessage(error, { verbose: true })}`);
|
|
15344
15568
|
schedule(options.timeoutMs);
|
|
15345
15569
|
}
|
|
15346
15570
|
};
|
|
@@ -15354,13 +15578,20 @@ function startLocalDevLaunchTokenSession(options) {
|
|
|
15354
15578
|
if (expirationTimer)
|
|
15355
15579
|
clearTimeout(expirationTimer);
|
|
15356
15580
|
expirationTimer = setTimeout(() => {
|
|
15357
|
-
if (!closed)
|
|
15358
|
-
options.onToken(undefined);
|
|
15581
|
+
if (!closed) {
|
|
15582
|
+
options.onToken(undefined, undefined);
|
|
15583
|
+
publishedToken = undefined;
|
|
15584
|
+
options.onReadiness({ browser: { state: 'ready' }, mobile: { state: 'blocked', reason: '调试凭证已过期' },
|
|
15585
|
+
companion: { state: 'blocked', reason: '调试凭证已过期' } });
|
|
15586
|
+
}
|
|
15359
15587
|
}, Math.max(1, ttl));
|
|
15360
15588
|
expirationTimer.unref?.();
|
|
15361
15589
|
};
|
|
15362
|
-
|
|
15590
|
+
options.onReadiness({ browser: { state: 'ready' }, mobile: { state: 'pending' }, companion: { state: 'pending' } });
|
|
15591
|
+
const ready = refresh();
|
|
15363
15592
|
return {
|
|
15593
|
+
getTicket,
|
|
15594
|
+
ready,
|
|
15364
15595
|
close: () => {
|
|
15365
15596
|
closed = true;
|
|
15366
15597
|
if (expirationTimer)
|
|
@@ -15374,22 +15605,128 @@ async function issueLocalDevLaunchToken(options) {
|
|
|
15374
15605
|
const issued = await loadRemoteDevContextWithTimeout({
|
|
15375
15606
|
fetchImpl: options.fetchImpl,
|
|
15376
15607
|
timeoutMs: options.timeoutMs,
|
|
15377
|
-
load: (fetchImpl) =>
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
15608
|
+
load: async (fetchImpl) => {
|
|
15609
|
+
const result = await context.createLocalDevLaunchToken({
|
|
15610
|
+
cwd: options.projectRoot,
|
|
15611
|
+
env: options.env,
|
|
15612
|
+
fetchImpl,
|
|
15613
|
+
miniProgramId: options.miniProgramId,
|
|
15614
|
+
...(options.companionManifestSha256
|
|
15615
|
+
? { companionManifestSha256: options.companionManifestSha256 }
|
|
15616
|
+
: {}),
|
|
15617
|
+
});
|
|
15618
|
+
return { result };
|
|
15619
|
+
},
|
|
15383
15620
|
});
|
|
15384
|
-
const token = typeof issued.token === 'string' ? issued.token.trim() : '';
|
|
15621
|
+
const token = typeof issued.result.token === 'string' ? issued.result.token.trim() : '';
|
|
15385
15622
|
if (!token) {
|
|
15386
15623
|
throw new Error('响应缺少 token');
|
|
15387
15624
|
}
|
|
15388
|
-
const expiresAt = normalizeExpiresAt(issued.expires_at);
|
|
15625
|
+
const expiresAt = normalizeExpiresAt(issued.result.expires_at);
|
|
15389
15626
|
if (!expiresAt || expiresAt <= Date.now()) {
|
|
15390
15627
|
throw new Error('响应缺少有效 expires_at');
|
|
15391
15628
|
}
|
|
15392
|
-
return { expiresAt, token
|
|
15629
|
+
return { ...(options.companionManifestSha256 ? { companionManifestSha256: options.companionManifestSha256 } : {}), expiresAt, token,
|
|
15630
|
+
refreshAt: Date.now() + Math.max(1, (expiresAt - Date.now()) * 0.8) };
|
|
15631
|
+
}
|
|
15632
|
+
function safeCompanionFailureReason(error) {
|
|
15633
|
+
const message = index.readRedactedErrorMessage(error, { verbose: true });
|
|
15634
|
+
if (/artifact-updating/.test(message))
|
|
15635
|
+
return '本地 Companion 产物准备中';
|
|
15636
|
+
if (/artifact-missing/.test(message))
|
|
15637
|
+
return '本地 Companion 产物目录不存在,请先构建产物';
|
|
15638
|
+
if (/snapshot-limit/.test(message))
|
|
15639
|
+
return '本地快照数量或大小已达上限,请结束会话后重启 dev';
|
|
15640
|
+
if (/invalid-manifest/.test(message))
|
|
15641
|
+
return '本地 Manifest 或权限配置无效';
|
|
15642
|
+
if (/invalid-artifact/.test(message))
|
|
15643
|
+
return '本地产物或原生入口校验失败';
|
|
15644
|
+
if (/404|not found/i.test(message))
|
|
15645
|
+
return '调试接口尚未部署或当前登录环境不匹配';
|
|
15646
|
+
if (/403|forbidden|permission|授权|权限/i.test(message))
|
|
15647
|
+
return 'Companion 授权未通过,请检查 COA 批准与开发者身份';
|
|
15648
|
+
if (/401|登录/i.test(message))
|
|
15649
|
+
return 'CLI 登录态已失效,请重新登录';
|
|
15650
|
+
if (/manifest|快照|ZIP|ENOENT/i.test(message))
|
|
15651
|
+
return '本地 Companion ZIP 或 Manifest 校验失败,请检查产物与配置';
|
|
15652
|
+
return 'PC Companion 暂不可用,请检查网络、登录环境和调试接口';
|
|
15653
|
+
}
|
|
15654
|
+
function hasCompanionApproval(value) {
|
|
15655
|
+
if (!value || typeof value !== 'object' || !('entries' in value) || !Array.isArray(value.entries))
|
|
15656
|
+
return false;
|
|
15657
|
+
return value.entries.some(entry => entry?.key === 'companion' && entry?.status === 'enabled');
|
|
15658
|
+
}
|
|
15659
|
+
async function readCompanionManifest(manifestUrl, companionArtifactAccessToken, fetchImpl) {
|
|
15660
|
+
try {
|
|
15661
|
+
const response = await fetchImpl(manifestUrl, {
|
|
15662
|
+
cache: 'no-store',
|
|
15663
|
+
credentials: 'omit',
|
|
15664
|
+
headers: {
|
|
15665
|
+
accept: 'application/json',
|
|
15666
|
+
[COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER]: companionArtifactAccessToken,
|
|
15667
|
+
},
|
|
15668
|
+
method: 'GET',
|
|
15669
|
+
redirect: 'error',
|
|
15670
|
+
referrerPolicy: 'no-referrer',
|
|
15671
|
+
});
|
|
15672
|
+
if (response.status === 404) {
|
|
15673
|
+
return undefined;
|
|
15674
|
+
}
|
|
15675
|
+
if (!response.ok) {
|
|
15676
|
+
const code = response.headers.get('x-hb-sdk-companion-error');
|
|
15677
|
+
const safeCode = ['artifact-updating', 'artifact-missing', 'snapshot-limit', 'invalid-manifest', 'invalid-artifact'].includes(code ?? '') ? code : '';
|
|
15678
|
+
throw new Error(`Companion manifest request failed with status ${response.status} ${safeCode}`);
|
|
15679
|
+
}
|
|
15680
|
+
if (!response.headers.get('content-type')?.toLowerCase().includes('application/json')) {
|
|
15681
|
+
throw new Error('Companion manifest response is not JSON');
|
|
15682
|
+
}
|
|
15683
|
+
const contentLength = Number(response.headers.get('content-length'));
|
|
15684
|
+
if (Number.isFinite(contentLength) && contentLength > COMPANION_DEV_MANIFEST_MAX_BYTES) {
|
|
15685
|
+
throw new Error('Companion manifest response is too large');
|
|
15686
|
+
}
|
|
15687
|
+
const bytes = await readBoundedCompanionManifestBytes(response, COMPANION_DEV_MANIFEST_MAX_BYTES);
|
|
15688
|
+
const value = JSON.parse(bytes.toString('utf8'));
|
|
15689
|
+
const artifacts = Object.entries(value.companions ?? {}).flatMap(([alias, definition]) => {
|
|
15690
|
+
if (!/^[a-z][a-z0-9-]{0,31}$/.test(alias) || !definition || typeof definition !== 'object') {
|
|
15691
|
+
throw new Error('Companion invalid-manifest');
|
|
15692
|
+
}
|
|
15693
|
+
return Object.keys(definition.targets ?? {}).map(target => {
|
|
15694
|
+
if (target !== 'windows-x64' && target !== 'macos-arm64')
|
|
15695
|
+
throw new Error('Companion invalid-manifest');
|
|
15696
|
+
return { alias, target };
|
|
15697
|
+
});
|
|
15698
|
+
});
|
|
15699
|
+
return { digest: node_crypto.createHash('sha256').update(bytes).digest('hex'), artifacts };
|
|
15700
|
+
}
|
|
15701
|
+
catch (error) {
|
|
15702
|
+
if (error instanceof Error)
|
|
15703
|
+
throw error;
|
|
15704
|
+
throw new Error('Companion manifest request failed');
|
|
15705
|
+
}
|
|
15706
|
+
}
|
|
15707
|
+
async function readBoundedCompanionManifestBytes(response, maxBytes) {
|
|
15708
|
+
if (!response.body)
|
|
15709
|
+
return Buffer.alloc(0);
|
|
15710
|
+
const reader = response.body.getReader();
|
|
15711
|
+
const chunks = [];
|
|
15712
|
+
let total = 0;
|
|
15713
|
+
try {
|
|
15714
|
+
for (;;) {
|
|
15715
|
+
const { done, value } = await reader.read();
|
|
15716
|
+
if (done)
|
|
15717
|
+
break;
|
|
15718
|
+
total += value.byteLength;
|
|
15719
|
+
if (total > maxBytes) {
|
|
15720
|
+
await reader.cancel('Companion manifest response is too large');
|
|
15721
|
+
throw new Error('Companion manifest response is too large');
|
|
15722
|
+
}
|
|
15723
|
+
chunks.push(value);
|
|
15724
|
+
}
|
|
15725
|
+
}
|
|
15726
|
+
finally {
|
|
15727
|
+
reader.releaseLock();
|
|
15728
|
+
}
|
|
15729
|
+
return Buffer.concat(chunks.map(chunk => Buffer.from(chunk)), total);
|
|
15393
15730
|
}
|
|
15394
15731
|
function normalizeExpiresAt(value) {
|
|
15395
15732
|
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
@@ -15502,8 +15839,10 @@ function isPrivateIPv4(address) {
|
|
|
15502
15839
|
|
|
15503
15840
|
var dev = /*#__PURE__*/Object.freeze({
|
|
15504
15841
|
__proto__: null,
|
|
15842
|
+
createMiniProgramDevLaunchVitePlugin: createMiniProgramDevLaunchVitePlugin,
|
|
15505
15843
|
normalizeExpiresAt: normalizeExpiresAt,
|
|
15506
|
-
runDevCommand: runDevCommand
|
|
15844
|
+
runDevCommand: runDevCommand,
|
|
15845
|
+
startLocalDevLaunchTokenSession: startLocalDevLaunchTokenSession
|
|
15507
15846
|
});
|
|
15508
15847
|
|
|
15509
15848
|
exports.RuntimeError = RuntimeError;
|