@heybox/hb-sdk 0.7.0-alpha.5 → 0.7.0-alpha.9
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/CHANGELOG.md +10 -0
- package/README.md +17 -16
- package/dist/cli-chunks/{build-35mI_IwM.cjs → build-d6CG6E09.cjs} +2 -2
- package/dist/cli-chunks/{context-DysC5qrh.cjs → context-C2bPKZMJ.cjs} +1 -1
- package/dist/cli-chunks/{create-BJSQzUeZ.cjs → create-Dzj1G1uW.cjs} +1 -1
- package/dist/cli-chunks/{dev-DiGtxt4q.cjs → dev-CExhyf96.cjs} +5 -5
- package/dist/cli-chunks/{doctor-DWWVwcd7.cjs → doctor-CaGZvOq1.cjs} +1 -1
- package/dist/cli-chunks/{index-CgCp6jGG.cjs → index-CrB1p5zm.cjs} +14 -14
- package/dist/cli-chunks/{index-k5wJ6TMJ.cjs → index-DVBUJhJU.cjs} +1 -1
- package/dist/cli-chunks/{login-CzkMdcXM.cjs → login-zHEHgagr.cjs} +2 -2
- package/dist/cli-chunks/{project-vite-TBMlJw8y.cjs → project-vite-CqOeeGgH.cjs} +1 -1
- package/dist/cli-chunks/{remote-DE9DIxqw.cjs → remote-C9FtSxmc.cjs} +4 -4
- package/dist/cli-chunks/{session--DdaUkcq.cjs → session-DnkyC4Xj.cjs} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/devtools/mock-host/main.js +43 -23
- package/dist/index.cjs.js +108 -68
- package/dist/index.esm.js +107 -68
- package/dist/protocol.cjs.js +19 -0
- package/dist/protocol.esm.js +19 -1
- package/dist/templates/vue3-vite-ts/src/App.vue +29 -19
- package/dist/templates/vue3-vite-ts/src/__tests__/App.spec.ts +82 -9
- package/dist/vite.cjs.js +1 -1
- package/dist/vite.esm.js +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +8 -7
- package/skill/references/api-protocol.md +6 -2
- package/skill/references/api-root.md +65 -19
- package/skill/references/examples.md +30 -9
- package/skill/references/recipes.md +48 -26
- package/skill/references/safety-boundaries.md +3 -1
- package/skill/references/smoke-evaluation.md +3 -1
- package/skill/scripts/sync-references.mjs +31 -9
- package/skill/skill.json +4 -4
- package/types/core/client.d.ts +15 -9
- package/types/core/handshake-state.d.ts +24 -0
- package/types/core/sdk.d.ts +6 -17
- package/types/core/singleton.d.ts +5 -7
- package/types/index.d.ts +5 -3
- package/types/protocol/trusted-user-gesture.d.ts +6 -0
- package/types/protocol/types.d.ts +1 -1
- package/types/protocol.d.ts +1 -0
package/dist/protocol.esm.js
CHANGED
|
@@ -30,6 +30,24 @@ function isMiniProgramBridgeMessage(value) {
|
|
|
30
30
|
typeof message.type === 'string');
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function readBrowserEnvironment() {
|
|
34
|
+
try {
|
|
35
|
+
return {
|
|
36
|
+
activeElement: globalThis.document?.activeElement ?? null,
|
|
37
|
+
userActivation: globalThis.navigator?.userActivation,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return { activeElement: null };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** 按当前浏览器消息、用户激活和目标 iframe 焦点生成请求级可信手势快照。 */
|
|
45
|
+
function computeTrustedIframeUserGesture(event, iframe, environment = readBrowserEnvironment()) {
|
|
46
|
+
return (event.isTrusted === true
|
|
47
|
+
&& environment.userActivation?.isActive === true
|
|
48
|
+
&& environment.activeElement === iframe);
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
const MANAGED_RUNTIME_PERMISSION_KEYS = new Set(['network.request']);
|
|
34
52
|
/**
|
|
35
53
|
* 判断 permission key 是否由 Runtime 权限快照管理。
|
|
@@ -433,4 +451,4 @@ const MINI_PROGRAM_PROTOCOL_CAPABILITIES = [
|
|
|
433
451
|
},
|
|
434
452
|
];
|
|
435
453
|
|
|
436
|
-
export { AUTH_LOGIN_METHOD, CLOUD_LEADERBOARD_DELETE_CURRENT_USER_ENTRY_METHOD, CLOUD_LEADERBOARD_GET_CURRENT_USER_ENTRY_METHOD, CLOUD_LEADERBOARD_GET_INFO_METHOD, CLOUD_LEADERBOARD_GET_LIST_METHOD, CLOUD_LEADERBOARD_SUBMIT_METHOD, DEVICE_SET_CLIPBOARD_METHOD, DEVICE_VIBRATE_METHOD, MINI_PROGRAM_BRIDGE_NONCE_PARAM, MINI_PROGRAM_MESSAGE_NAMESPACE, MINI_PROGRAM_MESSAGE_VERSION, MINI_PROGRAM_NETWORK_PROTECTED_PATHS, MINI_PROGRAM_PROTOCOL_CAPABILITIES, NAVIGATION_CLOSE_METHOD, NAVIGATION_OPEN_APP_PAGE_METHOD, NAVIGATION_RELOAD_METHOD, NETWORK_REQUEST_METHOD, RUNTIME_LOCATION_PROBE_METHOD, SDK_CSP_VIOLATION_METHOD, SDK_HANDSHAKE_METHOD, SDK_LOCATION_REPORT_METHOD, SHARE_SCREENSHOT_METHOD, SHARE_SHOW_SHARE_MENU_METHOD, STORAGE_GET_STORAGE_METHOD, STORAGE_SET_STORAGE_METHOD, UI_HIDE_LOADING_METHOD, UI_SHOW_LOADING_METHOD, UI_SHOW_TOAST_METHOD, USER_GET_INFO_METHOD, USER_GET_LOCAL_IDENTITY_METHOD, USER_GET_STEAM_GAME_LIST_METHOD, USER_REVOKE_AUTHORIZATION_METHOD, VIEWPORT_GET_WINDOW_INFO_METHOD, VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD, isManagedMiniProgramRuntimePermissionKey, isMiniProgramBridgeMessage, isOfficialMiniProgramNetworkUrl, isProtectedMiniProgramNetworkPath, normalizeMiniProgramNetworkPathname, parseMiniProgramRuntimePermissions };
|
|
454
|
+
export { AUTH_LOGIN_METHOD, CLOUD_LEADERBOARD_DELETE_CURRENT_USER_ENTRY_METHOD, CLOUD_LEADERBOARD_GET_CURRENT_USER_ENTRY_METHOD, CLOUD_LEADERBOARD_GET_INFO_METHOD, CLOUD_LEADERBOARD_GET_LIST_METHOD, CLOUD_LEADERBOARD_SUBMIT_METHOD, DEVICE_SET_CLIPBOARD_METHOD, DEVICE_VIBRATE_METHOD, MINI_PROGRAM_BRIDGE_NONCE_PARAM, MINI_PROGRAM_MESSAGE_NAMESPACE, MINI_PROGRAM_MESSAGE_VERSION, MINI_PROGRAM_NETWORK_PROTECTED_PATHS, MINI_PROGRAM_PROTOCOL_CAPABILITIES, NAVIGATION_CLOSE_METHOD, NAVIGATION_OPEN_APP_PAGE_METHOD, NAVIGATION_RELOAD_METHOD, NETWORK_REQUEST_METHOD, RUNTIME_LOCATION_PROBE_METHOD, SDK_CSP_VIOLATION_METHOD, SDK_HANDSHAKE_METHOD, SDK_LOCATION_REPORT_METHOD, SHARE_SCREENSHOT_METHOD, SHARE_SHOW_SHARE_MENU_METHOD, STORAGE_GET_STORAGE_METHOD, STORAGE_SET_STORAGE_METHOD, UI_HIDE_LOADING_METHOD, UI_SHOW_LOADING_METHOD, UI_SHOW_TOAST_METHOD, USER_GET_INFO_METHOD, USER_GET_LOCAL_IDENTITY_METHOD, USER_GET_STEAM_GAME_LIST_METHOD, USER_REVOKE_AUTHORIZATION_METHOD, VIEWPORT_GET_WINDOW_INFO_METHOD, VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD, computeTrustedIframeUserGesture, isManagedMiniProgramRuntimePermissionKey, isMiniProgramBridgeMessage, isOfficialMiniProgramNetworkUrl, isProtectedMiniProgramNetworkPath, normalizeMiniProgramNetworkPathname, parseMiniProgramRuntimePermissions };
|
|
@@ -3,15 +3,11 @@
|
|
|
3
3
|
<section class="card">
|
|
4
4
|
<p class="eyebrow">Heybox Mini Program</p>
|
|
5
5
|
<h1><%= projectName %></h1>
|
|
6
|
-
<p class="intro">
|
|
7
|
-
这个模板只保留最小接入示例:等待 SDK ready,并由用户主动选择受支持的操作。
|
|
8
|
-
</p>
|
|
6
|
+
<p class="intro">这个模板只保留最小接入示例:SDK 会自动完成初始化,由用户主动选择受支持的操作。</p>
|
|
9
7
|
|
|
10
8
|
<p class="status" data-testid="status">{{ status }}</p>
|
|
11
9
|
|
|
12
|
-
<button type="button" :disabled="loading" data-testid="login" @click="login">
|
|
13
|
-
获取授权码
|
|
14
|
-
</button>
|
|
10
|
+
<button type="button" :disabled="loading || !sdkReady" data-testid="login" @click="login">获取授权码</button>
|
|
15
11
|
|
|
16
12
|
<pre data-testid="output">{{ output }}</pre>
|
|
17
13
|
</section>
|
|
@@ -20,28 +16,37 @@
|
|
|
20
16
|
|
|
21
17
|
<script setup lang="ts">
|
|
22
18
|
import hbSDK from '@heybox/hb-sdk';
|
|
23
|
-
import {
|
|
19
|
+
import type { MiniProgramSDKHandshakeState } from '@heybox/hb-sdk';
|
|
20
|
+
import { onBeforeUnmount, ref } from 'vue';
|
|
24
21
|
import { loginAndExchangeAuthorizationCode } from './auth-handoff';
|
|
25
22
|
|
|
26
23
|
const loading = ref(false);
|
|
24
|
+
const sdkReady = ref(false);
|
|
27
25
|
const status = ref('正在连接 SDK...');
|
|
28
|
-
const output = ref('
|
|
26
|
+
const output = ref('请选择操作。');
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
function updateHandshakeState(state: MiniProgramSDKHandshakeState) {
|
|
29
|
+
if (state.status === 'connecting') {
|
|
30
|
+
sdkReady.value = false;
|
|
31
|
+
status.value = '正在连接 SDK...';
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
await hbSDK.ready();
|
|
37
|
-
status.value = 'SDK 已连接';
|
|
38
|
-
output.value = '请选择操作。';
|
|
39
|
-
} catch (error) {
|
|
35
|
+
if (state.status === 'failed') {
|
|
36
|
+
sdkReady.value = false;
|
|
40
37
|
status.value = 'SDK 连接失败';
|
|
41
|
-
output.value = readErrorMessage(error);
|
|
38
|
+
output.value = readErrorMessage(state.error);
|
|
39
|
+
return;
|
|
42
40
|
}
|
|
41
|
+
|
|
42
|
+
sdkReady.value = true;
|
|
43
|
+
status.value = 'SDK 已连接';
|
|
44
|
+
output.value = '请选择操作。';
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
const stopHandshakeState = hbSDK.onHandshakeStateChange(updateHandshakeState);
|
|
48
|
+
onBeforeUnmount(stopHandshakeState);
|
|
49
|
+
|
|
45
50
|
async function login() {
|
|
46
51
|
loading.value = true;
|
|
47
52
|
status.value = 'auth.login 调用中...';
|
|
@@ -63,6 +68,11 @@ async function login() {
|
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
function readErrorMessage(error: unknown) {
|
|
66
|
-
|
|
71
|
+
if (!(error instanceof Error)) {
|
|
72
|
+
return String(error);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const code = 'code' in error && typeof error.code === 'string' ? `\ncode: ${error.code}` : '';
|
|
76
|
+
return `${error.name}: ${error.message}${code}`;
|
|
67
77
|
}
|
|
68
78
|
</script>
|
|
@@ -3,9 +3,37 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
3
3
|
import App from '../App.vue';
|
|
4
4
|
|
|
5
5
|
const sdkMock = vi.hoisted(() => ({
|
|
6
|
+
...(() => {
|
|
7
|
+
type HandshakeState = { status: 'connecting' } | { status: 'ready' } | { status: 'failed'; error: Error & { code: string } };
|
|
8
|
+
|
|
9
|
+
let state: HandshakeState = { status: 'connecting' };
|
|
10
|
+
const handlers = new Set<(nextState: HandshakeState) => void>();
|
|
11
|
+
const unsubscribe = vi.fn();
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
getHandshakeState: vi.fn(() => state),
|
|
15
|
+
onHandshakeStateChange: vi.fn((handler: (nextState: HandshakeState) => void) => {
|
|
16
|
+
handlers.add(handler);
|
|
17
|
+
handler(state);
|
|
18
|
+
return () => {
|
|
19
|
+
unsubscribe();
|
|
20
|
+
handlers.delete(handler);
|
|
21
|
+
};
|
|
22
|
+
}),
|
|
23
|
+
reset(nextState: HandshakeState = { status: 'connecting' }) {
|
|
24
|
+
state = nextState;
|
|
25
|
+
handlers.clear();
|
|
26
|
+
unsubscribe.mockReset();
|
|
27
|
+
},
|
|
28
|
+
setHandshakeState(nextState: HandshakeState) {
|
|
29
|
+
state = nextState;
|
|
30
|
+
handlers.forEach((handler) => handler(state));
|
|
31
|
+
},
|
|
32
|
+
unsubscribe,
|
|
33
|
+
};
|
|
34
|
+
})(),
|
|
6
35
|
login: vi.fn(),
|
|
7
36
|
networkRequest: vi.fn(),
|
|
8
|
-
ready: vi.fn(),
|
|
9
37
|
}));
|
|
10
38
|
|
|
11
39
|
vi.mock('@heybox/hb-sdk', () => ({
|
|
@@ -16,34 +44,79 @@ vi.mock('@heybox/hb-sdk', () => ({
|
|
|
16
44
|
network: {
|
|
17
45
|
request: sdkMock.networkRequest,
|
|
18
46
|
},
|
|
19
|
-
|
|
47
|
+
getHandshakeState: sdkMock.getHandshakeState,
|
|
48
|
+
onHandshakeStateChange: sdkMock.onHandshakeStateChange,
|
|
20
49
|
},
|
|
21
50
|
}));
|
|
22
51
|
|
|
23
52
|
describe('App', () => {
|
|
24
53
|
beforeEach(() => {
|
|
25
|
-
vi.
|
|
54
|
+
vi.clearAllMocks();
|
|
26
55
|
vi.unstubAllEnvs();
|
|
27
56
|
|
|
28
|
-
sdkMock.
|
|
57
|
+
sdkMock.reset();
|
|
29
58
|
sdkMock.login.mockResolvedValue({ code: 'authorization_code', expiresIn: 300, scopes: ['identity'] });
|
|
30
59
|
sdkMock.networkRequest.mockResolvedValue({ status: 200 });
|
|
31
60
|
});
|
|
32
61
|
|
|
33
|
-
it('
|
|
62
|
+
it('keeps actions disabled while the SDK is connecting', () => {
|
|
63
|
+
const wrapper = mount(App);
|
|
64
|
+
|
|
65
|
+
expect(sdkMock.getHandshakeState).not.toHaveBeenCalled();
|
|
66
|
+
expect(wrapper.get('[data-testid="status"]').text()).toBe('正在连接 SDK...');
|
|
67
|
+
expect(wrapper.get<HTMLButtonElement>('[data-testid="login"]').element.disabled).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('enables actions when the handshake becomes ready', async () => {
|
|
34
71
|
const wrapper = mount(App);
|
|
72
|
+
|
|
73
|
+
sdkMock.setHandshakeState({ status: 'ready' });
|
|
35
74
|
await flushPromises();
|
|
36
75
|
|
|
37
|
-
expect(sdkMock.ready).toHaveBeenCalledTimes(1);
|
|
38
|
-
expect(sdkMock.login).not.toHaveBeenCalled();
|
|
39
76
|
expect(wrapper.get('[data-testid="status"]').text()).toBe('SDK 已连接');
|
|
40
77
|
expect(wrapper.get('[data-testid="output"]').text()).toContain('请选择操作');
|
|
78
|
+
expect(wrapper.get<HTMLButtonElement>('[data-testid="login"]').element.disabled).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('late mount', () => {
|
|
82
|
+
it('renders an already-ready handshake immediately', () => {
|
|
83
|
+
sdkMock.reset({ status: 'ready' });
|
|
84
|
+
|
|
85
|
+
const wrapper = mount(App);
|
|
86
|
+
|
|
87
|
+
expect(wrapper.get('[data-testid="status"]').text()).toBe('SDK 已连接');
|
|
88
|
+
expect(wrapper.get<HTMLButtonElement>('[data-testid="login"]').element.disabled).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('renders the original failed handshake details immediately', () => {
|
|
92
|
+
const error = Object.assign(new Error('Host 未响应'), {
|
|
93
|
+
code: 'HANDSHAKE_TIMEOUT',
|
|
94
|
+
name: 'HbMiniProgramSDKError',
|
|
95
|
+
});
|
|
96
|
+
sdkMock.reset({ status: 'failed', error });
|
|
97
|
+
|
|
98
|
+
const wrapper = mount(App);
|
|
99
|
+
|
|
100
|
+
expect(wrapper.get('[data-testid="status"]').text()).toBe('SDK 连接失败');
|
|
101
|
+
expect(wrapper.get<HTMLButtonElement>('[data-testid="login"]').element.disabled).toBe(true);
|
|
102
|
+
expect(wrapper.get('[data-testid="output"]').text()).toContain('HbMiniProgramSDKError');
|
|
103
|
+
expect(wrapper.get('[data-testid="output"]').text()).toContain('Host 未响应');
|
|
104
|
+
expect(wrapper.get('[data-testid="output"]').text()).toContain('HANDSHAKE_TIMEOUT');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('stops observing handshake state after unmount', () => {
|
|
109
|
+
const wrapper = mount(App);
|
|
110
|
+
|
|
111
|
+
wrapper.unmount();
|
|
112
|
+
|
|
113
|
+
expect(sdkMock.unsubscribe).toHaveBeenCalledTimes(1);
|
|
41
114
|
});
|
|
42
115
|
|
|
43
116
|
it('hands the authorization code to the developer server without rendering it', async () => {
|
|
44
117
|
vi.stubEnv('VITE_HEYBOX_AUTH_EXCHANGE_URL', 'https://developer.example.com/api/heybox/exchange');
|
|
118
|
+
sdkMock.reset({ status: 'ready' });
|
|
45
119
|
const wrapper = mount(App);
|
|
46
|
-
await flushPromises();
|
|
47
120
|
|
|
48
121
|
await wrapper.get('[data-testid="login"]').trigger('click');
|
|
49
122
|
await flushPromises();
|
|
@@ -61,8 +134,8 @@ describe('App', () => {
|
|
|
61
134
|
});
|
|
62
135
|
|
|
63
136
|
it('does not request a code when the developer server is not configured', async () => {
|
|
137
|
+
sdkMock.reset({ status: 'ready' });
|
|
64
138
|
const wrapper = mount(App);
|
|
65
|
-
await flushPromises();
|
|
66
139
|
|
|
67
140
|
await wrapper.get('[data-testid="login"]').trigger('click');
|
|
68
141
|
await flushPromises();
|
package/dist/vite.cjs.js
CHANGED
package/dist/vite.esm.js
CHANGED
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hb-sdk
|
|
3
|
-
description: Uses @heybox/hb-sdk and its hb-sdk CLI to build, debug, review, and publish workshop mini-programs for the Heybox App. Use for public SDK APIs, lifecycle events, hb-sdk create/dev/login/doctor, browser Mock and device debugging, or publishing checks. Don't use for private credentials, internal Heybox client protocols, private package paths, or non-Heybox SDKs.
|
|
3
|
+
description: Uses @heybox/hb-sdk and its hb-sdk CLI to build, debug, review, and publish workshop mini-programs for the Heybox App. Use for public SDK APIs, persistent handshake state, lifecycle events, hb-sdk create/dev/login/doctor, browser Mock and device debugging, or publishing checks. Don't use for private credentials, internal Heybox client protocols, private package paths, or non-Heybox SDKs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hb-sdk Agent Procedure
|
|
@@ -38,17 +38,18 @@ Apply these instructions when writing, reviewing, or debugging code that consume
|
|
|
38
38
|
|
|
39
39
|
## Step 4: Implement workshop mini-program code
|
|
40
40
|
|
|
41
|
-
1.
|
|
41
|
+
1. Import the root package eagerly; the SDK starts its handshake automatically and capability calls wait for it internally.
|
|
42
42
|
2. For a network-enabled mini-program, use `auth.login({ scopes? })` to obtain `{ code, expiresIn: 300, scopes }`. Identity is always implicit; request only optional scopes the business actually needs.
|
|
43
43
|
3. Send the code only to the developer's backend. That backend follows the backend OpenAPI documentation to exchange it for a token; mini-program code must never exchange application credentials itself.
|
|
44
44
|
4. For a network-disabled mini-program, call `user.getLocalIdentity()` from a trusted user action to obtain `{ appUserId }`, because first login or identity consent may require UI. It cannot obtain a code or call OpenAPI.
|
|
45
45
|
5. Handle `HbMiniProgramSDKError` for SDK initialization and capability failures.
|
|
46
46
|
6. Handle `HbMiniProgramNetworkError` separately when HTTP completed but `validateStatus` rejected the status.
|
|
47
|
-
7.
|
|
48
|
-
8.
|
|
49
|
-
9.
|
|
50
|
-
10. `user.
|
|
51
|
-
11.
|
|
47
|
+
7. Use `getHandshakeState()` for the current persistent handshake state and `onHandshakeStateChange()` for an immediate replay plus future changes. Cancel the returned subscription when the page or component unmounts.
|
|
48
|
+
8. Treat the non-replayed `ready` lifecycle event only as an edge notification. After a duplicate handshake it may repeat, so never use it as state or as the source for enabling a late-mounted control.
|
|
49
|
+
9. Use the default SDK instance exposed by the root package. For gesture-gated controls, initialize from the current handshake state, subscribe to changes, and enable the control only when `status === 'ready'`. Keep calls such as `auth.login()` in the original user-action callback; do not wait for handshake inside that callback.
|
|
50
|
+
10. When authorization UI is required, call `auth.login()` or `user.getLocalIdentity()` from a trusted user action. Missing gesture returns `USER_GESTURE_REQUIRED`; cancelling, rejecting, or closing returns `AUTHORIZATION_CANCELLED` and does not confirm or change authorization. An already-authorized `auth.login()` request may return a new code silently.
|
|
51
|
+
11. `user.getSteamGameList()` is available only to network-disabled mini-programs. In a network-enabled mini-program it returns `SERVER_API_REQUIRED`, and there is no Steam library authorization scope or OpenAPI resource. Treat `user.getInfo()` and `user.getLocalIdentity()` returning `SERVER_API_REQUIRED` as a server-boundary signal, not as logged-out state.
|
|
52
|
+
12. Use `share.showShareMenu({ post })` or `share.screenshot({ post })` to preset editable community destinations and topics. Pass partition IDs through `topicIds` and topic text without surrounding `#` through `topics`; do not construct the underlying client post protocol.
|
|
52
53
|
|
|
53
54
|
## Step 5: Use CLI workflows
|
|
54
55
|
|
|
@@ -26,6 +26,10 @@ export {
|
|
|
26
26
|
SDK_HANDSHAKE_METHOD,
|
|
27
27
|
} from './protocol/constants';
|
|
28
28
|
export { isMiniProgramBridgeMessage } from './protocol/guards';
|
|
29
|
+
export {
|
|
30
|
+
computeTrustedIframeUserGesture,
|
|
31
|
+
type TrustedIframeUserGestureEnvironment,
|
|
32
|
+
} from './protocol/trusted-user-gesture';
|
|
29
33
|
export { isManagedMiniProgramRuntimePermissionKey, parseMiniProgramRuntimePermissions } from './protocol/runtime-permissions';
|
|
30
34
|
export {
|
|
31
35
|
isOfficialMiniProgramNetworkUrl,
|
|
@@ -207,7 +211,7 @@ Reference 由 `@heybox/hb-sdk` 的公开导出与源码注释自动生成,不
|
|
|
207
211
|
|
|
208
212
|
| 导出面 | Classes | Functions | Interfaces | Types | Constants |
|
|
209
213
|
| --- | ---: | ---: | ---: | ---: | ---: |
|
|
210
|
-
| Root API | 2 |
|
|
211
|
-
| Protocol API | 0 |
|
|
214
|
+
| Root API | 2 | 4 | 51 | 54 | 0 |
|
|
215
|
+
| Protocol API | 0 | 7 | 47 | 65 | 31 |
|
|
212
216
|
| Miniapp Publish API | 0 | 5 | 2 | 0 | 0 |
|
|
213
217
|
| Vite API | 0 | 1 | 0 | 0 | 0 |
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
## Package metadata
|
|
25
25
|
|
|
26
26
|
- Package: `@heybox/hb-sdk`
|
|
27
|
-
- Version at generation time: `0.7.0-alpha.
|
|
27
|
+
- Version at generation time: `0.7.0-alpha.9`
|
|
28
28
|
- Public root export: `@heybox/hb-sdk`
|
|
29
29
|
- Protocol export: `@heybox/hb-sdk/protocol`
|
|
30
30
|
- Vite plugin export: `@heybox/hb-sdk/vite`
|
|
@@ -33,7 +33,23 @@
|
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
35
|
export { HbMiniProgramSDKError, HbMiniProgramNetworkError } from './core/errors';
|
|
36
|
-
export {
|
|
36
|
+
export {
|
|
37
|
+
getHandshakeState,
|
|
38
|
+
onHandshakeStateChange,
|
|
39
|
+
on,
|
|
40
|
+
off,
|
|
41
|
+
auth,
|
|
42
|
+
user,
|
|
43
|
+
share,
|
|
44
|
+
viewport,
|
|
45
|
+
storage,
|
|
46
|
+
network,
|
|
47
|
+
ui,
|
|
48
|
+
device,
|
|
49
|
+
navigation,
|
|
50
|
+
cloud,
|
|
51
|
+
} from './core/singleton';
|
|
52
|
+
export type { MiniProgramSDKHandshakeState, MiniProgramSDKHandshakeStateHandler } from './core/handshake-state';
|
|
37
53
|
export type { MiniProgramEventHandler, MiniProgramEventName, MiniProgramEventPayloadMap } from './protocol/types';
|
|
38
54
|
export type { LoginOptions, LoginPayload, LoginResult, LoginScope, MiniProgramAuthModule } from './modules/auth';
|
|
39
55
|
export type {
|
|
@@ -145,10 +161,26 @@ export type {
|
|
|
145
161
|
ReloadResult,
|
|
146
162
|
} from './modules/navigation';
|
|
147
163
|
|
|
148
|
-
import {
|
|
164
|
+
import {
|
|
165
|
+
auth,
|
|
166
|
+
cloud,
|
|
167
|
+
device,
|
|
168
|
+
getHandshakeState,
|
|
169
|
+
navigation,
|
|
170
|
+
network,
|
|
171
|
+
off,
|
|
172
|
+
on,
|
|
173
|
+
onHandshakeStateChange,
|
|
174
|
+
share,
|
|
175
|
+
storage,
|
|
176
|
+
ui,
|
|
177
|
+
viewport,
|
|
178
|
+
user,
|
|
179
|
+
} from './core/singleton';
|
|
149
180
|
|
|
150
181
|
const hbSDK = {
|
|
151
|
-
|
|
182
|
+
getHandshakeState,
|
|
183
|
+
onHandshakeStateChange,
|
|
152
184
|
on,
|
|
153
185
|
off,
|
|
154
186
|
auth,
|
|
@@ -360,7 +392,26 @@ hb-sdk build [--env <name>] [--verbose]
|
|
|
360
392
|
|
|
361
393
|
# 快速开始
|
|
362
394
|
|
|
363
|
-
如果页面不需要小黑盒开放能力,可以不接入 SDK
|
|
395
|
+
如果页面不需要小黑盒开放能力,可以不接入 SDK。SDK 导入时会自动启动握手,能力调用会自动等待;业务只需根据小程序是否开通网络权限选择身份流程。
|
|
396
|
+
|
|
397
|
+
## 交互控件与握手状态
|
|
398
|
+
|
|
399
|
+
`auth.login()`、`user.getLocalIdentity()` 等可能展示授权 UI 的能力必须保留原始可信用户手势。不要在点击回调中等待握手;页面加载时读取并订阅持久握手状态,只在 `status === 'ready'` 时启用按钮:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
import hbSDK, { type MiniProgramSDKHandshakeState } from '@heybox/hb-sdk'
|
|
403
|
+
import { computed, onUnmounted, ref } from 'vue'
|
|
404
|
+
|
|
405
|
+
const handshakeState = ref<MiniProgramSDKHandshakeState>(hbSDK.getHandshakeState())
|
|
406
|
+
const stopHandshakeState = hbSDK.onHandshakeStateChange((state) => {
|
|
407
|
+
handshakeState.value = state
|
|
408
|
+
})
|
|
409
|
+
const sdkReady = computed(() => handshakeState.value.status === 'ready')
|
|
410
|
+
|
|
411
|
+
onUnmounted(stopHandshakeState)
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
把 `sdkReady` 用作按钮的 `disabled` 条件。订阅会立即回放当前状态,因此晚挂载组件也能得到 `ready` 或 `failed` 终态。`ready` 生命周期事件只在握手成功瞬间派发且不会重放,不能作为当前状态来源。
|
|
364
415
|
|
|
365
416
|
## 未开通网络权限
|
|
366
417
|
|
|
@@ -370,8 +421,6 @@ hb-sdk build [--env <name>] [--verbose]
|
|
|
370
421
|
import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
|
|
371
422
|
|
|
372
423
|
async function getLocalIdentityFromUserAction() {
|
|
373
|
-
await hbSDK.ready()
|
|
374
|
-
|
|
375
424
|
try {
|
|
376
425
|
const { appUserId } = await hbSDK.user.getLocalIdentity()
|
|
377
426
|
return appUserId
|
|
@@ -390,11 +439,11 @@ async function getLocalIdentityFromUserAction() {
|
|
|
390
439
|
|
|
391
440
|
有网络小程序通过 `auth.login()` 获取短期授权码。登录可能展示 UI,因此应绑定到按钮点击等明确的用户操作:
|
|
392
441
|
|
|
442
|
+
SDK 会自动完成握手。登录按钮应按上面的持久握手状态启用;点击回调直接调用 `auth.login()`,不要先等待其他异步任务,以免首次操作丢失可信手势。
|
|
443
|
+
|
|
393
444
|
```ts
|
|
394
445
|
import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
|
|
395
446
|
|
|
396
|
-
await hbSDK.ready()
|
|
397
|
-
|
|
398
447
|
async function loginFromUserAction() {
|
|
399
448
|
try {
|
|
400
449
|
const result = await hbSDK.auth.login({ scopes: ['profile'] })
|
|
@@ -413,11 +462,7 @@ async function loginFromUserAction() {
|
|
|
413
462
|
}
|
|
414
463
|
```
|
|
415
464
|
|
|
416
|
-
返回值与边界以[用户身份与登录](/guide/auth)为准。页面只把 `code` 提交给自己的服务端。不要在页面初始化阶段自动调用 `auth.login()`。
|
|
417
|
-
|
|
418
|
-
## ready 的含义
|
|
419
|
-
|
|
420
|
-
`ready()` 只表示 SDK 已就绪,可以安全调用开放能力。它不表示已经取得用户授权,也不返回用户身份。
|
|
465
|
+
返回值与边界以[用户身份与登录](https://docs.xiaoheihe.cn/hb_sdk/guide/auth)为准。页面只把 `code` 提交给自己的服务端。不要在页面初始化阶段自动调用 `auth.login()`。
|
|
421
466
|
|
|
422
467
|
## 默认单例
|
|
423
468
|
|
|
@@ -475,7 +520,6 @@ type LoginViewState =
|
|
|
475
520
|
|
|
476
521
|
async function loginFromUserAction(): Promise<LoginViewState> {
|
|
477
522
|
try {
|
|
478
|
-
await hbSDK.ready()
|
|
479
523
|
const result = await hbSDK.auth.login()
|
|
480
524
|
await exchangeCodeWithDeveloperServer(result.code)
|
|
481
525
|
return { status: 'ready' }
|
|
@@ -511,7 +555,7 @@ function reportSDKError(errorCode: string, message: string, data?: unknown) {
|
|
|
511
555
|
|
|
512
556
|
## 能力概览
|
|
513
557
|
|
|
514
|
-
|
|
558
|
+
SDK 还提供 `getHandshakeState()` / `onHandshakeStateChange()` 管理持久握手状态,并通过 `on()` / `off()` 处理前后台、登录态等生命周期事件。
|
|
515
559
|
|
|
516
560
|
| 模块 | 用途 |
|
|
517
561
|
| ------------ | ------------------------------------------------------------ |
|
|
@@ -561,7 +605,7 @@ onUnmounted(stopLifecycleEvents)
|
|
|
561
605
|
| 事件 | 触发时机 | 典型用途 |
|
|
562
606
|
| ------------------------- | ------------------------ | -------------------------------- |
|
|
563
607
|
| `launch` | 小程序首次启动 | 初始化一次性数据 |
|
|
564
|
-
| `ready` | SDK
|
|
608
|
+
| `ready` | SDK 握手成功瞬间 | 不可重放的边沿通知 |
|
|
565
609
|
| `show` | 小程序页面展示 | 刷新可见态数据 |
|
|
566
610
|
| `hide` | 小程序页面隐藏 | 暂停轮询、暂停播放 |
|
|
567
611
|
| `unload` | 当前小程序运行环境终止 | 清理资源并停止请求 |
|
|
@@ -575,11 +619,13 @@ onUnmounted(stopLifecycleEvents)
|
|
|
575
619
|
|
|
576
620
|
## 生命周期建议
|
|
577
621
|
|
|
578
|
-
-
|
|
622
|
+
- SDK 导入时自动启动握手,能力调用会自动等待;不要在用户手势回调中额外等待握手。
|
|
623
|
+
- 当前握手状态只通过 `getHandshakeState()` 查询、通过 `onHandshakeStateChange()` 订阅;订阅会立即回放当前值并返回取消函数。
|
|
624
|
+
- `ready` 事件只在握手成功瞬间派发,不会向晚订阅者重放,不能用于驱动按钮可用状态或替代握手状态 API。
|
|
579
625
|
- UI 可见性相关逻辑放在 `show`、`hide`。
|
|
580
626
|
- 使用 Host current-user APIs 的无网络小程序,可以在 `heybox_app_login_change` 后刷新本地状态。已开通网络权限的小程序使用 `auth.login()` 获取 code,并由开发者服务端维护业务会话。
|
|
581
627
|
- 组件或页面销毁时清理 `on` 注册的监听,避免重复响应。
|
|
582
|
-
-
|
|
628
|
+
- 生命周期事件只派发给注册当时存在的监听器,不会重放;一次性 `launch` 信息应在页面启动阶段通过业务能力调用处理。
|
|
583
629
|
- 收到 `unload` 后,当前 SDK 上下文不可恢复,未完成请求会失败;不要在同一页面上下文继续重试能力调用。
|
|
584
630
|
|
|
585
631
|
## 云端排行榜
|
|
@@ -9,14 +9,37 @@
|
|
|
9
9
|
- apps/docs/hb-sdk/recipes/**
|
|
10
10
|
## Positive examples
|
|
11
11
|
|
|
12
|
+
### Gesture-gated control handshake state
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import hbSDK, { type MiniProgramSDKHandshakeState } from '@heybox/hb-sdk';
|
|
16
|
+
|
|
17
|
+
let handshakeState: MiniProgramSDKHandshakeState = hbSDK.getHandshakeState();
|
|
18
|
+
const stopHandshakeState = hbSDK.onHandshakeStateChange((state) => {
|
|
19
|
+
handshakeState = state;
|
|
20
|
+
setLoginButtonDisabled(state.status !== 'ready');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export function disposePage() {
|
|
24
|
+
stopHandshakeState();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function loginFromUserAction() {
|
|
28
|
+
if (handshakeState.status !== 'ready') return;
|
|
29
|
+
return hbSDK.auth.login();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function setLoginButtonDisabled(disabled: boolean) {
|
|
33
|
+
document.querySelector('button')?.toggleAttribute('disabled', disabled);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
12
37
|
### Local identity for a network-disabled mini-program
|
|
13
38
|
|
|
14
39
|
```ts
|
|
15
|
-
import {
|
|
40
|
+
import { user, HbMiniProgramSDKError } from '@heybox/hb-sdk';
|
|
16
41
|
|
|
17
42
|
export async function getLocalIdentityFromUserAction() {
|
|
18
|
-
await ready();
|
|
19
|
-
|
|
20
43
|
try {
|
|
21
44
|
return await user.getLocalIdentity();
|
|
22
45
|
} catch (error) {
|
|
@@ -31,11 +54,9 @@ export async function getLocalIdentityFromUserAction() {
|
|
|
31
54
|
### Developer server session for a network-enabled mini-program
|
|
32
55
|
|
|
33
56
|
```ts
|
|
34
|
-
import { auth, network,
|
|
57
|
+
import { auth, network, HbMiniProgramSDKError } from '@heybox/hb-sdk';
|
|
35
58
|
|
|
36
59
|
export async function createServerSessionFromUserAction() {
|
|
37
|
-
await ready();
|
|
38
|
-
|
|
39
60
|
try {
|
|
40
61
|
const result = await auth.login({ scopes: ['profile'] });
|
|
41
62
|
await exchangeCodeWithDeveloperServer(result.code);
|
|
@@ -60,11 +81,9 @@ async function exchangeCodeWithDeveloperServer(code: string) {
|
|
|
60
81
|
### Network request
|
|
61
82
|
|
|
62
83
|
```ts
|
|
63
|
-
import {
|
|
84
|
+
import { network, HbMiniProgramNetworkError, HbMiniProgramSDKError } from '@heybox/hb-sdk';
|
|
64
85
|
|
|
65
86
|
try {
|
|
66
|
-
await ready();
|
|
67
|
-
|
|
68
87
|
const response = await network.request<{ ok: boolean }>({
|
|
69
88
|
url: 'https://api.example.com/demo',
|
|
70
89
|
method: 'GET',
|
|
@@ -138,6 +157,8 @@ export default defineConfig({
|
|
|
138
157
|
## Negative examples
|
|
139
158
|
|
|
140
159
|
- Do not import from internal hb-sdk implementation paths.
|
|
160
|
+
- The non-replayed `ready` lifecycle event is only an edge notification. After a duplicate handshake it may repeat; never treat it as current state, and query or subscribe to the persistent state APIs instead.
|
|
161
|
+
- Do not wait for handshake inside a gesture-gated click handler; keep the control disabled until the persistent state is `ready`.
|
|
141
162
|
- Do not read cookies, tokens, phone numbers, or private credentials.
|
|
142
163
|
- Do not create raw `postMessage` bridge envelopes in business pages.
|
|
143
164
|
- Do not call unsupported storage delete/clear/info operations.
|