velven
Docs
Menu

Velven SDK

The Velven SDK adds sign-in, leaderboards, saved progress, achievements and stats, player-made content and multiplayer rooms to your space. Load one script and there is nothing to register: being listed on Velven is the registration. Velven stores the scores, saves and content, and hands them back for your space to draw in its own style.

View as Markdown

Install the Velven SDK

Use the script tag for a single page with no build step, or the npm package if you bundle. Both give you the same Velven object.

<script src="https://velven.ai/sdk/v1.js"></script>

With the package, import it where you need it:

JavaScript
import { Velven } from "@velven/sdk";

Note: If your page sends its own Content-Security-Policy, add https://velven.ai to its script-src so the script tag can load. The npm package needs no change, since it ships inside your bundle.

A first look

The fastest way in is Add a leaderboard in 10 minutes. In short, declare a board in the page's <head>, beside the proof tag that names you as the creator:

index.html
<meta name="velven" content="@handle"><script type="application/velven+json">{"boards":[{"key":"main","trust":"client"}]}</script><script src="https://velven.ai/sdk/v1.js"></script>

Then, in your game:

game.js
await Velven.ready();// A player signed in to Velven is known from the start.if (Velven.user) showName(Velven.user.handle);// When a run ends, post the score, then draw the board.const result = await Velven.scores.submit(points);if (!result.ok && result.error === "signed_out") showSignInButton();const top = await Velven.scores.top({ limit: 10 });if (top.ok) drawBoard(top.rows);

Where to go next:

Gameplay and sound

Call Velven.game.start() when play begins or resumes, and Velven.game.stop() at a natural break: a menu, the end of a level, a game over. Velven counts the time between the two calls as time spent playing.

