Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,32 @@ await bio.kv.set('user-preference', { theme: 'dark' });
const pref = await bio.kv.get('user-preference');
```

## Miniapp Context SDK

用于获取 KeyApp 上下文(safe area、宿主版本等),并订阅更新,避免业务侧直接使用 postMessage。

```typescript
import {
getMiniappContext,
onMiniappContextUpdate,
applyMiniappSafeAreaCssVars,
} from '@bioforest/bio-sdk';

const context = await getMiniappContext({ appId: window.name || undefined });
applyMiniappSafeAreaCssVars(context);

const unsubscribe = onMiniappContextUpdate((next) => {
applyMiniappSafeAreaCssVars(next);
});
```

SDK 行为要点:

- `getMiniappContext()` 无缓存时自动发起一次请求,超时会回退默认值并告警。
- `onMiniappContextUpdate()` 会回放最近一次 context,并在需要时触发刷新。
- `applyMiniappSafeAreaCssVars()` 会写入 `--keyapp-safe-area-*`、`--keyapp-lang`、`--keyapp-direction`、`--keyapp-color-mode`。
- 当宿主不支持时,SDK 会回退到浏览器的 `env(safe-area-inset-*)`、`prefers-color-scheme`、`document.documentElement.lang/dir` 等标准 Web API。

### 内部实现

```typescript
Expand Down
12 changes: 11 additions & 1 deletion miniapps/biobridge/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import './index.css'
import '@fontsource-variable/noto-sans-sc'
import './i18n'
import '@biochain/bio-sdk'
import {
applyMiniappSafeAreaCssVars,
getMiniappContext,
onMiniappContextUpdate,
} from '@biochain/bio-sdk'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
Expand All @@ -11,6 +15,12 @@ if (!rootElement) {
throw new Error('Root element not found')
}

const appId = window.name || undefined
void getMiniappContext({ appId }).then(applyMiniappSafeAreaCssVars)
onMiniappContextUpdate((context) => {
applyMiniappSafeAreaCssVars(context)
}, { appId })

createRoot(rootElement).render(
<StrictMode>
<App />
Expand Down
12 changes: 11 additions & 1 deletion miniapps/teleport/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import './index.css'
import '@biochain/bio-sdk'
import {
applyMiniappSafeAreaCssVars,
getMiniappContext,
onMiniappContextUpdate,
} from '@biochain/bio-sdk'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
Expand All @@ -14,6 +18,12 @@ const queryClient = new QueryClient({
},
})

const appId = window.name || undefined
void getMiniappContext({ appId }).then(applyMiniappSafeAreaCssVars)
onMiniappContextUpdate((context) => {
applyMiniappSafeAreaCssVars(context)
}, { appId })

createRoot(document.getElementById('root') as HTMLElement).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
Expand Down
85 changes: 85 additions & 0 deletions openspec/changes/add-miniapp-context-sdk/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Design: Miniapp context SDK

## Context Payload
```ts
export type MiniappContext = {
version: number;
env: {
safeAreaInsets: {
top: number;
right: number;
bottom: number;
left: number;
};
platform?: "ios" | "android" | "web" | "dweb";
locale?: string;
direction?: "ltr" | "rtl";
};
host: {
name: "KeyApp";
version: string;
build?: string;
};
updatedAt: string;
};
```

## Message Contract
- Request: `{ type: "miniapp:context-request", requestId, sdkVersion, payload?: { appId } }`
- Response: `{ type: "keyapp:context-update", requestId, payload }`
- Errors use `{ type: "keyapp:context-error", requestId, code, message }`

## SDK API (Atomic)
```ts
export async function getMiniappContext(options?: {
forceRefresh?: boolean;
timeoutMs?: number;
retries?: number;
appId?: string;
}): Promise<MiniappContext>;

export function onMiniappContextUpdate(
handler: (context: MiniappContext) => void,
options?: { emitCached?: boolean; appId?: string },
): () => void;
```

## Distribution
- Exposed from `@biochain/bio-sdk` as named exports to keep a single public package.
- APIs are side-effect-free; existing provider auto-init behavior remains unchanged.

## SDK Behavior
- `getMiniappContext()` returns cached context when available; if missing (or `forceRefresh`), it sends `miniapp:context-request` and waits for `keyapp:context-update`.
- `onMiniappContextUpdate()` registers a handler, replays cached context once (default), and ensures a refresh if nothing is cached.
- SDK uses a singleton message bridge to avoid duplicate event bindings.
- If host does not support the channel (timeout), SDK resolves with fallback context derived from standard Web APIs (safe-area env vars, prefers-color-scheme, document language) and logs a warning.

