SMeetMini Apps

SMeet Mini Apps

SDK

The SDK is one small script without dependencies. It connects the page to the SMeet window, checks that the window really is SMeet, and gives the page login codes, the environment and a way to send results to the bot.

Files and addresses#

Version 1.0.0 comes in two builds of the same code:

FileWhat it is
smeet-mini-apps-1.0.0.jsa classic script; defines the global object SMeetMiniApp
smeet-mini-apps-1.0.0.esm.jsan ES module with the same exports: connect, NotInSMeetError, SMeetBridgeError, version
smeet-mini-apps-1.js, smeet-mini-apps-1.esm.jsaliases: the newest 1.x of each build

SMeet serves them at https://messenger.scrile.com/docs/mini-apps/sdk/<file>, for example smeet-mini-apps-1.0.0.js. A version never changes and is cached for a year; an alias moves to each new 1.x and is cached for a few minutes. Both are served to any site (Access-Control-Allow-Origin: *), so the module build loads from another origin too.

For a production app, put the SDK into your own build or serve the file from your origin: your page then does not depend on another server, and your script-src names only your own origin. Each example archive has the files of the version its example uses in sdk/, with their TypeScript declarations (*.d.ts).

Loading#

Load the SDK synchronously on every page of the app, before the page's load event: a <script src> in <head>, or a <script type="module">. Never load it lazily, after load, or from a button.

HTML
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
  <script src="/static/smeet-mini-apps-1.0.0.js"></script>
  <script src="/static/page.js" defer></script>
</head>
JavaScript
import * as SMeetMiniApp from "/static/smeet-mini-apps-1.0.0.esm.js";

Why: SMeet waits 15 seconds for the first page of the window to connect, and shows "The app is not responding or does not allow embedding" otherwise. When the person moves to the next page of your app, SMeet covers the window until that page's SDK answers, because only a live SDK proves that the page is still on your domain. A page without the SDK, or with the SDK loaded late, stays covered.

Load one SDK per page.

connect()#

JavaScript
let app;
try {
  app = await SMeetMiniApp.connect();
} catch (error) {
  if (error instanceof SMeetMiniApp.NotInSMeetError) return showOpenInSMeet();
  if (error instanceof SMeetMiniApp.SMeetBridgeError) return showError(error.code);  // NOT_READY after 10 s
  throw error;                                       // TypeError: a wrong containerOrigins
}
drawFirstScreen(app.environment);
await app.ready();
  • Outside SMeet (the page is not in a frame and has no SMeet handler on iOS) it fails at once with NotInSMeetError and listens to nothing. A page opened with window.open has no bridge.
  • Without an answer within 10 seconds it fails with SMeetBridgeError NOT_READY.
  • A wrong containerOrigins fails at once with TypeError, before anything is sent, and keeps nothing: the next connect() starts afresh.
  • Once the handshake has started, connect() runs once per document: a second call returns the same promise, and a failure is final until the page loads again; the person can press Retry or Reload in the window.
  • The object app exists only after connect() has finished; before that the page has nothing to call.

connect({containerOrigins}) replaces the list of SMeet origins the SDK accepts, ["https://messenger.scrile.com"]. It is for a local SMeet stand only: an http origin in it counts only while the page itself is open on localhost or 127.0.0.1; on a production page a list of only http origins fails with TypeError, so a page that kept the stand's list connects to nothing.

The environment#

app.environment is what SMeet says about this launch. The SDK keeps it up to date when the theme, the size or the language changes; app.getEnvironment() asks SMeet again.

FieldMeaning
bridgeVersion1
platformweb, ios or macos
appIdthe id of the Mini App, a decimal string
deploymentproduction or test
startParamthe parameter of the button or the link, up to 64 characters of A-Z a-z 0-9 _ -, or null
languageru or en, the language of the person's SMeet
colorSchemelight or dark
themecolors: bg, text, hint, link, button, buttonText, secondaryBg (Theme)
viewport{width, height, isExpanded}: the visible area under the SMeet bar and above the on-screen keyboard
safeArea{top, bottom, left, right}: the cut-outs of the device that cross the visible area
methodsthe methods this SMeet client serves

