@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.
@@ -0,0 +1,500 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /** SDK 对外抛出的标准错误类型。 */
6
+ class HbMiniProgramSDKError extends Error {
7
+ /** 稳定错误码。 */
8
+ code;
9
+ /** 可选调试数据。 */
10
+ data;
11
+ constructor(error) {
12
+ super(error.message);
13
+ this.name = 'HbMiniProgramSDKError';
14
+ this.code = error.code;
15
+ this.data = error.data;
16
+ }
17
+ }
18
+ /** 创建 SDK 标准错误。 */
19
+ function createSDKError(code, message, data) {
20
+ return new HbMiniProgramSDKError({
21
+ code,
22
+ message,
23
+ data,
24
+ });
25
+ }
26
+
27
+ /** SDK 内部事件总线,负责管理沙盒生命周期与业务事件监听。 */
28
+ class MiniProgramEventBus {
29
+ eventHandlers = new Map();
30
+ /** 注册事件监听,并返回取消监听函数。 */
31
+ on(eventName, handler) {
32
+ const handlers = this.eventHandlers.get(eventName) || new Set();
33
+ handlers.add(handler);
34
+ this.eventHandlers.set(eventName, handlers);
35
+ return () => this.off(eventName, handler);
36
+ }
37
+ /** 移除事件监听。 */
38
+ off(eventName, handler) {
39
+ const handlers = this.eventHandlers.get(eventName);
40
+ if (!handlers) {
41
+ return;
42
+ }
43
+ handlers.delete(handler);
44
+ if (handlers.size === 0) {
45
+ this.eventHandlers.delete(eventName);
46
+ }
47
+ }
48
+ /** 派发事件。 */
49
+ emit(eventName, payload) {
50
+ const handlers = this.eventHandlers.get(eventName);
51
+ if (!handlers) {
52
+ return;
53
+ }
54
+ handlers.forEach((handler) => {
55
+ handler(payload);
56
+ });
57
+ }
58
+ }
59
+
60
+ /** 小程序沙盒通信命名空间,父容器与 iframe 内 SDK 必须保持一致。 */
61
+ const MINI_PROGRAM_MESSAGE_NAMESPACE = 'heybox:miniprogram';
62
+ /** 小程序沙盒通信协议版本。 */
63
+ const MINI_PROGRAM_MESSAGE_VERSION = 1;
64
+ /** 父容器注入到小程序 URL 上的通信 nonce 参数名。 */
65
+ const MINI_PROGRAM_BRIDGE_NONCE_PARAM = 'hb_mini_bridge_nonce';
66
+ /** SDK 初始化时发给父容器的握手方法名。 */
67
+ const SDK_HANDSHAKE_METHOD = 'sdk.handshake';
68
+
69
+ /** 获取浏览器全局 window。 */
70
+ function getGlobalWindow() {
71
+ return typeof window === 'undefined' ? undefined : window;
72
+ }
73
+ /** 获取父窗口;非 iframe 环境返回 null。 */
74
+ function getParentWindow(currentWindow) {
75
+ if (!currentWindow || currentWindow.parent === currentWindow) {
76
+ return null;
77
+ }
78
+ return currentWindow.parent;
79
+ }
80
+ /** 从父容器注入到 URL 的 query 中读取 bridge nonce。 */
81
+ function readBridgeNonce(currentWindow) {
82
+ const href = currentWindow?.location?.href;
83
+ if (!href) {
84
+ return '';
85
+ }
86
+ try {
87
+ return new URL(href).searchParams.get(MINI_PROGRAM_BRIDGE_NONCE_PARAM) || '';
88
+ }
89
+ catch {
90
+ return '';
91
+ }
92
+ }
93
+ /** 创建请求 ID。 */
94
+ function createMessageId() {
95
+ const cryptoApi = getGlobalWindow()?.crypto;
96
+ if (cryptoApi?.randomUUID) {
97
+ return cryptoApi.randomUUID();
98
+ }
99
+ return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
100
+ }
101
+
102
+ /** 判断未知数据是否符合小程序 bridge 消息信封。 */
103
+ function isMiniProgramBridgeMessage(value) {
104
+ if (!value || typeof value !== 'object') {
105
+ return false;
106
+ }
107
+ const message = value;
108
+ return (message.namespace === MINI_PROGRAM_MESSAGE_NAMESPACE &&
109
+ message.version === MINI_PROGRAM_MESSAGE_VERSION &&
110
+ typeof message.nonce === 'string' &&
111
+ typeof message.type === 'string');
112
+ }
113
+
114
+ const DEFAULT_TIMEOUT = 10000;
115
+ /** 底层 bridge client,负责握手、请求响应、事件分发与超时清理。 */
116
+ class MiniProgramBridgeClient {
117
+ timeout;
118
+ selfWindow;
119
+ targetWindow;
120
+ nonce;
121
+ pendingRequests = new Map();
122
+ eventBus = new MiniProgramEventBus();
123
+ handleMessage;
124
+ started = false;
125
+ readySettled = false;
126
+ readyPromise;
127
+ resolveReady;
128
+ rejectReady;
129
+ readyTimer;
130
+ constructor(options = {}) {
131
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
132
+ this.selfWindow = options.selfWindow || getGlobalWindow();
133
+ this.targetWindow =
134
+ options.targetWindow === undefined ? getParentWindow(this.selfWindow) : options.targetWindow;
135
+ this.nonce = options.nonce || readBridgeNonce(this.selfWindow);
136
+ this.handleMessage = this.onMessage.bind(this);
137
+ this.readyPromise = new Promise((resolve, reject) => {
138
+ this.resolveReady = resolve;
139
+ this.rejectReady = reject;
140
+ });
141
+ this.readyPromise.catch(() => undefined);
142
+ }
143
+ /** 等待父容器握手完成。 */
144
+ ready() {
145
+ this.ensureStarted();
146
+ return this.readyPromise;
147
+ }
148
+ /** 注册小程序事件监听。 */
149
+ on(eventName, handler) {
150
+ this.ensureStarted();
151
+ return this.eventBus.on(eventName, handler);
152
+ }
153
+ /** 移除小程序事件监听。 */
154
+ off(eventName, handler) {
155
+ this.eventBus.off(eventName, handler);
156
+ }
157
+ /** 调用父容器开放能力。 */
158
+ async request(method, payload) {
159
+ await this.ready();
160
+ const id = createMessageId();
161
+ const message = {
162
+ namespace: MINI_PROGRAM_MESSAGE_NAMESPACE,
163
+ version: MINI_PROGRAM_MESSAGE_VERSION,
164
+ nonce: this.nonce,
165
+ type: 'request',
166
+ id,
167
+ method,
168
+ payload,
169
+ };
170
+ return new Promise((resolve, reject) => {
171
+ const timer = setTimeout(() => {
172
+ this.pendingRequests.delete(id);
173
+ reject(createSDKError('REQUEST_TIMEOUT', `${method} 调用超时`));
174
+ }, this.timeout);
175
+ this.pendingRequests.set(id, {
176
+ resolve: resolve,
177
+ reject,
178
+ timer,
179
+ });
180
+ try {
181
+ this.postMessage(message);
182
+ }
183
+ catch (error) {
184
+ clearTimeout(timer);
185
+ this.pendingRequests.delete(id);
186
+ reject(error);
187
+ }
188
+ });
189
+ }
190
+ /** 销毁 SDK 实例,并拒绝尚未完成的请求。 */
191
+ destroy() {
192
+ this.selfWindow?.removeEventListener('message', this.handleMessage);
193
+ this.clearReadyTimer();
194
+ this.rejectAllPending(createSDKError('SDK_DESTROYED', '小程序 SDK 已销毁'));
195
+ }
196
+ ensureStarted() {
197
+ if (this.started) {
198
+ return;
199
+ }
200
+ this.started = true;
201
+ if (!this.selfWindow || !this.targetWindow) {
202
+ this.failReady(createSDKError('NOT_IN_IFRAME', '当前页面不在小程序沙盒 iframe 中'));
203
+ return;
204
+ }
205
+ if (!this.nonce) {
206
+ this.failReady(createSDKError('MISSING_NONCE', '缺少小程序沙盒通信标识'));
207
+ return;
208
+ }
209
+ this.selfWindow.addEventListener('message', this.handleMessage);
210
+ this.readyTimer = setTimeout(() => {
211
+ this.failReady(createSDKError('READY_TIMEOUT', '小程序沙盒握手超时'));
212
+ }, this.timeout);
213
+ this.postMessage({
214
+ namespace: MINI_PROGRAM_MESSAGE_NAMESPACE,
215
+ version: MINI_PROGRAM_MESSAGE_VERSION,
216
+ nonce: this.nonce,
217
+ type: 'handshake',
218
+ id: createMessageId(),
219
+ method: SDK_HANDSHAKE_METHOD,
220
+ payload: {
221
+ href: this.selfWindow.location.href,
222
+ userAgent: this.selfWindow.navigator.userAgent,
223
+ },
224
+ });
225
+ }
226
+ onMessage(event) {
227
+ if (event.source && event.source !== this.targetWindow) {
228
+ return;
229
+ }
230
+ if (!isMiniProgramBridgeMessage(event.data) || event.data.nonce !== this.nonce) {
231
+ return;
232
+ }
233
+ if (event.data.type === 'event') {
234
+ this.handleEvent(event.data);
235
+ return;
236
+ }
237
+ if (event.data.type === 'response') {
238
+ this.handleResponse(event.data);
239
+ }
240
+ }
241
+ handleEvent(message) {
242
+ const eventName = message.method;
243
+ if (eventName === 'ready') {
244
+ this.resolveReadyOnce();
245
+ }
246
+ this.eventBus.emit(eventName, message.payload);
247
+ }
248
+ handleResponse(message) {
249
+ if (!message.id) {
250
+ return;
251
+ }
252
+ const pending = this.pendingRequests.get(message.id);
253
+ if (!pending) {
254
+ return;
255
+ }
256
+ clearTimeout(pending.timer);
257
+ this.pendingRequests.delete(message.id);
258
+ if (message.error) {
259
+ pending.reject(new HbMiniProgramSDKError(message.error));
260
+ return;
261
+ }
262
+ pending.resolve(message.payload);
263
+ }
264
+ postMessage(message) {
265
+ if (!this.targetWindow) {
266
+ throw createSDKError('NOT_IN_IFRAME', '当前页面不在小程序沙盒 iframe 中');
267
+ }
268
+ this.targetWindow.postMessage(message, '*');
269
+ }
270
+ resolveReadyOnce() {
271
+ if (this.readySettled) {
272
+ return;
273
+ }
274
+ this.readySettled = true;
275
+ this.clearReadyTimer();
276
+ this.resolveReady();
277
+ }
278
+ failReady(error) {
279
+ if (this.readySettled) {
280
+ return;
281
+ }
282
+ this.readySettled = true;
283
+ this.clearReadyTimer();
284
+ this.rejectReady(error);
285
+ }
286
+ clearReadyTimer() {
287
+ if (!this.readyTimer) {
288
+ return;
289
+ }
290
+ clearTimeout(this.readyTimer);
291
+ this.readyTimer = undefined;
292
+ }
293
+ rejectAllPending(error) {
294
+ this.pendingRequests.forEach((pending) => {
295
+ clearTimeout(pending.timer);
296
+ pending.reject(error);
297
+ });
298
+ this.pendingRequests.clear();
299
+ }
300
+ }
301
+
302
+ /** 截图分享能力方法名。 */
303
+ const SHARE_SCREENSHOT_METHOD = 'share.screenshot';
304
+ /** 截图并唤起分享。 */
305
+ function screenshot(requester, options) {
306
+ return requester.request(SHARE_SCREENSHOT_METHOD, options);
307
+ }
308
+
309
+ /** 展示分享面板能力方法名。 */
310
+ const SHARE_SHOW_SHARE_MENU_METHOD = 'share.showShareMenu';
311
+ /** 展示基础分享面板。 */
312
+ function showShareMenu(requester, options) {
313
+ return requester.request(SHARE_SHOW_SHARE_MENU_METHOD, options);
314
+ }
315
+
316
+ /** 创建分享模块。 */
317
+ function createShareModule(requester) {
318
+ return {
319
+ showShareMenu: options => showShareMenu(requester, options),
320
+ screenshot: options => screenshot(requester, options),
321
+ };
322
+ }
323
+
324
+ /** 用户信息能力方法名。 */
325
+ const USER_GET_INFO_METHOD = 'user.getInfo';
326
+ /**
327
+ * 获取当前访问小程序用户的公开基础资料。
328
+ *
329
+ * 该能力只返回 `heybox_id`、`nickname`、`avatar` 三个公开字段。
330
+ * 未登录时不会触发登录流程,直接返回 `{ isLogin: false, userInfo: null }`。
331
+ */
332
+ function getInfo(requester) {
333
+ return requester.request(USER_GET_INFO_METHOD);
334
+ }
335
+
336
+ /** 用户登录能力方法名。 */
337
+ const USER_LOGIN_METHOD = 'user.login';
338
+ /**
339
+ * 唤起黑盒登录流程,并在流程返回后刷新用户公开基础资料。
340
+ *
341
+ * 该能力不会向小程序暴露 token、cookie 或任何登录凭据。
342
+ */
343
+ function login(requester) {
344
+ return requester.request(USER_LOGIN_METHOD);
345
+ }
346
+
347
+ /** 创建用户模块。 */
348
+ function createUserModule(requester) {
349
+ return {
350
+ getInfo: () => getInfo(requester),
351
+ login: () => login(requester),
352
+ };
353
+ }
354
+
355
+ /** 系统窗口信息能力方法名。 */
356
+ const SYSTEM_GET_WINDOW_INFO_METHOD = 'system.getWindowInfo';
357
+ /**
358
+ * 获取当前小程序窗口信息。
359
+ */
360
+ function getWindowInfo(requester) {
361
+ return requester.request(SYSTEM_GET_WINDOW_INFO_METHOD);
362
+ }
363
+
364
+ /** 获取小程序隔离 storage 能力方法名。 */
365
+ const SYSTEM_GET_STORAGE_METHOD = 'system.getStorage';
366
+ /** 获取小程序隔离 storage。 */
367
+ function getStorage(requester, options) {
368
+ return requester.request(SYSTEM_GET_STORAGE_METHOD, options);
369
+ }
370
+
371
+ /** 写入小程序隔离 storage 能力方法名。 */
372
+ const SYSTEM_SET_STORAGE_METHOD = 'system.setStorage';
373
+ /** 写入小程序隔离 storage。 */
374
+ function setStorage(requester, options) {
375
+ return requester.request(SYSTEM_SET_STORAGE_METHOD, options);
376
+ }
377
+
378
+ /** 创建系统模块。 */
379
+ function createSystemModule(requester) {
380
+ return {
381
+ getWindowInfo: () => getWindowInfo(requester),
382
+ getStorage: options => getStorage(requester, options),
383
+ setStorage: options => setStorage(requester, options),
384
+ };
385
+ }
386
+
387
+ /** 外部小程序 SDK 实例。 */
388
+ class MiniProgramSDK {
389
+ client;
390
+ /** 用户相关开放能力。 */
391
+ user;
392
+ /** 分享相关开放能力。 */
393
+ share;
394
+ /** 系统相关开放能力。 */
395
+ system;
396
+ constructor(options) {
397
+ this.client = new MiniProgramBridgeClient(options);
398
+ this.user = createUserModule(this.client);
399
+ this.share = createShareModule(this.client);
400
+ this.system = createSystemModule(this.client);
401
+ }
402
+ /** 等待 SDK 与父容器完成握手。 */
403
+ ready() {
404
+ return this.client.ready();
405
+ }
406
+ /** 注册小程序生命周期或业务事件。 */
407
+ on(eventName, handler) {
408
+ return this.client.on(eventName, handler);
409
+ }
410
+ /** 移除小程序生命周期或业务事件。 */
411
+ off(eventName, handler) {
412
+ this.client.off(eventName, handler);
413
+ }
414
+ /** 销毁 SDK 实例。 */
415
+ destroy() {
416
+ this.client.destroy();
417
+ }
418
+ }
419
+ /** 创建独立 SDK 实例,主要用于测试或多实例场景。 */
420
+ function createMiniProgramSDK(options) {
421
+ return new MiniProgramSDK(options);
422
+ }
423
+
424
+ let defaultSDK = null;
425
+ function getDefaultSDK() {
426
+ if (!defaultSDK) {
427
+ defaultSDK = createMiniProgramSDK();
428
+ }
429
+ return defaultSDK;
430
+ }
431
+ /** 等待默认 SDK 实例与父容器完成握手。 */
432
+ function ready() {
433
+ return getDefaultSDK().ready();
434
+ }
435
+ /** 注册默认 SDK 实例的事件监听。 */
436
+ function on(eventName, handler) {
437
+ return getDefaultSDK().on(eventName, handler);
438
+ }
439
+ /** 移除默认 SDK 实例的事件监听。 */
440
+ function off(eventName, handler) {
441
+ getDefaultSDK().off(eventName, handler);
442
+ }
443
+ /** 默认 SDK 实例的用户模块。 */
444
+ const user = {
445
+ getInfo: () => getDefaultSDK().user.getInfo(),
446
+ login: () => getDefaultSDK().user.login(),
447
+ };
448
+ /** 默认 SDK 实例的分享模块。 */
449
+ const share = {
450
+ showShareMenu: options => getDefaultSDK().share.showShareMenu(options),
451
+ screenshot: options => getDefaultSDK().share.screenshot(options),
452
+ };
453
+ /** 默认 SDK 实例的系统模块。 */
454
+ const system = {
455
+ getWindowInfo: () => getDefaultSDK().system.getWindowInfo(),
456
+ getStorage: options => getDefaultSDK().system.getStorage(options),
457
+ setStorage: options => getDefaultSDK().system.setStorage(options),
458
+ };
459
+
460
+ const hbSDK = {
461
+ ready,
462
+ on,
463
+ off,
464
+ user,
465
+ share,
466
+ system,
467
+ };
468
+
469
+ exports.HbMiniProgramSDKError = HbMiniProgramSDKError;
470
+ exports.MINI_PROGRAM_BRIDGE_NONCE_PARAM = MINI_PROGRAM_BRIDGE_NONCE_PARAM;
471
+ exports.MINI_PROGRAM_MESSAGE_NAMESPACE = MINI_PROGRAM_MESSAGE_NAMESPACE;
472
+ exports.MINI_PROGRAM_MESSAGE_VERSION = MINI_PROGRAM_MESSAGE_VERSION;
473
+ exports.MiniProgramSDK = MiniProgramSDK;
474
+ exports.SDK_HANDSHAKE_METHOD = SDK_HANDSHAKE_METHOD;
475
+ exports.SHARE_SCREENSHOT_METHOD = SHARE_SCREENSHOT_METHOD;
476
+ exports.SHARE_SHOW_SHARE_MENU_METHOD = SHARE_SHOW_SHARE_MENU_METHOD;
477
+ exports.SYSTEM_GET_STORAGE_METHOD = SYSTEM_GET_STORAGE_METHOD;
478
+ exports.SYSTEM_GET_WINDOW_INFO_METHOD = SYSTEM_GET_WINDOW_INFO_METHOD;
479
+ exports.SYSTEM_SET_STORAGE_METHOD = SYSTEM_SET_STORAGE_METHOD;
480
+ exports.USER_GET_INFO_METHOD = USER_GET_INFO_METHOD;
481
+ exports.USER_LOGIN_METHOD = USER_LOGIN_METHOD;
482
+ exports.createMiniProgramSDK = createMiniProgramSDK;
483
+ exports.createShareModule = createShareModule;
484
+ exports.createSystemModule = createSystemModule;
485
+ exports.createUserModule = createUserModule;
486
+ exports.default = hbSDK;
487
+ exports.getInfo = getInfo;
488
+ exports.getStorage = getStorage;
489
+ exports.getWindowInfo = getWindowInfo;
490
+ exports.isMiniProgramBridgeMessage = isMiniProgramBridgeMessage;
491
+ exports.login = login;
492
+ exports.off = off;
493
+ exports.on = on;
494
+ exports.ready = ready;
495
+ exports.screenshot = screenshot;
496
+ exports.setStorage = setStorage;
497
+ exports.share = share;
498
+ exports.showShareMenu = showShareMenu;
499
+ exports.system = system;
500
+ exports.user = user;