@mythryl/app-sdk
v0.1.11
Published
The browser SDK for Mythryl mini apps.
Readme
@mythryl/app-sdk
The browser SDK for Mythryl mini apps. It connects an app to the Mythryl window that embeds it.
npm install @mythryl/app-sdkimport sdk from "@mythryl/app-sdk";
await sdk.ready();
const context = await sdk.getContext();
console.log(context.user.handle);The default export is created on first use, so importing it has no side effects and is safe in code that also renders on the server. Call it from effects and event handlers, which run in the browser.
API
| Call | Result |
| --- | --- |
| sdk.ready() | Signals that the app has loaded. The host removes its loading screen. |
| sdk.close() | Closes the app. |
| sdk.getCapabilities() | The capabilities the host supports. Check before calling optional features. |
| sdk.getContext() | The signed-in user, how the app was launched, and the client environment. |
| sdk.auth.signin({ force? }) | { token }: a JWT that authenticates the user to the app's own server. |
| sdk.openProfile(actor) | Opens a profile (handle or DID) in the host and minimises the app. |
| sdk.openPost(uri) | Opens a post, by its at:// URI, and minimises the app. |
| sdk.openTopic(slug) | Opens a topic, by name without the slash, and minimises the app. |
| sdk.openUrl(url) | Opens an https address in the browser once the user confirms. Rejects with cancelled if they decline. |
| sdk.openStorePage(domain?) | Opens an app's store page in the host and minimises this app. Without a domain, it is this app's own page. |
| sdk.openApp({ domain, url? }) | Opens another mini app and closes this one. url is where to start, on that app's domain. |
| sdk.compose({ text?, embedUrl? }) | Opens the composer with a draft. { uri } if the user posted, {} if they did not. |
| sdk.notifications.request() | Asks the user to let the app notify them, adding it to their library too. On yes, Mythryl sends your server a notification token for the user (the notifications.enabled webhook). Resolves at once if they are already on; rejects with cancelled if they decline. |
| sdk.storage.get(key) | The JSON value saved under a key for this user, or undefined if there is none. |
| sdk.storage.set(key, value) | Saves a JSON value under a key, replacing what was there. |
| sdk.storage.delete(key) | Removes a key. Resolves if there is no such key. |
| sdk.storage.list() | Every key the user has saved for this app, as { key, size } with the size in bytes. |
| sdk.review.request() | Asks the user to review the app, in a sheet Mythryl shows. Resolves once they submit; rejects with cancelled if they dismiss it, and also when Mythryl does not show it (not used enough, already reviewed, asked recently). You never learn which, and never see the rating. Ask after something good happened, never at launch, and never reward or require a review. |
| sdk.install() | Asks the user to add this app to their library. Resolves at once if it is already there; rejects with cancelled if they decline. |
| sdk.follow(did) | Asks the user to follow an account. Rejects with cancelled if they decline. |
| sdk.on(event, listener) | Subscribes to a host event. Returns a function that removes the listener. |
| sdk.dispose() | Removes listeners and rejects pending calls with cancelled. |
openProfile, openPost, openTopic, openUrl and openApp need the navigation capability. compose, follow, install, notifications.request and review.request
each have a capability of the same name. Older hosts have none of them: check sdk.getCapabilities()
first, or handle the unsupported rejection. Anything that writes (compose, follow, install, notifications.request, review.request) is confirmed by the user. A target the host cannot open rejects with unavailable.
Storage
sdk.storage is the user's saves for your app, kept by Mythryl, so a game's state follows the player across devices
without a backend of your own. It needs the storage capability. The values are private to your app and that user.
- Limits: a key is up to 128 characters, a value up to 256 KB as JSON, and one user can keep 1 MB in all. A write over
a limit rejects with
too_largeorquota_exceeded. - Write rate: a key can be written about every 2 seconds, and more slowly the larger the value is (a 256 KB value
every 16 seconds). A write sooner rejects with
rate_limited, and its message says how long to wait. Save when something changes, such as the end of a level, not on a timer. - Last write wins, per key. Writing a value that has not changed costs nothing.
- Do not trust it: the player can edit their own saves. Keep scores and rewards on your server.
const progress = (await sdk.storage.get<{ level: number }>("progress")) ?? { level: 1 };
await sdk.storage.set("progress", { level: progress.level + 1 });Events
minimised and maximised are sent when the user hides the app and brings it back. colorSchemeChanged is sent with
"light" or "dark" when the host switches mode. closing and resized are defined by the protocol; hosts do not send
them yet. closing is best effort and must not be relied on to save state.
Light and dark mode
sdk.getContext() returns the host's current mode in client.colorScheme, and the colorSchemeChanged event reports
each change. What the app does with it is up to the app.
const { client } = await sdk.getContext();
setMode(client.colorScheme);
sdk.on("colorSchemeChanged", setMode);Signing in to your server
sdk.auth.signin() returns a token valid for 10 minutes, issued for the app's domain. Send it to your server as a
bearer token and verify it there with @mythryl/server-sdk. The SDK
caches the token and requests a new one when less than a minute of validity remains; signin({ force: true }) always
requests a new one.
const { token } = await sdk.auth.signin();
const headers = { authorization: `Bearer ${token}` };
const response = await fetch("/api/me", { headers });Errors
A call that fails rejects with a ProtocolError. Its code is cancelled, unsupported, not_consented,
rate_limited, too_large, quota_exceeded or unavailable. A call the host answers by itself that gets no answer within 30 seconds rejects with unavailable. A call that waits on the user (openUrl, openApp, compose, follow, install, notifications.request, review.request) has no time limit: it ends when they answer.
Outside a host
Outside a Mythryl window there is no host to answer, and calls time out. isEmbedded() returns whether the page is
framed (and false on a server, where there is no window):
import sdk, { isEmbedded } from "@mythryl/app-sdk";
if (isEmbedded()) await sdk.ready();Other transports
createSdk(transport) builds an SDK over any Transport, which is how the SDK runs in a webview or a test. The default
export uses windowTransport(), which talks to the embedding window.
License
MIT