Use startParam to open the right screen, never as a permission: anyone can put any parameter into a link. Your server gets the same value as start_param of the exchange.

Methods#

CallResultWhat it does
app.supports(method)booleanwhether environment.methods lists the method
app.getEnvironment()the environmentasks SMeet for the environment again
app.ready()the first screen is drawn: SMeet removes its loading screen. Call it within 15 seconds of connecting
app.authorize(){code, codeVerifier, expiresAt}a new login code (Sign-in)
app.submitToBot(data, {requestId}){submissionId, status}sends data to the bot after the person confirms it (Sending to the bot)
app.getSubmissionStatus(submissionId){status}where a submission stands
app.openExternalLink(url){opened}opens an https address in the browser after the person agrees; opened is false when they did not. In the web app, after three presses of Cancel in a row in one launch, the next calls get RATE_LIMITED until the person opens the app again
app.setBackHandler(handler)handler is called on SMeet's Back instead of SMeet acting itself; null gives Back back to SMeet
app.setClosingConfirmation(enabled)while true, SMeet asks once before it closes the window: the page has unsaved changes
app.close()closes the window

Every method returns a promise. A method that is not in environment.methods fails with UNSUPPORTED_METHOD without reaching SMeet; check with app.supports() when a feature is optional.

Events#

SubscribeDataWhen
app.onThemeChanged(callback){colorScheme, theme}the person switched the light or dark look of SMeet
app.onViewportChanged(callback){viewport, safeArea}the keyboard opened or closed, the device turned, the window was resized, expanded or collapsed
app.onVisibilityChanged(callback){visible}SMeet hid the window, for example during a call, and showed it again
app.onLanguageChanged(callback){language}web only: the person changed the language; on iOS and macOS that closes the window

Each on... returns a function that removes the callback. When a callback runs, app.environment already holds the new values. Back comes to the handler of setBackHandler(), not as a subscription.

Sending to the bot#

JavaScript
const draft = loadDraft() || { requestId: crypto.randomUUID(), fields: {} };
try {
  const { submissionId } = await app.submitToBot(draft.fields, { requestId: draft.requestId });
  clearDraft();
  showReceipt(submissionId);
} catch (error) {
  if (error.code === "USER_DECLINED") return;               // the person pressed Cancel
  if (error.code === "SUBMISSION_CONFLICT") draft.requestId = crypto.randomUUID();
  saveDraft(draft);                                          // try again later with the same requestId
}
  • SMeet shows the person the app, the bot, the size and the fields, and sends only after Send. One confirmation at a time: a second call meanwhile gets BUSY. In the web app, after three presses of Cancel in a row in one launch, submitToBot() answers RATE_LIMITED until the person opens the app again: neither waiting nor Reload in the window resets it. Keep the draft and its requestId; the person sends it from a new window.
  • data goes in its JSON form, which must be an object, not an array, of up to 16 KiB in UTF-8. A Date, a Map, a Set or an object whose toJSON gives no object is refused with INVALID_PARAMS.
  • requestId is 1 to 64 characters of A-Z a-z 0-9 . _ : -. Make it together with the draft and keep it with the draft. Sending again with the same requestId and the same data, after an error, a reload or in a new window, gives the same submissionId and no second update for the bot. The same requestId with other data fails with SUBMISSION_CONFLICT: make a new one.
  • The bot receives a mini_app_submission update with data exactly as sent, client_request_id = requestId, and the person and chat fixed by SMeet (The update).

app.getSubmissionStatus(submissionId) answers for submissions of the same person, app, environment and space, also those of an earlier launch:

StatusMeans
pendingin the bot's queue, not confirmed yet
deliveredthe bot confirmed it
failedit is in the owner's list of failed updates
cancelledcancelled before delivery, or skipped by the owner
expirednot confirmed within 7 days

