Architecture
Mini-app SDK (@archipelago/miniapp-sdk)
@archipelago/miniapp-sdk is the typed client for the mini-app bridge. It has no runtime dependencies, ships ESM and TypeScript types, and works with both hosts:
- the same-engine container from
webview_sdk_advanced; - the partner host from the advanced mini-app hosting mode.
Outside the app it answers from mock stubs, so the page runs in a normal browser.
Not on npm yet
The package lives in packages/miniapp-sdk in the Archipelago repo. It is not published. Build it with pnpm build in that folder.
Quick start
import { archipelago, BridgeError, ErrorCode } from '@archipelago/miniapp-sdk';
await archipelago.lifecycle.ready();
try {
const { token } = await archipelago.auth.getToken();
} catch (e) {
if (e instanceof BridgeError && e.code === ErrorCode.CONSENT_REQUIRED) {
await archipelago.consent.request({ scopes: ['identity'] });
}
}Each capability is a namespace on the client: archipelago.storage.get({ key }). The same call also works as archipelago.invoke('storage', 'get', { key }). Params and results are typed per method, so a wrong param name or a missing orderRef fails at compile time.
Use createClient(options) for your own timeout, stubs or transport.
Capabilities
The page can only call a capability that the manifest grants. lifecycle is always granted. consent and payment need a verified v2 manifest on the partner host.
| Capability | Methods |
|---|---|
lifecycle | ready(), setTitle({ title }) |
navigation | close(), openDeepLink({ path }), canGoBack() |
analytics | track({ name, params? }), trackScreen({ screenName, params? }) |
share | share({ text?, url? }) |
haptics | impact({ style? }) |
theme | get() |
auth | getToken({ scopes? }), getAuthState() |
profile | getProfile() |
storage | get({ key }), set({ key, value }), remove({ key }) |
permissions | request({ permission }), status({ permission }) |
consent | request({ scopes }), status() |
payment | request({ orderRef }) |
The bridge guide explains what each one does and which are remote-safe.
Consent
const { granted, token } = await archipelago.consent.request({ scopes: ['identity'] });grantedhas one entry per requested scope and shows the effective grant.tokenis the publisher token. It is present only whenidentityis effective.- A declined scope is
false, not an error. BAD_REQUEST: the list is empty, a scope is not declared, or the scope is implicit.RATE_LIMITED: a second sheet inside 30 seconds.consent.status()returns{ granted }for every declared scope that needs consent.
Do not keep token after its exp. Call auth.getToken() for a fresh one. In partner mode it fails with CONSENT_REQUIRED until identity is granted, with METHOD_NOT_FOUND when the host has no token exchange, and with AUTH_AUDIENCE_MISMATCH when the backend answers for another publisher or mini-app.
Payments
const { status, paymentRef } = await archipelago.payment.request({ orderRef: 'ord_81f' });Send only orderRef (1 to 128 characters, from your backend). The host looks up the amount, currency and payee itself, asks the user, and asks for a step-up.
status: 'pending'means the backend settles later. Learn the final state from the operator, server to server.- Errors:
CONSENT_REQUIRED,CAPABILITY_DENIED,RATE_LIMITED(5 a minute),CAPABILITY_UNAVAILABLE,BAD_REQUEST,PAYMENT_IN_PROGRESS,ORDER_NOT_FOUND,PAYMENT_CANCELLED,STEP_UP_FAILED,PAYMENT_FAILED. - After
PAYMENT_FAILEDfrom the pay step, the outcome is unknown to the page. Asking again with the sameorderRefdoes not charge twice.
Follow the app theme
const stop = await archipelago.followTheme();followTheme calls theme.get(), sets data-theme and the CSS variables on <html>, then follows theme.changed. To do it yourself:
const { brightness, tokens } = await archipelago.theme.get();
const off = archipelago.on('theme.changed', ({ brightness, tokens }) => { /* same shape */ });tokens maps CSS variable names to values. They match the Tailwind preset from monorepo_toolkit ui-kit export-tailwind.
Errors
Every failure rejects with BridgeError. Read error.code. A host can add codes, so treat an unknown code as a failure. The full list is the ErrorCode object and the table in the package README.
Transports
| Host | How the SDK calls it |
|---|---|
| Partner | window.archipelago.invoke({ capability, method, params }). The injected shim builds the envelope. Events come from window.archipelago.on(event, cb). |
| Same-engine | The archipelago_bridge WebView handler with the v1 envelope. Events come from window.archipelago.onNativeMessage. |
| Neither | Mock stubs (mock: 'auto'), or NOT_IN_APP with mock: false. |
Try it
Signed-in users can open the Bridge Tester in the dashboard (/dashboard/bridge-tester). Pick a capability and a method, write the params as JSON and call it. Open the page inside a mini-app WebView to reach the real host. In a browser it shows the mock answers.
Keeping the types honest
The Dart handlers are the source of truth. There is no machine-readable schema, so the types are written by hand from the handlers and the host-bridge and consent contracts. A test in the package parses those Dart files and contract tables and fails when a capability, a method or an error code is missing from the SDK.
See also
- Mini-app bridge (web side): the plain
archipelago-miniapp.jshelper and the envelope. webview_sdk_advancedbrick