uloggdDevelopers
Developer portaluloggd
Get startedResourcesArcade
Developer portaluloggdArcadeHostingThe frameSDKIdentityVerifying in other languagesData eventsTestingReview policyPrivacy dutiesEngines

SDK

@uloggd/arcade-sdk: the client, every command, event, error and close code, and the mock.

npm install @uloggd/arcade-sdk
  • MIT licensed, with no runtime dependencies. It targets ES2020 and is safe to import during server rendering.
  • It ships as ES modules and as CommonJS, both with type declarations, and as a build for a plain script tag (below).
  • @uloggd/arcade-sdk is the client, @uloggd/arcade-sdk/mock the mock, and @uloggd/arcade-sdk/server token verification for Node 20.19 or later, Deno, Bun and Workers (see Identity; for servers in other languages, see Verifying in other languages).
  • uloggd never serves the SDK to games: you bundle it, or host it with your game.
VersionWhat it ships
0.1.0ES modules only.
0.2.0Adds CommonJS, loaded through require, and the script-tag build, dist/uloggd-arcade.min.js. The API and the protocol are unchanged.

Without a bundler

The package includes dist/uloggd-arcade.min.js, a build for a plain <script> tag that defines one global, UloggdArcade. Copy it from node_modules/@uloggd/arcade-sdk/dist/ into your game, with uloggd-arcade.min.js.map if you want readable stack traces, and serve it from your game's own origin.

<script src="./uloggd-arcade.min.js"></script>
<script>
  const arcade = UloggdArcade.createArcadeSDK({
    appId: "YOUR_APP_ID",
    mockWhenStandalone: true,
  });
  arcade.ready().then(({ context }) => startGame(context));
</script>
  • UloggdArcade holds createArcadeSDK, ArcadeSDKError, ArcadeSDKMock and the constants (SDK_VERSION, ERROR_CODES, HostCloseCode, GuestCloseCode, PROTOCOL_VERSION, SUBSCRIBABLE_EVENTS and DEFAULT_HOST_ORIGIN). Token verification is not in it: it belongs on your server.
  • Serve it yourself. uloggd does not serve it, and a copy on a public CDN is a third party your release must declare, one that can change what the game runs without a new review.
  • Without npm, the same file is in https://registry.npmjs.org/@uloggd/arcade-sdk/-/arcade-sdk-0.2.0.tgz, under package/dist/.
  • Load it as a classic script, before the scripts that use it. As a module script (type="module") it defines no global.
  • The ES modules in dist also work from a module script, which imports dist/client.js with its chunks folder beside it.

Create the client

import { createArcadeSDK, ArcadeSDKError } from "@uloggd/arcade-sdk";

const arcade = createArcadeSDK({
  appId: "YOUR_APP_ID",
  mockWhenStandalone: true,
});

try {
  const { context } = await arcade.ready();
  // context: { appId, instanceId, mode, locale, platform, layout, game }
  startGame(context);
} catch (error) {
  if (error instanceof ArcadeSDKError && error.code === "not_embedded") {
    showMessage("Play this game on uloggd.com");
  }
}
Option
appIdYour app's id, as the portal shows it: a lowercase uuid.
hostOriginWhere uloggd runs. https://uloggd.com by default; an http:// origin is accepted only when the game itself runs on a loopback origin.
mockWhenStandaloneOpened directly on a loopback origin (localhost), outside any frame, the client is the mock. Anywhere else outside uloggd, ready() rejects with not_embedded, so a misdeployed game fails loudly.

createArcadeSDK throws invalid_args at once for a malformed appId or hostOrigin.

Member
ready()Resolves once, after the handshake, with { protocol, commands, context }. Rejects with not_embedded, with timeout when uloggd does not answer the handshake within 10 seconds, or with closed.
contextThe same context, or null before ready() resolves.
commandsThe commands below.
subscribe(event, listener, onError?)Listens to an event and returns the function that stops listening.
supports(command)Whether uloggd announced the command in READY. Check it before using a command newer than the SDK you shipped.
close({ code, message? })Ends the session, with a code from the close codes below.
destroy()Ends the session normally, with code 1000, as close would, and rejects everything pending. For an effect cleanup or a hot reload.

Commands

Every command returns a promise and times out: after 10 seconds, or after 5 minutes for authorize, which waits on the player.

CommandArgumentsResultNeeds
authorize{ scopes: ["identify"] }{ scopes }
getPlayernone{ id, nickname }identify
getIdentityToken{ nonce }{ token, expiresAt }identify
getGamenone{ appId, slug, name }
requestFullscreennone{ fullscreen }a gesture
exitFullscreennone{ fullscreen }
  • authorize opens uloggd's own consent dialog, outside the frame, where the player picks the nickname your game will see (a generated one by default). Without the pointer-lock capability the dialog opens directly, or as soon as the tab is shown again if it is hidden. With it, the dialog does not open on its own, because uloggd cannot see a lock your game holds: the player's bar shows the request with an Answer button, and the dialog opens from that click. Ask only for scopes the release declared, or it fails with invalid_args. If the player cancels, it fails with user_cancelled, and asking again keeps failing until the player opens the game again. Ask after a click of your own, such as "Save my progress", rather than on load. A player who already agreed to your game is not asked again.
  • getPlayer returns the player's per-game id and nickname, unverified. Show them; never trust them on your server.
  • getIdentityToken returns a token signed by uloggd, for your server to verify. The nonce comes from your server: 16 to 128 characters of letters, digits, _ and -. One token every 3 seconds, and 30 per player per hour.
  • getGame returns the approved name and the slug, the address reserved when Arcade was enabled. The slug is never null today; the type allows null for later versions.
  • requestFullscreen and exitFullscreen act on uloggd's container, with its bar visible. Call requestFullscreen from a click or a key press, or it fails with requires_gesture.
