@heybox/hb-sdk 0.6.8-alpha.1 → 0.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +2 -0
  3. package/dist/cli-chunks/{build-Do18KfQL.cjs → build-D4crgajb.cjs} +29 -25
  4. package/dist/cli-chunks/{context-CQ5481xZ.cjs → context-fRWWSXDj.cjs} +122 -23
  5. package/dist/cli-chunks/{create-BU7nViJ9.cjs → create-BU_EFgDz.cjs} +1 -1
  6. package/dist/cli-chunks/{dev-DJY-_cK4.cjs → dev-BrkChQ7v.cjs} +142 -108
  7. package/dist/cli-chunks/{doctor-BduOKHnV.cjs → doctor-DZXVO4rL.cjs} +1 -1
  8. package/dist/cli-chunks/{index-e2GK8bAH.cjs → index-CSjJrnkO.cjs} +14 -14
  9. package/dist/cli-chunks/{index-BVNo9ygg.cjs → index-DxH41nsb.cjs} +3 -3
  10. package/dist/cli-chunks/{login-C2wtKo_W.cjs → login-DLVozxrs.cjs} +2 -2
  11. package/dist/cli-chunks/{project-vite-D769ezhw.cjs → project-vite-CwmMfCk3.cjs} +1 -1
  12. package/dist/cli-chunks/{remote-Cf4i1DSL.cjs → remote-CfpkkC9I.cjs} +40 -22
  13. package/dist/cli-chunks/{runtime-gate-DfMJQGH9.cjs → runtime-gate-CgN_v4Te.cjs} +3 -3
  14. package/dist/cli-chunks/{index-v4-6fbXX.cjs → runtime-permission-env-B0jK9Rt1.cjs} +71 -0
  15. package/dist/cli-chunks/{session-z1D8xVrB.cjs → session-DB7ARwm4.cjs} +1 -1
  16. package/dist/cli.cjs +1 -1
  17. package/dist/devtools/mock-host/main.js +609 -6
  18. package/dist/index.cjs.js +403 -7
  19. package/dist/index.esm.js +403 -7
  20. package/dist/vite.cjs.js +17 -2
  21. package/dist/vite.esm.js +17 -2
  22. package/package.json +2 -2
  23. package/skill/SKILL.md +7 -6
  24. package/skill/references/api-root.md +7 -2
  25. package/skill/references/cli.md +4 -0
  26. package/skill/references/examples.md +17 -2
  27. package/skill/references/safety-boundaries.md +2 -0
  28. package/skill/scripts/sync-references.mjs +17 -2
  29. package/skill/skill.json +4 -4
  30. package/types/core/network-sanitize.d.ts +31 -0
  31. package/types/modules/network/index.d.ts +2 -0
  32. package/types/modules/network/observability.d.ts +39 -0
  33. package/types/modules/network/request-shape.d.ts +44 -0
  34. package/types/vite/html-policy.d.ts +1 -0
  35. package/types/vite/runtime-permission-env.d.ts +9 -0
