WebView Kit Development
A WebView Kit is a web app you host that opens inside Koee. People find it by its @handle, add it to Chats, and open it in the app's Kit container: an isolated WebView with a native title bar, a small JavaScript bridge to selected app capabilities, and its own website data for every account.
You create and manage WebView Kits yourself in the Developer Console. A new Kit is a private draft that only you and the admins you add can open. It becomes available to other people after it passes review.
For Dart modules compiled into the app, see Native Kit Development.
Status
Creating and managing WebView Kits requires developer access granted by an administrator and acceptance of the current Kit Developer Agreement. Developer access does not make a Kit official and does not express a user’s consent.
| Platform | Support |
|---|---|
| iOS | iOS 17 or later |
| Android | Android System WebView 120 or later, with multi-profile support |
| macOS | macOS 14 or later |
| Koee Web | Not yet supported |
On an older system or WebView the app explains what needs updating; there is no fallback mode.
The identity integration described below is implemented for the first native identity release and requires the matching server configuration and compatible app. It is not a statement that every deployed product/app version has enabled it. Check app.getInfo.capabilities; an older host cannot grant identity silently.
The bridge is at version 1. Methods and events are only ever added, never changed; detect what is available with KitBridge.version and the capabilities returned by app.getInfo.
How it works
- Your Kit has a fixed entry URL and a list of allowed domains. The container loads the entry URL and keeps the main frame on HTTPS pages of the allowed domains.
- Pages of allowed domains get
window.KitBridgein the main frame. Frames inside the page (iframes) never get the bridge. - The Kit ID identifies your Kit everywhere: in the catalog, in network requests, in the User-Agent and in debugging tools. It is not secret.
- Each Kit has an owner and up to 10 admins. Changes to what people see, or to what the Kit may do, go live only after review. Making things stricter (private, closing a capability, maintenance) needs no review and is saved at once; an open Kit picks it up the next time it is opened or returns to the foreground.
Official identity and first use
Official identity is assigned by the platform only to its own services. A Kit’s badge does not come from its developer account, a linked Bot or a Plaza promotion. Official Kits skip the third-party confirmation; website and device permissions still apply.
Before opening a third-party website for the first time, the app shows a native panel with its published name, handle, introduction, provider and policy links. The website is loaded only after the person selects Agree and open and the server accepts the displayed snapshot. Closing or declining leaves the added Kit in Chats. Opening a policy link is not consent.
The native panel accurately lists the scopes your Kit currently requests:
- Without identity capabilities it confirms use of the third-party website and does not grant account access. Existing use-only consent is never upgraded silently into identity permission.
identity.profilerequests profile: a Kit-specific account identifier, display name, avatar and effective app UI language. It enables display profile and signed launch tokens for backend authentication.- Optional
identity.handlerequests handle separately. A public handle can link the person to their public account and across Kits; request it only when needed. It requiresidentity.profile.
The platform never gives your website the app's session token, raw account ID, email, phone number, contacts or chat content. Opening the website also makes ordinary network requests, which the provider may process under its privacy policy.
Adding profile or handle requires new explicit consent; reducing enabled scopes immediately removes the corresponding fields from subsequent identity requests. Official Kits follow current platform policy instead of recording a fictitious user approval; their identity capabilities and access are still checked.
Consent belongs to the account across devices. People revoke it in the Kit’s About menu or Settings → Authorized applications (a separate Kit group). Removing a Kit revokes consent too; adding it again requires confirmation. Revocation clears local website data before reuse, but cannot delete data the provider already holds or guarantee sign-out from its server.
Provider, policy declaration, policy links and allowed-domain changes require new consent. Declare a new policy_version whenever policy content changes, even if its URL stays the same, and submit the draft for review before publishing. The platform does not automatically detect changes on your policy website. Name, icon and introduction changes do not invalidate all existing consents, but a panel already being submitted must be refreshed if its displayed snapshot changed.
Unpublished team previews are labelled Development preview and use separate consent records. Preview consent never authorizes the published version. Older clients without the required consent/identity protocol receive an update-required response; this does not prevent your public website being visited in a normal browser.
Create a Kit
In the Developer Console, open Kit and choose Create Kit:
| Field | Rules |
|---|---|
| Name | Up to 20 characters. You can change it later (with review). |
| handle | 4–32 lowercase letters, digits or underscores. Shared with users, groups, channels and Bots; permanent once taken. |
| Kit ID | 5–100 characters, 1–5 dot-separated segments. Each segment starts with a lowercase letter and uses only a–z and 0–9, up to 30 characters. Permanent. |
| Agreement | Accept the current Kit Developer Agreement. |
About the Kit ID:
- It works like an app's bundle ID or package name. A reverse domain such as
com.example.shopis recommended but not required, and it does not have to be a domain you own. It is not checked against your web domains. - No segment can be
koeeor another reserved platform name; the first segment cannot betest,exampleorlocalhost; and a few whole IDs (such asadminoragreement) are reserved. - Kit IDs and handles are never released, even after a Kit is retired.
The new Kit is private, has no capabilities and has debugging off. The Overview page lists what is still required before you can submit it.
Kit settings
Settings marked Needs review are saved to the Kit's draft; they reach users only after the draft passes review. Settings marked Takes effect now need no review and are saved at once. A Kit that is already open picks them up the next time it is opened or returns to the foreground; a page that stays open in the foreground keeps its current settings until then. Identity reads additionally recheck current server authority on every request, including disabled capabilities and revocation.
| Page | Contents | Applies |
|---|---|---|
| Store listing | Name, short description (up to 80 characters), category, icon | Needs review |
| Provider & policies | Provider name (up to 40 characters), contact (email or HTTPS link, up to 200 characters), privacy policy (HTTPS, required), policy version declaration (required, up to 100 characters), terms of use (HTTPS, optional); links up to 2048 characters | Needs review |
| Web settings | Entry URL, allowed domains, title bar, orientation, loading background, pull to refresh | Needs review |
| Capabilities | Request capabilities; close or suspend approved ones | Requests need review; closing takes effect now |
| Access & release | Request public access; make private; who can open a private Kit; maintenance; WebView debugging | Public needs review; the rest takes effect now |
| Team | The owner adds and removes admins | Takes effect now |
| Review | Submit, withdraw, history | — |
Two people editing at once cannot overwrite each other: a save based on an older version is refused, and the console asks you to reload.
Web settings
Entry URL and allowed domains
- Allowed domains: up to 10 exact HTTPS origins (each up to 300 characters), such as
https://app.example.com, without paths, wildcards or IP addresses. Koee's own service hosts cannot be used. - Entry URL: an HTTPS URL on one of the allowed domains, up to 2048 characters. It is loaded every time the Kit opens.
- Changing either one is a draft change and needs review.
- Use HTTPS everywhere. There is no HTTP, no local file access and no mixed content. For local development, expose your machine through an HTTPS tunnel (for example Cloudflare Tunnel or ngrok) and use that as a development Kit's domain.
Navigation inside the Kit
- Links and redirects between pages of allowed domains stay in the Kit.
- Other HTTPS links,
target="_blank"andwindow.opengo through the app's external-link handling (a safety prompt or the system browser). Other schemes are blocked. - On Android, a form POST that leaves the allowed domains stops the Kit and shows a "blocked" page; the form data has already been sent by then. Keep forms on your own domains.
- The system back button or gesture goes back in the page history first, then closes the Kit. The title bar has no back button of its own: it has close (which leaves the Kit at once) and the More menu, so provide in-page navigation where your app needs it.
alert,confirmandpromptare shown as app dialogs labelled with your Kit.- Camera, microphone and location permission requests from the page are refused. Use bridge capabilities instead (for example
scan.qrCode). - Offline, DNS failures and HTTP 5xx responses show an app error page with a retry. Certificate errors show an error page without a retry button, and the certificate check cannot be bypassed (refreshing from the More menu checks it again).
Display
| Setting | Values |
|---|---|
| Title bar | App title bar (native, with close and the More menu), or Immersive (your page fills the screen; floating buttons stay available) |
| Title | The page's <title>, or always the Kit name. ui.setTitle changes it at runtime. |
| Orientation | Portrait, landscape, or follow the device (iOS and Android) |
| Loading background | Optional #RRGGBB colors for light and dark mode, shown while the page loads |
| Pull to refresh | On or off (iOS and Android) |
In immersive mode, lay out around safeArea from app.getInfo.
The More menu always offers refresh, copy link, report and about (name, provider and privacy policy).
JavaScript bridge
Getting the bridge
The container injects window.KitBridge into the main frame of allowed-domain pages when the document starts. When a page may load before the bridge is ready, wait for the kitbridgeready event:
function kitBridge() {
if (window.KitBridge) return Promise.resolve(window.KitBridge);
return new Promise((resolve) =>
window.addEventListener('kitbridgeready', () => resolve(window.KitBridge), { once: true }));
}
const bridge = await kitBridge();
const info = await bridge.call('app.getInfo');
Outside the app (a normal browser), window.KitBridge never appears; build your page so it still works, or show a message.
interface KitBridge {
readonly version: 1;
call<T>(method: string, params?: object): Promise<T>;
on(event: string, handler: (data: unknown) => void): () => void; // returns an unsubscribe function
}
A failed call rejects with { code, message }. Each method can be called at most 10 times per second.
A call message longer than 65,536 characters (UTF-16 code units, after JSON encoding) is dropped without an answer, so its promise never settles. Keep parameters small, and add your own timeout where a call might be large.
Methods
| Method | Parameters | Result | Capability |
|---|---|---|---|
app.getInfo | — | { product, platform, bridgeVersion, appBuild, kitId, locale, theme, safeArea, capabilities } | — |
ui.setTitle | { title }, up to 40 characters | — | — (ignored when the title is fixed to the Kit name) |
ui.close | — | — | — |
ui.openExternal | { url }, HTTPS, up to 2048 characters, no user name or password | — | — |
share.system | { text?, url? }, at least one; url is HTTPS on an allowed domain, up to 2048 characters, no user name or password | { completed } | share.system |
scan.qrCode | — | { text }: the scanned text with leading and trailing whitespace removed (never empty) | scan.qr |
user.getProfile | — | { sub, name, avatar, locale, handle } | identity.profile; handle additionally requires identity.handle and consent |
auth.getLaunchToken | { nonce?: string }, 1–128 characters when present | { token, expiresAt } | identity.profile |
platformisios,androidormacos;themeis the system'slightordarkmode;localeis the app's language when the page asks (changing the language inside the app does not sendlocale.changedyet);safeAreais{ top, right, bottom, left }in logical pixels (all 0 with the app title bar).capabilitieslists the capabilities this Kit may use right now, in this app version.
Events
| Event | Data | When |
|---|---|---|
app.pause | — | The app goes to the background |
app.resume | — | The app is back in the foreground and the Kit is still available |
theme.changed | { theme } | The system switches between light and dark mode |
locale.changed | { locale } | The app UI language changes |
auth.changed | { reason: "revoked" or "account_switched" } | The host invalidates the current identity; clear local state and end your own session |
When the app returns to the foreground, the host covers the page and blocks interaction and bridge calls while it checks current access, use consent, capabilities and debugging. An unchanged authorization keeps the existing page and receives app.resume; a changed boundary restarts it, clearing old website data before reuse. If consent is needed, native confirmation appears first. Identity replies from an old document, account or consent boundary are discarded. A refused or failed check removes the page. This is host authorization checking, not a network-freeze guarantee for an already consented website's background requests. Before the first consent, the website is never constructed or loaded.
Error codes
| Code | Meaning |
|---|---|
unsupported_method | The method does not exist in this app version |
capability_denied | The Kit has not been approved for this capability, or it is suspended |
invalid_params | Parameters are missing or invalid |
user_cancelled | The person cancelled (for example closed the scanner) |
rate_limited | Too many calls |
unavailable | The app could not complete the call right now |
consent_required | Current consent is absent or changed; return to native confirmation |
identity_unavailable | The product identity service is not configured or cannot safely issue identity |
Capabilities
| Capability | Enables | Status |
|---|---|---|
share.system | share.system | Available |
scan.qr | scan.qrCode | Available |
identity.profile | Display profile and signed launch token | Native identity release; requires approval and user consent |
identity.handle | Optional public handle in profile and signed claims | Native identity release; additionally requires identity.profile |
bot.chat | Opening the Kit's companion Bot | Coming soon |
chat.share | Sharing to chats | Coming soon |
Request capabilities on the Capabilities page; for the published Kit they are granted when the review is approved. Before the first approval, the owner and admins can already test the available capabilities the draft requests. Ask only for what your Kit uses.
You can close or suspend an approved capability at any time without review. It is saved at once and reaches open Kits when they are next opened or return to the foreground; it also removes the request from your draft, so turning it back on needs a new review. For profile/token reads the server enforces closure or suspension immediately on the next request, without waiting for foreground resume.
Icon
- Upload a PNG, exactly 512 × 512 pixels, up to 1 MB. The console can crop and export another image to the right size in your browser.
- Do not round the corners: the app applies its own mask. An opaque background is recommended.
- Without an uploaded icon, the Kit uses a default icon: one of a few symbols on a background color you choose. The default icon is also shown whenever the uploaded one cannot be displayed.
- Icons you stop using are kept for 90 days for the review history. Each Kit can keep up to 50 MB of icons; delete old ones in the icon history to free space.
Website data and sign-in
- Cookies,
localStorage, IndexedDB and caches are kept separately for every account and Kit. Another account on the same device, another Kit and the app's other web views never see your Kit's data. - Each account has its own storage, so one account's site login never carries over to another account. Switching accounts shows the other account's own data (signed in only if that account signed in before), and switching back finds the first account's data as it was.
- Signing out of the app or removing the Kit starts deleting that account's data for the Kit. Deletion is best effort (it is retried if the storage is in use) and does not sign the person out on your server; end server sessions yourself where that matters.
- A draft and published Kit have the same Kit ID, but preview and published consent are separate boundaries. Transitioning between them clears website data before reuse; preview consent never grants published access. Use a separate development Kit for independent account IDs, keys and backend environments.
- Use the signed launch-token flow below to sign people into your own backend. Display profile alone cannot authenticate a backend request.
Identity: display and backend sign-in
Display profile
interface KitProfile {
sub: string; // stable pseudonymous ID for this product and Kit
name: string;
avatar: string; // host-supplied image data URL, including a default avatar
locale: string; // effective app UI language, never a timezone
handle: string | null;
}
interface LaunchToken {
token: string;
expiresAt: number; // epoch milliseconds, exactly JWT exp * 1000
}
const info = await bridge.call('app.getInfo');
if (info.capabilities.includes('identity.profile')) {
const profile = await bridge.call('user.getProfile');
document.querySelector('#name').textContent = profile.name;
document.querySelector('#avatar').src = profile.avatar;
}
Every profile call requests current authority from the server. It has no stale profile fallback. handle is null unless enabled and authorized, and may still be null if the person has no handle. Avatar bytes come from the person's own host-authorized avatar or a generated host default, not a public media URL or an app session token. Allow data: in your image Content Security Policy. Do not assume a specific image encoding; consume the data URL as an image.
sub is stable for this product and Kit, derived by a keyed one-way HMAC. It is not the product's internal account ID and differs between Kits and products. Deleted accounts that later register again receive a new identity. A static page can display profile without a backend. A backend must never trust a profile object posted by a browser; anyone can forge it.
Authenticated launch token
For backend authentication, issue a fresh unpredictable nonce bound to a secure pre-session cookie, request a launch token through the bridge, and exchange it with your own backend. Do not put tokens or nonces in URLs, logs, analytics or persistent browser storage. The app's session credentials never enter this flow.
// Your endpoints must enforce HTTPS, same-origin/CSRF checks, rate limits,
// no-store responses and a fresh secure pre-session cookie. See example README.
const { nonce } = await fetch('/session/kit/nonce', {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },
body: '{}',
}).then((response) => {
if (!response.ok) throw new Error('Cannot start sign-in');
return response.json();
});
const { token } = await bridge.call('auth.getLaunchToken', { nonce });
const response = await fetch('/session/kit/exchange', {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },
body: JSON.stringify({ token }),
});
if (!response.ok) throw new Error('Sign-in failed');
The host supplies the main-frame origin observed by the native WebView. The page cannot override it. Every token request rechecks current account, Kit access, publication/preview channel, capabilities, consent versions and revocation. The server signs only after revalidating authority; the host suppresses stale delivery. Tokens are limited to 30 per rolling minute and 500 per rolling 24 hours per user and Kit, in addition to bridge limits. Do not request one per animation frame or cache it as a long-lived session.
The signed header is { alg: "ES256", typ: "kit-launch+jwt", kid: "…" }.
| Claim | Contract |
|---|---|
iss | Exactly https://ims.koee.app for this product |
aud | Exactly your Kit ID (a single string) |
sub | The Kit-specific identifier; use (iss, sub) as your account key |
iat, exp | Integer JWT seconds; exp = iat + 300 |
jti | Unique token ID |
origin | Native-observed HTTPS origin, checked against current Kit configuration |
nonce | The nonce supplied to the bridge, if present |
scope | profile or profile handle |
name, locale | Current display name and app UI language |
handle | Present only with handle scope; nullable when the person has none |
Tokens deliberately exclude avatar bytes. Use user.getProfile for display. The bridge expiry is milliseconds: expiresAt = exp * 1000. JWT timestamps are seconds; do not mix these units or infer timezones from locale.
Public keys and backend verification
The public endpoint is https://ims.koee.app/kit-keys/<kit_id>/jwks.json. It needs no login or App build/platform headers, is exempt from client minimum-version checks, permits CORS and returns Cache-Control: public, max-age=300. Keys are unique per Kit. Only the platform controls signing keys; developers receive public keys/JWKS information, never a private key or shared signing secret.
Your backend must verify all of the following before creating its own session:
- ES256 signature, exact
typofkit-launch+jwt, and a knownkidselected only from your configured Kit's JWKS. Never follow JWTjku,x5u, embedded keys, or a client-supplied issuer/JWKS URL. Cache at most 300 seconds; an unknownkidmay refresh once, then fails closed. Network/key errors must reject. - Exact product
iss, exact Kitaudand anoriginin your own configured HTTPS origin set. Do not accept another Kit's or another product's token. - Integer
iatandexp, positive lifetime no greater than 300 seconds, and strict expiry:nowMs >= exp * 1000rejects with no leeway. Onlyiatmay be at most 60 seconds ahead of your clock. Do not use a library-wide 60-second clock tolerance; that also extends expiry. Recheck time after async key fetch. - A matching, fresh nonce bound to this pre-session, consumed atomically once. Alternatively build an atomic shared replay store for
(iss, aud, jti)until at least expiry, together with secure request binding. Merely comparing a nonce, storing it per process, or doing separate read/delete is not enough. - Only after all checks succeed, create a fresh your-own-backend session keyed by
(iss, sub). Name, handle and avatar are display attributes, not authentication keys. Enforce your own business permissions separately.
Download the complete runnable examples, dependency manifests and offline tests:
- Integration README
- Node: verifier, tests, package.json, package-lock.json. Run
npm ci && npm testwith Node 22.13 or later. - Python: verifier, tests, requirements.txt. Install in a virtual environment, then run
python -m unittest -v test_verification.py.
Both examples use a durable SQLite atomic nonce store shared by backend processes. For multiple hosts/serverless instances use one shared transactional store; local per-instance databases are not sufficient. The README describes the required pre-session cookie, CSRF and HTTP integration. They are backend modules and tests, not a ready-made login server.
Revocation and session lifetime
Removing/revoking a Kit, losing access, account deletion or disabling identity stops new identity reads for that authorization. Unpublishing ends published user access; authorized managers may separately consent to an unpublished development preview with isolated grants. Existing signed tokens may still verify offline for their remaining lifetime (at most 300 seconds). Platform revocation cannot erase data already held by your backend or automatically end your sessions. Ask for a fresh launch token before sensitive operations and enforce your own session lifetime/logout policy.
On auth.changed, clear the displayed profile and your local identity state and call your own logout route. The host also closes or resets the affected document and website data. Treat the event as best effort: your backend cannot depend on a browser receiving it as a revocation guarantee.
bridge.on('auth.changed', () => {
document.querySelector('#name').textContent = '';
document.querySelector('#avatar').removeAttribute('src');
// End the site's own session through its normal CSRF-protected logout flow.
void logoutOwnSession();
});
Normal platform key rotation publishes the previous key for 24 hours alongside the new one. Revoked keys disappear from fresh JWKS immediately, but an existing key cache may still accept them for up to five minutes. On a revocation notice, refresh all backend key caches immediately. Neither rotation nor the overlap extends a token's expiry. Kit identity is distinct from the external Account Sign-in protocol.
Test and debug
- Before the first approval, only the Kit's owner and admins can open it: search for its
@handlein the app and open it. This uses the draft's settings, including the available capabilities it requests. People you add under Access & release can open it only after it is published. - A draft missing its entry URL or allowed domains shows "This Kit hasn't finished setup yet".
- WebView debugging (on Access & release) needs no review and applies only to the owner and admins; it is always off for everyone else. An open Kit picks up a change when it is next opened or returns to the foreground. With it on, connect Safari Web Inspector (iOS, macOS) or
chrome://inspect(Android). On Android, WebView debugging applies to the whole app process on your device while the Kit is on screen, so other web content in the app can be inspected too. - With debugging on, the More menu has Developer tools: Kit ID and container version, entry URL, enabled capabilities, reload, reset website data, and the last 100 bridge calls (method, result code and duration; no parameters or results).
- The User-Agent ends with
KoeeKit/1 (<Kit ID>). Use it for statistics or troubleshooting only; never for security decisions. - Recommended setup: a development Kit (for example
@shop_dev) that stays private, points at your test domain and has debugging on, and a separate production Kit (@shop). Handles are permanent, so pick the names deliberately.
Submit for review
- Complete every item on the Overview checklist: name, description, provider name, contact, privacy policy, entry URL and allowed domains.
- On Review, check the list of changes against the live version, then choose Submit for review.
- While it is in review you can keep editing; later edits are not part of that submission. Submitting again replaces the version in review, and you can withdraw it at any time.
- Approval publishes the submitted version. A rejection comes with a reason, shown on the Overview page.
Review checks the settings you submit: addresses, capabilities and listing. You remain responsible for your web content after approval; reported problems can lead to maintenance, suspension of capabilities or retirement.
If the published content changes while a submission waits (you make the Kit private, close or suspend a capability, or the operators edit it), the submission expires and you need to submit again. Maintenance, debugging, team and access-list changes do not affect it. If the operators disable or retire the Kit, the submission is withdrawn.
Visibility and discovery
- Private (the default): only the users, groups and channels on your access list can open the Kit; for a group or channel that means its current members. Add them by handle: users must be people (not Bots), and you must be an owner or admin of a group or channel you add. A Kit can list up to 1000 users and 50 groups and channels.
- Public: anyone can find the Kit by its full
@handle. Request it on Access & release; it applies after review. Making a Kit private again needs no review: people outside the access list can no longer open it, and a Kit they already have open closes when it next returns to the foreground. - Approval does not put a Kit in Plaza. Plaza is curated by the operators, and only public, published Kits are eligible.
- Maintenance stops the Kit from opening for a while; people who added it see "under maintenance". The operators can also disable or retire a Kit. Retirement is permanent.
Limits
| Action | Limit |
|---|---|
| Active Kits you own | 10 |
| Kits you can ever create | 30 |
| Kits in review at the same time | 5 |
| Creating Kits | 3 per 24 hours |
| Submitting and withdrawing | 10 each per 24 hours |
| Checking a Kit ID or handle | 30 per minute |
| Icon uploads | 50 per 24 hours, 2 at a time, 50 MB stored per Kit |
| Admins per Kit | 10 |
Admins and icon storage are counted per Kit; the other limits per account. The number of creations, submissions, withdrawals, ID and handle checks, and icon uploads also has a per-network-address limit of three times the account limit, so a team sharing one connection shares that allowance; the two uploads at a time are per account only. The 24-hour limits are rolling windows, not calendar days; checking Kit IDs and handles share one allowance; and attempts that fail can still count.
Security and privacy checklist
- Put no secrets in your pages or JavaScript: anyone can inspect them. Validate everything on your server.
user.getProfileis unsigned display data and cannot authenticate a backend request. Useauth.getLaunchTokenand verify its signature, claims and single-use nonce on your server before creating a session.- Keep the allowed domains under your control. Removing a domain is a draft change that needs review, so if you lose control of one, put the Kit into maintenance right away, contact support at support@koee.app, and submit the change.
- Keep your privacy policy link working and accurate, and collect only what your Kit needs.
- Do not try to escape the Kit: no navigation tricks, hidden frames that impersonate the app, or requests for permissions the container refuses.
Coming soon
- A companion Bot (
bot.chat) and sharing to chats (chat.share). - Support in Koee Web.
- A browser shim of
KitBridgefor developing without the app.