@heybox/hb-sdk 0.6.8-alpha.2 → 0.6.9-alpha.0

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 (38) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +2 -0
  3. package/dist/cli-chunks/{build-CglqyB9Z.cjs → build-9hbhrRbU.cjs} +29 -25
  4. package/dist/cli-chunks/{context-CP7W_8aR.cjs → context-7rmakzS_.cjs} +122 -23
  5. package/dist/cli-chunks/{create-DldsTIVt.cjs → create-BkX4rtRP.cjs} +1 -1
  6. package/dist/cli-chunks/{dev-BG0icySa.cjs → dev-DiZGT4FQ.cjs} +142 -108
  7. package/dist/cli-chunks/{doctor-Cd6Xq0m-.cjs → doctor-BcpqXVRh.cjs} +1 -1
  8. package/dist/cli-chunks/{index-jXyfZKy2.cjs → index-6HfcwZ_r.cjs} +3 -3
  9. package/dist/cli-chunks/{index-paN77avR.cjs → index-DofTxdoX.cjs} +14 -14
  10. package/dist/cli-chunks/{login-DlD9n_AF.cjs → login-DUiejZcD.cjs} +2 -2
  11. package/dist/cli-chunks/{project-vite-vHezGsg7.cjs → project-vite-CfD8Bp1K.cjs} +1 -1
  12. package/dist/cli-chunks/{remote-O5_nRdZN.cjs → remote-A4-PKgOn.cjs} +462 -190
  13. package/dist/cli-chunks/{runtime-gate-DfMJQGH9.cjs → runtime-gate-BWlU-R4h.cjs} +3 -3
  14. package/dist/cli-chunks/{index-v4-6fbXX.cjs → runtime-permission-env-DKrhgVM3.cjs} +276 -49
  15. package/dist/cli-chunks/{session-CiQplems.cjs → session-D5u3XzHN.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/miniapp-publish.cjs.js +205 -0
  21. package/dist/miniapp-publish.esm.js +201 -1
  22. package/dist/vite.cjs.js +17 -2
  23. package/dist/vite.esm.js +17 -2
  24. package/package.json +2 -2
  25. package/skill/SKILL.md +7 -6
  26. package/skill/references/api-root.md +7 -2
  27. package/skill/references/cli.md +12 -0
  28. package/skill/references/examples.md +17 -2
  29. package/skill/references/safety-boundaries.md +2 -0
  30. package/skill/scripts/sync-references.mjs +17 -2
  31. package/skill/skill.json +4 -4
  32. package/types/core/network-sanitize.d.ts +31 -0
  33. package/types/miniapp-publish/index.d.ts +77 -0
  34. package/types/modules/network/index.d.ts +2 -0
  35. package/types/modules/network/observability.d.ts +39 -0
  36. package/types/modules/network/request-shape.d.ts +44 -0
  37. package/types/vite/html-policy.d.ts +1 -0
  38. package/types/vite/runtime-permission-env.d.ts +9 -0
@@ -2834,6 +2834,10 @@ const UPDATE_SQUARE_DISPLAY_API_PATH = '/mall/developer/user_miniprogram/square_
2834
2834
  const LIST_USER_MINIPROGRAM_VERSION_API_PATH = '/mall/developer/user_miniprogram/version/list';
2835
2835
  const FNV_OFFSET = 0x811c9dc5;
2836
2836
  const FNV_PRIME = 0x01000193;
2837
+ const MINIAPP_UPLOAD_SESSION_REF_LENGTH = 10;
2838
+ const MINIAPP_ARTIFACT_SIGNATURE_MAGIC = 'heybox-user-miniprogram-artifact\0v1\0';
2839
+ const UNPAIRED_SURROGATE_PATTERN = /[\ud800-\udbff](?![\udc00-\udfff])|(^|[^\ud800-\udbff])[\udc00-\udfff]/;
2840
+ const textEncoder = new TextEncoder();
2837
2841
  function normalizeRelativePath(relativePath) {
2838
2842
  return String(relativePath || '')
2839
2843
  .replace(/\\/g, '/')
@@ -2927,5 +2931,201 @@ function shouldUploadDistFile(relativePath) {
2927
2931
  }
2928
2932
  return true;
2929
2933
  }
2934
+ /**
2935
+ * 校验构建产物路径和大小,并按路径 UTF-8 字节序排序。
2936
+ *
2937
+ * @param files 待处理的构建产物文件。
2938
+ * @returns 保留输入附加字段的有序文件列表。
2939
+ * @throws 路径不规范、路径重复或文件大小非法时抛出错误。
2940
+ */
2941
+ function prepareMiniappArtifactFiles(files) {
2942
+ if (files.length > 0xffffffff) {
2943
+ throw new Error('构建产物文件数量超出签名协议上限');
2944
+ }
2945
+ const usedPaths = new Set();
2946
+ const prepared = files.map(file => {
2947
+ const relativePath = normalizeMiniappArtifactRelativePath(file.relativePath);
2948
+ if (usedPaths.has(relativePath)) {
2949
+ throw new Error(`构建产物包含重复文件路径:${relativePath}`);
2950
+ }
2951
+ usedPaths.add(relativePath);
2952
+ validateArtifactFileSize(file.size, relativePath);
2953
+ return { ...file, relativePath };
2954
+ });
2955
+ return prepared.sort((left, right) => compareBytes(textEncoder.encode(left.relativePath), textEncoder.encode(right.relativePath)));
2956
+ }
2957
+ /**
2958
+ * 按服务端 session key 前缀校验最终上传路径的 UTF-8 字节长度。
2959
+ *
2960
+ * @param files 待上传的构建产物文件。
2961
+ * @param options 小程序和长度限制配置。
2962
+ * @returns 第一个超长路径的错误提示;全部合法时返回 `undefined`。
2963
+ */
2964
+ function validateMiniappUploadPathLengths(files, options) {
2965
+ const maxLength = options.maxLength ?? ACTIVITY_UPLOAD_KEY_MAX_LENGTH;
2966
+ const prefix = `/u/${getMiniProgramUploadAlias(options.miniProgramId)}/${'x'.repeat(MINIAPP_UPLOAD_SESSION_REF_LENGTH)}/`;
2967
+ const prefixLength = textEncoder.encode(prefix).length;
2968
+ const tooLongFile = files.find(file => {
2969
+ const relativePath = normalizeMiniappArtifactRelativePath(file.relativePath);
2970
+ return prefixLength + textEncoder.encode(relativePath).length > maxLength;
2971
+ });
2972
+ return tooLongFile ? `文件路径过长,请缩短构建产物文件名或目录层级:${tooLongFile.relativePath}` : undefined;
2973
+ }
2974
+ /**
2975
+ * 创建 v1 整包签名头。
2976
+ *
2977
+ * @param fileCount 参与签名的文件数量。
2978
+ * @returns 包含协议标识和文件数量的字节数组。
2979
+ * @throws 文件数量超出协议范围时抛出错误。
2980
+ */
2981
+ function createMiniappArtifactSignatureHeader(fileCount) {
2982
+ if (!Number.isInteger(fileCount) || fileCount < 0 || fileCount > 0xffffffff) {
2983
+ throw new Error('构建产物文件数量超出签名协议上限');
2984
+ }
2985
+ return concatBytes(textEncoder.encode(MINIAPP_ARTIFACT_SIGNATURE_MAGIC), encodeUint32BE(fileCount));
2986
+ }
2987
+ /**
2988
+ * 创建单个文件的 v1 framing header。
2989
+ *
2990
+ * @param file 已规范化或待校验的构建产物文件。
2991
+ * @returns 包含路径长度、路径和文件大小的字节数组。
2992
+ * @throws 文件路径或大小非法时抛出错误。
2993
+ */
2994
+ function createMiniappArtifactFileHeader(file) {
2995
+ const relativePath = normalizeMiniappArtifactRelativePath(file.relativePath);
2996
+ validateArtifactFileSize(file.size, relativePath);
2997
+ const pathBytes = textEncoder.encode(relativePath);
2998
+ return concatBytes(encodeUint32BE(pathBytes.length), pathBytes, encodeUint64BE(file.size));
2999
+ }
3000
+ /**
3001
+ * 校验服务端 session 文件映射,并计算仍需上传的文件。
3002
+ *
3003
+ * @param localFiles 本地构建产物文件。
3004
+ * @param serverFiles 服务端签发的文件映射。
3005
+ * @param uploadedKeys 服务端已确认上传成功的 key。
3006
+ * @returns 完整映射、待上传文件和已确认 key。
3007
+ * @throws 服务端映射与本地产物不一致时抛出错误。
3008
+ */
3009
+ function resolveMiniappUploadSessionFiles(localFiles, serverFiles, uploadedKeys) {
3010
+ const preparedFiles = prepareMiniappArtifactFiles(localFiles);
3011
+ if (serverFiles.length !== preparedFiles.length) {
3012
+ throw new Error(`上传会话文件数量异常:期望 ${preparedFiles.length},实际 ${serverFiles.length}`);
3013
+ }
3014
+ const mappings = new Map();
3015
+ const serverSizes = new Map();
3016
+ const knownKeys = new Set();
3017
+ for (const serverFile of serverFiles) {
3018
+ const relativePath = normalizeMiniappArtifactRelativePath(serverFile.relative_path);
3019
+ if (mappings.has(relativePath)) {
3020
+ throw new Error(`上传会话返回重复文件路径:${relativePath}`);
3021
+ }
3022
+ const key = String(serverFile.key || '');
3023
+ if (!key) {
3024
+ throw new Error(`上传会话未返回文件 key:${relativePath}`);
3025
+ }
3026
+ if (knownKeys.has(key)) {
3027
+ throw new Error(`上传会话返回重复文件 key:${relativePath}`);
3028
+ }
3029
+ const size = Number(serverFile.size);
3030
+ if (!Number.isSafeInteger(size) || size < 0) {
3031
+ throw new Error(`上传会话返回文件大小异常:${relativePath}`);
3032
+ }
3033
+ mappings.set(relativePath, key);
3034
+ serverSizes.set(relativePath, size);
3035
+ knownKeys.add(key);
3036
+ }
3037
+ const files = preparedFiles.map(file => {
3038
+ const key = mappings.get(file.relativePath);
3039
+ if (!key) {
3040
+ throw new Error(`上传会话缺少文件映射:${file.relativePath}`);
3041
+ }
3042
+ if (serverSizes.get(file.relativePath) !== file.size) {
3043
+ throw new Error(`上传会话文件大小与本地构建不一致:${file.relativePath}`);
3044
+ }
3045
+ return { file, key };
3046
+ });
3047
+ const completed = new Set();
3048
+ for (const rawKey of uploadedKeys) {
3049
+ const key = String(rawKey || '');
3050
+ if (!knownKeys.has(key)) {
3051
+ throw new Error('上传会话返回了不属于当前产物的已上传文件');
3052
+ }
3053
+ completed.add(key);
3054
+ }
3055
+ return {
3056
+ files,
3057
+ missingFiles: files.filter(file => !completed.has(file.key)),
3058
+ uploadedKeys: [...completed],
3059
+ };
3060
+ }
3061
+ function normalizeMiniappArtifactRelativePath(relativePath) {
3062
+ const value = String(relativePath || '');
3063
+ if (!value) {
3064
+ throw new Error('构建产物包含空文件路径');
3065
+ }
3066
+ if (value.includes('\\')) {
3067
+ throw new Error(`构建产物路径不能包含反斜杠:${value}`);
3068
+ }
3069
+ if (value.startsWith('/') || value.endsWith('/') || /^[a-zA-Z]:\//.test(value)) {
3070
+ throw new Error(`构建产物必须使用相对文件路径:${value}`);
3071
+ }
3072
+ if (containsControlCharacter(value) || UNPAIRED_SURROGATE_PATTERN.test(value)) {
3073
+ throw new Error(`构建产物路径包含非法字符:${value}`);
3074
+ }
3075
+ if (value.split('/').some(segment => !segment || segment === '.' || segment === '..')) {
3076
+ throw new Error(`构建产物路径包含非法目录段:${value}`);
3077
+ }
3078
+ if (value.includes(',')) {
3079
+ throw new Error(`构建产物路径不能包含逗号:${value}`);
3080
+ }
3081
+ if (value.includes('*') || value.includes('?')) {
3082
+ throw new Error(`构建产物路径不能包含通配符:${value}`);
3083
+ }
3084
+ return value;
3085
+ }
3086
+ function containsControlCharacter(value) {
3087
+ for (const character of value) {
3088
+ const code = character.charCodeAt(0);
3089
+ if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) {
3090
+ return true;
3091
+ }
3092
+ }
3093
+ return false;
3094
+ }
3095
+ function validateArtifactFileSize(size, relativePath) {
3096
+ if (!Number.isSafeInteger(size) || size < 0) {
3097
+ throw new Error(`构建产物文件大小无效:${relativePath}`);
3098
+ }
3099
+ }
3100
+ function encodeUint32BE(value) {
3101
+ const bytes = new Uint8Array(4);
3102
+ new DataView(bytes.buffer).setUint32(0, value, false);
3103
+ return bytes;
3104
+ }
3105
+ function encodeUint64BE(value) {
3106
+ const bytes = new Uint8Array(8);
3107
+ const view = new DataView(bytes.buffer);
3108
+ view.setUint32(0, Math.floor(value / 0x100000000), false);
3109
+ view.setUint32(4, value % 0x100000000, false);
3110
+ return bytes;
3111
+ }
3112
+ function concatBytes(...parts) {
3113
+ const result = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
3114
+ let offset = 0;
3115
+ for (const part of parts) {
3116
+ result.set(part, offset);
3117
+ offset += part.length;
3118
+ }
3119
+ return result;
3120
+ }
3121
+ function compareBytes(left, right) {
3122
+ const length = Math.min(left.length, right.length);
3123
+ for (let index = 0; index < length; index += 1) {
3124
+ if (left[index] !== right[index]) {
3125
+ return left[index] - right[index];
3126
+ }
3127
+ }
3128
+ return left.length - right.length;
3129
+ }
2930
3130
 
