Skip to content

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 ​

ts
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.

CapabilityMethods
lifecycleready(), setTitle({ title })
navigationclose(), openDeepLink({ path }), canGoBack()
analyticstrack({ name, params? }), trackScreen({ screenName, params? })
shareshare({ text?, url? })
hapticsimpact({ style? })
themeget()
authgetToken({ scopes? }), getAuthState()
profilegetProfile()
storageget({ key }), set({ key, value }), remove({ key })
permissionsrequest({ permission }), status({ permission })
consentrequest({ scopes }), status()
paymentrequest({ orderRef })

The bridge guide explains what each one does and which are remote-safe.

ts
const { granted, token } = await archipelago.consent.request({ scopes: ['identity'] });
  • granted has one entry per requested scope and shows the effective grant.
  • token is the publisher token. It is present only when identity is 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 ​

ts
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_FAILED from the pay step, the outcome is unknown to the page. Asking again with the same orderRef does not charge twice.

Follow the app theme ​

ts
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:

ts
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 ​

HostHow the SDK calls it
Partnerwindow.archipelago.invoke({ capability, method, params }). The injected shim builds the envelope. Events come from window.archipelago.on(event, cb).
Same-engineThe archipelago_bridge WebView handler with the v1 envelope. Events come from window.archipelago.onNativeMessage.
NeitherMock 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 ​