package/dist/index.esm.js CHANGED
@@ -1,3 +1,274 @@
1
+ /**
2
+ * network.request 请求体形态校验与宿主失败诊断文案。
3
+ *
4
+ * @remarks
5
+ * App Host 的 heybox 客户端代发 transport 仅声明 form/json,不支持 multipart。
6
+ * 本地 Mock 若直接走 fetch 会“能通”,上线后却变成难以理解的 500 network_error。
7
+ * 因此在 SDK 侧统一提前拒绝 multipart,并在宿主伪装 HTTP 失败时给出可操作提示。
8
+ */
9
+ /** multipart 不被 App Host 支持时的稳定说明(开发者可见)。 */
10
+ const NETWORK_REQUEST_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。';
11
+ /**
12
+ * 读取请求头中的 Content-Type(大小写不敏感)。
13
+ */
14
+ function readNetworkContentType(headers) {
15
+ if (!headers) {
16
+ return undefined;
17
+ }
18
+ for (const [key, value] of Object.entries(headers)) {
19
+ if (key.trim().toLowerCase() === 'content-type' && typeof value === 'string') {
20
+ return value;
21
+ }
22
+ }
23
+ return undefined;
24
+ }
25
+ /**
26
+ * 判断 Content-Type 是否声明为 multipart/form-data。
27
+ */
28
+ function isMultipartContentType(contentType) {
29
+ return typeof contentType === 'string' && contentType.toLowerCase().includes('multipart/form-data');
30
+ }
31
+ /**
32
+ * 判断字符串 body 是否像手写 multipart 实体。
33
+ */
34
+ function looksLikeMultipartBody(data) {
35
+ if (typeof data !== 'string') {
36
+ return false;
37
+ }
38
+ const trimmed = data.trimStart();
39
+ return trimmed.startsWith('--') && /content-disposition\s*:\s*form-data/i.test(data);
40
+ }
41
+ /**
42
+ * 判断公开请求配置是否使用了 Host 不支持的 multipart 形态。
43
+ */
44
+ function isUnsupportedMultipartNetworkRequest(config) {
45
+ return isMultipartContentType(readNetworkContentType(config.headers)) || looksLikeMultipartBody(config.data);
46
+ }
47
+ function isPlainObject$2(value) {
48
+ return Object.prototype.toString.call(value) === '[object Object]';
49
+ }
50
+ /**
51
+ * 从宿主返回的“完成态”响应中提取更清晰的失败原因。
52
+ *
53
+ * @remarks
54
+ * Host 可能把 transport 不支持等内部错误包装成 HTTP 500 + `{ status: 'network_error', msg }`。
55
+ * 此时 `status=500` 并不代表目标站点返回了 500。
56
+ */
57
+ function describeHostNetworkFailure(response) {
58
+ const data = response.data;
59
+ const hostStatus = isPlainObject$2(data) && typeof data.status === 'string' ? data.status : undefined;
60
+ const hostMsg = isPlainObject$2(data) && typeof data.msg === 'string'
61
+ ? data.msg
62
+ : isPlainObject$2(data) && typeof data.message === 'string'
63
+ ? data.message
64
+ : undefined;
65
+ const isHostNetworkError = hostStatus === 'network_error';
66
+ const mentionsUnsupportedShape = typeof hostMsg === 'string' &&
67
+ (hostMsg.includes('unsupported-request-shape') || hostMsg.includes('unsupported_request_shape'));
68
+ const requestLooksMultipart = isUnsupportedMultipartNetworkRequest(response.config || {});
69
+ if (!isHostNetworkError && !mentionsUnsupportedShape && !(requestLooksMultipart && response.status >= 500)) {
70
+ return undefined;
71
+ }
72
+ const lines = [
73
+ '原因: 这是宿主网络层失败,不是目标 URL 的业务 HTTP 响应。',
74
+ ];
75
+ if (hostMsg) {
76
+ lines.push(`宿主信息: ${hostMsg}`);
77
+ }
78
+ if (requestLooksMultipart || mentionsUnsupportedShape) {
79
+ lines.push(NETWORK_REQUEST_MULTIPART_UNSUPPORTED_MESSAGE);
80
+ }
81
+ else if (isHostNetworkError) {
82
+ lines.push('请检查请求 URL、headers、body 编码与 Host 网络权限;本地 Mock 与 App Host 能力并不完全一致。');
83
+ }
84
+ return lines.join(' ');
85
+ }
86
+
87
+ /**
88
+ * network.request 诊断日志的纯脱敏工具。
89
+ *
90
+ * @remarks
91
+ * 放在 `core` 而非 `modules/network`:无错误类型依赖,供 SDK 错误文案、
92
+ * modules observability、devtools Mock Host / proxy 共用,并满足 boundary
93
+ *(devtools 不得直接 import runtime capability modules)。
94
+ */
95
+ const DATA_PREVIEW_MAX_CHARS = 500;
96
+ const SENSITIVE_HEADER_NAMES = new Set([
97
+ 'authorization',
98
+ 'cookie',
99
+ 'proxy-authorization',
100
+ 'set-cookie',
101
+ 'token',
102
+ 'x-pkey',
103
+ 'api-key',
104
+ 'apikey',
105
+ 'x-api-key',
106
+ 'x-auth-token',
107
+ 'x-access-token',
108
+ 'session',
109
+ 'session-id',
110
+ 'jwt',
111
+ 'refresh-token',
112
+ 'x-csrf-token',
113
+ ]);
114
+ /** 敏感字段名:header / query / body key 共用。 */
115
+ const SENSITIVE_KEY_PATTERN = /(authorization|cookie|password|passwd|secret|token|pkey|private[_-]?key|access[_-]?key|client[_-]?secret|api[_-]?key|apikey|session|jwt|refresh[_-]?token|csrf)/i;
116
+ /**
117
+ * 脱敏请求 URL:去掉 userinfo,并将 query / fragment 中的敏感参数值替换为 `[redacted]`。
118
+ */
119
+ function sanitizeNetworkRequestUrl(url) {
120
+ if (!url) {
121
+ return url;
122
+ }
123
+ try {
124
+ const parsed = new URL(url);
125
+ parsed.username = '';
126
+ parsed.password = '';
127
+ redactUrlSearchParams(parsed.searchParams);
128
+ if (parsed.hash && parsed.hash.length > 1) {
129
+ const hashBody = parsed.hash.slice(1);
130
+ if (hashBody.includes('=')) {
131
+ const hashParams = new URLSearchParams(hashBody);
132
+ if (redactUrlSearchParams(hashParams)) {
133
+ parsed.hash = hashParams.toString();
134
+ }
135
+ }
136
+ }
137
+ return parsed.toString();
138
+ }
139
+ catch {
140
+ return redactSensitivePairsInRawText(url);
141
+ }
142
+ }
143
+ /**
144
+ * 脱敏请求/响应头;敏感名(含 heybox 前缀)替换为 `[redacted]`。
145
+ */
146
+ function sanitizeNetworkHeaders(headers) {
147
+ if (!headers) {
148
+ return {};
149
+ }
150
+ return Object.fromEntries(Object.entries(headers).map(([key, value]) => [
151
+ key,
152
+ isSensitiveNetworkHeaderName(key) ? '[redacted]' : value,
153
+ ]));
154
+ }
155
+ /**
156
+ * 预览请求/响应 data:递归脱敏敏感字段,截断长文本与大数组。
157
+ */
158
+ function previewNetworkData(data) {
159
+ if (data === undefined) {
160
+ return undefined;
161
+ }
162
+ if (typeof data === 'string') {
163
+ return previewNetworkStringData(data);
164
+ }
165
+ if (typeof data === 'number' || typeof data === 'boolean' || data === null) {
166
+ return data;
167
+ }
168
+ try {
169
+ const sanitized = sanitizeNetworkDataValue(data);
170
+ const serialized = JSON.stringify(sanitized);
171
+ if (serialized === undefined) {
172
+ return '[unserializable]';
173
+ }
174
+ if (serialized.length <= DATA_PREVIEW_MAX_CHARS) {
175
+ return sanitized;
176
+ }
177
+ return truncateText(serialized, DATA_PREVIEW_MAX_CHARS);
178
+ }
179
+ catch {
180
+ return '[unserializable]';
181
+ }
182
+ }
183
+ /**
184
+ * 诊断 message 中的 URL 片段做脱敏,避免 error.message 二次泄漏 query secrets。
185
+ */
186
+ function sanitizeDiagnosticMessage(message) {
187
+ if (!message) {
188
+ return message;
189
+ }
190
+ return message.replace(/https?:\/\/[^\s]+/gi, matched => sanitizeNetworkRequestUrl(matched.replace(/[),.;]+$/g, '')));
191
+ }
192
+ function isSensitiveNetworkHeaderName(headerName) {
193
+ const normalized = headerName.trim().toLowerCase();
194
+ return (SENSITIVE_HEADER_NAMES.has(normalized) ||
195
+ isSensitiveNetworkKey(normalized) ||
196
+ normalized.startsWith('x-heybox-') ||
197
+ normalized.startsWith('x-xhh-'));
198
+ }
199
+ function isSensitiveNetworkKey(key) {
200
+ return SENSITIVE_KEY_PATTERN.test(key.trim());
201
+ }
202
+ function previewNetworkStringData(data) {
203
+ const trimmed = data.trim();
204
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
205
+ try {
206
+ return previewNetworkData(JSON.parse(trimmed));
207
+ }
208
+ catch {
209
+ // fall through
210
+ }
211
+ }
212
+ if (trimmed.includes('=') && !trimmed.includes('\n') && trimmed.length <= DATA_PREVIEW_MAX_CHARS * 2) {
213
+ try {
214
+ const params = new URLSearchParams(trimmed);
215
+ if ([...params.keys()].length > 0) {
216
+ const redacted = Object.fromEntries([...params.entries()].map(([key, value]) => [
217
+ key,
218
+ isSensitiveNetworkKey(key) ? '[redacted]' : value,
219
+ ]));
220
+ return previewNetworkData(redacted);
221
+ }
222
+ }
223
+ catch {
224
+ // fall through
225
+ }
226
+ }
227
+ return truncateText(redactSensitivePairsInRawText(data), DATA_PREVIEW_MAX_CHARS);
228
+ }
229
+ function sanitizeNetworkDataValue(value) {
230
+ if (Array.isArray(value)) {
231
+ return value.slice(0, 20).map(item => sanitizeNetworkDataValue(item));
232
+ }
233
+ if (!isPlainObject$1(value)) {
234
+ if (typeof value === 'string') {
235
+ return truncateText(value, DATA_PREVIEW_MAX_CHARS);
236
+ }
237
+ return value;
238
+ }
239
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
240
+ key,
241
+ isSensitiveNetworkKey(key) ? '[redacted]' : sanitizeNetworkDataValue(item),
242
+ ]));
243
+ }
244
+ function redactUrlSearchParams(params) {
245
+ let changed = false;
246
+ for (const key of [...params.keys()]) {
247
+ if (isSensitiveNetworkKey(key)) {
248
+ params.set(key, '[redacted]');
249
+ changed = true;
250
+ }
251
+ }
252
+ return changed;
253
+ }
254
+ function redactSensitivePairsInRawText(text) {
255
+ return text.replace(/([?&#]|^|[?&])([^=&#\s]+)=([^&#\s]*)/g, (full, prefix, key, value) => {
256
+ if (!isSensitiveNetworkKey(key)) {
257
+ return full;
258
+ }
259
+ return `${prefix}${key}=[redacted]`;
260
+ });
261
+ }
262
+ function truncateText(value, maxChars) {
263
+ if (value.length <= maxChars) {
264
+ return value;
265
+ }
266
+ return `${value.slice(0, maxChars)}…(truncated, total=${value.length})`;
267
+ }
268
+ function isPlainObject$1(value) {
269
+ return Object.prototype.toString.call(value) === '[object Object]';
270
+ }
271
+
1
272
  /**
2
273
  * SDK 对外抛出的标准 bridge / runtime 错误类型。
3
274
  *
@@ -60,7 +331,14 @@ class HbMiniProgramNetworkError extends Error {
60
331
  * @param response 已完成请求的标准化网络响应。
61
332
  */
62
333
  constructor(response) {
63
- super(`network.request failed with status ${response.status}`);
334
+ const method = (response.config.method || 'GET').toUpperCase();
335
+ // 诊断文案使用脱敏 URL,避免 query 中的 token 等经 error.message 泄漏。
336
+ const url = sanitizeNetworkRequestUrl(response.config.url || '');
337
+ const statusText = response.statusText ? ` ${response.statusText}` : '';
338
+ const baseMessage = `network.request failed with status ${response.status}${statusText}: ${method} ${url}`;
339
+ // 宿主可能把 transport 不支持等内部错误伪装成 HTTP 500;补充可操作提示。
340
+ const hostHint = describeHostNetworkFailure(response);
341
+ super(hostHint ? `${baseMessage}\n${hostHint}` : baseMessage);
64
342
  this.name = 'HbMiniProgramNetworkError';
65
343
  this.status = response.status;
66
344
  this.data = response.data;
@@ -321,7 +599,7 @@ function createMessageId() {
321
599
  /** 构建时替换为当前发布包的实际版本。 */
322
600
  const HB_SDK_VERSION = typeof undefined === 'string'
323
601
  ? undefined
324
- : '0.6.8-alpha.1';
602
+ : '0.6.8';
325
603
 
326
604
  /**
327
605
  * 判断未知数据是否符合小程序 bridge 消息信封。
@@ -888,6 +1166,77 @@ function createStorageModule(requester) {
888
1166
  };
889
1167
  }
890
1168
 
1169
+ const LOG_PREFIX = '[hb-sdk][network.request]';
1170
+ /**
1171
+ * 记录 `network.request` 失败,保证异常路径在开发者控制台可观测。
1172
+ *
1173
+ * @remarks
1174
+ * - 仅用于诊断,不改变错误抛出语义。
1175
+ * - 会脱敏 token/cookie 等敏感头与字段,并截断 body 预览。
1176
+ * - detail 构造与 logger 调用均包在 try 内,日志失败不得影响业务错误抛出。
1177
+ */
1178
+ function logNetworkRequestFailure(config, error, extras) {
1179
+ try {
1180
+ const consoleRef = typeof console === 'undefined' ? undefined : console;
1181
+ const logger = consoleRef?.warn || consoleRef?.error || consoleRef?.log;
1182
+ if (!logger) {
1183
+ return;
1184
+ }
1185
+ const detail = createNetworkRequestFailureLog(config, error, extras);
1186
+ logger.call(consoleRef, LOG_PREFIX, formatNetworkRequestFailureMessage(detail), detail);
1187
+ }
1188
+ catch {
1189
+ // 日志失败不得影响业务错误抛出。
1190
+ }
1191
+ }
1192
+ function createNetworkRequestFailureLog(config, error, extras) {
1193
+ const method = (config.method || 'GET').toUpperCase();
1194
+ const detail = {
1195
+ kind: extras.kind,
1196
+ method,
1197
+ url: sanitizeNetworkRequestUrl(config.url),
1198
+ };
1199
+ if (typeof extras.status === 'number') {
1200
+ detail.status = extras.status;
1201
+ }
1202
+ else if (error instanceof HbMiniProgramNetworkError) {
1203
+ detail.status = error.status;
1204
+ }
1205
+ if (typeof extras.statusText === 'string' && extras.statusText) {
1206
+ detail.statusText = extras.statusText;
1207
+ }
1208
+ // NetworkError 已有 method/url/status 结构化字段;不再回写 message,避免 raw URL 二次泄漏。
1209
+ if (!(error instanceof HbMiniProgramNetworkError)) {
1210
+ if (error instanceof HbMiniProgramSDKError) {
1211
+ detail.code = error.code;
1212
+ detail.message = sanitizeDiagnosticMessage(error.message);
1213
+ }
1214
+ else if (error instanceof Error) {
1215
+ detail.message = sanitizeDiagnosticMessage(error.message);
1216
+ }
1217
+ }
1218
+ if (config.timeout !== undefined) {
1219
+ detail.timeout = config.timeout;
1220
+ }
1221
+ if (config.withCredentials !== undefined) {
1222
+ detail.withCredentials = config.withCredentials;
1223
+ }
1224
+ const headers = sanitizeNetworkHeaders(extras.headers || config.headers);
1225
+ if (Object.keys(headers).length > 0) {
1226
+ detail.headers = headers;
1227
+ }
1228
+ const dataPreview = previewNetworkData(extras.data !== undefined ? extras.data : config.data);
1229
+ if (dataPreview !== undefined) {
1230
+ detail.dataPreview = dataPreview;
1231
+ }
1232
+ return detail;
1233
+ }
1234
+ function formatNetworkRequestFailureMessage(detail) {
1235
+ const statusPart = detail.status === undefined ? '' : ` status=${detail.status}`;
1236
+ const codePart = detail.code ? ` code=${detail.code}` : '';
1237
+ return `${detail.kind} ${detail.method} ${detail.url}${statusPart}${codePart}`;
1238
+ }
1239
+
891
1240
  const DEFAULT_VALIDATE_STATUS = status => status >= 200 && status < 300;
892
1241
  function isPlainObject(value) {
893
1242
  return Object.prototype.toString.call(value) === '[object Object]';
@@ -931,6 +1280,9 @@ function normalizeHeaders(headers) {
931
1280
  return Object.fromEntries(Object.entries(headers).flatMap(([key, value]) => (typeof value === 'string' ? [[key, value]] : [])));
932
1281
  }
933
1282
  function toNetworkResponse(payload, config) {
1283
+ if (payload == null || typeof payload !== 'object') {
1284
+ throw createSDKError('INVALID_NETWORK_RESPONSE', 'network.request 返回了无效的响应', payload);
1285
+ }
934
1286
  if (typeof payload.status !== 'number' || Number.isNaN(payload.status)) {
935
1287
  throw createSDKError('INVALID_NETWORK_RESPONSE', 'network.request 返回了无效的 status', payload);
936
1288
  }
@@ -965,12 +1317,56 @@ function toNetworkResponse(payload, config) {
965
1317
  */
966
1318
  async function request(requester, config) {
967
1319
  const validateStatus = config.validateStatus || DEFAULT_VALIDATE_STATUS;
968
- const responsePayload = await requester.request(NETWORK_REQUEST_METHOD, toRequestPayload(config));
969
- const response = toNetworkResponse(responsePayload, config);
970
- if (!validateStatus(response.status)) {
971
- throw new HbMiniProgramNetworkError(response);
1320
+ try {
1321
+ assertSupportedPublicNetworkRequest(config);
1322
+ const responsePayload = await requester.request(NETWORK_REQUEST_METHOD, toRequestPayload(config));
1323
+ const response = toNetworkResponse(responsePayload, config);
1324
+ if (!validateStatus(response.status)) {
1325
+ const error = new HbMiniProgramNetworkError(response);
1326
+ logNetworkRequestFailure(config, error, {
1327
+ kind: 'http_status',
1328
+ status: response.status,
1329
+ statusText: response.statusText,
1330
+ data: response.data,
1331
+ headers: response.headers,
1332
+ });
1333
+ throw error;
1334
+ }
1335
+ return response;
1336
+ }
1337
+ catch (error) {
1338
+ if (error instanceof HbMiniProgramNetworkError) {
1339
+ throw error;
1340
+ }
1341
+ if (error instanceof HbMiniProgramSDKError && error.code === 'INVALID_PARAMS') {
1342
+ logNetworkRequestFailure(config, error, {
1343
+ kind: 'bridge_error',
1344
+ });
1345
+ throw error;
1346
+ }
1347
+ if (error instanceof HbMiniProgramSDKError && error.code === 'INVALID_NETWORK_RESPONSE') {
1348
+ logNetworkRequestFailure(config, error, {
1349
+ kind: 'invalid_response',
1350
+ data: error.data,
1351
+ });
1352
+ throw error;
1353
+ }
1354
+ logNetworkRequestFailure(config, error, {
1355
+ kind: error instanceof HbMiniProgramSDKError ? 'bridge_error' : 'unknown',
1356
+ });
1357
+ throw error;
1358
+ }
1359
+ }
1360
+ /**
1361
+ * 在进入 bridge 前拒绝 Host 能力明确不支持的公开请求形态。
1362
+ *
1363
+ * @remarks
1364
+ * 提前失败可避免本地 Mock(fetch)与 App Host(heybox)能力差造成“本地成功、上线 500”。
1365
+ */
1366
+ function assertSupportedPublicNetworkRequest(config) {
1367
+ if (isUnsupportedMultipartNetworkRequest(config)) {
1368
+ throw createSDKError('INVALID_PARAMS', NETWORK_REQUEST_MULTIPART_UNSUPPORTED_MESSAGE);
972
1369
  }
973
- return response;
974
1370
  }
975
1371
  /**
976
1372
  * 创建 network 模块。
package/dist/vite.cjs.js CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  var node_fs = require('node:fs');
4
4
  var path = require('node:path');
5
+ var node_async_hooks = require('node:async_hooks');
5
6
 
6
7
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
7
8
  /** 构建时替换为当前发布包的实际版本。 */
8
9
  const HB_SDK_VERSION = typeof undefined === 'string'
9
10
  ? undefined
10
- : '0.6.8-alpha.1';
11
+ : '0.6.8';
11
12
 
12
13
  var re = {exports: {}};
13
14
 
@@ -11475,6 +11476,9 @@ function enforceMiniappHtmlPolicy(html, options = {}) {
11475
11476
  removePlatformCspMarkers(document);
11476
11477
  walk(document, element => validateElement(element));
11477
11478
  injectMiniappRuntimeGate(document);
11479
+ if (options.skipPlatformCsp) {
11480
+ return serialize(document);
11481
+ }
11478
11482
  const content = options.hmrWebSocketUrl
11479
11483
  ? PRODUCTION_CSP.replace("connect-src 'none'", `connect-src ${options.hmrWebSocketUrl}`)
11480
11484
  : PRODUCTION_CSP;
@@ -11617,9 +11621,19 @@ function walk(node, visit) {
11617
11621
  walk(node.content, visit);
11618
11622
  }
11619
11623
 
11624
+ const HB_SDK_RUNTIME_USE_OFFICIAL_DOMAIN_ENV = 'HB_SDK_RUNTIME_USE_OFFICIAL_DOMAIN';
11625
+ const HB_SDK_RUNTIME_PERMISSION_CONTEXT_ENV = 'HB_SDK_RUNTIME_PERMISSION_CONTEXT';
11626
+ const VERIFIED_RUNTIME_PERMISSION_CONTEXT = 'verified';
11627
+ new node_async_hooks.AsyncLocalStorage();
11628
+ Promise.resolve();
11629
+ function shouldSkipMiniappPlatformCspFromEnv(env = process.env) {
11630
+ return env[HB_SDK_RUNTIME_PERMISSION_CONTEXT_ENV] === VERIFIED_RUNTIME_PERMISSION_CONTEXT && env[HB_SDK_RUNTIME_USE_OFFICIAL_DOMAIN_ENV] === '1';
11631
+ }
11632
+
11620
11633
  const sdkVersionPlaceholder = ['__HB_SDK', 'VERSION__'].join('_');
11621
11634
  const sdkVersionBuildConstant = ['__HB_SDK_BUILD', 'VERSION__'].join('_');
11622
11635
  function miniappManifest() {
11636
+ const skipPlatformCsp = shouldSkipMiniappPlatformCspFromEnv();
11623
11637
  let root = process.cwd();
11624
11638
  let outDir = 'dist';
11625
11639
  let command = 'build';
@@ -11651,6 +11665,7 @@ function miniappManifest() {
11651
11665
  transformIndexHtml(html) {
11652
11666
  return enforceMiniappHtmlPolicy(html, {
11653
11667
  ...(command === 'serve' ? { hmrWebSocketUrl } : {}),
11668
+ skipPlatformCsp,
11654
11669
  });
11655
11670
  },
11656
11671
  async closeBundle() {
@@ -11673,7 +11688,7 @@ function miniappManifest() {
11673
11688
  if (!htmlFiles.includes(indexHtmlPath)) {
11674
11689
  throw new Error('构建产物必须包含入口 dist/index.html');
11675
11690
  }
11676
- node_fs.writeFileSync(indexHtmlPath, enforceMiniappHtmlPolicy(node_fs.readFileSync(indexHtmlPath, 'utf8')));
11691
+ node_fs.writeFileSync(indexHtmlPath, enforceMiniappHtmlPolicy(node_fs.readFileSync(indexHtmlPath, 'utf8'), { skipPlatformCsp }));
11677
11692
  const manifestPath = path.join(outputRoot, 'manifest.json');
11678
11693
  node_fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
11679
11694
  node_fs.writeFileSync(manifestPath, renderMiniappManifest({ version, sdkVersion }));
package/dist/vite.esm.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
4
 
4
5
  /** 构建时替换为当前发布包的实际版本。 */
5
6
  const HB_SDK_VERSION = typeof undefined === 'string'
6
7
  ? undefined
7
- : '0.6.8-alpha.1';
8
+ : '0.6.8';
8
9
 
9
10
  var re = {exports: {}};
10
11
 
@@ -11472,6 +11473,9 @@ function enforceMiniappHtmlPolicy(html, options = {}) {
11472
11473
  removePlatformCspMarkers(document);
11473
11474
  walk(document, element => validateElement(element));
11474
11475
  injectMiniappRuntimeGate(document);
11476
+ if (options.skipPlatformCsp) {
11477
+ return serialize(document);
11478
+ }
11475
11479
  const content = options.hmrWebSocketUrl
11476
11480
  ? PRODUCTION_CSP.replace("connect-src 'none'", `connect-src ${options.hmrWebSocketUrl}`)
11477
11481
  : PRODUCTION_CSP;
@@ -11614,9 +11618,19 @@ function walk(node, visit) {
11614
11618
  walk(node.content, visit);
11615
11619
  }
11616
11620
 
11621
+ const HB_SDK_RUNTIME_USE_OFFICIAL_DOMAIN_ENV = 'HB_SDK_RUNTIME_USE_OFFICIAL_DOMAIN';
11622
+ const HB_SDK_RUNTIME_PERMISSION_CONTEXT_ENV = 'HB_SDK_RUNTIME_PERMISSION_CONTEXT';
11623
+ const VERIFIED_RUNTIME_PERMISSION_CONTEXT = 'verified';
11624
+ new AsyncLocalStorage();
11625
+ Promise.resolve();
11626
+ function shouldSkipMiniappPlatformCspFromEnv(env = process.env) {
11627
+ return env[HB_SDK_RUNTIME_PERMISSION_CONTEXT_ENV] === VERIFIED_RUNTIME_PERMISSION_CONTEXT && env[HB_SDK_RUNTIME_USE_OFFICIAL_DOMAIN_ENV] === '1';
11628
+ }
11629
+
11617
11630
  const sdkVersionPlaceholder = ['__HB_SDK', 'VERSION__'].join('_');
11618
11631
  const sdkVersionBuildConstant = ['__HB_SDK_BUILD', 'VERSION__'].join('_');
11619
11632
  function miniappManifest() {
11633
+ const skipPlatformCsp = shouldSkipMiniappPlatformCspFromEnv();
11620
11634
  let root = process.cwd();
11621
11635
  let outDir = 'dist';
11622
11636
  let command = 'build';
@@ -11648,6 +11662,7 @@ function miniappManifest() {
11648
11662
  transformIndexHtml(html) {
11649
11663
  return enforceMiniappHtmlPolicy(html, {
11650
11664
  ...(command === 'serve' ? { hmrWebSocketUrl } : {}),
11665
+ skipPlatformCsp,
11651
11666
  });
11652
11667
  },
11653
11668
  async closeBundle() {
@@ -11670,7 +11685,7 @@ function miniappManifest() {
11670
11685
  if (!htmlFiles.includes(indexHtmlPath)) {
11671
11686
  throw new Error('构建产物必须包含入口 dist/index.html');
11672
11687
  }
11673
- writeFileSync(indexHtmlPath, enforceMiniappHtmlPolicy(readFileSync(indexHtmlPath, 'utf8')));
11688
+ writeFileSync(indexHtmlPath, enforceMiniappHtmlPolicy(readFileSync(indexHtmlPath, 'utf8'), { skipPlatformCsp }));
11674
11689
  const manifestPath = path.join(outputRoot, 'manifest.json');
11675
11690
  mkdirSync(path.dirname(manifestPath), { recursive: true });
11676
11691
  writeFileSync(manifestPath, renderMiniappManifest({ version, sdkVersion }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heybox/hb-sdk",
3
- "version": "0.6.8-alpha.1",
3
+ "version": "0.6.8",
4
4
  "sideEffects": [
5
5
  "./src/index.ts",
6
6
  "./src/core/singleton.ts",
@@ -95,7 +95,7 @@
95
95
  "vue": "^2.7.16",
96
96
  "vite": "^8.0.12",
97
97
  "vitest": "^3.2.4",
98
- "@heybox/hb-api": "~1.25.8"
98
+ "@heybox/hb-api": "~1.25.10"
99
99
  },
100
100
  "publishConfig": {
101
101
  "registry": "https://registry.npmjs.org/",
package/skill/SKILL.md CHANGED
@@ -61,12 +61,13 @@ Apply these instructions when writing, reviewing, or debugging code that consume
61
61
  10. Use `hb-sdk remote create` to create and bind a mini-program; use `hb-sdk remote bind <mini-program-id>` to bind an existing manageable mini-program.
62
62
  11. Use `hb-sdk remote info`, `hb-sdk remote list`, `hb-sdk remote access`, `hb-sdk remote versions`, `hb-sdk remote preview <version>`, and `hb-sdk remote allowlist ...` for remote inspection and preview management.
63
63
  12. Use `hb-sdk remote deploy --release-note <text>` to run the project's `scripts.build`, upload, and submit the current project for audit. Do not recommend the removed top-level `hb-sdk deploy` alias.
64
- 13. After approval, use `hb-sdk remote release <version>` for manual release or `--auto-publish` when an eligible low-risk version should release automatically.
65
- 14. Treat approval, release, and public display as separate states. Do not promise square, search, or recommendation visibility after release.
66
- 15. Use `hb-sdk remote withdraw`, `hb-sdk remote take-down`, `hb-sdk remote reopen`, and square visibility commands only after showing the target and obtaining required confirmation.
67
- 16. Use `--json` for remote script consumption and `--verbose` only when concise output is insufficient for diagnosis.
68
- 17. Use `hb-sdk doctor` to diagnose whether the local Skill matches the installed SDK; follow its output to install or refresh the Skill.
69
- 18. Do not print or expose cookies, tokens, private headers, or other credentials.
64
+ 13. `hb-sdk dev` and `hb-sdk remote deploy` skip the platform CSP only when the validated remote permission snapshot has `network.request.status=enabled` and `useOfficialDomain=true`. Direct `hb-sdk build`, direct Vite build, anonymous or invalid snapshots, and local Dev Context overrides must keep the platform CSP. Runtime Gate, Manifest, and HTML validation always remain active.
65
+ 14. After approval, use `hb-sdk remote release <version>` for manual release or `--auto-publish` when an eligible low-risk version should release automatically.
66
+ 15. Treat approval, release, and public display as separate states. Do not promise square, search, or recommendation visibility after release.
67
+ 16. Use `hb-sdk remote withdraw`, `hb-sdk remote take-down`, `hb-sdk remote reopen`, and square visibility commands only after showing the target and obtaining required confirmation.
68
+ 17. Use `--json` for remote script consumption and `--verbose` only when concise output is insufficient for diagnosis.
69
+ 18. Use `hb-sdk doctor` to diagnose whether the local Skill matches the installed SDK; follow its output to install or refresh the Skill.
70
+ 19. Do not print or expose cookies, tokens, private headers, or other credentials.
70
71
 
71
72
  ## Step 6: Preserve capability boundaries
72
73
 
@@ -23,7 +23,7 @@
23
23
  ## Package metadata
24
24
 
25
25
  - Package: `@heybox/hb-sdk`
26
- - Version at generation time: `0.6.8-alpha.0`
26
+ - Version at generation time: `0.6.8`
27
27
  - Public root export: `@heybox/hb-sdk`
28
28
  - Protocol export: `@heybox/hb-sdk/protocol`
29
29
  - Vite plugin export: `@heybox/hb-sdk/vite`
@@ -208,6 +208,7 @@ import { HB_SDK_VERSION } from '../core/version';
208
208
  import { renderMiniappManifest, validateMiniappPackageVersionForBuild } from '../miniapp-manifest/schema';
209
209
  import { readMiniappVersionFromPackageJson } from '../miniapp-manifest/node';
210
210
  import { enforceMiniappHtmlPolicy } from './html-policy';
211
+ import { shouldSkipMiniappPlatformCspFromEnv } from './runtime-permission-env';
211
212
 
212
213
  type MiniappManifestPlugin = {
213
214
  name: string;
@@ -253,6 +254,7 @@ const sdkVersionPlaceholder = ['__HB_SDK', 'VERSION__'].join('_');
253
254
  const sdkVersionBuildConstant = ['__HB_SDK_BUILD', 'VERSION__'].join('_');
254
255
 
255
256
  export function miniappManifest(): MiniappManifestPlugin {
257
+ const skipPlatformCsp = shouldSkipMiniappPlatformCspFromEnv();
256
258
  let root = process.cwd();
257
259
  let outDir = 'dist';
258
260
  let command: 'build' | 'serve' = 'build';
@@ -285,6 +287,7 @@ export function miniappManifest(): MiniappManifestPlugin {
285
287
  transformIndexHtml(html) {
286
288
  return enforceMiniappHtmlPolicy(html, {
287
289
  ...(command === 'serve' ? { hmrWebSocketUrl } : {}),
290
+ skipPlatformCsp,
288
291
  });
289
292
  },
290
293
  async closeBundle() {
@@ -310,7 +313,7 @@ export function miniappManifest(): MiniappManifestPlugin {
310
313
  if (!htmlFiles.includes(indexHtmlPath)) {
311
314
  throw new Error('构建产物必须包含入口 dist/index.html');
312
315
  }
313
- writeFileSync(indexHtmlPath, enforceMiniappHtmlPolicy(readFileSync(indexHtmlPath, 'utf8')));
316
+ writeFileSync(indexHtmlPath, enforceMiniappHtmlPolicy(readFileSync(indexHtmlPath, 'utf8'), { skipPlatformCsp }));
314
317
 
315
318
  const manifestPath = path.join(outputRoot, 'manifest.json');
316
319
  mkdirSync(path.dirname(manifestPath), { recursive: true });
@@ -369,6 +372,8 @@ hb-sdk build [--env <name>] [--verbose]
369
372
 
370
373
  `hb-sdk build` 直接使用项目安装的 Vite,先清理再生成固定的 `dist/`,并校验小程序入口、Manifest 和可上传产物。它不执行类型检查,不要求 CLI 登录或绑定小程序,也不访问远端服务。项目必须在 `vite.config.ts` 中显式注册 `miniappManifest()`。
371
374
 
375
+ 直接运行 `hb-sdk build` 或 `vite build` 时默认注入平台 CSP。`hb-sdk remote deploy` 会在构建前读取绑定小程序的远端权限;仅当 `network.request.status=enabled` 且 `useOfficialDomain=true` 时向子构建传递已验证上下文并跳过平台 CSP。权限缺失、非法或读取失败时继续注入,Runtime Gate、Manifest 与 HTML 构建检查始终保留。
376
+
372
377
  推荐由项目的 `scripts.build` 保留类型检查:
373
378
 
374
379
  ```json