2931
- export { ACTIVITY_UPLOAD_KEY_MAX_LENGTH, CREATE_USER_MINIPROGRAM_API_PATH, CREATE_USER_MINIPROGRAM_LEADERBOARD_API_PATH, DELETE_USER_MINIPROGRAM_LEADERBOARD_API_PATH, DETAIL_USER_MINIPROGRAM_API_PATH, DETAIL_USER_MINIPROGRAM_LEADERBOARD_API_PATH, DEVELOPER_ENTITY_LIST_API_PATH, DEVELOPER_ENTITY_SWITCH_API_PATH, LIST_USER_MINIPROGRAM_API_PATH, LIST_USER_MINIPROGRAM_LEADERBOARD_API_PATH, LIST_USER_MINIPROGRAM_VERSION_API_PATH, MINIAPP_UPLOAD_BATCH_SIZE, MINIAPP_UPLOAD_SCOPE, MINIAPP_UPLOAD_TOTAL_SIZE_LIMIT_BYTES, PRECHECK_USER_MINIPROGRAM_VERSION_API_PATH, RELEASE_USER_MINIPROGRAM_VERSION_API_PATH, REOPEN_USER_MINIPROGRAM_API_PATH, SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH, TAKE_DOWN_USER_MINIPROGRAM_API_PATH, UPDATE_SQUARE_DISPLAY_API_PATH, UPDATE_USER_MINIPROGRAM_PREVIEW_ALLOWLIST_API_PATH, USER_MINIPROGRAM_ACCESS_STATUS_API_PATH, USER_MINIPROGRAM_PREVIEW_ALLOWLIST_API_PATH, USER_MINIPROGRAM_VERSION_PREVIEW_INFO_API_PATH, WITHDRAW_USER_MINIPROGRAM_VERSION_API_PATH, getMiniProgramUploadAlias, getMiniappUploadKey, isValidMiniappManifestVersion as isValidVersion, normalizeRelativePath, relativePathContainsNodeModulesSegment, shouldUploadDistFile, validateUploadPaths, validateUploadTotalSize };
3131
+ export { ACTIVITY_UPLOAD_KEY_MAX_LENGTH, CREATE_USER_MINIPROGRAM_API_PATH, CREATE_USER_MINIPROGRAM_LEADERBOARD_API_PATH, DELETE_USER_MINIPROGRAM_LEADERBOARD_API_PATH, DETAIL_USER_MINIPROGRAM_API_PATH, DETAIL_USER_MINIPROGRAM_LEADERBOARD_API_PATH, DEVELOPER_ENTITY_LIST_API_PATH, DEVELOPER_ENTITY_SWITCH_API_PATH, LIST_USER_MINIPROGRAM_API_PATH, LIST_USER_MINIPROGRAM_LEADERBOARD_API_PATH, LIST_USER_MINIPROGRAM_VERSION_API_PATH, MINIAPP_UPLOAD_BATCH_SIZE, MINIAPP_UPLOAD_SCOPE, MINIAPP_UPLOAD_TOTAL_SIZE_LIMIT_BYTES, PRECHECK_USER_MINIPROGRAM_VERSION_API_PATH, RELEASE_USER_MINIPROGRAM_VERSION_API_PATH, REOPEN_USER_MINIPROGRAM_API_PATH, SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH, TAKE_DOWN_USER_MINIPROGRAM_API_PATH, UPDATE_SQUARE_DISPLAY_API_PATH, UPDATE_USER_MINIPROGRAM_PREVIEW_ALLOWLIST_API_PATH, USER_MINIPROGRAM_ACCESS_STATUS_API_PATH, USER_MINIPROGRAM_PREVIEW_ALLOWLIST_API_PATH, USER_MINIPROGRAM_VERSION_PREVIEW_INFO_API_PATH, WITHDRAW_USER_MINIPROGRAM_VERSION_API_PATH, createMiniappArtifactFileHeader, createMiniappArtifactSignatureHeader, getMiniProgramUploadAlias, getMiniappUploadKey, isValidMiniappManifestVersion as isValidVersion, normalizeRelativePath, prepareMiniappArtifactFiles, relativePathContainsNodeModulesSegment, resolveMiniappUploadSessionFiles, shouldUploadDistFile, validateMiniappUploadPathLengths, validateUploadPaths, validateUploadTotalSize };
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.2';
11
+ : '0.6.9-alpha.0';
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.2';
8
+ : '0.6.9-alpha.0';
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.2",
3
+ "version": "0.6.9-alpha.0",
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.11"
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.2`
26
+ - Version at generation time: `0.6.9-alpha.0`
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
@@ -106,6 +106,8 @@ CLI 会启动页面服务并自动打开本地调试页。调试页会展示小
106
106
 
107
107
  未登录或未绑定小程序时,浏览器、Mac 和手机调试入口仍然可用,但受管能力会默认拒绝。登录并绑定后,调试页才能根据当前小程序的线上配置提示差异。
108
108
 
109
+ `hb-sdk dev` 会在启动 Vite 前读取远端权限。只有远端权限快照有效、`network.request.status=enabled` 且 `useOfficialDomain=true` 时才跳过平台 CSP;未登录、未绑定、读取失败、非法快照或仅在 Dev Context 中开启本地权限覆盖时都继续注入 CSP。
110
+
109
111
  ### 2. 先用浏览器 Mock 验收
110
112
 
111
113
  浏览器 Mock 支持热更新,适合快速检查:
@@ -152,6 +154,8 @@ hb-sdk build [--env <name>] [--verbose]
152
154
 
153
155
  `hb-sdk build` 直接使用项目安装的 Vite,先清理再生成固定的 `dist/`,并校验小程序入口、Manifest 和可上传产物。它不执行类型检查,不要求 CLI 登录或绑定小程序,也不访问远端服务。项目必须在 `vite.config.ts` 中显式注册 `miniappManifest()`。
154
156
 
157
+ 直接运行 `hb-sdk build` 或 `vite build` 时默认注入平台 CSP。`hb-sdk remote deploy` 会在构建前读取绑定小程序的远端权限;仅当 `network.request.status=enabled` 且 `useOfficialDomain=true` 时向子构建传递已验证上下文并跳过平台 CSP。权限缺失、非法或读取失败时继续注入,Runtime Gate、Manifest 与 HTML 构建检查始终保留。
158
+
155
159
  推荐由项目的 `scripts.build` 保留类型检查:
156
160
 
157
161
  ```json
