@heybox/hb-sdk 0.5.18 → 0.6.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 (48) hide show
  1. package/README.md +29 -25
  2. package/dist/cli-chunks/{create-OZOIdeBz.cjs → create-DYb53EQ-.cjs} +1 -1
  3. package/dist/cli-chunks/{dev-DXz8izSJ.cjs → dev-CPKTuA6q.cjs} +35 -6
  4. package/dist/cli-chunks/{doctor-DjMSJoQ5.cjs → doctor-TshFvqHC.cjs} +1 -1
  5. package/dist/cli-chunks/{index-CsOcGUzF.cjs → index-CCeHouBU.cjs} +21 -16
  6. package/dist/cli-chunks/{index-Cl5HFTBW.cjs → index-NOO3q8_C.cjs} +3 -3
  7. package/dist/cli-chunks/{login-Ct-46gLx.cjs → login-DtXHSm8R.cjs} +2 -2
  8. package/dist/cli-chunks/remote-BWfn7qIN.cjs +9915 -0
  9. package/dist/cli-chunks/{context-CKJlNcSN.cjs → sdk-version-policy-B1CFC1Dk.cjs} +122 -1
  10. package/dist/cli-chunks/{session-dRGPpyS1.cjs → session-DIcRjyDY.cjs} +2 -1
  11. package/dist/cli.cjs +1 -1
  12. package/dist/devtools/mock-host/index.html +40 -0
  13. package/dist/devtools/mock-host/main.js +85 -0
  14. package/dist/index.cjs.js +232 -4
  15. package/dist/index.esm.js +233 -3
  16. package/dist/miniapp-publish.cjs.js +4 -0
  17. package/dist/miniapp-publish.esm.js +4 -1
  18. package/dist/protocol.cjs.js +9 -0
  19. package/dist/protocol.esm.js +7 -1
  20. package/dist/vite.cjs.js +9159 -22
  21. package/dist/vite.esm.js +9159 -23
  22. package/package.json +19 -7
  23. package/skill/SKILL.md +23 -23
  24. package/skill/references/api-protocol.md +11 -2
  25. package/skill/references/api-root.md +145 -19
  26. package/skill/references/cli.md +13 -15
  27. package/skill/references/recipes.md +12 -44
  28. package/skill/references/safety-boundaries.md +1 -2
  29. package/skill/scripts/sync-references.mjs +2 -2
  30. package/skill/skill.json +4 -4
  31. package/types/cli/auth/base-url.d.ts +20 -0
  32. package/types/cli/config.d.ts +11 -0
  33. package/types/core/client.d.ts +7 -0
  34. package/types/core/csp-violation.d.ts +5 -0
  35. package/types/core/history-observer.d.ts +4 -0
  36. package/types/core/version.d.ts +2 -0
  37. package/types/index.d.ts +0 -2
  38. package/types/miniapp-manifest/schema.d.ts +6 -1
  39. package/types/miniapp-manifest/sdk-version-policy.d.ts +9 -0
  40. package/types/miniapp-publish/index.d.ts +1 -0
  41. package/types/protocol/constants.d.ts +6 -0
  42. package/types/protocol/types.d.ts +45 -0
  43. package/types/protocol.d.ts +2 -2
  44. package/types/vite/html-policy.d.ts +4 -0
  45. package/types/vite/index.d.ts +23 -3
  46. package/types/vite/runtime-gate.d.ts +5 -0
  47. package/types/vite/sdk-version-gate.d.ts +8 -0
  48. package/dist/cli-chunks/remote-C77axNSw.cjs +0 -1604
@@ -1,9 +1,29 @@
1
1
  'use strict';
2
2
 
3
- var session = require('./session-dRGPpyS1.cjs');
3
+ var session = require('./session-DIcRjyDY.cjs');
4
4
  var fs$1 = require('node:fs');
5
5
  var fs = require('node:fs/promises');
6
6
  var path = require('node:path');
7
+ var index = require('./index-CCeHouBU.cjs');
8
+
9
+ const APIUserMiniprogramPublicSdkConfigPath = '/user_miniprogram/public/sdk_config';
10
+ function createUserMiniprogramPublicSdkConfigRequest(serviceTag) {
11
+ return {
12
+ path: APIUserMiniprogramPublicSdkConfigPath,
13
+ searchParams: serviceTag ? { special_tag: serviceTag } : {},
14
+ headers: serviceTag ? { 'x-rylai-service-tag': serviceTag } : {},
15
+ };
16
+ }
17
+ function parseUserMiniprogramPublicSdkConfigResult(value) {
18
+ if (!isRecord(value) ||
19
+ typeof value.minimum_sdk_version !== 'string') {
20
+ throw new Error('公共 SDK 配置响应无效');
21
+ }
22
+ return value;
23
+ }
24
+ function isRecord(value) {
25
+ return typeof value === 'object' && value !== null;
26
+ }
7
27
 