The answer of submitToBot() itself has the status accepted. An unknown, foreign or deleted submission gets INVALID_PARAMS.

Errors#

Every refusal of SMeet comes as SMeetBridgeError with one of these codes in error.code. Any other code from SMeet comes as INTERNAL, with the original code in error.message.

CodeMeansWhat to do
UNSUPPORTED_METHODthis SMeet client does not serve the methodcheck app.supports() and do without it
INVALID_PARAMSthe parameters are wrong, or the submission is unknownfix the call; do not repeat it as it is
NOT_READYthe window shows an error screen, or SMeet did not answer connect() in timenothing; the person retries in the window
USER_DECLINEDthe person pressed Cancel on the confirmationkeep the draft
BUSYa confirmation is open alreadywait for the first call
LAUNCH_CLOSEDthe launch is over; SMeet tells the person whystop; keep the draft
CODE_EXPIREDthe code of this request expired before it reached the pagecall authorize() again
RATE_LIMITEDtoo many calls, or, in the web app, three presses of Cancel in a row in this launchwait and repeat; after three Cancel keep the draft: the method stays refused until the person opens the app again
PAYLOAD_TOO_LARGEa message over 64 KiB, or data over 16 KiBsend less
SUBMISSION_CONFLICTthis requestId was used with other datamake a new requestId
QUEUE_FULLthe bot's queue is fullkeep the draft, repeat later with the same requestId
NOT_ALLOWEDthe configuration of the app does not allow it (for example sending to the bot)ask for the capability in the settings
MAINTENANCEMini Apps are pausedsay so, keep the draft, repeat later
NETWORKSMeet could not reach its server, or the page was replaced while the call was opencall the method again
INTERNALa fault of SMeetrepeat once, then show an error

CODE_EXPIRED and NETWORK are meant to be repeated: call the method again, which is a new request. A repeated submitToBot() with the same requestId asks the person again.

The bridge#

The SDK talks to the SMeet window over the bridge, version 1. You do not need to know it to use the SDK; it is described for those who check messages or test their pages.

  • In web the page and the window meet with a hello from the page and a connect from SMeet, which hands over a MessagePort. The SDK takes a port only from window.parent, only from an origin of its list (https://messenger.scrile.com), only with the bridge's name and version and exactly one port. Then requests, replies and events travel over the port; SMeet sends ping every second, the SDK answers pong, and on pagehide it sends leaving.
  • The domain in the bar of the window stays confirmed only while the page answers. When the page goes to another site or stops answering, SMeet covers it within 2 seconds and says "The page left <domain> or is not responding".
  • On iOS and macOS the page sends requests to the smeet message handler of the web view and gets each reply as the handler's answer; events arrive through window.__smeetMiniAppEvent.
  • A message is at most 64 KiB; a page sends at most 20 requests a second.

Every message, both ways, is described by the JSON Schema bridge.schema.json; the SDK's tests check the messages it sends and accepts against it. The schema at this address describes the current bridge exactly. Within version 1 each new release of it only adds optional fields, methods, events, status values and error codes, so what was valid stays valid. Check what your page sends, and your test doubles of SMeet, against the current schema; at run time skip what a message of SMeet carries beyond what you know instead of rejecting the message (Changelog).

What the SDK does not do#

  • It stores nothing: no browser storage, no cookies, nothing between documents. Unfinished requests live in the page's memory only to be sent again over a new port.
  • It checks no rights. SMeet checks them on its server for every call.
  • It has no switches for tests. To try your page outside SMeet, handle NotInSMeetError; to test against a local SMeet stand, pass containerOrigins.

TypeScript#

The declarations come with the SDK in every example archive: smeet-mini-apps-1.0.0.d.ts describes the global namespace SMeetMiniApp of the classic script, smeet-mini-apps-1.0.0.esm.d.ts the module. The types Environment, MiniApp, BridgeErrorCode, SubmitResult and the others have the names used on this page.