@@ -192,6 +196,14 @@ hb-sdk remote deploy --release-note "修复登录状态展示"
192
196
 
193
197
  这条命令会完成检查、执行项目的 `scripts.build`、上传和提交审核,因此项目定义的类型检查仍会执行。默认在审核通过后等待手动发布;普通低风险版本可以追加 `--auto-publish`。完整命令见 `hb-sdk remote --help`,提交要求见[小程序工坊上架规则](https://docs.xiaoheihe.cn/hb_sdk/guide/mini-program-publishing-rules)。
194
198
 
199
+ 构建产物上传从 `0.6.8` 起强制使用服务端上传 session,`0.6.7` 及更早版本不能继续部署,请先升级 `@heybox/hb-sdk`。deploy 从整包签名开始到上传结束都把 `dist/` 视为稳定输出,不要在此期间由其他进程修改构建目录;上传中断后可重新执行 deploy,服务端会按已确认回执恢复缺失文件。
200
+
201
+ 普通 deploy 会按 API 环境、`miniProgramId` 和版本在本机缓存目录获取短文件锁,避免两个 CLI 进程同时构建并上传同一版本。进程异常退出后锁可按 PID 回收;缓存目录不可用时会降级为无锁执行,不影响上传主流程。`--from-version` 不执行本地构建和上传,因此不会获取该锁。
202
+
203
+ 上传信息、上传凭证和上传回调按每批最多 50 个文件串行处理,批内保持 4 并发。CLI 每批获取一次仅授权当前 session 目录前缀的凭证,并校验服务端返回的 key 和 size 与本地文件完全一致;任何异常都会停止后续上传且不会提交审核。
204
+
205
+ 普通构建上传时,CLI 会计算整包签名,并把相对路径和文件大小一起提交给服务端创建或恢复上传会话;同一账号下签名和文件清单一致时,服务端返回已有 session 与 `uploaded_keys`,CLI 只上传缺失文件。CLI 不保存本地上传会话缓存。构建产物变化时服务端会创建新的隔离目录,并尽力清理旧目录。上传回调在当前命令内失败会重试;仍失败时可重新执行 deploy,服务端根据 `uploaded_keys` 继续恢复,同 key 的 COS 重传是幂等的。
206
+
195
207
  Before publishing, `hb-sdk remote deploy` verifies that the bound mini-program belongs to the current developer account. If it does not, the command stops and asks the user to switch accounts.
196
208
 
197
209
  Agent rules:
@@ -48,7 +48,7 @@ export async function ensureLogin() {
48
48
  ### Network request
49
49
 
50
50
  ```ts
51
- import { ready, network, HbMiniProgramNetworkError } from '@heybox/hb-sdk';
51
+ import { ready, network, HbMiniProgramNetworkError, HbMiniProgramSDKError } from '@heybox/hb-sdk';
52
52
 
53
53
  try {
54
54
  await ready();
@@ -59,9 +59,23 @@ try {
59
59
  validateStatus: status => status >= 200 && status < 400,
60
60
  });
61
61
  console.log(response.data.ok);
62
+
63
+ // form POST only: App Host does not support multipart/form-data
64
+ await network.request({
65
+ url: 'https://api.example.com/form',
66
+ method: 'POST',
67
+ headers: {
68
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
69
+ },
70
+ data: new URLSearchParams({ gameId: '3', roleId: '1' }).toString(),
71
+ });
62
72
  } catch (error) {
63
73
  if (error instanceof HbMiniProgramNetworkError) {
64
- console.warn(error.status, error.response?.data);
74
+ // Target HTTP non-2xx, or host-side network_error (message says so)
75
+ console.warn(error.status, error.message, error.data);
76
+ } else if (error instanceof HbMiniProgramSDKError) {
77
+ // e.g. INVALID_PARAMS for unsupported multipart shape
78
+ console.warn(error.code, error.message);
65
79
  }
66
80
  }
67
81
  ```
@@ -116,6 +130,7 @@ export default defineConfig({
116
130
  - Do not create raw `postMessage` bridge envelopes in business pages.
117
131
  - Do not call unsupported storage delete/clear/info operations.
118
132
  - Do not pass raw internal share/network protocol fields from mini-program code.
133
+ - Do not send `multipart/form-data` (or handcrafted multipart bodies) through `network.request`; use form-urlencoded string body or a dedicated upload capability.
119
134
  - Do not treat `hb-sdk login` as iframe SDK authentication state.
120
135
  - Do not create a second mock runtime package when `hb-sdk dev` is the supported local mock workflow.
121
136
  - Do not import `@heybox/hb-sdk/vite` from iframe business code or fetch a deployed `manifest.json` directly.
@@ -14,6 +14,8 @@
14
14
  - `auth.login()` 只能由明确的用户操作触发,不要在页面初始化时自动登录。
15
15
  - `on()` 返回取消监听函数,组件卸载或页面销毁时需要调用。
16
16
  - 业务网络请求使用 `network.request()`,不要依赖浏览器原生网络出口。
17
+ - `network.request` 不支持 `multipart/form-data`;App Host 仅支持 form(`application/x-www-form-urlencoded`)与 JSON。表单请用 `URLSearchParams#toString()` 作为 `data`,文件上传请走专用上传能力。
18
+ - 仅当远端已启用 `network.request` 且 `useOfficialDomain=true` 时,`hb-sdk dev` 与 `hb-sdk remote deploy` 构建才会跳过平台 CSP;其他构建继续注入平台 CSP。
17
19
  - 构建必须启用 `miniappManifest()`,推荐统一使用 `hb-sdk build`。
18
20
 
19
21
  ## Agent rules
@@ -587,7 +587,7 @@ export async function ensureLogin() {
587
587
 
588
588
  ### Network request
589
589
 
590
- ${fenced('ts', `import { ready, network, HbMiniProgramNetworkError } from '@heybox/hb-sdk';
590
+ ${fenced('ts', `import { ready, network, HbMiniProgramNetworkError, HbMiniProgramSDKError } from '@heybox/hb-sdk';
591
591
 
592
592
  try {
593
593
  await ready();
@@ -598,9 +598,23 @@ try {
598
598
  validateStatus: status => status >= 200 && status < 400,
599
599
  });
600
600
  console.log(response.data.ok);
601
+
602
+ // form POST only: App Host does not support multipart/form-data
603
+ await network.request({
604
+ url: 'https://api.example.com/form',
605
+ method: 'POST',
606
+ headers: {
607
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
608
+ },
609
+ data: new URLSearchParams({ gameId: '3', roleId: '1' }).toString(),
610
+ });
601
611
  } catch (error) {
602
612
  if (error instanceof HbMiniProgramNetworkError) {
603
- console.warn(error.status, error.response?.data);
613
+ // Target HTTP non-2xx, or host-side network_error (message says so)
614
+ console.warn(error.status, error.message, error.data);
615
+ } else if (error instanceof HbMiniProgramSDKError) {
616
+ // e.g. INVALID_PARAMS for unsupported multipart shape
617
+ console.warn(error.code, error.message);
604
618
  }
605
619
  }`)}
606
620
 
@@ -646,6 +660,7 @@ export default defineConfig({
646
660
  - Do not create raw \`postMessage\` bridge envelopes in business pages.
647
661
  - Do not call unsupported storage delete/clear/info operations.
648
662
  - Do not pass raw internal share/network protocol fields from mini-program code.
663
+ - Do not send \`multipart/form-data\` (or handcrafted multipart bodies) through \`network.request\`; use form-urlencoded string body or a dedicated upload capability.
649
664
  - Do not treat \`hb-sdk login\` as iframe SDK authentication state.
650
665
  - Do not create a second mock runtime package when \`hb-sdk dev\` is the supported local mock workflow.
651
666
  - Do not import \`@heybox/hb-sdk/vite\` from iframe business code or fetch a deployed \`manifest.json\` directly.
package/skill/skill.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "hb-sdk",
3
- "skillVersion": "0.6.8-alpha.2+skill.03258ceeb905",
3
+ "skillVersion": "0.6.9-alpha.0+skill.a578097b3a33",
4
4
  "sdk": {
5
5
  "package": "@heybox/hb-sdk",
6
- "version": "0.6.8-alpha.2",
7
- "compatibility": "0.6.8-alpha.2"
6
+ "version": "0.6.9-alpha.0",
7
+ "compatibility": "0.6.9-alpha.0"
8
8
  },
9
9
  "source": "https://open.xiaoheihe.cn/agent-skills/hb-sdk",
10
- "integrity": "sha256-03258ceeb90522900faaeb182c1949333923c794a9f4ff83530923585302fddb"
10
+ "integrity": "sha256-a578097b3a3302d8149c3a09107a71516d84861022ddc4adb6ef1d465a423bfa"
11
11
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * network.request 诊断日志的纯脱敏工具。
3
+ *
4
+ * @remarks
5
+ * 放在 `core` 而非 `modules/network`:无错误类型依赖,供 SDK 错误文案、
6
+ * modules observability、devtools Mock Host / proxy 共用,并满足 boundary
7
+ *(devtools 不得直接 import runtime capability modules)。
8
+ */
9
+ export type MiniProgramNetworkHeadersLike = Record<string, string>;
10
+ /**
11
+ * 脱敏请求 URL:去掉 userinfo,并将 query / fragment 中的敏感参数值替换为 `[redacted]`。
12
+ */
13
+ export declare function sanitizeNetworkRequestUrl(url: string): string;
14
+ /**
15
+ * 脱敏请求/响应头;敏感名(含 heybox 前缀)替换为 `[redacted]`。
16
+ */
17
+ export declare function sanitizeNetworkHeaders(headers?: MiniProgramNetworkHeadersLike): MiniProgramNetworkHeadersLike;
18
+ /**
19
+ * 预览请求/响应 data:递归脱敏敏感字段,截断长文本与大数组。
20
+ */
21
+ export declare function previewNetworkData(data: unknown): unknown;
22
+ /**
23
+ * 递归脱敏日志对象(payload / result):敏感 key、headers、url 字段统一处理。
24
+ */
25
+ export declare function redactNetworkLogValue(value: unknown): unknown;
26
+ /**
27
+ * 诊断 message 中的 URL 片段做脱敏,避免 error.message 二次泄漏 query secrets。
28
+ */
29
+ export declare function sanitizeDiagnosticMessage(message: string): string;
30
+ export declare function isSensitiveNetworkHeaderName(headerName: string): boolean;
31
+ export declare function isSensitiveNetworkKey(key: string): boolean;