8
28
  var re = {exports: {}};
9
29
 
@@ -2794,6 +2814,7 @@ const MINIAPP_UPLOAD_SCOPE = 'activity';
2794
2814
  const ACTIVITY_UPLOAD_KEY_MAX_LENGTH = 64;
2795
2815
  const MINIAPP_UPLOAD_BATCH_SIZE = 50;
2796
2816
  const MINIAPP_UPLOAD_TOTAL_SIZE_LIMIT_BYTES = 100 * 1024 * 1024;
2817
+ const PUBLIC_USER_MINIPROGRAM_SDK_CONFIG_API_PATH = APIUserMiniprogramPublicSdkConfigPath;
2797
2818
  const USER_MINIPROGRAM_ACCESS_STATUS_API_PATH = '/mall/developer/user_miniprogram/access_status';
2798
2819
  const DEVELOPER_ENTITY_LIST_API_PATH = '/mall/developer/entity/list';
2799
2820
  const DEVELOPER_ENTITY_SWITCH_API_PATH = '/mall/developer/entity/switch';
@@ -2909,6 +2930,20 @@ function createRemoteApiClient(options) {
2909
2930
  listUserMiniprogramVersions: (requestOptions) => listUserMiniprogramVersions(requestOptions, options),
2910
2931
  };
2911
2932
  }
2933
+ async function getPublicSdkConfig(runtime) {
2934
+ const fetchImpl = runtime.fetchImpl ?? fetch;
2935
+ const serviceTag = index.resolveHeyboxRylaiServiceTag(PUBLIC_USER_MINIPROGRAM_SDK_CONFIG_API_PATH);
2936
+ const request = createUserMiniprogramPublicSdkConfigRequest(serviceTag);
2937
+ const response = await fetchImpl(session.createHeyboxApiUrl(runtime.baseUrl ?? 'https://api.xiaoheihe.cn', request.path, request.searchParams), {
2938
+ method: 'GET',
2939
+ headers: {
2940
+ Referer: session.HEYBOX_WEB_REFERER,
2941
+ ...request.headers,
2942
+ },
2943
+ });
2944
+ const result = await readRequiredResult(response, request.path);
2945
+ return parseUserMiniprogramPublicSdkConfigResult(result);
2946
+ }
2912
2947
  async function listDeveloperEntities(runtime) {
2913
2948
  return remoteGet(DEVELOPER_ENTITY_LIST_API_PATH, {}, runtime);
2914
2949
  }
@@ -3179,6 +3214,91 @@ function isTruthyEnvFlag(value) {
3179
3214
  .toLowerCase());
3180
3215
  }
3181
3216
 
