npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bountyboard/arcade-sdk

v1.1.0

Published

Bounty Board Arcade SDK — leaderboards, cloud saves, rewarded ads, A/B variants, and multiplayer rooms for games on bountyboard.gg

Readme

@bountyboard/arcade-sdk

Connect your HTML5 game to the Bounty Board arcade: global leaderboards, cross-device cloud saves, player identity, A/B variants, rewarded-ad revenue (90% studio share), site-lock anti-theft, and authoritative multiplayer rooms. Zero dependencies. Every call is optional and safe standalone — off Bounty Board the SDK quietly does nothing, so the same build runs anywhere.

Install

npm install @bountyboard/arcade-sdk
import { BBArcade } from '@bountyboard/arcade-sdk';

BBArcade.lockToHost(); // optional: refuse to run on scrape-and-reupload sites
BBArcade.init(); // handshake with the host (fire-and-forget is fine)
BBArcade.gameLoadingFinished(); // the player can press start
BBArcade.gameplayStart(); // a run began
BBArcade.submitScore(score); // as the score changes (host throttles)
BBArcade.gameOver(finalScore); // once per run — feeds the leaderboards

Full TypeScript definitions are included. Importing the module never installs a window.BBArcade global and is SSR-safe; if your setup wants the global, use import '@bountyboard/arcade-sdk/global'.

No build step? Use the script tag instead — same code, same protocol:

<script src="https://www.bountyboard.gg/arcade-sdk/v1.js"></script>

with standalone typings at /arcade-sdk.d.ts.

The surface in one look

| Call | What it does | | ---------------------------------------- | --------------------------------------------------------------------------- | | lockToHost({ allow? , signed? }) | Block scraped re-hosts of your build (deterrent, not DRM) | | init(options?) / configure(options?) | Configure modules (rewarded ads), announce to the host | | ready() / gameLoadingFinished() | Loading finished, player can start | | gameplayStart() / gameplayStop() | Bracket active play (playtime + feed ranking) | | submitScore(n, { mode?: 'daily' }) | Live integer score; server enforces plausibility caps | | gameOver(n, { mode?: 'daily' }) | Final score, once per run | | save(blob) / load() | 1MB cloud save per player (hosted builds; always catch rejections) | | getPlayer() | { name, avatarUrl } or null — display identity only, always handle null | | getVariant(key, variants) | Deterministic A/B split; alphabetical first variant = control | | prepareRewardedAd(opts) | Prepare an ad; call ready show() directly from the player's click | | rewardedBreak(opts) | Legacy one-call ad flow (kept for compatibility; avoid in new integrations) | | xrSessionStart() / xrSessionEnd() | Keep WebXR headset time counting as playtime | | multiplayer.joinRoom(...) | Authoritative multiplayer rooms — see below |

Rejections from save()/load() are real Errors carrying a typed machine-readable code: 'unsupported' | 'unauthenticated' | 'too_large' | 'rejected' | 'error'.

Rewarded ads without losing the player's click

Google can report that an ad is ready asynchronously. Prepare at the natural break (for example, when the defeat panel opens), then call the retained show() as the first SDK action inside the player's explicit Watch Ad click. Do not await, queue a microtask, or schedule a timer before show().

const prepared = await BBArcade.prepareRewardedAd({
  placement: 'death_revive',
  reward: 'extra_life',
  onStart: () => pauseGameAndAudio(),
});

if (prepared.status === 'ready') {
  watchAdButton.disabled = false;
  watchAdButton.onclick = () => {
    const result = prepared.show(); // synchronous: keep this first in the click handler
    void result.then(final => {
      if (final.status === 'viewed') revivePlayer();
      else showAdUnavailable(final.error, final.breakStatus);
    });
  };
} else {
  showAdUnavailable(prepared.error, prepared.breakStatus);
}

show() is one-shot and always returns the same final-result promise. Grant the reward only when status === 'viewed'; a raw breakStatus string is diagnostic context, not proof that Google fired its completion callback. rewardedBreak() remains available so existing games do not break, but new integrations should use the prepared flow because an asynchronous beforeReward callback cannot reliably preserve browser user activation.

Multiplayer

import { joinRoom } from '@bountyboard/arcade-sdk/multiplayer';

const room = await joinRoom({
  create: true, // or code: 'ABCD'
  joinData: { avatar: 'golem', mode: 'ffa' }, // untrusted, validated by your module
});
room.on('snapshot', ({ state }) => render(state)); // server-authoritative state
room.on('end', ({ results }) => showResults(results));
room.on('connection', status => showReconnecting(!status.connected));
if (!room.trySend({ move: dir })) showReconnecting(true); // inputs only

Rooms run on Bounty Board's authoritative room servers: clients send inputs, the server validates and simulates, and each player receives only the state they're allowed to see (your hiders stay hidden). Auth is a short-lived signed ticket the SDK obtains from the host automatically; off Bounty Board joinRoom rejects with code: 'unsupported'. joinData is limited to a 1 KiB JSON object, is never trusted or broadcast by the platform, and must not contain secrets. The room runtime supports separate simulation and snapshot rates (for example 60 Hz simulation / 30 Hz snapshots), a 15-second seat grace for automatic reconnects, and bounded server frames.

Multiplayer is currently a curated integration: Bounty Board must add and deploy a server-side game module for your slug before joinRoom() can accept players. The initial reference module is hide-and-seek; arbitrary games do not become authoritative multiplayer games merely by importing the client SDK. A production module must validate join data and every input, clear held controls on disconnect, serialize only per-viewer-safe state, and return final authoritative standings.

Docs

  • Human docs: https://www.bountyboard.gg/arcade/sdk
  • Agent-readable reference: https://www.bountyboard.gg/arcade/sdk/llms.txt
  • Integration best practices for coding agents: AGENTS.md
  • What makes a game perform on Bounty Board: docs/game-design-playbook.md
  • Submit your game: https://www.bountyboard.gg/arcade/submit

License

MIT. See LICENSE.