game.js
function startLevel() {  Velven.game.start();  // ...}function showMenu() {  Velven.game.stop();  // ...}

Both calls are safe to repeat, since only a change reaches Velven. They return nothing, so there is nothing to await. You can call them before ready() settles; the Velven page gets the state once it answers.

If your space can turn its own sound off, register a handler with Velven.onMute. The Velven page then shows a sound button in the bar under your space (on a phone, in the row under its title) and calls your handler with the player's choice: true to mute, false to play sound again. Your handler hears the choice the player already made first, then every change.

game.js
const off = Velven.onMute((muted) => {  music.muted = muted;  effects.muted = muted;}); // off() to stop listening

Mute everything your space plays, music and effects alike. Without a handler, the Velven page shows no sound button. The player's choice is kept in their browser and applies to every space they play.

Note: On your own site (the site environment), both calls do nothing and the handler is never called, so the same code runs everywhere. To test a muted player locally, add ?velven_muted=1.

Leave Escape to the browser

The fullscreen button under your space uses the browser's own fullscreen, so Escape leaves fullscreen even while your space has the keys. If your space locks the pointer, Escape releases it too. Your page cannot stop either.

So don't bind Escape to anything in your space. If Escape pauses the game or opens a menu, one press does that and also drops the player out of fullscreen. Use another key, such as P, to pause. If you lock the pointer, pause when the lock is lost and lock it again on the next click:

game.js
canvas.addEventListener("click", () => canvas.requestPointerLock());document.addEventListener("pointerlockchange", () => {  if (!document.pointerLockElement) pause(); // Escape released the pointer});document.addEventListener("keydown", (event) => {  if (event.code === "KeyP") togglePause(); // not Escape: the browser takes it});

Call Velven.game.stop() in pause(), as at any other break.

Where it runs

Velven.ready() resolves with the environment the page is in, and Velven.environment holds it after that. Everything works in velven. In site and local your space still plays, with what the table lists.

Environment
velven
When
Inside the Velven frame, once the Velven page has answered.
What works
Everything: sign-in from the start, scores, reads and saves.
Environment
site
When
A top-level window on a real host, or a frame where nothing answered within 7 seconds.
What works
The space plays. Sign-in and score calls answer unavailable; saves use the page's own localStorage.
Environment
local
When
localhost, 127.0.0.1 or [::1], or any host with ?velven_local=1.
What works
Fake sign-in and in-memory boards, for local testing.

On your own site there is no board to draw, so check the environment first:

game.js
const environment = await Velven.ready();if (environment !== "site") drawBoard();

To have players who open your own address play on Velven instead, send them to your Velven page.

Send visitors to your Velven page

Players who open your space's own address, from a bookmark or an old post, can be sent to its page on Velven, where sign-in, boards and saves work. Add data-redirect to the script tag, in the page's <head> so it runs before anything draws:

<script src="https://velven.ai/sdk/v1.js" data-redirect></script>

A full example

A game listed on Velven at https://star-runner.example.com. The script tag sits right after the proof tag and the boards block, above every stylesheet, font and game script, so a visitor who is sent on leaves before the rest loads:

index.html
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Star Runner</title><meta name="velven" content="@handle"><script type="application/velven+json">{"boards":[{"key":"main","trust":"client"}]}</script><script src="https://velven.ai/sdk/v1.js" data-redirect></script><link rel="stylesheet" href="style.css"></head><body><canvas id="game"></canvas><script src="game.js"></script></body></html>

Where each visit ends up:

Opened
https://star-runner.example.com from a bookmark
Ends up
Star Runner's page on Velven.
Opened
https://star-runner.example.com/play?room=k7f2, an invite a friend sent
Ends up
Star Runner's page on Velven. The path is under the listed address, but ?room=k7f2 is dropped, so the friend lands on the game, not in the room.
Opened
Star Runner's page on Velven
Ends up
The game plays in the frame, signed in, as always.
Opened
https://preview.star-runner.example.com, a preview deployment
Ends up
Back on itself with ?velven_stay=1, and it stays for the rest of the tab.
Opened
Your game on localhost, while you build it
Ends up
Stays, in the local environment.

What goes and what stays

  • Only a page opened directly goes. Inside Velven, in another site's frame, in a popup your game opens, from a file on disk, on localhost or on your own network (a phone testing over Wi-Fi) it stays, and Velven's own recorder sees your page as it is.
  • Only the page's address and path go along, never its query or hash, so an invite like ?room=k7f2 does not carry over. Keep secrets out of the path too, and leave the browser's default referrer policy in place: a page set to unsafe-url sends its whole address, query included.
  • A page at or under a listed space's address goes to that space, so list a new game first, then add data-redirect. An address that is not a published space on Velven, such as a preview deployment, another domain of yours, or a space not listed yet, comes straight back to itself with ?velven_stay=1 and stays for the rest of the tab. That needs the browser to say where it came from, so a page that sends no referrer (<meta name="referrer" content="no-referrer">) sees a Velven page saying the space is not there instead, with a link back when the space is listed. With npm, call createVelven before anything rewrites the page's address, so it can read ?velven_stay=1.
  • Every direct visit goes, your own sign-in callback or admin page included. Search engines that run scripts may follow it and show the Velven page for your address.

Note: The game may show for a moment before the Velven page arrives. The script tag at the top of the <head> keeps that moment short; a bundled copy starts later, so it shows longer.

How calls answer

Every call resolves, with ok telling success from failure. A signed-out player or a refused score is an answer, not an exception, so you never wrap a call in try/catch. Only a programming mistake throws, such as a score that is not a number.

JavaScript
const result = await Velven.scores.submit(1240);if (result.ok) {  console.log(result.rank);} else {  console.log(result.error); // a code, such as "signed_out"}

The script has no dependencies.

Versions

The script tag always serves the latest release, so a page that loads it gets every fix with no change. Releases only add: nothing a page already calls changes or goes away. The npm package stays on the release you installed: update it when a release comes out, since a fix reaches a bundled copy only then. The changelog lists what each release added.

Note: For up to 2 minutes after a release, a player's browser may still run the release before. If you use a recently added call, check for it first, as in if (Velven.scores.mine), and do without it when it is missing. Velven.version reports the release, such as 0.3.0; it arrived in 0.3.0 itself, so when it is absent the release is older.

It works the other way too. A player who opened your space's Velven page before Velven last updated keeps that page until they reload it, while your space, reloaded inside it, may get the newest release. From 0.5.1, each release asks the Velven page what it supports and uses a newer message only when the page knows it, so every call still answers, as it did in the release before. 0.5.0 sent its batch reads without asking, so a page that bundles 0.5.0 should update.