3217
+ const CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1_000;
3218
+ const CACHE_FILE_NAME = 'minimum-sdk-versions.json';
3219
+ async function resolveMinimumSdkVersion(options) {
3220
+ const now = options.now ?? Date.now();
3221
+ const cachePath = path.join(options.cacheDir, CACHE_FILE_NAME);
3222
+ const cache = await readCache(cachePath);
3223
+ let minimumSdkVersion;
3224
+ try {
3225
+ minimumSdkVersion = validateStableSdkVersion(await options.fetchMinimumSdkVersion(), 'minimumSdkVersion');
3226
+ }
3227
+ catch {
3228
+ const cached = cache.environments[options.environment];
3229
+ const cacheAge = cached ? now - cached.fetchedAt : Number.NaN;
3230
+ if (cached && Number.isFinite(cached.fetchedAt) && cacheAge >= 0 && cacheAge <= CACHE_MAX_AGE_MS) {
3231
+ try {
3232
+ return validateStableSdkVersion(cached.minimumSdkVersion, 'cached minimumSdkVersion');
3233
+ }
3234
+ catch {
3235
+ // 非法缓存按不存在处理。
3236
+ }
3237
+ }
3238
+ throw new Error('MINIMUM_SDK_VERSION_UNAVAILABLE:无法获取最低 SDK 版本,且没有 24 小时内的有效缓存');
3239
+ }
3240
+ cache.environments[options.environment] = { fetchedAt: now, minimumSdkVersion };
3241
+ try {
3242
+ await writeCache(cachePath, cache);
3243
+ }
3244
+ catch {
3245
+ // 已校验的远端策略仍然可信;缓存只是离线回退优化。
3246
+ }
3247
+ return minimumSdkVersion;
3248
+ }
3249
+ function validateCurrentSdkVersion(currentSdkVersion, minimumSdkVersion, options) {
3250
+ const current = validateSdkVersion(currentSdkVersion, '当前 SDK 版本');
3251
+ const minimum = validateStableSdkVersion(minimumSdkVersion, 'minimumSdkVersion');
3252
+ if (!options.allowPrerelease && semverExports.prerelease(current) !== null) {
3253
+ throw new Error('正式发布不允许使用预发布 SDK,请运行 pnpm add @heybox/hb-sdk@latest 后重新构建');
3254
+ }
3255
+ if (!semverExports.satisfies(current, `>=${minimum}`, { includePrerelease: options.allowPrerelease })) {
3256
+ throw new Error(`SDK_VERSION_TOO_LOW:当前 @heybox/hb-sdk@${current} 低于最低版本 ${minimum},请运行 pnpm add @heybox/hb-sdk@latest`);
3257
+ }
3258
+ return current;
3259
+ }
3260
+ function validateSdkVersion(value, label) {
3261
+ const normalized = String(value).trim();
3262
+ const parsed = semverExports.parse(normalized);
3263
+ if (!parsed || parsed.version !== normalized || parsed.build.length > 0) {
3264
+ throw new Error(`${label} 必须是合法 SemVer:${normalized}`);
3265
+ }
3266
+ return normalized;
3267
+ }
3268
+ function validateStableSdkVersion(value, label) {
3269
+ const normalized = validateSdkVersion(value, label);
3270
+ if (semverExports.prerelease(normalized) !== null)
3271
+ throw new Error(`${label} 必须是稳定 SemVer:${normalized}`);
3272
+ return normalized;
3273
+ }
3274
+ async function readCache(cachePath) {
3275
+ try {
3276
+ const parsed = JSON.parse(await fs.readFile(cachePath, 'utf8'));
3277
+ if (parsed && typeof parsed === 'object' && parsed.environments && typeof parsed.environments === 'object')
3278
+ return parsed;
3279
+ }
3280
+ catch {
3281
+ // 缓存缺失或损坏时继续请求最新配置。
3282
+ }
3283
+ return { environments: {} };
3284
+ }
3285
+ async function writeCache(cachePath, cache) {
3286
+ await fs.mkdir(path.dirname(cachePath), { recursive: true });
3287
+ const temporaryPath = `${cachePath}.${process.pid}.tmp`;
3288
+ await fs.writeFile(temporaryPath, `${JSON.stringify(cache, null, 2)}\n`, { mode: 0o600 });
3289
+ await fs.rename(temporaryPath, cachePath);
3290
+ }
3291
+
3292
+ const paths = index.envPaths('hb-sdk');
3293
+ async function enforceRemoteSdkVersion(options) {
3294
+ const minimumSdkVersion = await resolveMinimumSdkVersion({
3295
+ cacheDir: options.cacheDir ?? paths.cache,
3296
+ environment: options.environment,
3297
+ fetchMinimumSdkVersion: async () => (await getPublicSdkConfig({ baseUrl: options.environment, fetchImpl: options.fetchImpl })).minimum_sdk_version,
3298
+ });
3299
+ return validateCurrentSdkVersion(options.sdkVersion, minimumSdkVersion, { allowPrerelease: options.allowPrerelease });
3300
+ }
3301
+
3182
3302
  exports.ACTIVITY_UPLOAD_KEY_MAX_LENGTH = ACTIVITY_UPLOAD_KEY_MAX_LENGTH;
3183
3303
  exports.MINIAPP_UPLOAD_BATCH_SIZE = MINIAPP_UPLOAD_BATCH_SIZE;
3184
3304
  exports.MINIAPP_UPLOAD_SCOPE = MINIAPP_UPLOAD_SCOPE;
@@ -3186,6 +3306,7 @@ exports.MiniProgramProjectBindingError = MiniProgramProjectBindingError;
3186
3306
  exports.PRECHECK_USER_MINIPROGRAM_VERSION_API_PATH = PRECHECK_USER_MINIPROGRAM_VERSION_API_PATH;
3187
3307
  exports.SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH = SUBMIT_USER_MINIPROGRAM_AUDIT_API_PATH;
3188
3308
  exports.createRemoteApiClientForEnvironment = createRemoteApiClientForEnvironment;
3309
+ exports.enforceRemoteSdkVersion = enforceRemoteSdkVersion;
3189
3310
  exports.getBoundMiniProgram = getBoundMiniProgram;
3190
3311
  exports.getMiniappUploadKey = getMiniappUploadKey;
3191
3312
  exports.normalizeRelativePath = normalizeRelativePath;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-CsOcGUzF.cjs');
3
+ var index = require('./index-CCeHouBU.cjs');
4
4
  var node_crypto = require('node:crypto');
5
5
  var path = require('node:path');
6
6
  var require$$0$2 = require('fs');
@@ -3321,6 +3321,7 @@ function isRecord(value) {
3321
3321
  return Object.prototype.toString.call(value) === '[object Object]';
3322
3322
  }
3323
3323
 