## Host Behavior
- Host maintains a context snapshot (safeAreaInsets, env info, version) and updates it on layout changes.
- On `miniapp:context-request`, respond with `keyapp:context-update` (including `requestId`).
- When context changes, broadcast `keyapp:context-update` to active miniapp frames.

## CSS Variable Hook
Provide a small helper in the SDK so UI can apply safe-area CSS variables without manual mapping.

```ts
export function applyMiniappSafeAreaCssVars(
context: MiniappContext,
options?: {
target?: HTMLElement | Document;
},
): void;
```

Behavior:
- Default target: `document.documentElement`.
- Always sets:
- `--keyapp-safe-area-top`
- `--keyapp-safe-area-right`
- `--keyapp-safe-area-bottom`
- `--keyapp-safe-area-left`
- Also sets when available:
- `--keyapp-lang`
- `--keyapp-direction`
- `--keyapp-color-mode`
- Values are `${number}px`, derived from `context.env.safeAreaInsets`.
15 changes: 15 additions & 0 deletions openspec/changes/add-miniapp-context-sdk/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Change: Add miniapp context SDK and host channel

## Why
Miniapps need safe area and host context without directly using postMessage. A normalized SDK plus host-side channel prevents fragile integrations and provides backward-compatible defaults.

## What Changes
- Define a schema-first miniapp context payload (safeAreaInsets, env info, host version).
- Add a host-side context channel that responds to `miniapp:context-request` with `keyapp:context-update`.
- Provide a miniapp SDK wrapper with atomic APIs (`getMiniappContext`, `onMiniappContextUpdate`) that handle refresh, retry, timeout, and caching.
- Ensure iframe mode automatically replays context on subscription and on-demand refresh.
- Document the new SDK usage and context structure.

## Impact
- Affected specs: miniapp-runtime (new)
- Affected code: miniapp runtime messaging, keyapp-sdk, docs/white-book
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## ADDED Requirements

### Requirement: Miniapp context channel
The host SHALL accept `miniapp:context-request` messages and respond with `keyapp:context-update` containing the latest context payload and the originating `requestId`.

#### Scenario: Context request success
- **WHEN** a miniapp sends `miniapp:context-request`
- **THEN** the host responds with `keyapp:context-update` within the configured timeout
- **AND** the payload includes safe area insets and host version information

### Requirement: Context update broadcast
The host SHALL broadcast `keyapp:context-update` whenever the context changes (safe area, environment, host version) so running miniapps can react without polling.

#### Scenario: Safe area change
- **WHEN** the host safe area insets change
- **THEN** all active miniapps receive a `keyapp:context-update` event

### Requirement: SDK access and caching
The SDK SHALL provide `getMiniappContext` and `onMiniappContextUpdate` APIs, cache the latest context, and avoid duplicate event bindings.

#### Scenario: Cached context replay
- **WHEN** a consumer calls `onMiniappContextUpdate`
- **THEN** the SDK replays the latest cached context once
- **AND** returns an unsubscribe function

### Requirement: Automatic refresh on enter
The SDK SHALL request context once when accessed if no cached context is available.

#### Scenario: First load without cached context
- **WHEN** the miniapp loads with no cached context
- **THEN** the SDK sends a `miniapp:context-request`
- **AND** resolves `getMiniappContext()` with the next `keyapp:context-update`

### Requirement: Compatibility fallback
When the host does not support the context channel, the SDK SHALL resolve with fallback values derived from standard Web APIs (safe-area env vars, prefers-color-scheme, document language/dir) and emit a warning.

#### Scenario: Unsupported host
- **WHEN** the SDK times out waiting for a `keyapp:context-update`
- **THEN** it resolves with default context values
- **AND** logs a warning about unsupported host capability
20 changes: 20 additions & 0 deletions openspec/changes/add-miniapp-context-sdk/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## 1. Schema & Protocol
- [x] Define Zod schema for `MiniappContext` and message envelope.
- [x] Add typed message channel constants and payload types.

## 2. Host Integration
- [x] Add context provider in miniapp runtime (safeAreaInsets, env, host version).
- [x] Handle `miniapp:context-request` and emit `keyapp:context-update`.
- [x] Broadcast updates on safe area/layout changes.

## 3. SDK Implementation
- [x] Add `getMiniappContext` + `onMiniappContextUpdate` APIs with optional `forceRefresh`.
- [x] Implement retry/timeout + default fallback when unsupported.
- [x] Cache context and avoid duplicate event bindings.

## 4. UI/CSS Bridge
- [x] Provide `applyMiniappSafeAreaCssVars` helper and usage guidance for safe area CSS variables.

## 5. Documentation & Tests
- [x] Update white-book/SDK docs with usage examples.
- [x] Add unit tests for SDK message flow and fallback.
Loading