@heybox/hb-sdk 0.2.0-alpha.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -2
- package/dist/cli.cjs +71438 -3859
- package/dist/devtools/mock-host/main.js +336 -203
- package/dist/index.cjs.js +2 -14
- package/dist/index.esm.js +3 -2
- package/dist/miniapp-publish.cjs.js +78 -0
- package/dist/miniapp-publish.esm.js +67 -0
- package/dist/templates/vue3-vite-ts/README.md.ejs +2 -0
- package/dist/templates/vue3-vite-ts/package.json.ejs +5 -0
- package/dist/templates/vue3-vite-ts/vite.config.ts +2 -1
- package/dist/vite.cjs.js +86 -0
- package/dist/vite.esm.js +84 -0
- package/package.json +27 -1
- package/skill/SKILL.md +28 -17
- package/skill/references/api-root.md +93 -38
- package/skill/references/cli.md +19 -1
- package/skill/references/examples.md +12 -0
- package/skill/references/safety-boundaries.md +2 -0
- package/skill/scripts/skill-metadata.mjs +4 -1
- package/skill/scripts/sync-references.mjs +24 -0
- package/skill/scripts/validate-skill.mjs +4 -2
- package/skill/skill.json +4 -4
- package/types/core/client.d.ts +7 -16
- package/types/index.d.ts +2 -6
- package/types/miniapp-manifest/index.d.ts +2 -0
- package/types/miniapp-manifest/node.d.ts +1 -0
- package/types/miniapp-manifest/schema.d.ts +14 -0
- package/types/miniapp-publish/index.d.ts +23 -0
- package/types/skill-metadata.d.ts +6 -0
- package/types/vite/index.d.ts +17 -0
|
@@ -146,26 +146,96 @@ function createMacAppProtocol(appUrl) {
|
|
|
146
146
|
return `heybox://${encodeURIComponent(JSON.stringify(protocolPayload))}`;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
async function getMiniProgramRuntimeUserInfo(platformAdapter) {
|
|
150
|
+
const userId = await resolveCurrentUserId(platformAdapter);
|
|
151
|
+
if (!userId) {
|
|
152
|
+
return {
|
|
153
|
+
isLogin: false,
|
|
154
|
+
userInfo: null,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const result = await platformAdapter.user.getUserBasicInfo(userId);
|
|
158
|
+
return {
|
|
159
|
+
isLogin: true,
|
|
160
|
+
userInfo: {
|
|
161
|
+
heybox_id: userId,
|
|
162
|
+
nickname: result?.nickname || '',
|
|
163
|
+
avatar: result?.avatar || '',
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function resolveCurrentUserId(platformAdapter) {
|
|
168
|
+
return platformAdapter.auth.getCurrentUserId()
|
|
169
|
+
.then(userId => (userId ? String(userId) : ''))
|
|
170
|
+
.catch(() => '');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function loginAndGetMiniProgramRuntimeUserInfo(platformAdapter) {
|
|
174
|
+
await platformAdapter.auth.login();
|
|
175
|
+
return getMiniProgramRuntimeUserInfo(platformAdapter);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getMiniProgramRuntimeWindowInfo(platformAdapter) {
|
|
179
|
+
const metrics = platformAdapter.viewport.getViewportMetrics();
|
|
180
|
+
const windowWidth = getViewportWidth(metrics);
|
|
181
|
+
const windowHeight = getViewportHeight(metrics);
|
|
182
|
+
const screenWidth = getPositiveNumber(metrics.screenWidth, windowWidth);
|
|
183
|
+
const screenHeight = getPositiveNumber(metrics.screenHeight, windowHeight);
|
|
184
|
+
const navigationBarHeight = getNavigationBarHeight(platformAdapter);
|
|
185
|
+
const safeAreaHeight = Math.max(windowHeight - navigationBarHeight, 0);
|
|
186
|
+
return {
|
|
187
|
+
pixelRatio: getPositiveNumber(metrics.pixelRatio, 1),
|
|
188
|
+
screenWidth,
|
|
189
|
+
screenHeight,
|
|
190
|
+
windowWidth,
|
|
191
|
+
windowHeight,
|
|
192
|
+
statusBarHeight: navigationBarHeight,
|
|
193
|
+
safeArea: {
|
|
194
|
+
left: 0,
|
|
195
|
+
right: windowWidth,
|
|
196
|
+
top: navigationBarHeight,
|
|
197
|
+
bottom: windowHeight,
|
|
198
|
+
width: windowWidth,
|
|
199
|
+
height: safeAreaHeight,
|
|
200
|
+
},
|
|
201
|
+
screenTop: navigationBarHeight,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function getMiniProgramRuntimeViewportRect(platformAdapter) {
|
|
205
|
+
const metrics = platformAdapter.viewport.getViewportMetrics();
|
|
206
|
+
const width = getPositiveNumber(metrics.innerWidth, getPositiveNumber(metrics.documentElementClientWidth, 0));
|
|
207
|
+
const height = getPositiveNumber(metrics.innerHeight, getPositiveNumber(metrics.documentElementClientHeight, 0));
|
|
208
|
+
return {
|
|
209
|
+
left: 0,
|
|
210
|
+
top: 0,
|
|
211
|
+
width,
|
|
212
|
+
height,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function getNavigationBarHeight(platformAdapter) {
|
|
216
|
+
try {
|
|
217
|
+
return platformAdapter.viewport.getNavigationBarHeight();
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function getViewportWidth(metrics) {
|
|
224
|
+
return getPositiveNumber(metrics.innerWidth, getPositiveNumber(metrics.documentElementClientWidth, 0));
|
|
225
|
+
}
|
|
226
|
+
function getViewportHeight(metrics) {
|
|
227
|
+
return getPositiveNumber(metrics.innerHeight, getPositiveNumber(metrics.documentElementClientHeight, 0));
|
|
228
|
+
}
|
|
229
|
+
function getPositiveNumber(value, fallback) {
|
|
230
|
+
const numberValue = Number(value);
|
|
231
|
+
if (!Number.isFinite(numberValue) || numberValue <= 0) {
|
|
232
|
+
return fallback;
|
|
233
|
+
}
|
|
234
|
+
return numberValue;
|
|
235
|
+
}
|
|
236
|
+
|
|
149
237
|
const DEFAULT_STORAGE_KEY_MAX_LENGTH = 128;
|
|
150
238
|
const STORAGE_KEY_CHAR_RE = /^[A-Za-z0-9_-]+$/;
|
|
151
|
-
const NETWORK_REQUEST_ALLOWED_KEYS = new Set([
|
|
152
|
-
'url',
|
|
153
|
-
'method',
|
|
154
|
-
'params',
|
|
155
|
-
'data',
|
|
156
|
-
'headers',
|
|
157
|
-
'timeout',
|
|
158
|
-
'withCredentials',
|
|
159
|
-
]);
|
|
160
|
-
const NETWORK_HTTP_METHODS = new Set([
|
|
161
|
-
'GET',
|
|
162
|
-
'POST',
|
|
163
|
-
'PUT',
|
|
164
|
-
'PATCH',
|
|
165
|
-
'DELETE',
|
|
166
|
-
'HEAD',
|
|
167
|
-
'OPTIONS',
|
|
168
|
-
]);
|
|
169
239
|
function createMiniProgramRuntimeBridgeError(code, message, data) {
|
|
170
240
|
return {
|
|
171
241
|
code,
|
|
@@ -248,68 +318,83 @@ function stringifyMiniProgramRuntimeJsonData(data, options) {
|
|
|
248
318
|
}
|
|
249
319
|
return value;
|
|
250
320
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
isLogin: false,
|
|
256
|
-
userInfo: null,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
const result = await platformAdapter.user.getUserBasicInfo(userId);
|
|
260
|
-
return {
|
|
261
|
-
isLogin: true,
|
|
262
|
-
userInfo: {
|
|
263
|
-
heybox_id: userId,
|
|
264
|
-
nickname: result?.nickname || '',
|
|
265
|
-
avatar: result?.avatar || '',
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
async function loginAndGetMiniProgramRuntimeUserInfo(platformAdapter) {
|
|
270
|
-
await platformAdapter.auth.login();
|
|
271
|
-
return getMiniProgramRuntimeUserInfo(platformAdapter);
|
|
321
|
+
|
|
322
|
+
/** 展示基础分享面板。 */
|
|
323
|
+
function showMiniProgramRuntimeShareMenu(payload, platformAdapter) {
|
|
324
|
+
return platformAdapter.share.showShareMenu(readMiniProgramShareMenuOptions(payload));
|
|
272
325
|
}
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
326
|
+
function readMiniProgramShareMenuOptions(payload) {
|
|
327
|
+
assertMiniProgramRuntimeRecord(payload, 'share.showShareMenu 参数必须是对象');
|
|
328
|
+
const title = readMiniProgramRuntimeRequiredString(payload.title, 'share.showShareMenu title 必须是非空字符串');
|
|
329
|
+
const desc = readMiniProgramRuntimeRequiredString(payload.desc, 'share.showShareMenu desc 必须是非空字符串');
|
|
330
|
+
const url = readMiniProgramRuntimeHttpUrl(payload.url, 'share.showShareMenu url 必须是 HTTP(S) URL');
|
|
331
|
+
const imageUrl = payload.imageUrl === undefined
|
|
332
|
+
? undefined
|
|
333
|
+
: readMiniProgramRuntimeHttpUrl(payload.imageUrl, 'share.showShareMenu imageUrl 必须是 HTTP(S) URL');
|
|
334
|
+
const channel = payload.channel;
|
|
335
|
+
if (channel !== undefined && !isShareChannel(channel)) {
|
|
336
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'share.showShareMenu channel 不合法');
|
|
337
|
+
}
|
|
281
338
|
return {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
statusBarHeight: navigationBarHeight,
|
|
288
|
-
safeArea: {
|
|
289
|
-
left: 0,
|
|
290
|
-
right: windowWidth,
|
|
291
|
-
top: navigationBarHeight,
|
|
292
|
-
bottom: windowHeight,
|
|
293
|
-
width: windowWidth,
|
|
294
|
-
height: safeAreaHeight,
|
|
295
|
-
},
|
|
296
|
-
screenTop: navigationBarHeight,
|
|
339
|
+
title,
|
|
340
|
+
desc,
|
|
341
|
+
url,
|
|
342
|
+
imageUrl,
|
|
343
|
+
channel,
|
|
297
344
|
};
|
|
298
345
|
}
|
|
299
|
-
function
|
|
300
|
-
return
|
|
346
|
+
function isShareChannel(value) {
|
|
347
|
+
return (value === 'wechatSession' ||
|
|
348
|
+
value === 'wechatTimeline' ||
|
|
349
|
+
value === 'qqFriend' ||
|
|
350
|
+
value === 'qzone' ||
|
|
351
|
+
value === 'weibo');
|
|
301
352
|
}
|
|
353
|
+
|
|
354
|
+
/** 截图并唤起分享。 */
|
|
302
355
|
function shareMiniProgramRuntimeScreenshot(payload, platformAdapter) {
|
|
303
|
-
const options =
|
|
304
|
-
const rect = options.rect ||
|
|
356
|
+
const options = readMiniProgramScreenshotOptions(payload);
|
|
357
|
+
const rect = options.rect || getMiniProgramRuntimeViewportRect(platformAdapter);
|
|
305
358
|
return platformAdapter.share.shareScreenshot({
|
|
306
359
|
rect,
|
|
307
360
|
delay: options.delay,
|
|
308
361
|
saveToAlbum: options.saveToAlbum,
|
|
309
362
|
});
|
|
310
363
|
}
|
|
364
|
+
function readMiniProgramScreenshotOptions(payload) {
|
|
365
|
+
if (payload === undefined || payload === null) {
|
|
366
|
+
return {};
|
|
367
|
+
}
|
|
368
|
+
assertMiniProgramRuntimeRecord(payload, 'share.screenshot 参数必须是对象');
|
|
369
|
+
const options = {};
|
|
370
|
+
if (payload.rect !== undefined) {
|
|
371
|
+
options.rect = readScreenshotRect(payload.rect);
|
|
372
|
+
}
|
|
373
|
+
if (payload.delay !== undefined) {
|
|
374
|
+
options.delay = readMiniProgramRuntimeNonNegativeNumber(payload.delay, 'share.screenshot delay 必须是非负数字');
|
|
375
|
+
}
|
|
376
|
+
if (payload.saveToAlbum !== undefined) {
|
|
377
|
+
options.saveToAlbum = readMiniProgramRuntimeBoolean(payload.saveToAlbum, 'share.screenshot saveToAlbum 必须是布尔值');
|
|
378
|
+
}
|
|
379
|
+
return options;
|
|
380
|
+
}
|
|
381
|
+
function readScreenshotRect(value) {
|
|
382
|
+
assertMiniProgramRuntimeRecord(value, 'share.screenshot rect 必须是对象');
|
|
383
|
+
const left = readMiniProgramRuntimeNonNegativeNumber(value.left, 'share.screenshot rect.left 必须是非负数字');
|
|
384
|
+
const top = readMiniProgramRuntimeNonNegativeNumber(value.top, 'share.screenshot rect.top 必须是非负数字');
|
|
385
|
+
const width = readMiniProgramRuntimePositiveNumber(value.width, 'share.screenshot rect.width 必须是正数');
|
|
386
|
+
const height = readMiniProgramRuntimePositiveNumber(value.height, 'share.screenshot rect.height 必须是正数');
|
|
387
|
+
return {
|
|
388
|
+
left,
|
|
389
|
+
top,
|
|
390
|
+
width,
|
|
391
|
+
height,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** 获取小程序隔离 storage。 */
|
|
311
396
|
function getMiniProgramRuntimeStorage(payload, options, platformAdapter) {
|
|
312
|
-
const { key } =
|
|
397
|
+
const { key } = readMiniProgramGetStoragePayload(payload, options.maxKeyLength);
|
|
313
398
|
const storageValue = platformAdapter.storage.getStorage({
|
|
314
399
|
key: createMiniProgramRuntimeStorageKey(key, options.scope || getMiniProgramRuntimeStorageScope(undefined, platformAdapter), options.maxKeyLength),
|
|
315
400
|
});
|
|
@@ -317,8 +402,9 @@ function getMiniProgramRuntimeStorage(payload, options, platformAdapter) {
|
|
|
317
402
|
data: parseStorageData(storageValue),
|
|
318
403
|
};
|
|
319
404
|
}
|
|
405
|
+
/** 写入小程序隔离 storage。 */
|
|
320
406
|
async function setMiniProgramRuntimeStorage(payload, options, platformAdapter) {
|
|
321
|
-
const { key, data } =
|
|
407
|
+
const { key, data } = readMiniProgramSetStoragePayload(payload, options.maxKeyLength);
|
|
322
408
|
await platformAdapter.storage.setStorage({
|
|
323
409
|
key: createMiniProgramRuntimeStorageKey(key, options.scope || getMiniProgramRuntimeStorageScope(undefined, platformAdapter), options.maxKeyLength),
|
|
324
410
|
value: stringifyMiniProgramRuntimeJsonData(data, {
|
|
@@ -327,8 +413,9 @@ async function setMiniProgramRuntimeStorage(payload, options, platformAdapter) {
|
|
|
327
413
|
}),
|
|
328
414
|
});
|
|
329
415
|
}
|
|
416
|
+
/** 根据容器入口生成小程序 storage 隔离 scope。 */
|
|
330
417
|
function getMiniProgramRuntimeStorageScope(href, platformAdapter) {
|
|
331
|
-
const currentHref = platformAdapter
|
|
418
|
+
const currentHref = platformAdapter?.app.getCurrentHref() ?? '';
|
|
332
419
|
try {
|
|
333
420
|
const url = new URL(currentHref);
|
|
334
421
|
const miniProgramId = url.searchParams.get('mini_program_id')?.trim();
|
|
@@ -345,10 +432,90 @@ function getMiniProgramRuntimeStorageScope(href, platformAdapter) {
|
|
|
345
432
|
return `url_${hashString(currentHref)}`;
|
|
346
433
|
}
|
|
347
434
|
}
|
|
435
|
+
/** 生成真实写入黑盒客户端的 storage key。 */
|
|
348
436
|
function createMiniProgramRuntimeStorageKey(key, storageScope, maxKeyLength) {
|
|
349
437
|
readMiniProgramRuntimeStorageKey(key, 'storage.setStorage', maxKeyLength);
|
|
350
438
|
return `hb_miniprogram_${storageScope}_${key}`;
|
|
351
439
|
}
|
|
440
|
+
function readMiniProgramGetStoragePayload(payload, maxKeyLength) {
|
|
441
|
+
if (!isMiniProgramRuntimeRecord(payload)) {
|
|
442
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'storage.getStorage 参数必须是对象');
|
|
443
|
+
}
|
|
444
|
+
const key = readMiniProgramRuntimeStorageKey(payload.key, 'storage.getStorage', maxKeyLength);
|
|
445
|
+
return {
|
|
446
|
+
key,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function readMiniProgramSetStoragePayload(payload, maxKeyLength) {
|
|
450
|
+
if (!isMiniProgramRuntimeRecord(payload)) {
|
|
451
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'storage.setStorage 参数必须是对象');
|
|
452
|
+
}
|
|
453
|
+
const key = readMiniProgramRuntimeStorageKey(payload.key, 'storage.setStorage', maxKeyLength);
|
|
454
|
+
return {
|
|
455
|
+
key,
|
|
456
|
+
data: payload.data,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function parseStorageData(value) {
|
|
460
|
+
if (value === undefined || value === null || value === '') {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
return JSON.parse(value);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return value;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function toStorageScopeToken(value) {
|
|
471
|
+
if (/^[A-Za-z0-9_-]{1,64}$/.test(value)) {
|
|
472
|
+
return value;
|
|
473
|
+
}
|
|
474
|
+
return hashString(value);
|
|
475
|
+
}
|
|
476
|
+
function hashString(value) {
|
|
477
|
+
let hash = 2166136261;
|
|
478
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
479
|
+
hash ^= value.charCodeAt(index);
|
|
480
|
+
hash = Math.imul(hash, 16777619);
|
|
481
|
+
}
|
|
482
|
+
return (hash >>> 0).toString(36);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const NETWORK_REQUEST_ALLOWED_KEYS = new Set([
|
|
486
|
+
'url',
|
|
487
|
+
'method',
|
|
488
|
+
'params',
|
|
489
|
+
'data',
|
|
490
|
+
'headers',
|
|
491
|
+
'timeout',
|
|
492
|
+
'withCredentials',
|
|
493
|
+
]);
|
|
494
|
+
const NETWORK_REQUEST_MAX_TIMEOUT = 30000;
|
|
495
|
+
const NETWORK_REQUEST_MAX_BODY_BYTES = 2 * 1024 * 1024;
|
|
496
|
+
const NETWORK_REQUEST_DENIED_HEADERS = new Set([
|
|
497
|
+
'authorization',
|
|
498
|
+
'connection',
|
|
499
|
+
'content-length',
|
|
500
|
+
'cookie',
|
|
501
|
+
'host',
|
|
502
|
+
'origin',
|
|
503
|
+
'proxy-authorization',
|
|
504
|
+
'referer',
|
|
505
|
+
'set-cookie',
|
|
506
|
+
'transfer-encoding',
|
|
507
|
+
'user-agent',
|
|
508
|
+
]);
|
|
509
|
+
const NETWORK_HTTP_METHODS = new Set([
|
|
510
|
+
'GET',
|
|
511
|
+
'POST',
|
|
512
|
+
'PUT',
|
|
513
|
+
'PATCH',
|
|
514
|
+
'DELETE',
|
|
515
|
+
'HEAD',
|
|
516
|
+
'OPTIONS',
|
|
517
|
+
]);
|
|
518
|
+
/** network.request:校验桥接参数并委托平台适配层完成真实 HTTP 交换。 */
|
|
352
519
|
async function requestMiniProgramRuntimeNetwork(payload, platformAdapter) {
|
|
353
520
|
return platformAdapter.network.request(readMiniProgramRuntimeNetworkRequest(payload));
|
|
354
521
|
}
|
|
@@ -356,7 +523,7 @@ function readMiniProgramRuntimeNetworkRequest(payload) {
|
|
|
356
523
|
assertMiniProgramRuntimeRecord(payload, 'network.request 参数必须是对象');
|
|
357
524
|
assertNetworkAllowedKeys(payload);
|
|
358
525
|
const request = {
|
|
359
|
-
url:
|
|
526
|
+
url: readNetworkRequestUrl(payload.url),
|
|
360
527
|
};
|
|
361
528
|
if (payload.method !== undefined) {
|
|
362
529
|
request.method = readNetworkMethod(payload.method);
|
|
@@ -365,92 +532,129 @@ function readMiniProgramRuntimeNetworkRequest(payload) {
|
|
|
365
532
|
request.params = readUnknownRecord(payload.params, 'network.request params 必须是对象');
|
|
366
533
|
}
|
|
367
534
|
if (payload.data !== undefined) {
|
|
535
|
+
assertNetworkRequestBody(payload.data);
|
|
368
536
|
request.data = payload.data;
|
|
369
537
|
}
|
|
370
538
|
if (payload.headers !== undefined) {
|
|
371
|
-
request.headers =
|
|
539
|
+
request.headers = readNetworkRequestHeaders(payload.headers);
|
|
372
540
|
}
|
|
373
541
|
if (payload.timeout !== undefined) {
|
|
374
542
|
request.timeout = readMiniProgramRuntimeNonNegativeNumber(payload.timeout, 'network.request timeout 必须是非负数字');
|
|
543
|
+
if (request.timeout > NETWORK_REQUEST_MAX_TIMEOUT) {
|
|
544
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', `network.request timeout 不能超过 ${NETWORK_REQUEST_MAX_TIMEOUT}ms`);
|
|
545
|
+
}
|
|
375
546
|
}
|
|
376
547
|
if (payload.withCredentials !== undefined) {
|
|
377
548
|
request.withCredentials = readMiniProgramRuntimeBoolean(payload.withCredentials, 'network.request withCredentials 必须是布尔值');
|
|
378
549
|
}
|
|
379
550
|
return request;
|
|
380
551
|
}
|
|
381
|
-
function
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const url = readMiniProgramRuntimeHttpUrl(payload.url, 'share.showShareMenu url 必须是 HTTP(S) URL');
|
|
386
|
-
const imageUrl = payload.imageUrl === undefined
|
|
387
|
-
? undefined
|
|
388
|
-
: readMiniProgramRuntimeHttpUrl(payload.imageUrl, 'share.showShareMenu imageUrl 必须是 HTTP(S) URL');
|
|
389
|
-
const channel = payload.channel;
|
|
390
|
-
if (channel !== undefined && !isShareChannel(channel)) {
|
|
391
|
-
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'share.showShareMenu channel 不合法');
|
|
552
|
+
function assertNetworkAllowedKeys(payload) {
|
|
553
|
+
const invalidKeys = Object.keys(payload).filter(key => !NETWORK_REQUEST_ALLOWED_KEYS.has(key));
|
|
554
|
+
if (invalidKeys.length > 0) {
|
|
555
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', `network.request 不支持字段: ${invalidKeys.join(', ')}`);
|
|
392
556
|
}
|
|
393
|
-
return {
|
|
394
|
-
title,
|
|
395
|
-
desc,
|
|
396
|
-
url,
|
|
397
|
-
imageUrl,
|
|
398
|
-
channel,
|
|
399
|
-
};
|
|
400
557
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
|
|
558
|
+
function readNetworkRequestUrl(value) {
|
|
559
|
+
const requestUrl = readMiniProgramRuntimeHttpUrl(value, 'network.request url 必须是 HTTP(S) URL');
|
|
560
|
+
assertPublicNetworkRequestUrl(requestUrl);
|
|
561
|
+
return requestUrl;
|
|
562
|
+
}
|
|
563
|
+
function assertPublicNetworkRequestUrl(requestUrl) {
|
|
564
|
+
const url = new URL(requestUrl);
|
|
565
|
+
if (url.username || url.password) {
|
|
566
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'network.request url 不允许包含用户信息');
|
|
404
567
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (payload.rect !== undefined) {
|
|
408
|
-
options.rect = readScreenshotRect(payload.rect);
|
|
568
|
+
if (isUnsafeNetworkHost(url.hostname)) {
|
|
569
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'network.request url 不允许指向本机、内网或保留地址');
|
|
409
570
|
}
|
|
410
|
-
|
|
411
|
-
|
|
571
|
+
}
|
|
572
|
+
function isUnsafeNetworkHost(hostname) {
|
|
573
|
+
const normalizedHost = hostname.toLowerCase();
|
|
574
|
+
const ipv6Host = normalizedHost.startsWith('[') && normalizedHost.endsWith(']')
|
|
575
|
+
? normalizedHost.slice(1, -1)
|
|
576
|
+
: normalizedHost;
|
|
577
|
+
if (normalizedHost === 'localhost' ||
|
|
578
|
+
normalizedHost.endsWith('.localhost') ||
|
|
579
|
+
normalizedHost.endsWith('.local') ||
|
|
580
|
+
normalizedHost === 'metadata.google.internal') {
|
|
581
|
+
return true;
|
|
412
582
|
}
|
|
413
|
-
if (
|
|
414
|
-
|
|
583
|
+
if (isUnsafeIPv4Host(normalizedHost)) {
|
|
584
|
+
return true;
|
|
415
585
|
}
|
|
416
|
-
return
|
|
586
|
+
return isUnsafeIPv6Host(ipv6Host);
|
|
417
587
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
588
|
+
function isUnsafeIPv4Host(hostname) {
|
|
589
|
+
if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
const parts = hostname.split('.').map(part => Number(part));
|
|
593
|
+
if (parts.some(part => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
const [a, b] = parts;
|
|
597
|
+
return (a === 0 ||
|
|
598
|
+
a === 10 ||
|
|
599
|
+
a === 127 ||
|
|
600
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
601
|
+
(a === 169 && b === 254) ||
|
|
602
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
603
|
+
(a === 192 && b === 168));
|
|
430
604
|
}
|
|
431
|
-
function
|
|
432
|
-
|
|
433
|
-
|
|
605
|
+
function isUnsafeIPv6Host(hostname) {
|
|
606
|
+
return (hostname === '::' ||
|
|
607
|
+
hostname === '::1' ||
|
|
608
|
+
hostname.startsWith('fc') ||
|
|
609
|
+
hostname.startsWith('fd') ||
|
|
610
|
+
hostname.startsWith('fe80:'));
|
|
611
|
+
}
|
|
612
|
+
function readNetworkRequestHeaders(value) {
|
|
613
|
+
const headers = readStringRecord(value, 'network.request headers 必须是 Record<string, string>');
|
|
614
|
+
const deniedHeader = Object.keys(headers).find(isDeniedNetworkRequestHeader);
|
|
615
|
+
if (deniedHeader) {
|
|
616
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', `network.request 不允许设置敏感请求头: ${deniedHeader}`);
|
|
434
617
|
}
|
|
435
|
-
|
|
436
|
-
return {
|
|
437
|
-
key,
|
|
438
|
-
};
|
|
618
|
+
return headers;
|
|
439
619
|
}
|
|
440
|
-
function
|
|
441
|
-
|
|
442
|
-
|
|
620
|
+
function isDeniedNetworkRequestHeader(headerName) {
|
|
621
|
+
const normalizedHeaderName = headerName.trim().toLowerCase();
|
|
622
|
+
return (NETWORK_REQUEST_DENIED_HEADERS.has(normalizedHeaderName) ||
|
|
623
|
+
normalizedHeaderName.startsWith('x-heybox-') ||
|
|
624
|
+
normalizedHeaderName.startsWith('x-xhh-'));
|
|
625
|
+
}
|
|
626
|
+
function assertNetworkRequestBody(data) {
|
|
627
|
+
if (isUnsupportedNetworkRequestBody(data)) {
|
|
628
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'network.request data 不支持 File、Blob 或 FormData,请使用专用上传能力');
|
|
629
|
+
}
|
|
630
|
+
const bodyBytes = estimateNetworkRequestBodyBytes(data);
|
|
631
|
+
if (bodyBytes > NETWORK_REQUEST_MAX_BODY_BYTES) {
|
|
632
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', `network.request data 不能超过 ${NETWORK_REQUEST_MAX_BODY_BYTES} 字节`);
|
|
443
633
|
}
|
|
444
|
-
const key = readMiniProgramRuntimeStorageKey(payload.key, 'storage.setStorage', maxKeyLength);
|
|
445
|
-
return {
|
|
446
|
-
key,
|
|
447
|
-
data: payload.data,
|
|
448
|
-
};
|
|
449
634
|
}
|
|
450
|
-
function
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
635
|
+
function isUnsupportedNetworkRequestBody(data) {
|
|
636
|
+
return ((typeof Blob !== 'undefined' && data instanceof Blob) ||
|
|
637
|
+
(typeof FormData !== 'undefined' && data instanceof FormData));
|
|
638
|
+
}
|
|
639
|
+
function estimateNetworkRequestBodyBytes(data) {
|
|
640
|
+
if (typeof data === 'string') {
|
|
641
|
+
return getMiniProgramRuntimeUtf8ByteLength(data);
|
|
642
|
+
}
|
|
643
|
+
if (data instanceof ArrayBuffer) {
|
|
644
|
+
return data.byteLength;
|
|
645
|
+
}
|
|
646
|
+
if (ArrayBuffer.isView(data)) {
|
|
647
|
+
return data.byteLength;
|
|
648
|
+
}
|
|
649
|
+
if (typeof URLSearchParams !== 'undefined' && data instanceof URLSearchParams) {
|
|
650
|
+
return getMiniProgramRuntimeUtf8ByteLength(data.toString());
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
const serialized = JSON.stringify(data);
|
|
654
|
+
return serialized ? getMiniProgramRuntimeUtf8ByteLength(serialized) : 0;
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'network.request data 必须可序列化');
|
|
454
658
|
}
|
|
455
659
|
}
|
|
456
660
|
function readNetworkMethod(value) {
|
|
@@ -474,75 +678,6 @@ function readStringRecord(value, message) {
|
|
|
474
678
|
return headers;
|
|
475
679
|
}, {});
|
|
476
680
|
}
|
|
477
|
-
function resolveCurrentUserId(platformAdapter) {
|
|
478
|
-
return platformAdapter.auth.getCurrentUserId()
|
|
479
|
-
.then(userId => (userId ? String(userId) : ''))
|
|
480
|
-
.catch(() => '');
|
|
481
|
-
}
|
|
482
|
-
function getNavigationBarHeight(platformAdapter) {
|
|
483
|
-
try {
|
|
484
|
-
return platformAdapter.viewport.getNavigationBarHeight();
|
|
485
|
-
}
|
|
486
|
-
catch {
|
|
487
|
-
return 0;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
function getViewportRect(platformAdapter) {
|
|
491
|
-
const metrics = platformAdapter.viewport.getViewportMetrics();
|
|
492
|
-
const width = getPositiveNumber(metrics.innerWidth, getPositiveNumber(metrics.documentElementClientWidth, 0));
|
|
493
|
-
const height = getPositiveNumber(metrics.innerHeight, getPositiveNumber(metrics.documentElementClientHeight, 0));
|
|
494
|
-
return {
|
|
495
|
-
left: 0,
|
|
496
|
-
top: 0,
|
|
497
|
-
width,
|
|
498
|
-
height,
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
function getViewportWidth(metrics) {
|
|
502
|
-
return getPositiveNumber(metrics.innerWidth, getPositiveNumber(metrics.documentElementClientWidth, 0));
|
|
503
|
-
}
|
|
504
|
-
function getViewportHeight(metrics) {
|
|
505
|
-
return getPositiveNumber(metrics.innerHeight, getPositiveNumber(metrics.documentElementClientHeight, 0));
|
|
506
|
-
}
|
|
507
|
-
function getPositiveNumber(value, fallback) {
|
|
508
|
-
const numberValue = Number(value);
|
|
509
|
-
if (!Number.isFinite(numberValue) || numberValue <= 0) {
|
|
510
|
-
return fallback;
|
|
511
|
-
}
|
|
512
|
-
return numberValue;
|
|
513
|
-
}
|
|
514
|
-
function parseStorageData(value) {
|
|
515
|
-
if (value === undefined || value === null || value === '') {
|
|
516
|
-
return undefined;
|
|
517
|
-
}
|
|
518
|
-
try {
|
|
519
|
-
return JSON.parse(value);
|
|
520
|
-
}
|
|
521
|
-
catch {
|
|
522
|
-
return value;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
function toStorageScopeToken(value) {
|
|
526
|
-
if (/^[A-Za-z0-9_-]{1,64}$/.test(value)) {
|
|
527
|
-
return value;
|
|
528
|
-
}
|
|
529
|
-
return hashString(value);
|
|
530
|
-
}
|
|
531
|
-
function hashString(value) {
|
|
532
|
-
let hash = 2166136261;
|
|
533
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
534
|
-
hash ^= value.charCodeAt(index);
|
|
535
|
-
hash = Math.imul(hash, 16777619);
|
|
536
|
-
}
|
|
537
|
-
return (hash >>> 0).toString(36);
|
|
538
|
-
}
|
|
539
|
-
function isShareChannel(value) {
|
|
540
|
-
return (value === 'wechatSession' ||
|
|
541
|
-
value === 'wechatTimeline' ||
|
|
542
|
-
value === 'qqFriend' ||
|
|
543
|
-
value === 'qzone' ||
|
|
544
|
-
value === 'weibo');
|
|
545
|
-
}
|
|
546
681
|
|
|
547
682
|
const MINI_PROGRAM_MOCK_RUNTIME_METHODS = MINI_PROGRAM_PROTOCOL_CAPABILITIES.map(capability => capability.method);
|
|
548
683
|
const MINI_PROGRAM_MOCK_RUNTIME_METHOD_HANDLERS = {
|
|
@@ -556,8 +691,6 @@ const MINI_PROGRAM_MOCK_RUNTIME_METHOD_HANDLERS = {
|
|
|
556
691
|
[NETWORK_REQUEST_METHOD]: (runtime, payload) => runtime.requestNetwork(payload),
|
|
557
692
|
};
|
|
558
693
|
class MiniProgramMockRuntime {
|
|
559
|
-
adapter;
|
|
560
|
-
options;
|
|
561
694
|
constructor(adapter, options = {}) {
|
|
562
695
|
this.adapter = adapter;
|
|
563
696
|
this.options = options;
|
package/dist/index.cjs.js
CHANGED
|
@@ -240,8 +240,9 @@ class MiniProgramBridgeClient {
|
|
|
240
240
|
this.eventBus.off(eventName, handler);
|
|
241
241
|
}
|
|
242
242
|
/** 调用父容器开放能力。 */
|
|
243
|
-
async request(method,
|
|
243
|
+
async request(method, ...args) {
|
|
244
244
|
await this.ready();
|
|
245
|
+
const payload = args[0];
|
|
245
246
|
const id = createMessageId();
|
|
246
247
|
const message = {
|
|
247
248
|
namespace: MINI_PROGRAM_MESSAGE_NAMESPACE,
|
|
@@ -888,25 +889,12 @@ const hbSDK = {
|
|
|
888
889
|
network,
|
|
889
890
|
};
|
|
890
891
|
|
|
891
|
-
exports.AUTH_LOGIN_METHOD = AUTH_LOGIN_METHOD;
|
|
892
892
|
exports.HbMiniProgramNetworkError = HbMiniProgramNetworkError;
|
|
893
893
|
exports.HbMiniProgramSDKError = HbMiniProgramSDKError;
|
|
894
|
-
exports.MINI_PROGRAM_BRIDGE_NONCE_PARAM = MINI_PROGRAM_BRIDGE_NONCE_PARAM;
|
|
895
|
-
exports.MINI_PROGRAM_MESSAGE_NAMESPACE = MINI_PROGRAM_MESSAGE_NAMESPACE;
|
|
896
|
-
exports.MINI_PROGRAM_MESSAGE_VERSION = MINI_PROGRAM_MESSAGE_VERSION;
|
|
897
894
|
exports.MiniProgramSDK = MiniProgramSDK;
|
|
898
|
-
exports.NETWORK_REQUEST_METHOD = NETWORK_REQUEST_METHOD;
|
|
899
|
-
exports.SDK_HANDSHAKE_METHOD = SDK_HANDSHAKE_METHOD;
|
|
900
|
-
exports.SHARE_SCREENSHOT_METHOD = SHARE_SCREENSHOT_METHOD;
|
|
901
|
-
exports.SHARE_SHOW_SHARE_MENU_METHOD = SHARE_SHOW_SHARE_MENU_METHOD;
|
|
902
|
-
exports.STORAGE_GET_STORAGE_METHOD = STORAGE_GET_STORAGE_METHOD;
|
|
903
|
-
exports.STORAGE_SET_STORAGE_METHOD = STORAGE_SET_STORAGE_METHOD;
|
|
904
|
-
exports.USER_GET_INFO_METHOD = USER_GET_INFO_METHOD;
|
|
905
|
-
exports.VIEWPORT_GET_WINDOW_INFO_METHOD = VIEWPORT_GET_WINDOW_INFO_METHOD;
|
|
906
895
|
exports.auth = auth;
|
|
907
896
|
exports.createMiniProgramSDK = createMiniProgramSDK;
|
|
908
897
|
exports.default = hbSDK;
|
|
909
|
-
exports.isMiniProgramBridgeMessage = isMiniProgramBridgeMessage;
|
|
910
898
|
exports.network = network;
|
|
911
899
|
exports.off = off;
|
|
912
900
|
exports.on = on;
|