3324
+ exports.HEYBOX_WEB_REFERER = HEYBOX_WEB_REFERER;
3324
3325
  exports.clearHeyboxAuthSession = clearHeyboxAuthSession;
3325
3326
  exports.createHeyboxApiUrl = createHeyboxApiUrl;
3326
3327
  exports.createHeyboxAuthSession = createHeyboxAuthSession;
package/dist/cli.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./cli-chunks/index-CsOcGUzF.cjs');
3
+ var index = require('./cli-chunks/index-CCeHouBU.cjs');
4
4
  require('node:module');
5
5
  require('node:fs');
6
6
  require('node:fs/promises');
@@ -212,6 +212,42 @@
212
212
  overflow: auto;
213
213
  }
214
214
 
215
+ .csp-violations {
216
+ border: 1px solid #dc2626;
217
+ border-radius: 8px;
218
+ padding: 12px;
219
+ color: #7f1d1d;
220
+ background: #fef2f2;
221
+ }
222
+
223
+ .csp-violations[hidden] {
224
+ display: none;
225
+ }
226
+
227
+ .csp-violations > strong {
228
+ display: block;
229
+ color: #991b1b;
230
+ font-size: 14px;
231
+ }
232
+
233
+ .csp-violations ol {
234
+ display: grid;
235
+ gap: 8px;
236
+ margin: 8px 0 0;
237
+ padding-left: 20px;
238
+ }
239
+
240
+ .csp-violations li strong,
241
+ .csp-violations li span {
242
+ display: block;
243
+ overflow-wrap: anywhere;
244
+ }
245
+
246
+ .csp-violations li span {
247
+ margin-top: 2px;
248
+ font-size: 12px;
249
+ }
250
+
215
251
  .log {
216
252
  border: 1px solid #d7dee8;
217
253
  border-radius: 8px;
@@ -317,6 +353,10 @@
317
353
  <strong id="mini-url"></strong>
318
354
  </section>
319
355
 
356
+ <section class="csp-violations" id="csp-violations" hidden>
357
+ <strong>网络或资源请求已被 CSP 阻止</strong>
358
+ </section>
359
+
320
360
  <section class="block">
321
361
  <span>Mac App</span>
322
362
  <div class="actions" style="margin-top: 10px">
@@ -4,6 +4,8 @@ const MINI_PROGRAM_MESSAGE_NAMESPACE = 'heybox:miniprogram';
4
4
  const MINI_PROGRAM_MESSAGE_VERSION = 1;
5
5
  /** 父容器注入到小程序 URL 上的通信 nonce 参数名。 */
6
6
  const MINI_PROGRAM_BRIDGE_NONCE_PARAM = 'hb_mini_bridge_nonce';
7
+ /** SDK 向 Runtime 报告 CSP 违规的内部方法名。 */
8
+ const SDK_CSP_VIOLATION_METHOD = 'sdk.csp.violation';
7
9
 
8
10
  /**
9
11
  * 登录授权能力方法名。
@@ -3373,6 +3375,76 @@ function requireBrowser () {
3373
3375
 
3374
3376
  var browserExports = requireBrowser();
3375
3377
 
3378
+ function createCSPViolationPanel(container) {
3379
+ const seen = new Set();
3380
+ const list = document.createElement('ol');
3381
+ container.setAttribute('role', 'alert');
3382
+ container.setAttribute('aria-live', 'assertive');
3383
+ container.appendChild(list);
3384
+ return {
3385
+ report(payload) {
3386
+ if (!isCSPViolationPayload(payload)) {
3387
+ return false;
3388
+ }
3389
+ const key = createViolationKey(payload);
3390
+ if (seen.has(key)) {
3391
+ return false;
3392
+ }
3393
+ seen.add(key);
3394
+ const item = document.createElement('li');
3395
+ const directive = document.createElement('strong');
3396
+ directive.textContent = payload.effectiveDirective;
3397
+ const detail = document.createElement('span');
3398
+ detail.textContent = formatBlockedResource(payload);
3399
+ item.append(directive, detail);
3400
+ list.appendChild(item);
3401
+ container.hidden = false;
3402
+ return true;
3403
+ },
3404
+ };
3405
+ }
3406
+ function isCSPViolationPayload(payload) {
3407
+ if (!payload || typeof payload !== 'object') {
3408
+ return false;
3409
+ }
3410
+ const candidate = payload;
3411
+ return (typeof candidate.effectiveDirective === 'string' &&
3412
+ typeof candidate.violatedDirective === 'string' &&
3413
+ typeof candidate.disposition === 'string' &&
3414
+ ['url', 'inline', 'eval', 'unknown'].includes(candidate.blockedResourceType ?? '') &&
3415
+ typeof candidate.sdkVersion === 'string' &&
3416
+ typeof candidate.timestamp === 'number' &&
3417
+ isOptionalString(candidate.blockedOrigin) &&
3418
+ isOptionalString(candidate.redactedPathname) &&
3419
+ isOptionalString(candidate.sanitizedPathnameHash));
3420
+ }
3421
+ function isOptionalString(value) {
3422
+ return value === undefined || typeof value === 'string';
3423
+ }
3424
+ function createViolationKey(payload) {
3425
+ return [
3426
+ payload.effectiveDirective,
3427
+ payload.violatedDirective,
3428
+ payload.disposition,
3429
+ payload.blockedResourceType,
3430
+ payload.blockedOrigin,
3431
+ payload.redactedPathname,
3432
+ payload.sanitizedPathnameHash,
3433
+ ].join('\n');
3434
+ }
3435
+ function formatBlockedResource(payload) {
3436
+ if (payload.blockedResourceType === 'url') {
3437
+ return `${payload.blockedOrigin ?? '未知来源'}${payload.redactedPathname ?? ''}`;
3438
+ }
3439
+ if (payload.blockedResourceType === 'inline') {
3440
+ return '内联资源';
3441
+ }
3442
+ if (payload.blockedResourceType === 'eval') {
3443
+ return '动态代码执行';
3444
+ }
3445
+ return '未知资源';
3446
+ }
3447
+
3376
3448
  async function getMiniProgramRuntimeUserInfo(platformAdapter) {
3377
3449
  const userId = await resolveCurrentUserId(platformAdapter);
3378
3450
  if (!userId) {
@@ -6211,6 +6283,7 @@ function isPlainRecord(value) {
6211
6283
 
6212
6284
  const NETWORK_PROXY_PATH = '/__hb_sdk_mock_network__';
6213
6285
  const MOCK_HOST_BOOTSTRAP_PATH = '/__hb_sdk_bootstrap__';
6286
+ const RUNTIME_GATE_PROBE_METHOD = 'runtime.gate.probe';
6214
6287
  const params = new URL(location.href).searchParams;
6215
6288
  const miniUrl = params.get('mini_url');
6216
6289
  let lanAddresses = [];
@@ -6230,6 +6303,7 @@ let currentUser = {
6230
6303
  const elements = {
6231
6304
  bridgeStatus: queryElement('#bridge-status'),
6232
6305
  copyDebugPageUrlButton: queryElement('#copy-debug-page-url-button'),
6306
+ cspViolations: queryElement('#csp-violations'),
6233
6307
  debugPageCopyStatus: queryElement('#debug-page-copy-status'),
6234
6308
  debugPageUrl: queryElement('#debug-page-url'),
6235
6309
  device: queryElement('#device'),
@@ -6244,6 +6318,7 @@ const elements = {
6244
6318
  storageSnapshot: queryElement('#storage-snapshot'),
6245
6319
  userStatus: queryElement('#user-status'),
6246
6320
  };
6321
+ const cspViolationPanel = createCSPViolationPanel(elements.cspViolations);
6247
6322
  if (!miniUrl) {
6248
6323
  elements.device.innerHTML = '<div class="error">Missing mini_url query.</div>';
6249
6324
  throw new Error('Missing mini_url query.');
@@ -6323,6 +6398,12 @@ function handleMessage(event) {
6323
6398
  }
6324
6399
  if (message.type === 'request') {
6325
6400
  void handleRequest(message);
6401
+ return;
6402
+ }
6403
+ if (message.type === 'event' && message.method === SDK_CSP_VIOLATION_METHOD) {
6404
+ if (cspViolationPanel.report(message.payload)) {
6405
+ pushLog(message.method, message.payload);
6406
+ }
6326
6407
  }
6327
6408
  }
6328
6409
  function handleHandshake() {
@@ -6344,6 +6425,10 @@ async function handleRequest(message) {
6344
6425
  if (!message.id || !message.method) {
6345
6426
  return;
6346
6427
  }
6428
+ if (!handshaken && message.method === RUNTIME_GATE_PROBE_METHOD) {
6429
+ postResponse(message, { ready: true });
6430
+ return;
6431
+ }
6347
6432
  try {
6348
6433
  const payload = await runtime.runMethod(message.method, message.payload);
6349
6434
  postResponse(message, payload);
package/dist/index.cjs.js CHANGED
@@ -74,6 +74,54 @@ class HbMiniProgramNetworkError extends Error {
74
74
  }
75
75
  }
76
76
 
77
+ /** 将浏览器 CSP 事件转换为不包含原始 URL 敏感部分的 bridge 载荷。 */
78
+ async function createCSPViolationPayload(event, baseHref, sdkVersion, cryptoImplementation) {
79
+ const { pathnameForHash, ...blockedResource } = normalizeBlockedResource(event.blockedURI, baseHref);
80
+ return {
81
+ effectiveDirective: event.effectiveDirective,
82
+ violatedDirective: event.violatedDirective,
83
+ disposition: event.disposition,
84
+ ...blockedResource,
85
+ sanitizedPathnameHash: pathnameForHash
86
+ ? await hashPathname(pathnameForHash, cryptoImplementation)
87
+ : undefined,
88
+ sdkVersion,
89
+ timestamp: Date.now(),
90
+ };
91
+ }
92
+ function normalizeBlockedResource(blockedURI, baseHref) {
93
+ if (blockedURI === 'inline' || blockedURI === 'eval' || blockedURI === 'wasm-eval') {
94
+ return {
95
+ blockedResourceType: blockedURI === 'inline' ? 'inline' : 'eval',
96
+ };
97
+ }
98
+ if (!blockedURI) {
99
+ return { blockedResourceType: 'unknown' };
100
+ }
101
+ try {
102
+ const url = new URL(blockedURI, baseHref);
103
+ const segmentCount = url.pathname.split('/').filter(Boolean).length;
104
+ return {
105
+ blockedResourceType: 'url',
106
+ blockedOrigin: url.origin === 'null' ? undefined : url.origin,
107
+ redactedPathname: segmentCount === 0 ? '/' : `/${Array(segmentCount).fill(':segment').join('/')}`,
108
+ pathnameForHash: url.pathname,
109
+ };
110
+ }
111
+ catch {
112
+ return { blockedResourceType: 'unknown' };
113
+ }
114
+ }
115
+ async function hashPathname(pathname, cryptoImplementation) {
116
+ try {
117
+ const digest = await cryptoImplementation.subtle.digest('SHA-256', new TextEncoder().encode(pathname));
118
+ return Array.from(new Uint8Array(digest), value => value.toString(16).padStart(2, '0')).join('');
119
+ }
120
+ catch {
121
+ return undefined;
122
+ }
123
+ }
124
+
77
125
  /** SDK 内部事件总线,负责管理沙盒生命周期与业务事件监听。 */
78
126
  class MiniProgramEventBus {
79
127
  eventHandlers = new Map();
@@ -107,6 +155,100 @@ class MiniProgramEventBus {
107
155
  }
108
156
  }
109
157
 
158
+ const historyObservers = new WeakMap();
159
+ /** 共享同一个 History patch,并在最后一个订阅者退出时精确恢复原属性。 */
160
+ function subscribeHistoryChanges(history, listener) {
161
+ let state = historyObservers.get(history);
162
+ if (!state) {
163
+ state = createHistoryObserver(history);
164
+ if (!state) {
165
+ return () => undefined;
166
+ }
167
+ historyObservers.set(history, state);
168
+ }
169
+ state.listeners.add(listener);
170
+ let active = true;
171
+ return () => {
172
+ if (!active) {
173
+ return;
174
+ }
175
+ active = false;
176
+ state.listeners.delete(listener);
177
+ if (state.listeners.size === 0) {
178
+ restoreHistoryObserver(history, state);
179
+ historyObservers.delete(history);
180
+ }
181
+ };
182
+ }
183
+ function createHistoryObserver(history) {
184
+ const listeners = new Set();
185
+ const originalPushState = readOriginalMethod(history, 'pushState');
186
+ const originalReplaceState = readOriginalMethod(history, 'replaceState');
187
+ const notify = () => {
188
+ [...listeners].forEach(currentListener => {
189
+ try {
190
+ currentListener();
191
+ }
192
+ catch {
193
+ // 单个 SDK 诊断失败不能影响 History API 或其他订阅者。
194
+ }
195
+ });
196
+ };
197
+ const patchedPushState = (...args) => {
198
+ originalPushState.value.apply(history, args);
199
+ notify();
200
+ };
201
+ const patchedReplaceState = (...args) => {
202
+ originalReplaceState.value.apply(history, args);
203
+ notify();
204
+ };
205
+ const state = {
206
+ listeners,
207
+ originalPushState,
208
+ originalReplaceState,
209
+ patchedPushState,
210
+ patchedReplaceState,
211
+ };
212
+ try {
213
+ definePatchedMethod(history, 'pushState', patchedPushState, originalPushState.descriptor);
214
+ definePatchedMethod(history, 'replaceState', patchedReplaceState, originalReplaceState.descriptor);
215
+ return state;
216
+ }
217
+ catch {
218
+ restoreHistoryObserver(history, state);
219
+ return undefined;
220
+ }
221
+ }
222
+ function readOriginalMethod(history, name) {
223
+ return {
224
+ descriptor: Object.getOwnPropertyDescriptor(history, name),
225
+ value: history[name],
226
+ };
227
+ }
228
+ function definePatchedMethod(history, name, value, originalDescriptor) {
229
+ Object.defineProperty(history, name, {
230
+ configurable: true,
231
+ enumerable: originalDescriptor?.enumerable ?? false,
232
+ writable: true,
233
+ value,
234
+ });
235
+ }
236
+ function restoreHistoryObserver(history, state) {
237
+ restoreMethod(history, 'pushState', state.patchedPushState, state.originalPushState);
238
+ restoreMethod(history, 'replaceState', state.patchedReplaceState, state.originalReplaceState);
239
+ }
240
+ function restoreMethod(history, name, patchedValue, original) {
241
+ if (history[name] !== patchedValue) {
242
+ return;
243
+ }
244
+ if (original.descriptor) {
245
+ Object.defineProperty(history, name, original.descriptor);
246
+ }
247
+ else {
248
+ delete history[name];
249
+ }
250
+ }
251
+
110
252
  /** 小程序沙盒通信命名空间,父容器与 iframe 内 SDK 必须保持一致。 */
111
253
  const MINI_PROGRAM_MESSAGE_NAMESPACE = 'heybox:miniprogram';
112
254
  /** 小程序沙盒通信协议版本。 */
@@ -115,6 +257,12 @@ const MINI_PROGRAM_MESSAGE_VERSION = 1;
115
257
  const MINI_PROGRAM_BRIDGE_NONCE_PARAM = 'hb_mini_bridge_nonce';
116
258
  /** SDK 初始化时发给父容器的握手方法名。 */
117
259
  const SDK_HANDSHAKE_METHOD = 'sdk.handshake';
260
+ /** Runtime 主动探测 SDK 当前文档地址的内部方法名。 */
261
+ const RUNTIME_LOCATION_PROBE_METHOD = 'runtime.location.probe';
262
+ /** SDK 向 Runtime 报告当前文档地址的内部方法名。 */
263
+ const SDK_LOCATION_REPORT_METHOD = 'sdk.location.report';
264
+ /** SDK 向 Runtime 报告 CSP 违规的内部方法名。 */
265
+ const SDK_CSP_VIOLATION_METHOD = 'sdk.csp.violation';
118
266
 
119
267
  /** 获取浏览器全局 window。 */
120
268
  function getGlobalWindow() {
@@ -174,6 +322,11 @@ function createMessageId() {
174
322
  return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
175
323
  }
176
324
 
325
+ /** 构建时替换为当前发布包的实际版本。 */
326
+ const HB_SDK_VERSION = typeof undefined === 'string'
327
+ ? undefined
328
+ : '0.6.0';
329
+
177
330
  /**
178
331
  * 判断未知数据是否符合小程序 bridge 消息信封。
179
332
  *
@@ -203,6 +356,13 @@ class MiniProgramBridgeClient {
203
356
  pendingRequests = new Map();
204
357
  eventBus = new MiniProgramEventBus();
205
358
  handleMessage;
359
+ handlePopState = () => this.postLocationReport('popstate');
360
+ handleHashChange = () => this.postLocationReport('hashchange');
361
+ handlePageShow = () => this.postLocationReport('pageshow');
362
+ handleSecurityPolicyViolation = (event) => {
363
+ this.postCSPViolation(event);
364
+ };
365
+ unsubscribeHistoryChanges;
206
366
  started = false;
207
367
  readySettled = false;
208
368
  readyPromise;
@@ -290,6 +450,12 @@ class MiniProgramBridgeClient {
290
450
  destroy() {
291
451
  this.destroyed = true;
292
452
  this.selfWindow?.removeEventListener('message', this.handleMessage);
453
+ this.selfWindow?.removeEventListener('popstate', this.handlePopState);
454
+ this.selfWindow?.removeEventListener('hashchange', this.handleHashChange);
455
+ this.selfWindow?.removeEventListener('pageshow', this.handlePageShow);
456
+ this.selfWindow?.removeEventListener('securitypolicyviolation', this.handleSecurityPolicyViolation);
457
+ this.unsubscribeHistoryChanges?.();
458
+ this.unsubscribeHistoryChanges = undefined;
293
459
  const error = createSDKError('SDK_DESTROYED', '小程序 SDK 已销毁');
294
460
  this.failReady(error);
295
461
  this.rejectAllPending(error);
@@ -316,6 +482,13 @@ class MiniProgramBridgeClient {
316
482
  return;
317
483
  }
318
484
  this.selfWindow.addEventListener('message', this.handleMessage);
485
+ this.selfWindow.addEventListener('popstate', this.handlePopState);
486
+ this.selfWindow.addEventListener('hashchange', this.handleHashChange);
487
+ this.selfWindow.addEventListener('pageshow', this.handlePageShow);
488
+ this.selfWindow.addEventListener('securitypolicyviolation', this.handleSecurityPolicyViolation);
489
+ this.unsubscribeHistoryChanges = subscribeHistoryChanges(this.selfWindow.history, () => {
490
+ this.postLocationReport('history');
491
+ });
319
492
  this.readyTimer = setTimeout(() => {
320
493
  this.failReady(createSDKError('READY_TIMEOUT', '小程序沙盒握手超时'));
321
494
  }, this.timeout);
@@ -352,11 +525,12 @@ class MiniProgramBridgeClient {
352
525
  payload: {
353
526
  href: this.selfWindow.location.href,
354
527
  userAgent: this.selfWindow.navigator.userAgent,
528
+ sdkVersion: HB_SDK_VERSION,
355
529
  },
356
530
  });
357
531
  }
358
532
  onMessage(event) {
359
- if (event.source && event.source !== this.targetWindow) {
533
+ if (event.source !== this.targetWindow) {
360
534
  return;
361
535
  }
362
536
  if (!isMiniProgramBridgeMessage(event.data) || event.data.nonce !== this.nonce) {
@@ -368,6 +542,59 @@ class MiniProgramBridgeClient {
368
542
  }
369
543
  if (event.data.type === 'response') {
370
544
  this.handleResponse(event.data);
545
+ return;
546
+ }
547
+ if (event.data.type === 'request' && event.data.method === RUNTIME_LOCATION_PROBE_METHOD) {
548
+ const payload = event.data.payload;
549
+ if (typeof payload?.probeId === 'string') {
550
+ this.postLocationReport('probe', payload.probeId);
551
+ }
552
+ }
553
+ }
554
+ postLocationReport(trigger, probeId) {
555
+ if (this.destroyed || !this.selfWindow) {
556
+ return;
557
+ }
558
+ try {
559
+ this.postMessage({
560
+ namespace: MINI_PROGRAM_MESSAGE_NAMESPACE,
561
+ version: MINI_PROGRAM_MESSAGE_VERSION,
562
+ nonce: this.nonce,
563
+ type: 'event',
564
+ method: SDK_LOCATION_REPORT_METHOD,
565
+ payload: {
566
+ probeId,
567
+ href: this.selfWindow.location.href,
568
+ sdkVersion: HB_SDK_VERSION,
569
+ trigger,
570
+ timestamp: Date.now(),
571
+ },
572
+ });
573
+ }
574
+ catch {
575
+ // 内部诊断消息失败不能改变 Mini-program 自身的导航语义。
576
+ }
577
+ }
578
+ async postCSPViolation(event) {
579
+ if (!this.selfWindow) {
580
+ return;
581
+ }
582
+ try {
583
+ const payload = await createCSPViolationPayload(event, this.selfWindow.location.href, HB_SDK_VERSION, this.selfWindow.crypto);
584
+ if (this.destroyed) {
585
+ return;
586
+ }
587
+ this.postMessage({
588
+ namespace: MINI_PROGRAM_MESSAGE_NAMESPACE,
589
+ version: MINI_PROGRAM_MESSAGE_VERSION,
590
+ nonce: this.nonce,
591
+ type: 'event',
592
+ method: SDK_CSP_VIOLATION_METHOD,
593
+ payload,
594
+ });
595
+ }
596
+ catch {
597
+ // CSP 违规已经由浏览器阻断,诊断发送失败不影响页面继续降级运行。
371
598
  }
372
599
  }
373
600
  handleEvent(message) {
@@ -378,6 +605,9 @@ class MiniProgramBridgeClient {
378
605
  else if (eventName === 'ready' && !this.runtimeUnavailable) {
379
606
  this.resolveReadyOnce();
380
607
  }
608
+ else if (eventName === 'show' && !this.runtimeUnavailable) {
609
+ this.postLocationReport('resume');
610
+ }
381
611
  this.eventBus.emit(eventName, message.payload);
382
612
  }
383
613
  handleResponse(message) {
@@ -1120,7 +1350,7 @@ function createMiniProgramSDK(options) {
1120
1350
  return new MiniProgramSDK(options);
1121
1351
  }
1122
1352
 
1123
- let defaultSDK = null;
1353
+ let defaultSDK = createMiniProgramSDK();
1124
1354
  function getDefaultSDK() {
1125
1355
  if (!defaultSDK) {
1126
1356
  defaultSDK = createMiniProgramSDK();
@@ -1234,10 +1464,8 @@ const hbSDK = {
1234
1464
 
1235
1465
  exports.HbMiniProgramNetworkError = HbMiniProgramNetworkError;
1236
1466
  exports.HbMiniProgramSDKError = HbMiniProgramSDKError;
1237
- exports.MiniProgramSDK = MiniProgramSDK;
1238
1467
  exports.auth = auth;
1239
1468
  exports.cloud = cloud;
1240
- exports.createMiniProgramSDK = createMiniProgramSDK;
1241
1469
  exports.default = hbSDK;
1242
1470
  exports.device = device;
1243
1471
  exports.navigation = navigation;