document.querySelector("#save")!.addEventListener("click", async () => {
  await arcade.commands.authorize({ scopes: ["identify"] });
  const { nonce } = await fetch("/session/nonce", { method: "POST" }).then(
    (r) => r.json(),
  );
  const { token } = await arcade.commands.getIdentityToken({ nonce });
  await fetch("/session", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ token }),
  });
});

Events

const off = arcade.subscribe("LAYOUT_UPDATE", (layout) => {
  resize(layout.width, layout.height);
});
// later
off();
EventData
READY{ protocol: 1, commands, context }. Sent once, without subscribing; ready() resolves with it.
VISIBILITY_UPDATE{ visible }
LAYOUT_UPDATE{ width, height, fullscreen, safeArea: { top, right, bottom, left } }
LOCALE_UPDATE{ locale }
CLOSING{ reason, deadlineMs }, where reason is user, hidden or suspended.
GRANT_UPDATE{ scopes }
  • The first listener of an event subscribes, and the last off() unsubscribes. A subscription uloggd refuses removes its listeners and calls onError.
  • The SDK also writes the safe area on :root, as --uloggd-safe-area-inset-top, -right, -bottom and -left.
  • CLOSING leaves you until deadlineMs to save.
  • GRANT_UPDATE arrives when the player revokes your game's access in Settings. Stop calling getPlayer and getIdentityToken, and treat the player as signed out of your server.

Errors

Every rejection is an ArcadeSDKError, with a code:

CodeMeans
not_embeddedNot inside uloggd, and not on the mock.
unknown_commanduloggd does not know the command; check supports() first.
invalid_argsThe arguments were refused: a scope the release did not declare, a malformed nonce, an extra key.
missing_scopeThe player has not granted the scope the command needs.
user_cancelledThe player closed uloggd's dialog.
requires_gestureThe command needs a click or a key press in the game first.
rate_limitedToo many commands, or too many tokens.
timeoutNo answer in time.
closedThe session closed before the answer. When a close code ended it, the error carries it as closeCode.
unavailableuloggd cannot do it right now, such as issuing identity tokens with no signing key available.
internalSomething failed on uloggd's side.

Close codes

uloggd ends the session with one of these, and removes the frame:

CodeMeans
4000Invalid frame or app.
4001Invalid origin.
4002Too many messages.
4003The player revoked the game's access.
4004Unsupported protocol version.
4010The game was suspended or switched off.

The game may close with 1000 (normal) or a code of its own from 4500 to 4599. The message, up to 200 characters, reaches the browser console in test mode only: players only ever see uloggd's own words.

Limits

The SDK keeps you inside them, but they matter if you send many commands at once: each message is at most 64 KiB, at most 32 commands wait for an answer at a time, and commands are limited to 20 a second with bursts of 50. Past that, uloggd closes the session with 4002.

The mock

On localhost, with mockWhenStandalone: true, the client is an ArcadeSDKMock, which has the same interface:

  • every command answers from fixtures. authorize grants at once, as a player who agrees would, and until then getPlayer and getIdentityToken fail with missing_scope, as they do inside uloggd;
  • ?mock_player=2 in the page's address gives a second player, so two tabs can be two players;
  • identity tokens are signed with a throwaway key and iss: "mock", which the server verifiers refuse unless you pass allowMock: true.

From the script-tag build, the mock is UloggdArcade.ArcadeSDKMock.

In tests, build the mock yourself and steer it. Besides appId it takes mode, locale, platform, player, and scopes for a player who already agreed:

import { ArcadeSDKError } from "@uloggd/arcade-sdk";
import { ArcadeSDKMock } from "@uloggd/arcade-sdk/mock";

const arcade = new ArcadeSDKMock({
  appId: "YOUR_APP_ID",
  scopes: ["identify"],
});
arcade.setPlayer({
  id: "6d0c0d6e-3f7a-4d4b-9d61-2f1f5b0a8c11",
  nickname: "Ada",
});
arcade.overrideCommand("authorize", async () => {
  throw new ArcadeSDKError("user_cancelled");
});
arcade.emit("VISIBILITY_UPDATE", { visible: false });

The frame

What a game can and cannot do inside uloggd's player, how it starts and stops, and the parameters it is launched with.

Identity

Know who is playing, from a five-minute token signed by uloggd and verified by your server, offline.

On this page

Without a bundlerCreate the clientCommandsEventsErrorsClose codesLimitsThe mock