@heybox/hb-sdk 0.1.2

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 ADDED
@@ -0,0 +1,463 @@
1
+ # @heybox/hb-sdk
2
+
3
+ 面向黑盒外部小程序 iframe 沙盒的前端 SDK。它负责与父容器完成握手、收发 bridge 消息,并以受控模块的形式向外部小程序开放用户、分享、系统信息和隔离 storage 能力。
4
+
5
+ ## 为什么需要它
6
+
7
+ 外部小程序运行在父容器创建的 iframe 沙盒中,业务页面不能直接读取主站登录态,也不应该关心底层 `postMessage` 协议、nonce 校验、请求响应关联、超时清理、客户端协议参数过滤等细节。
8
+
9
+ `@heybox/hb-sdk` 将这些通信细节封装为稳定接口,让小程序只通过被允许的能力访问父容器。父容器仍负责调用黑盒客户端协议,并在运行时过滤不适合开放给外部小程序的内部参数。
10
+
11
+ ## 当前对外导出
12
+
13
+ > 以 `src/index.ts` 为准;历史目录、旧示例或构建产物若与当前入口不一致,以当前源码导出能力为准。
14
+
15
+ - 默认导出 `hbSDK`:包含 `ready`、`on`、`off`、`user`、`share`、`system`。
16
+ - 命名导出 `ready` / `on` / `off` / `user` / `share` / `system`:默认 SDK 单例能力。
17
+ - `createMiniProgramSDK` / `MiniProgramSDK`:创建独立 SDK 实例,适合测试、多实例或显式生命周期管理场景。
18
+ - `HbMiniProgramSDKError`:SDK 对外抛出的标准错误类型。
19
+ - `MiniProgramRequester` / `MiniProgramSDKOptions`:底层请求与 SDK 初始化配置类型。
20
+ - bridge 协议常量、类型与守卫:`MINI_PROGRAM_MESSAGE_NAMESPACE`、`MINI_PROGRAM_MESSAGE_VERSION`、`MINI_PROGRAM_BRIDGE_NONCE_PARAM`、`SDK_HANDSHAKE_METHOD`、`isMiniProgramBridgeMessage` 等。
21
+ - 模块类型与方法常量:`MiniProgramUserModule`、`MiniProgramShareModule`、`MiniProgramSystemModule` 以及各模块 method 常量。
22
+
23
+ ## 快速开始
24
+
25
+ 最小示例:
26
+
27
+ ```ts
28
+ import hbSDK from '@heybox/hb-sdk';
29
+
30
+ await hbSDK.ready();
31
+
32
+ const result = await hbSDK.user.getInfo();
33
+
34
+ if (result.isLogin) {
35
+ console.log(result.userInfo.nickname);
36
+ }
37
+ ```
38
+
39
+ 按需导入:
40
+
41
+ ```ts
42
+ import { on, ready, user } from '@heybox/hb-sdk';
43
+
44
+ const unsubscribe = on('show', (payload) => {
45
+ console.log('小程序展示', payload.timestamp, payload.source);
46
+ });
47
+
48
+ await ready();
49
+
50
+ const currentUser = await user.getInfo();
51
+
52
+ unsubscribe();
53
+ ```
54
+
55
+ 分享与 storage:
56
+
57
+ ```ts
58
+ import { share, system } from '@heybox/hb-sdk';
59
+
60
+ await share.showShareMenu({
61
+ title: '分享标题',
62
+ desc: '分享描述',
63
+ url: 'https://web.xiaoheihe.cn/tools/demo',
64
+ imageUrl: 'https://imgheybox.max-c.com/demo.png',
65
+ });
66
+
67
+ await system.setStorage({
68
+ key: 'settings',
69
+ data: {
70
+ theme: 'dark',
71
+ },
72
+ });
73
+
74
+ const { data } = await system.getStorage<{ theme: string }>({
75
+ key: 'settings',
76
+ });
77
+ ```
78
+
79
+ ## 核心概念
80
+
81
+ ### 默认单例
82
+
83
+ 普通小程序页面优先使用默认导出的 `hbSDK`,或直接按需导入 `ready`、`on`、`off`、`user`、`share`、`system`。默认单例会在首次调用时懒创建,业务不需要手动初始化。
84
+
85
+ ```ts
86
+ import { ready, system } from '@heybox/hb-sdk';
87
+
88
+ await ready();
89
+ const windowInfo = await system.getWindowInfo();
90
+ ```
91
+
92
+ ### 独立实例
93
+
94
+ 需要显式控制生命周期、注入测试环境 window、调整超时时间,或同页存在多套沙盒通信上下文时,可以使用 `createMiniProgramSDK`。
95
+
96
+ ```ts
97
+ import { createMiniProgramSDK } from '@heybox/hb-sdk';
98
+
99
+ const sdk = createMiniProgramSDK({
100
+ timeout: 5000,
101
+ });
102
+
103
+ await sdk.ready();
104
+
105
+ const loginResult = await sdk.user.login();
106
+
107
+ sdk.destroy();
108
+ ```
109
+
110
+ ### bridge 通信
111
+
112
+ SDK 与父容器之间通过 `postMessage` 通信,消息会带上固定命名空间、协议版本与 iframe 实例 nonce。SDK 内部负责:
113
+
114
+ - 从 URL query 读取 `hb_mini_bridge_nonce`,也支持通过 `MiniProgramSDKOptions.nonce` 显式传入。
115
+ - 向父容器发送 `sdk.handshake` 握手消息,并等待 `ready` 事件。
116
+ - 为每次能力调用生成请求 ID,匹配父容器返回的 response。
117
+ - 过滤非小程序消息、非当前 nonce 消息以及非目标父窗口消息。
118
+ - 处理请求超时、握手超时和销毁后的未完成请求。
119
+
120
+ ### 标准错误
121
+
122
+ 请求失败和 SDK 内部错误会统一包装为 `HbMiniProgramSDKError`,包含稳定的 `code`、面向开发者的 `message` 和可选 `data`。
123
+
124
+ ```ts
125
+ import { HbMiniProgramSDKError, user } from '@heybox/hb-sdk';
126
+
127
+ try {
128
+ await user.login();
129
+ } catch (error) {
130
+ if (error instanceof HbMiniProgramSDKError) {
131
+ console.log(error.code, error.message, error.data);
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## 模块说明
137
+
138
+ ### Core
139
+
140
+ 核心模块位于 `src/core`,负责 SDK 实例、默认单例、bridge client、事件总线、错误和工具函数。
141
+
142
+ | 模块 | 作用 | 代表导出 |
143
+ | --- | --- | --- |
144
+ | `core/sdk` | SDK 实例封装 | `MiniProgramSDK`、`createMiniProgramSDK` |
145
+ | `core/singleton` | 默认单例入口 | `ready`、`on`、`off`、`user`、`share`、`system` |
146
+ | `core/client` | bridge 握手、请求响应、事件分发 | `MiniProgramSDKOptions`、`MiniProgramRequester` |
147
+ | `core/errors` | 标准错误类型 | `HbMiniProgramSDKError` |
148
+
149
+ ### User
150
+
151
+ 用户模块位于 `src/modules/user`,只暴露允许小程序访问的公开用户能力。
152
+
153
+ | API | 说明 |
154
+ | --- | --- |
155
+ | `user.getInfo()` | 获取当前用户登录态与公开基础资料。未登录时不会触发登录流程。 |
156
+ | `user.login()` | 唤起黑盒登录流程,并返回登录后的最新用户公开资料。 |
157
+
158
+ 用户信息只包含允许暴露给外部小程序的公开字段:
159
+
160
+ ```ts
161
+ interface MiniProgramUserInfo {
162
+ heybox_id: string;
163
+ nickname: string;
164
+ avatar: string;
165
+ }
166
+
167
+ interface MiniProgramUserInfoResult {
168
+ isLogin: boolean;
169
+ userInfo: MiniProgramUserInfo | null;
170
+ }
171
+ ```
172
+
173
+ SDK 不会向小程序暴露 token、cookie、手机号或任何可用于直接调用主站私有接口的凭据。
174
+
175
+ ### Share
176
+
177
+ 分享模块位于 `src/modules/share`,对外提供简洁配置,父容器侧再映射到黑盒客户端协议。
178
+
179
+ | API | 对应父容器能力 | 说明 |
180
+ | --- | --- | --- |
181
+ | `share.showShareMenu(options)` | `share.showShareMenu` -> `share` 协议 | 展示基础分享面板。 |
182
+ | `share.screenshot(options?)` | `share.screenshot` -> `screenShotShareV2` 协议 | 截图并唤起分享。 |
183
+
184
+ 基础分享参数:
185
+
186
+ ```ts
187
+ interface MiniProgramShowShareMenuOptions {
188
+ title: string;
189
+ desc: string;
190
+ url: string;
191
+ imageUrl?: string;
192
+ channel?: 'wechatSession' | 'wechatTimeline' | 'qqFriend' | 'qzone' | 'weibo';
193
+ }
194
+ ```
195
+
196
+ 截图分享参数:
197
+
198
+ ```ts
199
+ interface MiniProgramScreenshotOptions {
200
+ rect?: {
201
+ left: number;
202
+ top: number;
203
+ width: number;
204
+ height: number;
205
+ };
206
+ delay?: number;
207
+ saveToAlbum?: boolean;
208
+ }
209
+ ```
210
+
211
+ 为了保持外部 API 稳定,`share` 模块不开放 raw protocol、JS callback、活动上报、发帖、自定义按钮、upload-only 等内部能力。
212
+
213
+ ### System
214
+
215
+ 系统模块位于 `src/modules/system`,当前提供窗口信息与隔离 storage 能力。
216
+
217
+ | API | 对应父容器能力 | 说明 |
218
+ | --- | --- | --- |
219
+ | `system.getWindowInfo()` | `system.getWindowInfo` | 获取窗口、屏幕、安全区域等信息。 |
220
+ | `system.getStorage(options)` | `system.getStorage` -> `getStorage` 协议 | 读取小程序隔离 storage。 |
221
+ | `system.setStorage(options)` | `system.setStorage` -> `setStorage` 协议 | 写入小程序隔离 storage。 |
222
+
223
+ `getWindowInfo()` 返回字段对齐微信小程序 `wx.getWindowInfo` 的常用字段:
224
+
225
+ ```ts
226
+ interface MiniProgramWindowInfoResult {
227
+ pixelRatio: number;
228
+ screenWidth: number;
229
+ screenHeight: number;
230
+ windowWidth: number;
231
+ windowHeight: number;
232
+ statusBarHeight: number;
233
+ safeArea: {
234
+ left: number;
235
+ right: number;
236
+ top: number;
237
+ bottom: number;
238
+ width: number;
239
+ height: number;
240
+ };
241
+ screenTop: number;
242
+ }
243
+ ```
244
+
245
+ storage API:
246
+
247
+ ```ts
248
+ await system.setStorage({
249
+ key: 'settings',
250
+ data: {
251
+ enabled: true,
252
+ },
253
+ });
254
+
255
+ const result = await system.getStorage<{ enabled: boolean }>({
256
+ key: 'settings',
257
+ });
258
+ ```
259
+
260
+ storage 只开放 `get` / `set`,不开放 `removeStorage`、`clearStorage`、`getStorageInfo`、`getStorageV2`。父容器会强制加小程序级命名空间,外部小程序不能读写黑盒客户端全局 storage key。用户传入的 `key` 只允许 1-128 位字母、数字、下划线和连字符。
261
+
262
+ ### Protocol
263
+
264
+ 协议模块位于 `src/protocol`,定义父容器与 iframe SDK 之间的消息格式、事件名、事件 payload 映射、协议常量和消息守卫。
265
+
266
+ 当前支持的小程序事件:
267
+
268
+ - `launch`:小程序首次完成握手并启动。
269
+ - `ready`:SDK 已可安全调用开放能力。
270
+ - `show`:小程序页面展示。
271
+ - `hide`:小程序页面隐藏。
272
+ - `unload`:小程序页面即将卸载。
273
+ - `error`:父容器或开放能力运行异常。
274
+ - `authChange`:登录状态发生变化后的最新用户信息。
275
+
276
+ ## 常见场景示例
277
+
278
+ ### 场景 1:进入页面后获取用户信息
279
+
280
+ ```ts
281
+ import { ready, user } from '@heybox/hb-sdk';
282
+
283
+ await ready();
284
+
285
+ const result = await user.getInfo();
286
+
287
+ if (!result.isLogin) {
288
+ // 可按业务需要展示登录按钮,不会在 getInfo 阶段自动唤起登录。
289
+ return;
290
+ }
291
+
292
+ renderUser(result.userInfo);
293
+ ```
294
+
295
+ ### 场景 2:点击按钮唤起登录
296
+
297
+ ```ts
298
+ import { user } from '@heybox/hb-sdk';
299
+
300
+ async function handleLoginClick() {
301
+ const result = await user.login();
302
+
303
+ if (result.isLogin) {
304
+ renderUser(result.userInfo);
305
+ }
306
+ }
307
+ ```
308
+
309
+ ### 场景 3:展示分享面板
310
+
311
+ ```ts
312
+ import { share } from '@heybox/hb-sdk';
313
+
314
+ async function handleShareClick() {
315
+ await share.showShareMenu({
316
+ title: '我的小程序页面',
317
+ desc: '来自黑盒小程序的分享',
318
+ url: location.href,
319
+ imageUrl: 'https://imgheybox.max-c.com/demo.png',
320
+ channel: 'wechatSession',
321
+ });
322
+ }
323
+ ```
324
+
325
+ ### 场景 4:截图分享当前页面
326
+
327
+ ```ts
328
+ import { share } from '@heybox/hb-sdk';
329
+
330
+ async function handleScreenshotShare() {
331
+ await share.screenshot({
332
+ delay: 100,
333
+ saveToAlbum: true,
334
+ });
335
+ }
336
+ ```
337
+
338
+ 也可以指定截图区域:
339
+
340
+ ```ts
341
+ await share.screenshot({
342
+ rect: {
343
+ left: 0,
344
+ top: 0,
345
+ width: 375,
346
+ height: 667,
347
+ },
348
+ });
349
+ ```
350
+
351
+ ### 场景 5:读取窗口信息
352
+
353
+ ```ts
354
+ import { system } from '@heybox/hb-sdk';
355
+
356
+ const windowInfo = await system.getWindowInfo();
357
+
358
+ console.log(windowInfo.windowWidth, windowInfo.windowHeight, windowInfo.safeArea.top);
359
+ ```
360
+
361
+ ### 场景 6:使用隔离 storage
362
+
363
+ ```ts
364
+ import { system } from '@heybox/hb-sdk';
365
+
366
+ await system.setStorage({
367
+ key: 'card_mode',
368
+ data: {
369
+ enabled: true,
370
+ },
371
+ });
372
+
373
+ const { data } = await system.getStorage<{ enabled: boolean }>({
374
+ key: 'card_mode',
375
+ });
376
+
377
+ if (data?.enabled) {
378
+ enableCardMode();
379
+ }
380
+ ```
381
+
382
+ ### 场景 7:监听生命周期与登录态变化
383
+
384
+ ```ts
385
+ import { off, on } from '@heybox/hb-sdk';
386
+
387
+ const handleShow = (payload) => {
388
+ console.log('页面展示', payload.timestamp);
389
+ };
390
+
391
+ const handleAuthChange = (payload) => {
392
+ console.log('登录态变化', payload.isLogin, payload.userInfo);
393
+ };
394
+
395
+ on('show', handleShow);
396
+ on('authChange', handleAuthChange);
397
+
398
+ // 页面销毁时移除监听
399
+ off('show', handleShow);
400
+ off('authChange', handleAuthChange);
401
+ ```
402
+
403
+ 也可以使用 `on` 返回的取消函数:
404
+
405
+ ```ts
406
+ const unsubscribe = on('hide', (payload) => {
407
+ console.log('页面隐藏', payload.timestamp);
408
+ });
409
+
410
+ unsubscribe();
411
+ ```
412
+
413
+ ### 场景 8:测试中注入通信环境
414
+
415
+ ```ts
416
+ import { createMiniProgramSDK } from '@heybox/hb-sdk';
417
+
418
+ const sdk = createMiniProgramSDK({
419
+ nonce: 'test_nonce',
420
+ selfWindow: window,
421
+ targetWindow: parentWindow,
422
+ timeout: 100,
423
+ });
424
+
425
+ await sdk.ready();
426
+ sdk.destroy();
427
+ ```
428
+
429
+ ## 能力边界与注意事项
430
+
431
+ - SDK 只能在父容器创建的小程序 iframe 沙盒中正常完成握手;非 iframe 环境会抛出 `NOT_IN_IFRAME`。
432
+ - 父容器必须在小程序 URL 上注入 `hb_mini_bridge_nonce`,否则会抛出 `MISSING_NONCE`。
433
+ - 调用模块能力前会自动等待 `ready()`,但业务仍建议在页面启动阶段显式 `await ready()`,便于集中处理握手失败。
434
+ - `user.getInfo()` 只查询登录态,不会主动唤起登录。
435
+ - `user.login()` 只返回公开用户资料,不返回 token、cookie 或私有凭据。
436
+ - `share.showShareMenu()` 只开放基础分享字段,不开放活动任务上报、发帖、自定义按钮等内部协议参数。
437
+ - `share.screenshot()` 只做截图并分享,不开放 upload-only 和客户端 JS callback。
438
+ - `system.getWindowInfo()` 的 `statusBarHeight`、`screenTop`、`safeArea.top` 在黑盒容器内按顶部可用偏移处理。
439
+ - `system.getStorage()` / `system.setStorage()` 只访问小程序隔离 storage;业务 key 必须符合 `/^[A-Za-z0-9_-]{1,128}$/`。
440
+ - `on()` 会返回取消监听函数;页面卸载或组件销毁时应及时取消监听。
441
+ - 独立实例不再使用时应调用 `destroy()`,以移除 message 监听并拒绝未完成请求。
442
+
443
+ ## 开发命令
444
+
445
+ 优先使用仓库统一的 `hbexec` 入口:
446
+
447
+ ```bash
448
+ # 运行单元测试
449
+ pnpm exec hbexec test unit @heybox/hb-sdk
450
+
451
+ # 构建产物与类型声明
452
+ pnpm exec hbexec build package -d packages/hb-sdk
453
+ ```
454
+
455
+ 也可以直接运行包内脚本:
456
+
457
+ ```bash
458
+ # 运行单元测试
459
+ pnpm --filter @heybox/hb-sdk run test:unit
460
+
461
+ # 运行单元测试并生成覆盖率
462
+ pnpm --filter @heybox/hb-sdk run test:unit:coverage
463
+ ```