@bountyboard/arcade-sdk 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,8 @@ var AD_PLACEMENT_SRC = "https://pagead2.googlesyndication.com/pagead/js/adsbygoo
10
10
  var AD_PLACEMENT_SCRIPT_ID = "bb-arcade-h5-ads";
11
11
  var DEFAULT_AD_LOAD_TIMEOUT_MS = 6e3;
12
12
  var SAVE_TIMEOUT_MS = 15e3;
13
+ var LOCK_TIMEOUT_MS = 5e3;
14
+ var MAX_SAVE_BYTES = 1024 * 1024;
13
15
  var INIT_CONFIG_GRACE_MS = 1500;
14
16
  var DEFAULT_ALLOW = [
15
17
  "bountyboard.gg",
@@ -34,6 +36,14 @@ function cleanText(value, fallback, max) {
34
36
  if (typeof value !== "string") return fallback || "";
35
37
  return value.replace(/[^\w:.-]+/g, "_").slice(0, max || 80) || fallback || "";
36
38
  }
39
+ var cachedTextEncoder = null;
40
+ function utf8ByteLength(value) {
41
+ if (typeof TextEncoder !== "undefined") {
42
+ cachedTextEncoder = cachedTextEncoder || new TextEncoder();
43
+ return cachedTextEncoder.encode(value).length;
44
+ }
45
+ return new Blob([value]).size;
46
+ }
37
47
  function toBoolean(value) {
38
48
  return value === true || value === "true" || value === "1";
39
49
  }
@@ -153,13 +163,14 @@ function createBBArcade() {
153
163
  };
154
164
  let hostConfigReceived = false;
155
165
  let hostPlayer = null;
166
+ const playerChangeHandlers = [];
156
167
  let blocked = false;
157
168
  const pending = {};
158
169
  let seq = 0;
159
170
  function post(type, score, extra) {
160
171
  if (blocked) return;
161
172
  const message = { source: SDK_MESSAGE_SOURCE, v: VERSION, type };
162
- if (typeof score === "number" && isFinite(score)) message.score = Math.floor(score);
173
+ if (typeof score === "number" && isFinite(score)) message.score = Math.trunc(score);
163
174
  if (extra && typeof extra === "object") {
164
175
  const fields = extra;
165
176
  for (const key in fields) {
@@ -180,7 +191,7 @@ function createBBArcade() {
180
191
  return true;
181
192
  }
182
193
  }
183
- function request(type, payload) {
194
+ function request(type, payload, timeoutMs = SAVE_TIMEOUT_MS) {
184
195
  return new Promise(function(resolve, reject) {
185
196
  if (blocked) return reject(sdkError("error"));
186
197
  if (!isEmbedded()) return reject(sdkError("unsupported"));
@@ -190,7 +201,7 @@ function createBBArcade() {
190
201
  delete pending[requestId];
191
202
  reject(sdkError("error"));
192
203
  }
193
- }, SAVE_TIMEOUT_MS);
204
+ }, timeoutMs);
194
205
  pending[requestId] = { resolve, reject, timer };
195
206
  try {
196
207
  window.parent.postMessage(
@@ -282,7 +293,7 @@ function createBBArcade() {
282
293
  if (!hostnameAllowed(origin, allow)) blockHost(options);
283
294
  return;
284
295
  }
285
- request("host").then(
296
+ request("host", void 0, LOCK_TIMEOUT_MS).then(
286
297
  function(res) {
287
298
  const r = res;
288
299
  if (!hostnameAllowed(r && r.origin, allow)) blockHost(options);
@@ -306,7 +317,7 @@ function createBBArcade() {
306
317
  lockToHostBestEffort(opts, allow);
307
318
  return;
308
319
  }
309
- request("host_attest").then(
320
+ request("host_attest", void 0, LOCK_TIMEOUT_MS).then(
310
321
  function(res) {
311
322
  const r = res;
312
323
  if (!r || !r.attestation) {
@@ -368,7 +379,15 @@ function createBBArcade() {
368
379
  if (msg.source !== HOST_MESSAGE_SOURCE || msg.type !== "config") return;
369
380
  hostConfigReceived = true;
370
381
  if (msg.player !== void 0) {
371
- hostPlayer = sanitizePlayer(msg.player);
382
+ const nextPlayer = sanitizePlayer(msg.player);
383
+ const changed = nextPlayer === null ? hostPlayer !== null : hostPlayer === null || nextPlayer.name !== hostPlayer.name || nextPlayer.avatarUrl !== hostPlayer.avatarUrl;
384
+ hostPlayer = nextPlayer;
385
+ if (changed) {
386
+ const snapshot = nextPlayer ? { name: nextPlayer.name, avatarUrl: nextPlayer.avatarUrl } : null;
387
+ for (let i = 0; i < playerChangeHandlers.length; i++) {
388
+ safeCall(playerChangeHandlers[i], snapshot);
389
+ }
390
+ }
372
391
  }
373
392
  const rewardedAds = msg.rewardedAds;
374
393
  if (rewardedAds && typeof rewardedAds === "object") {
@@ -500,6 +519,7 @@ function createBBArcade() {
500
519
  let resolved = false;
501
520
  let status = "unavailable";
502
521
  let rewardOffered = false;
522
+ let adShown = false;
503
523
  const waitTimer = setTimeout(function() {
504
524
  if (rewardOffered || resolved) return;
505
525
  const timeout = Object.assign(
@@ -544,6 +564,7 @@ function createBBArcade() {
544
564
  }
545
565
  post("rewarded_ad", void 0, Object.assign({ event: "started" }, base));
546
566
  try {
567
+ adShown = true;
547
568
  showAdFn();
548
569
  } catch (err) {
549
570
  const showError = Object.assign(
@@ -593,7 +614,13 @@ function createBBArcade() {
593
614
  },
594
615
  adBreakDone: function(placementInfo) {
595
616
  if (resolved) return;
596
- const result = rewardOffered || status !== "unavailable" ? { status } : { status: "unavailable", error: "no_rewarded_ad" };
617
+ const result = rewardOffered || status !== "unavailable" ? {
618
+ // A reward was offered but the ad never reached the screen
619
+ // (the game retained show() and let the break lapse): the
620
+ // break is over without a view, so resolve 'dismissed',
621
+ // not the non-terminal-looking 'ready'.
622
+ status: status === "ready" && !adShown ? "dismissed" : status
623
+ } : { status: "unavailable", error: "no_rewarded_ad" };
597
624
  if (placementInfo && typeof placementInfo === "object") {
598
625
  result.breakStatus = cleanText(
599
626
  placementInfo.breakStatus
@@ -796,12 +823,25 @@ function createBBArcade() {
796
823
  prepareRewardedBreak: prepareRewardedAd,
797
824
  rewardedBreak,
798
825
  save: function(blob) {
799
- return request("save", String(blob));
826
+ const data = String(blob);
827
+ if (utf8ByteLength(data) > MAX_SAVE_BYTES) {
828
+ return Promise.reject(sdkError("too_large"));
829
+ }
830
+ return request("save", data);
800
831
  },
801
832
  load: function() {
802
833
  return request("load");
803
834
  },
804
835
  getPlayer,
836
+ onPlayerChange: function(handler) {
837
+ if (typeof handler !== "function") return function() {
838
+ };
839
+ playerChangeHandlers.push(handler);
840
+ return function() {
841
+ const idx = playerChangeHandlers.indexOf(handler);
842
+ if (idx !== -1) playerChangeHandlers.splice(idx, 1);
843
+ };
844
+ },
805
845
  getVariant: function(key, variants) {
806
846
  const sorted = Array.isArray(variants) ? variants.slice().sort() : [];
807
847
  const fallback = sorted.length > 0 ? sorted[0] : null;
@@ -868,6 +908,8 @@ function createInertBBArcade() {
868
908
  save: () => Promise.reject(sdkError2("unsupported")),
869
909
  load: () => Promise.reject(sdkError2("unsupported")),
870
910
  getPlayer: () => Promise.resolve(null),
911
+ onPlayerChange: () => () => {
912
+ },
871
913
  getVariant: (key, variants) => {
872
914
  const sorted = Array.isArray(variants) ? variants.slice().sort() : [];
873
915
  return Promise.resolve(sorted.length > 0 ? sorted[0] : null);
@@ -885,4 +927,4 @@ export {
885
927
  BBArcade,
886
928
  src_default
887
929
  };
888
- //# sourceMappingURL=chunk-OWOESH4X.js.map
930
+ //# sourceMappingURL=chunk-KDBBR532.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protocol.ts","../src/core.ts","../src/inert.ts","../src/index.ts"],"sourcesContent":["/**\n * Wire-protocol constants for the bb-arcade postMessage protocol. The protocol\n * version is deliberately decoupled from the npm package's semver: every\n * message carries `v: VERSION`, and the host accepts all messages of the same\n * major protocol. Bump VERSION only for a breaking wire change (which also\n * means a new /arcade-sdk/v2.js URL).\n */\nexport const VERSION = 1;\n\n/** `source` field on every message the game/SDK posts to the embedding page. */\nexport const SDK_MESSAGE_SOURCE = 'bb-arcade';\n\n/** `source` field on every message the Bounty Board host posts to the game. */\nexport const HOST_MESSAGE_SOURCE = 'bb-arcade-host';\n","/**\n * Bounty Board Arcade SDK core — a faithful, typed port of the original\n * public/arcade-sdk.js IIFE. The whole SDK is one factory so every piece of\n * state (blocked flag, pending requests, ad config, host config) lives in one\n * closure per instance, exactly like the original script. Behavior parity with\n * the shipped v1 script is the hard requirement here; the v1 behavior test\n * suite (src/__tests__/arcade-sdk.test.ts at the repo root) runs against the\n * generated artifact and must pass unchanged.\n */\nimport { HOST_MESSAGE_SOURCE, SDK_MESSAGE_SOURCE, VERSION } from './protocol';\nimport type {\n BBArcadeConfig,\n BBArcadeError,\n BBArcadeErrorCode,\n BBArcadeLockToHostOptions,\n BBArcadePlayer,\n BBArcadePreparedRewardedAd,\n BBArcadePrepareRewardedAdOptions,\n BBArcadeRewardedAdOptions,\n BBArcadeRewardedAdPreparation,\n BBArcadeRewardedAdPrepareFailure,\n BBArcadeRewardedAdResult,\n BBArcadeRewardedBreakOptions,\n BBArcadeSDK,\n} from './types';\n\n// Canonical production home, used for the site-lock \"play the original\" link.\nconst BOUNTY_BOARD_URL = 'https://www.bountyboard.gg/arcade';\nconst ADSENSE_CLIENT_ID = 'ca-pub-7727110228190547';\nconst AD_PLACEMENT_SRC =\n 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=' + ADSENSE_CLIENT_ID;\nconst AD_PLACEMENT_SCRIPT_ID = 'bb-arcade-h5-ads';\nconst DEFAULT_AD_LOAD_TIMEOUT_MS = 6000;\nconst SAVE_TIMEOUT_MS = 15000;\n// The site-lock handshake (host / host_attest) gets a shorter fuse than data\n// requests: when nothing answers, the game is a scraped copy and every second\n// of delay is free playtime for the re-hosting site.\nconst LOCK_TIMEOUT_MS = 5000;\n// Client-side mirror of the server's per-save byte cap\n// (MAX_SAVE_BYTES in src/lib/arcade/buildAssets.ts) so an oversized blob is\n// rejected with 'too_large' immediately instead of after a host round trip.\nconst MAX_SAVE_BYTES = 1024 * 1024;\n// How long init() waits for the host's config reply before resolving anyway.\n// Standalone play resolves immediately (there is no host to answer), so this\n// grace only delays games embedded somewhere that isn't a Bounty Board host.\nconst INIT_CONFIG_GRACE_MS = 1500;\n\n// Allowed by default: bountyboard.gg (and any *.bountyboard.gg subdomain),\n// Bounty Board's fixed staging alias, and localhost/127.0.0.1 for local\n// development — i.e. every environment Bounty Board itself serves the game\n// from. The staging alias is a single PINNED Bounty Board deployment, so\n// hardcoding that one host is safe; we still deliberately DON'T trust shared\n// or dynamic preview domains broadly (e.g. *.vercel.app) — a per-deploy\n// preview host or your own site must be added via the `allow` option.\n// This list gates BOTH the sitelock AND which parent origin may send the host\n// config (player identity / rewarded-ads), so a missing environment here\n// silently breaks the whole SDK handshake there, not just the sitelock.\nconst DEFAULT_ALLOW = [\n 'bountyboard.gg',\n 'bountyboard-staging.vercel.app',\n 'localhost',\n '127.0.0.1',\n];\n\n// Public half of the sitelock attestation keypair (ECDSA P-256, SPKI). The\n// matching private key lives ONLY in Bounty Board server env\n// (ARCADE_SITELOCK_PRIVATE_KEY); a re-hosting site can neither sign a fresh\n// attestation nor replay a captured one (the sender-origin check below).\nconst SITELOCK_PUBLIC_KEY_B64 =\n 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6WC8fJweDpfKRcZW9Q9eOLxPXmFCO8R8lY1kfZbuAQt65E0CTu2rM80XfW3nYrSAqO+Eev8gxrGS0bAnNaMDGg==';\n/** Attestations older/newer than this are stale (replay window). */\nconst SITELOCK_MAX_SKEW_MS = 5 * 60 * 1000;\n\n/**\n * Google H5 ads globals the SDK shims/loads on demand. A standalone shape\n * (not `extends Window`) so host apps that augment Window themselves (e.g.\n * test files declaring `adBreak?: jest.Mock`) can't create a conflicting\n * interface under a shared tsconfig.\n */\ntype AdsWindow = {\n adsbygoogle?: unknown[];\n adBreak?: (placement: Record<string, unknown>) => void;\n adConfig?: (settings: Record<string, unknown>) => void;\n};\n\ninterface HostAttestation {\n origin: string;\n ts: number;\n sig: string;\n}\n\n/** The identifying fields every rewarded-ad result/lifecycle event carries. */\ninterface AdResultBase {\n placement: string;\n reward: string;\n adBreakId: string;\n size?: 'small' | 'medium' | 'large';\n}\n\ninterface PendingEntry {\n resolve: (value: unknown) => void;\n reject: (err: BBArcadeError) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface NormalizedRewardedOptions {\n placement: string;\n reward: string;\n adBreakId: string;\n size: '' | 'small' | 'medium' | 'large';\n adChannel: string;\n testMode: boolean;\n loadTimeoutMs: number;\n beforeReward?: (showAd: () => void) => void;\n adViewed?: (result: BBArcadeRewardedAdResult) => void;\n adDismissed?: (result: BBArcadeRewardedAdResult) => void;\n adBreakDone?: (placementInfo: unknown) => void;\n onReward?: (result: BBArcadeRewardedAdResult) => void;\n onDismissed?: (result: BBArcadeRewardedAdResult) => void;\n onDone?: (result: BBArcadeRewardedAdResult) => void;\n onError?: (result: BBArcadeRewardedAdResult) => void;\n onUnavailable?: (result: BBArcadeRewardedAdResult) => void;\n}\n\nfunction sdkError(code: BBArcadeErrorCode): BBArcadeError {\n const err = new Error(code) as BBArcadeError;\n err.code = code;\n return err;\n}\n\nfunction b64ToBytes(b64: string): Uint8Array {\n const bin = atob(b64);\n const bytes = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);\n return bytes;\n}\n\nfunction cleanText(value: unknown, fallback?: string, max?: number): string {\n if (typeof value !== 'string') return fallback || '';\n return value.replace(/[^\\w:.-]+/g, '_').slice(0, max || 80) || fallback || '';\n}\n\n// Lazy: the SDK must import cleanly even where TextEncoder is missing (some\n// test/SSR shims); Blob gives the same exact UTF-8 byte count as a fallback.\nlet cachedTextEncoder: TextEncoder | null = null;\n\nfunction utf8ByteLength(value: string): number {\n if (typeof TextEncoder !== 'undefined') {\n cachedTextEncoder = cachedTextEncoder || new TextEncoder();\n return cachedTextEncoder.encode(value).length;\n }\n return new Blob([value]).size;\n}\n\nfunction toBoolean(value: unknown): boolean {\n return value === true || value === 'true' || value === '1';\n}\n\nfunction cleanRewardedSize(value: unknown): '' | 'small' | 'medium' | 'large' {\n return value === 'small' || value === 'medium' || value === 'large' ? value : '';\n}\n\n/**\n * Google requires showAdFn to run while the browser still has transient user\n * activation from the player's direct click/tap. Fail closed when the browser\n * cannot positively prove that activation is active; showing without it is\n * both unreliable and contrary to the placement API contract.\n */\nfunction hasTransientUserActivation(): boolean {\n if (typeof navigator === 'undefined') return false;\n const activation = (\n navigator as Navigator & {\n userActivation?: { isActive?: boolean };\n }\n ).userActivation;\n return activation?.isActive === true;\n}\n\nfunction safeCall<T>(fn: ((arg: T) => void) | undefined, arg?: T): void {\n if (typeof fn !== 'function') return;\n try {\n fn(arg as T);\n } catch (err) {\n setTimeout(function () {\n throw err;\n }, 0);\n }\n}\n\n// Normalize an allow-list entry to a bare hostname, so a game author can pass a\n// plain host (\"mygame.com\"), a full URL (\"https://mygame.com/play\"), or a\n// host:port and still match against the embedder's hostname.\nfunction toAllowedHost(value: unknown): string {\n const raw = String(value).toLowerCase().trim();\n if (!raw) return '';\n try {\n return new URL(raw.indexOf('//') === -1 ? 'https://' + raw : raw).hostname;\n } catch (err) {\n return raw;\n }\n}\n\nfunction hostnameAllowed(origin: unknown, allowHosts: string[]): boolean {\n if (!origin || origin === 'null' || typeof origin !== 'string') return false;\n let host: string;\n try {\n host = new URL(origin).hostname.toLowerCase();\n } catch (err) {\n return false;\n }\n for (let i = 0; i < allowHosts.length; i++) {\n const allowed = allowHosts[i];\n // exact host, or a subdomain of an allowed host (\".allowed\")\n if (host === allowed || host.slice(-(allowed.length + 1)) === '.' + allowed) return true;\n }\n return false;\n}\n\n/**\n * Verify a host attestation: shape, freshness, the LOAD-BEARING sender\n * check (the postMessage carrying it must come from the origin it attests —\n * event.origin is browser-authenticated, so a stolen attestation replayed\n * from another site fails here), then the ECDSA signature over\n * \"<origin>:<ts>\". Resolves boolean; never throws.\n */\nfunction verifyHostAttestation(att: unknown, senderOrigin: string): Promise<boolean> {\n const a = att as HostAttestation | null | undefined;\n if (!a || typeof a.origin !== 'string' || typeof a.ts !== 'number' || typeof a.sig !== 'string')\n return Promise.resolve(false);\n if (a.origin !== senderOrigin) return Promise.resolve(false);\n if (Math.abs(Date.now() - a.ts) > SITELOCK_MAX_SKEW_MS) return Promise.resolve(false);\n try {\n return window.crypto.subtle\n .importKey(\n 'spki',\n b64ToBytes(SITELOCK_PUBLIC_KEY_B64),\n { name: 'ECDSA', namedCurve: 'P-256' },\n false,\n ['verify']\n )\n .then(function (key) {\n return window.crypto.subtle.verify(\n { name: 'ECDSA', hash: 'SHA-256' },\n key,\n b64ToBytes(a.sig),\n new TextEncoder().encode(a.origin + ':' + a.ts)\n );\n })\n .then(\n function (ok) {\n return ok === true;\n },\n function () {\n return false;\n }\n );\n } catch (err) {\n return Promise.resolve(false);\n }\n}\n\n// Normalize the host-sent player identity. Only a non-empty string name\n// counts; the avatar is a plain URL string or null. Anything malformed\n// collapses to null so games can rely on the { name, avatarUrl } shape.\nfunction sanitizePlayer(value: unknown): BBArcadePlayer | null {\n if (!value || typeof value !== 'object') return null;\n const raw = value as { name?: unknown; avatarUrl?: unknown };\n const name = typeof raw.name === 'string' ? raw.name.trim().slice(0, 120) : '';\n if (!name) return null;\n const avatarUrl =\n typeof raw.avatarUrl === 'string' && raw.avatarUrl.trim() ? raw.avatarUrl : null;\n return { name: name, avatarUrl: avatarUrl };\n}\n\n/**\n * Build one SDK instance. Attaches this instance's `message` listeners to\n * `window` immediately (the host pushes its config at load time, so listener\n * timing is part of the protocol). Browser-only — the module entry substitutes\n * an inert stub when `window` is undefined (SSR-safety for bundled games).\n */\nexport function createBBArcade(): BBArcadeSDK {\n let adPlacementLoad: Promise<AdsWindow['adBreak'] | null> | null = null;\n let adPreloadConfigured = false;\n let rewardedBreakActive: { state: 'loading' | 'ready' | 'showing' } | null = null;\n let rewardedBreakSequence = 0;\n\n function isLocalAdHost(): boolean {\n try {\n const protocol = window.location && window.location.protocol;\n const host = window.location && window.location.hostname;\n return (\n protocol === 'file:' ||\n host === 'localhost' ||\n host === '127.0.0.1' ||\n host === '::1' ||\n host === '[::1]'\n );\n } catch (err) {\n return false;\n }\n }\n\n const localAdHost = isLocalAdHost();\n const config = {\n rewardedAds: {\n enabled: true,\n adChannel: '',\n testMode: localAdHost,\n loadTimeoutMs: DEFAULT_AD_LOAD_TIMEOUT_MS,\n preload: false,\n },\n };\n const hostConfig = {\n rewardedAds: {\n received: false,\n enabled: localAdHost,\n adChannel: '',\n testMode: localAdHost,\n preload: false,\n },\n };\n // True once ANY valid host config message has arrived — this (not the\n // rewarded-ads sub-flag) is what resolves the init()/getPlayer() handshake.\n let hostConfigReceived = false;\n // The logged-in player's display identity from the host config:\n // { name, avatarUrl } or null for guests. Display name + avatar ONLY — the\n // host never sends ids, emails, or roles.\n let hostPlayer: BBArcadePlayer | null = null;\n // Subscribers notified when the host delivers a CHANGED player identity\n // (login/logout mid-session). Change-only: subscribing does not replay the\n // current value, and a config message that repeats the same identity stays\n // silent.\n const playerChangeHandlers: Array<(player: BBArcadePlayer | null) => void> = [];\n\n // Set once lockToHost() decides the game is running somewhere it shouldn't.\n // After that the SDK goes silent so a scraped copy can't keep posting.\n let blocked = false;\n\n const pending: Record<string, PendingEntry> = {};\n let seq = 0;\n\n // The game can be embedded by any Bounty Board environment (prod, staging,\n // previews), so target '*' — the payload carries nothing sensitive and the\n // parent page validates the message's source window + origin itself.\n function post(type: string, score?: number, extra?: object): void {\n if (blocked) return;\n const message: Record<string, unknown> = { source: SDK_MESSAGE_SOURCE, v: VERSION, type: type };\n if (typeof score === 'number' && isFinite(score)) message.score = Math.trunc(score);\n if (extra && typeof extra === 'object') {\n const fields = extra as Record<string, unknown>;\n for (const key in fields) {\n if (Object.prototype.hasOwnProperty.call(fields, key) && fields[key] !== undefined) {\n message[key] = fields[key];\n }\n }\n }\n try {\n window.parent.postMessage(message, '*');\n } catch (err) {\n /* not embedded — standalone play is fine */\n }\n }\n\n function isEmbedded(): boolean {\n try {\n return window.parent != null && window.parent !== window;\n } catch (err) {\n return true; // touching a cross-origin parent threw → we are embedded\n }\n }\n\n // ── Request/response over postMessage (cloud save, experiments, host) ──────\n // Cloud saves only work for Bounty-Board-hosted build uploads: those run in a\n // sandbox with no localStorage/IndexedDB, so saves go through the embedding\n // page (which holds the player's login). save/load return a Promise; the\n // parent posts back a result for the matching requestId. Rejections are Error\n // objects that also carry a machine-readable `code` property.\n function request(\n type: string,\n payload?: unknown,\n timeoutMs: number = SAVE_TIMEOUT_MS\n ): Promise<unknown> {\n return new Promise(function (resolve, reject) {\n if (blocked) return reject(sdkError('error'));\n // Standalone play (no embedding page): nothing will ever answer, so fail\n // fast instead of hanging until the timeout.\n if (!isEmbedded()) return reject(sdkError('unsupported'));\n const requestId = type + '_' + ++seq + '_' + Date.now();\n const timer = setTimeout(function () {\n if (pending[requestId]) {\n delete pending[requestId];\n reject(sdkError('error'));\n }\n }, timeoutMs);\n pending[requestId] = { resolve: resolve, reject: reject, timer: timer };\n try {\n window.parent.postMessage(\n {\n source: SDK_MESSAGE_SOURCE,\n v: VERSION,\n type: type,\n requestId: requestId,\n payload: payload,\n },\n '*'\n );\n } catch (err) {\n clearTimeout(timer);\n delete pending[requestId];\n reject(sdkError('error'));\n }\n });\n }\n\n window.addEventListener('message', function (event: MessageEvent) {\n // Replies come from the embedding page only; ignore anything else (e.g. a\n // co-resident ad/nested frame, or a null-source synthetic event) so it\n // can't resolve a pending request with a forged payload. Mirrors the\n // host-config listener's source check below.\n if (event.source !== window.parent) return;\n const data = event.data as\n | {\n source?: string;\n type?: string;\n requestId?: string;\n ok?: boolean;\n code?: BBArcadeErrorCode;\n data?: unknown;\n attestation?: unknown;\n variant?: unknown;\n }\n | null\n | undefined;\n if (!data || data.source !== SDK_MESSAGE_SOURCE) return;\n // Any '<type>_result' with a pending requestId is a legitimate reply: the\n // sender is already proven to be the embedding host (source check above)\n // and requestIds are per-request — so enumerating reply types here would\n // only create a synchronized-edit hazard with the host (a forgotten entry\n // = that request silently timing out after 15s).\n if (typeof data.type !== 'string' || !data.type.endsWith('_result')) return;\n const entry = pending[data.requestId as string];\n if (!entry) return;\n clearTimeout(entry.timer);\n delete pending[data.requestId as string];\n // The browser-authenticated sender origin is authoritative. Never trust a\n // parent-supplied origin string: an arbitrary embedder could claim to be\n // bountyboard.gg and enable host-only SDK features.\n if (data.type === 'host_result') return entry.resolve({ origin: event.origin });\n if (data.type === 'host_attest_result')\n // Keep the browser-authenticated sender origin alongside the claimed\n // attestation — the verifier requires them to MATCH.\n return entry.resolve({ origin: event.origin, attestation: data.attestation || null });\n if (data.type === 'experiment_result')\n return entry.resolve(typeof data.variant === 'string' ? data.variant : null);\n // Generic ok/code convention (save_result, load_result, mp_ticket_result,\n // and any future reply type): resolve the carried data — load()'s \"no\n // save yet\" stays null, save()'s bare ack stays undefined.\n if (data.ok)\n entry.resolve(\n data.type === 'load_result' ? (data.data != null ? data.data : null) : data.data\n );\n else entry.reject(sdkError(data.code || 'error'));\n });\n\n // ── Site lock (anti scrape-and-reupload) ───────────────────────────────────\n\n // The origin of the page that embeds this game, read from the browser (which\n // the embedding page can't forge). Returns null when it can't be determined\n // synchronously (opaque-origin builds with no referrer) — callers fall back to\n // the host handshake then.\n function embeddingOriginSync(): string | null {\n try {\n if (window.top === window.self) return window.location.origin; // not embedded\n } catch (err) {\n /* cross-origin top → we are embedded; keep probing */\n }\n try {\n const ancestors = window.location.ancestorOrigins;\n if (ancestors && ancestors.length && ancestors[0] && ancestors[0] !== 'null')\n return ancestors[0]; // the immediate embedder\n } catch (err) {\n /* not supported (Firefox) → fall through to referrer */\n }\n if (document.referrer) {\n try {\n return new URL(document.referrer).origin;\n } catch (err) {\n /* unparseable referrer */\n }\n }\n return null;\n }\n\n function blockHost(options: BBArcadeLockToHostOptions): void {\n if (blocked) return;\n blocked = true;\n if (typeof options.onBlocked === 'function') {\n try {\n options.onBlocked();\n } catch (err) {\n /* the game's own handler threw — nothing more we can do */\n }\n return;\n }\n if (options.redirect) {\n try {\n (window.top as Window).location.href = options.redirect; // escape the framing if allowed\n return;\n } catch (err) {\n try {\n window.location.href = options.redirect;\n return;\n } catch (err2) {\n /* navigation blocked (sandbox) → fall through to the splash */\n }\n }\n }\n try {\n // Link the splash out to Bounty Board so a player who lands on a scraped\n // copy can reach the real game. target=\"_blank\" works even from a\n // sandboxed re-host iframe that can't navigate its top; the canonical\n // production URL is intentional (a copy blocked anywhere points home).\n document.documentElement.innerHTML =\n '<div style=\"position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;' +\n 'font-family:system-ui,-apple-system,sans-serif;background:#1a1206;color:#fff;text-align:center;padding:24px;\">' +\n '<div style=\"font-size:18px;font-weight:700;\">This game isn\\'t available here</div>' +\n '<a href=\"' +\n BOUNTY_BOARD_URL +\n '\" target=\"_blank\" rel=\"noopener\" ' +\n 'style=\"display:inline-block;background:#f5b942;color:#1a1206;font-weight:700;font-size:14px;' +\n 'text-decoration:none;padding:10px 20px;border-radius:999px;\">Play the original on Bounty Board</a>' +\n '</div>';\n } catch (err) {\n /* document not writable — the silent `blocked` flag still stops SDK posts */\n }\n }\n\n /** The original best-effort check (browser signals + host handshake). */\n function lockToHostBestEffort(options: BBArcadeLockToHostOptions, allow: string[]): void {\n const origin = embeddingOriginSync();\n if (origin != null) {\n if (!hostnameAllowed(origin, allow)) blockHost(options);\n return; // conclusive without the parent\n }\n // Opaque-origin build with no referrer: ask the parent where we're hosted.\n // Shorter fuse than data requests — an unanswered lock handshake means a\n // scraped copy, and it should not get 15s of free play before the block.\n request('host', undefined, LOCK_TIMEOUT_MS).then(\n function (res) {\n const r = res as { origin?: string } | null;\n if (!hostnameAllowed(r && r.origin, allow)) blockHost(options);\n },\n function () {\n blockHost(options); // no Bounty Board parent answered\n }\n );\n }\n\n function lockToHost(options?: BBArcadeLockToHostOptions): void {\n const opts = options || {};\n const allow = DEFAULT_ALLOW.slice();\n if (opts.allow && opts.allow.length) {\n for (let i = 0; i < opts.allow.length; i++) {\n const host = toAllowedHost(opts.allow[i]);\n if (host) allow.push(host);\n }\n }\n // Signed mode ({ signed: true }): demand a server-signed origin\n // attestation from the parent and verify it cryptographically. Graceful\n // degradation is deliberate and narrow: no crypto.subtle (insecure dev\n // context) or an honest \"not configured\" reply falls back to the\n // best-effort check; a PRESENT attestation that fails verification, or no\n // parent answering at all, blocks.\n if (opts.signed) {\n if (!(window.crypto && window.crypto.subtle)) {\n lockToHostBestEffort(opts, allow);\n return;\n }\n request('host_attest', undefined, LOCK_TIMEOUT_MS).then(\n function (res) {\n const r = res as { origin: string; attestation: HostAttestation | null } | null;\n if (!r || !r.attestation) {\n lockToHostBestEffort(opts, allow); // host has no signing key yet\n return;\n }\n verifyHostAttestation(r.attestation, r.origin).then(function (ok) {\n if (!ok || !hostnameAllowed(r.attestation && r.attestation.origin, allow))\n blockHost(opts);\n });\n },\n function () {\n blockHost(opts); // no Bounty Board parent answered\n }\n );\n return;\n }\n lockToHostBestEffort(opts, allow);\n }\n\n // ── Rewarded ads (Google H5 placement API, host-gated) ─────────────────────\n\n function nextAdBreakId(): string {\n rewardedBreakSequence += 1;\n return [\n 'bbad',\n Date.now().toString(36),\n rewardedBreakSequence.toString(36),\n Math.random().toString(36).slice(2, 10),\n ].join('_');\n }\n\n function configure(nextConfig?: BBArcadeConfig): void {\n if (!nextConfig || typeof nextConfig !== 'object') return;\n const rewardedAds = nextConfig.rewardedAds;\n if (!rewardedAds || typeof rewardedAds !== 'object') return;\n\n if (rewardedAds.enabled !== undefined) {\n config.rewardedAds.enabled = rewardedAds.enabled !== false;\n }\n if (rewardedAds.adChannel !== undefined) {\n config.rewardedAds.adChannel = cleanText(rewardedAds.adChannel);\n }\n if (rewardedAds.testMode !== undefined) {\n config.rewardedAds.testMode = toBoolean(rewardedAds.testMode);\n }\n if (rewardedAds.preload !== undefined) {\n config.rewardedAds.preload = toBoolean(rewardedAds.preload);\n }\n if (\n typeof rewardedAds.loadTimeoutMs === 'number' &&\n isFinite(rewardedAds.loadTimeoutMs) &&\n rewardedAds.loadTimeoutMs > 0\n ) {\n config.rewardedAds.loadTimeoutMs = Math.min(15000, Math.floor(rewardedAds.loadTimeoutMs));\n }\n if (config.rewardedAds.preload) preloadRewardedAds();\n }\n\n // init() promises waiting on the host's config message (the real handshake:\n // the host re-sends its config whenever the game posts init/ready, so a\n // late-loading SDK can't miss the load-time push).\n let configWaiters: Array<() => void> = [];\n\n function notifyConfigReceived(): void {\n const waiters = configWaiters;\n configWaiters = [];\n for (let i = 0; i < waiters.length; i++) safeCall(waiters[i]);\n }\n\n function applyHostConfig(message: unknown): void {\n if (!message || typeof message !== 'object') return;\n const msg = message as {\n source?: string;\n type?: string;\n player?: unknown;\n rewardedAds?: {\n enabled?: unknown;\n adChannel?: unknown;\n testMode?: unknown;\n preload?: unknown;\n };\n };\n if (msg.source !== HOST_MESSAGE_SOURCE || msg.type !== 'config') return;\n\n hostConfigReceived = true;\n\n // Player display identity (name + avatar only; null for guests). Only\n // applied when the key is present, so an older host that doesn't send it\n // can't clear a value a newer message already delivered.\n if (msg.player !== undefined) {\n const nextPlayer = sanitizePlayer(msg.player);\n const changed =\n nextPlayer === null\n ? hostPlayer !== null\n : hostPlayer === null ||\n nextPlayer.name !== hostPlayer.name ||\n nextPlayer.avatarUrl !== hostPlayer.avatarUrl;\n hostPlayer = nextPlayer;\n if (changed) {\n // Hand out a copy so a handler can't mutate the SDK's own record.\n const snapshot = nextPlayer\n ? { name: nextPlayer.name, avatarUrl: nextPlayer.avatarUrl }\n : null;\n for (let i = 0; i < playerChangeHandlers.length; i++) {\n safeCall(playerChangeHandlers[i], snapshot);\n }\n }\n }\n\n const rewardedAds = msg.rewardedAds;\n if (rewardedAds && typeof rewardedAds === 'object') {\n hostConfig.rewardedAds.received = true;\n if (rewardedAds.enabled !== undefined) {\n hostConfig.rewardedAds.enabled = toBoolean(rewardedAds.enabled);\n }\n if (rewardedAds.adChannel !== undefined) {\n hostConfig.rewardedAds.adChannel = cleanText(rewardedAds.adChannel);\n }\n if (rewardedAds.testMode !== undefined) {\n hostConfig.rewardedAds.testMode = toBoolean(rewardedAds.testMode);\n }\n if (rewardedAds.preload !== undefined) {\n hostConfig.rewardedAds.preload = toBoolean(rewardedAds.preload);\n }\n\n if (\n hostConfig.rewardedAds.enabled &&\n (hostConfig.rewardedAds.preload || config.rewardedAds.preload)\n ) {\n preloadRewardedAds();\n }\n }\n\n notifyConfigReceived();\n }\n\n function areRewardedAdsEnabled(): boolean {\n return config.rewardedAds.enabled !== false && hostConfig.rewardedAds.enabled === true;\n }\n\n function installAdPlacementShim(): void {\n const w = window as unknown as AdsWindow;\n w.adsbygoogle = w.adsbygoogle || [];\n if (typeof w.adBreak !== 'function') {\n w.adBreak = function (placement) {\n (w.adsbygoogle as unknown[]).push(placement);\n };\n }\n if (typeof w.adConfig !== 'function') {\n w.adConfig = function (settings) {\n (w.adsbygoogle as unknown[]).push(settings);\n };\n }\n }\n\n function ensureAdPlacement(\n options: NormalizedRewardedOptions\n ): Promise<AdsWindow['adBreak'] | null> {\n if (typeof document === 'undefined') return Promise.resolve(null);\n installAdPlacementShim();\n const w = window as unknown as AdsWindow;\n if (typeof w.adBreak !== 'function') return Promise.resolve(null);\n if (adPlacementLoad) return adPlacementLoad;\n\n adPlacementLoad = new Promise(function (resolve) {\n let script = document.getElementById(AD_PLACEMENT_SCRIPT_ID);\n if (!script) {\n const el = document.createElement('script');\n el.id = AD_PLACEMENT_SCRIPT_ID;\n el.async = true;\n el.src = AD_PLACEMENT_SRC;\n el.crossOrigin = 'anonymous';\n el.setAttribute('data-ad-client', ADSENSE_CLIENT_ID);\n if (options.adChannel) el.setAttribute('data-ad-channel', options.adChannel);\n if (options.testMode) el.setAttribute('data-adbreak-test', 'on');\n (document.head || document.documentElement).appendChild(el);\n script = el;\n }\n resolve(w.adBreak);\n });\n\n return adPlacementLoad;\n }\n\n function requestAdPreload(): void {\n const w = window as unknown as AdsWindow;\n if (adPreloadConfigured || typeof w.adConfig !== 'function') return;\n adPreloadConfigured = true;\n try {\n w.adConfig({ preloadAdBreaks: 'on' });\n } catch (err) {\n // Preloading is best-effort; the rewarded placement can still report\n // availability through adBreakDone.\n }\n }\n\n function normalizeRewardedOptions(\n options?: BBArcadeRewardedAdOptions\n ): NormalizedRewardedOptions {\n const opts = options && typeof options === 'object' ? options : {};\n const hostHasConfig = hostConfig.rewardedAds.received;\n return {\n placement: cleanText(opts.placement || opts.name, 'rewarded'),\n reward: cleanText(opts.reward, 'reward'),\n adBreakId: cleanText(opts.adBreakId, nextAdBreakId(), 128),\n size: cleanRewardedSize(opts.size),\n adChannel: cleanText(\n hostHasConfig\n ? hostConfig.rewardedAds.adChannel\n : opts.adChannel || config.rewardedAds.adChannel\n ),\n testMode: hostHasConfig\n ? hostConfig.rewardedAds.testMode\n : opts.testMode !== undefined\n ? toBoolean(opts.testMode)\n : config.rewardedAds.testMode || hostConfig.rewardedAds.testMode,\n loadTimeoutMs:\n typeof opts.loadTimeoutMs === 'number' && isFinite(opts.loadTimeoutMs)\n ? Math.min(15000, Math.max(1, Math.floor(opts.loadTimeoutMs)))\n : config.rewardedAds.loadTimeoutMs,\n beforeReward: opts.beforeReward,\n adViewed: opts.adViewed,\n adDismissed: opts.adDismissed,\n adBreakDone: opts.adBreakDone,\n onReward: opts.onReward,\n onDismissed: opts.onDismissed,\n onDone: opts.onDone,\n onError: opts.onError,\n onUnavailable: opts.onUnavailable,\n };\n }\n\n function rewardedAd(rawOptions?: BBArcadeRewardedAdOptions): Promise<BBArcadeRewardedAdResult> {\n const options = normalizeRewardedOptions(rawOptions);\n const base: AdResultBase = {\n placement: options.placement,\n reward: options.reward,\n adBreakId: options.adBreakId,\n };\n if (options.size) base.size = options.size;\n\n post('rewarded_ad', undefined, Object.assign({ event: 'requested' }, base));\n if (!areRewardedAdsEnabled()) {\n const disabled: BBArcadeRewardedAdResult = Object.assign(\n { status: 'unavailable' as const, error: 'host_disabled' },\n base\n );\n post('rewarded_ad', undefined, Object.assign({ event: 'unavailable' }, disabled));\n safeCall(options.onUnavailable, disabled);\n safeCall(options.onDone, disabled);\n return Promise.resolve(disabled);\n }\n\n return ensureAdPlacement(options).then(function (adBreak) {\n if (typeof adBreak !== 'function') {\n const unavailable: BBArcadeRewardedAdResult = Object.assign(\n { status: 'unavailable' as const, error: 'ad_break_unavailable' },\n base\n );\n post('rewarded_ad', undefined, Object.assign({ event: 'unavailable' }, unavailable));\n safeCall(options.onUnavailable, unavailable);\n safeCall(options.onDone, unavailable);\n return unavailable;\n }\n requestAdPreload();\n\n return new Promise<BBArcadeRewardedAdResult>(function (resolve) {\n let resolved = false;\n let status: BBArcadeRewardedAdResult['status'] = 'unavailable';\n let rewardOffered = false;\n // True once Google's showAdFn was actually invoked. Distinguishes \"the\n // break ended with the ad on screen but no view/dismiss callback\" (a\n // terminal 'ready', kept for v1 compatibility) from \"a prepared ad was\n // never shown before the break ended\" (terminal 'dismissed').\n let adShown = false;\n const waitTimer = setTimeout(function () {\n if (rewardOffered || resolved) return;\n const timeout: BBArcadeRewardedAdResult = Object.assign(\n { status: 'unavailable' as const, error: 'ad_break_timeout' },\n base\n );\n post('rewarded_ad', undefined, Object.assign({ event: 'unavailable' }, timeout));\n safeCall(options.onUnavailable, timeout);\n done(timeout);\n }, options.loadTimeoutMs);\n\n function done(result?: Partial<BBArcadeRewardedAdResult>): void {\n if (resolved) return;\n resolved = true;\n clearTimeout(waitTimer);\n const finalResult = Object.assign(\n { status: 'unavailable' as BBArcadeRewardedAdResult['status'] },\n base,\n result || {}\n );\n post('rewarded_ad', undefined, Object.assign({ event: 'done' }, finalResult));\n safeCall(options.onDone, finalResult);\n resolve(finalResult);\n }\n\n function startAd(showAdFn: () => void): void {\n // A late beforeReward (ad becomes ready after the load timeout already\n // resolved the placement 'unavailable') must not still show an ad — the\n // game has moved on, so it would be an unrewarded, involuntary impression.\n if (resolved) return;\n rewardOffered = true;\n clearTimeout(waitTimer);\n status = 'ready';\n let shown = false;\n function show(): void {\n if (shown) return;\n shown = true;\n if (!hasTransientUserActivation()) {\n const activationError: BBArcadeRewardedAdResult = Object.assign(\n { status: 'error' as const, error: 'direct_user_action_required' },\n base\n );\n post('rewarded_ad', undefined, Object.assign({ event: 'error' }, activationError));\n safeCall(options.onError, activationError);\n done(activationError);\n return;\n }\n post('rewarded_ad', undefined, Object.assign({ event: 'started' }, base));\n try {\n adShown = true;\n showAdFn();\n } catch (err) {\n const showError: BBArcadeRewardedAdResult = Object.assign(\n { status: 'error' as const, error: 'show_ad_error' },\n base\n );\n post('rewarded_ad', undefined, Object.assign({ event: 'error' }, showError));\n safeCall(options.onError, showError);\n done(showError);\n }\n }\n\n post('rewarded_ad', undefined, Object.assign({ event: 'ready' }, base));\n try {\n if (typeof options.beforeReward === 'function') options.beforeReward(show);\n else show();\n } catch (err) {\n const beforeError: BBArcadeRewardedAdResult = Object.assign(\n { status: 'error' as const, error: 'before_reward_error' },\n base\n );\n post('rewarded_ad', undefined, Object.assign({ event: 'error' }, beforeError));\n // If the callback called show() before throwing, Google's ad is\n // already in flight. Keep the placement alive so a later adViewed\n // can still grant the reward; this error is diagnostic only.\n if (shown) return;\n safeCall(options.onError, beforeError);\n done(beforeError);\n }\n }\n\n try {\n adBreak({\n type: 'reward',\n name: options.placement,\n beforeReward: startAd,\n adViewed: function () {\n if (resolved) return;\n status = 'viewed';\n const viewed: BBArcadeRewardedAdResult = Object.assign({ status: status }, base);\n post('rewarded_ad', undefined, Object.assign({ event: 'viewed' }, viewed));\n safeCall(options.adViewed, viewed);\n safeCall(options.onReward, viewed);\n },\n adDismissed: function () {\n if (resolved) return;\n if (status !== 'viewed') status = 'dismissed';\n const dismissed: BBArcadeRewardedAdResult = Object.assign({ status: status }, base);\n post('rewarded_ad', undefined, Object.assign({ event: 'dismissed' }, dismissed));\n safeCall(options.adDismissed, dismissed);\n safeCall(options.onDismissed, dismissed);\n },\n adBreakDone: function (placementInfo: unknown) {\n if (resolved) return;\n const result: Partial<BBArcadeRewardedAdResult> =\n rewardOffered || status !== 'unavailable'\n ? {\n // A reward was offered but the ad never reached the screen\n // (the game retained show() and let the break lapse): the\n // break is over without a view, so resolve 'dismissed',\n // not the non-terminal-looking 'ready'.\n status: status === 'ready' && !adShown ? 'dismissed' : status,\n }\n : { status: 'unavailable', error: 'no_rewarded_ad' };\n if (placementInfo && typeof placementInfo === 'object') {\n result.breakStatus = cleanText(\n (placementInfo as { breakStatus?: unknown }).breakStatus\n );\n }\n if (result.status === 'unavailable') {\n post(\n 'rewarded_ad',\n undefined,\n Object.assign({ event: 'unavailable' }, base, result)\n );\n safeCall(\n options.onUnavailable,\n Object.assign({ status: 'unavailable' as const }, base, result)\n );\n }\n safeCall(options.adBreakDone, placementInfo);\n done(result);\n },\n });\n } catch (err) {\n const error: BBArcadeRewardedAdResult = Object.assign(\n { status: 'error' as const, error: 'ad_break_error' },\n base\n );\n post('rewarded_ad', undefined, Object.assign({ event: 'error' }, error));\n safeCall(options.onError, error);\n done(error);\n }\n });\n });\n }\n\n /**\n * Two-stage rewarded flow. Google's beforeReward callback can arrive after\n * the original request stack, when the browser's transient user activation\n * is already gone. Retain the SDK's one-shot show function here and hand it\n * back to the game so show() itself can be invoked synchronously by the\n * player's later click/tap.\n *\n * The existing rewardedAd implementation remains the single lifecycle\n * engine: this wrapper only owns beforeReward, so callbacks, structured\n * errors, Google's breakStatus, and the viewed-only reward signal cannot\n * drift between the legacy and prepared APIs.\n */\n function prepareRewardedAd(\n rawOptions?: BBArcadePrepareRewardedAdOptions\n ): Promise<BBArcadeRewardedAdPreparation> {\n const normalized = normalizeRewardedOptions(rawOptions);\n const base: AdResultBase = {\n placement: normalized.placement,\n reward: normalized.reward,\n adBreakId: normalized.adBreakId,\n };\n if (normalized.size) base.size = normalized.size;\n\n let ready = false;\n let finalPromise: Promise<BBArcadeRewardedAdResult>;\n\n return new Promise<BBArcadeRewardedAdPreparation>(function (resolvePreparation) {\n finalPromise = rewardedAd(\n Object.assign({}, rawOptions, {\n // Preserve the id used by the ready handle and every lifecycle event;\n // rewardedAd normalizes again, but this explicit id keeps both stages\n // tied to the same placement.\n adBreakId: normalized.adBreakId,\n beforeReward: function (showAd: () => void) {\n if (ready) return;\n ready = true;\n let shown = false;\n const prepared: BBArcadePreparedRewardedAd = Object.assign(\n { status: 'ready' as const },\n base,\n {\n show: function (): Promise<BBArcadeRewardedAdResult> {\n // The first meaningful operation is synchronous. A click\n // handler can call prepared.show() without an await/microtask\n // stealing the browser's transient user activation.\n if (!shown) {\n shown = true;\n // Do not pause the game/audio when the display attempt is\n // about to fail closed for missing activation. showAd()\n // still runs so the caller receives the structured error.\n if (hasTransientUserActivation()) {\n safeCall(rawOptions && rawOptions.onStart);\n }\n showAd();\n }\n return finalPromise;\n },\n }\n );\n resolvePreparation(prepared);\n },\n })\n );\n\n finalPromise.then(function (result) {\n // No beforeReward means Google never offered an ad. Return the exact\n // structured terminal result, including error + breakStatus, rather\n // than collapsing the preparation to a boolean.\n if (!ready) resolvePreparation(result as BBArcadeRewardedAdPrepareFailure);\n });\n });\n }\n\n function preloadRewardedAds(rawOptions?: BBArcadeRewardedAdOptions): Promise<boolean> {\n const options = normalizeRewardedOptions(rawOptions);\n if (!areRewardedAdsEnabled()) return Promise.resolve(false);\n return ensureAdPlacement(options).then(function (adBreak) {\n if (typeof adBreak !== 'function') return false;\n requestAdPreload();\n return true;\n });\n }\n\n function whenHostConfig(graceMs: number): Promise<void> {\n if (hostConfigReceived || !isEmbedded()) return Promise.resolve();\n return new Promise(function (resolve) {\n const timer = setTimeout(resolve, graceMs);\n configWaiters.push(function () {\n clearTimeout(timer);\n resolve();\n });\n });\n }\n\n // The player's display identity, delivered with the host's config message.\n // Waits on the same handshake (and grace) as init(), so it resolves null —\n // never hangs — for guests, standalone play, or when no host ever answers.\n function getPlayer(): Promise<BBArcadePlayer | null> {\n if (blocked) return Promise.resolve(null);\n return whenHostConfig(INIT_CONFIG_GRACE_MS).then(function () {\n // Hand out a copy so a game can't mutate the SDK's own record.\n return hostPlayer ? { name: hostPlayer.name, avatarUrl: hostPlayer.avatarUrl } : null;\n });\n }\n\n function init(options?: BBArcadeConfig): Promise<void> {\n configure(options);\n // Announce to the host. A Bounty Board host replies to init/ready with its\n // config message, which resolves this promise — so config delivery doesn't\n // depend on the host's load-time push racing the SDK attaching a listener.\n post('init');\n return whenHostConfig(INIT_CONFIG_GRACE_MS);\n }\n\n function ready(): void {\n post('ready');\n }\n\n function normalizeRewardedBreakInput(\n optionsOrOnStart?: BBArcadeRewardedBreakOptions | (() => void)\n ): BBArcadeRewardedBreakOptions {\n if (typeof optionsOrOnStart === 'function') return { onStart: optionsOrOnStart };\n return optionsOrOnStart && typeof optionsOrOnStart === 'object' ? optionsOrOnStart : {};\n }\n\n function rewardedBreak(\n optionsOrOnStart?: BBArcadeRewardedBreakOptions | (() => void)\n ): Promise<boolean> {\n const rawOptions = normalizeRewardedBreakInput(optionsOrOnStart);\n const normalized = normalizeRewardedOptions(rawOptions);\n const base: AdResultBase = {\n adBreakId: normalized.adBreakId,\n placement: normalized.placement,\n reward: normalized.reward,\n };\n if (normalized.size) base.size = normalized.size;\n\n if (rewardedBreakActive && rewardedBreakActive.state !== 'ready') {\n post(\n 'rewarded_ad',\n undefined,\n Object.assign(\n { event: 'unavailable', status: 'unavailable', error: 'ad_in_progress' },\n base\n )\n );\n return Promise.resolve(false);\n }\n\n const activeBreak: NonNullable<typeof rewardedBreakActive> = { state: 'loading' };\n rewardedBreakActive = activeBreak;\n let startCalled = false;\n const callerBeforeReward = rawOptions.beforeReward;\n return rewardedAd(\n Object.assign({}, rawOptions, {\n beforeReward: function (showAd: () => void) {\n if (rewardedBreakActive === activeBreak) activeBreak.state = 'ready';\n const showWithStart = function (): void {\n // A newer unshown rewardedBreak supersedes this retained offer.\n // Ignore a stale click so it cannot pause the game or emit a\n // misleading started event after Google has invalidated it.\n if (startCalled || rewardedBreakActive !== activeBreak) return;\n startCalled = true;\n activeBreak.state = 'showing';\n if (hasTransientUserActivation()) safeCall(rawOptions.onStart);\n showAd();\n };\n // Preserve the caller's two-stage readiness UI. Legacy callers that\n // omitted beforeReward keep their original one-call behavior, but the\n // guarded show wrapper above now fails closed if activation is gone.\n if (typeof callerBeforeReward === 'function') callerBeforeReward(showWithStart);\n else showWithStart();\n },\n })\n ).then(\n function (result) {\n if (rewardedBreakActive === activeBreak) rewardedBreakActive = null;\n return Boolean(result && result.status === 'viewed');\n },\n function () {\n if (rewardedBreakActive === activeBreak) rewardedBreakActive = null;\n return false;\n }\n );\n }\n\n window.addEventListener('message', function (event: MessageEvent) {\n // Host config must come from the embedding page itself — a null/other\n // source (nested frame, synthetic event) must not be able to flip flags.\n if (event.source !== window.parent) return;\n if (!hostnameAllowed(event.origin, DEFAULT_ALLOW)) return;\n applyHostConfig(event.data);\n });\n\n const sdk: BBArcadeSDK = {\n lockToHost: lockToHost,\n init: init,\n configure: configure,\n preloadRewardedAds: preloadRewardedAds,\n preloadRewardedAd: preloadRewardedAds,\n ready: ready,\n gameLoadingFinished: ready,\n gameplayStart: function () {\n post('gameplay_start');\n },\n gameplayStop: function () {\n post('gameplay_stop');\n },\n submitScore: function (score, opts) {\n post('score', score, opts && opts.mode === 'daily' ? { mode: 'daily' } : undefined);\n },\n gameOver: function (score, opts) {\n post('gameover', score, opts && opts.mode === 'daily' ? { mode: 'daily' } : undefined);\n },\n xrSessionStart: function () {\n post('xr_session_start');\n },\n xrSessionEnd: function () {\n post('xr_session_end');\n },\n rewardedAd: rewardedAd,\n showRewardedAd: rewardedAd,\n prepareRewardedAd: prepareRewardedAd,\n prepareRewardedBreak: prepareRewardedAd,\n rewardedBreak: rewardedBreak,\n save: function (blob) {\n const data = String(blob);\n // Fail fast on an oversized blob instead of waiting out a host round\n // trip that can only end in the same 'too_large' rejection.\n if (utf8ByteLength(data) > MAX_SAVE_BYTES) {\n return Promise.reject(sdkError('too_large'));\n }\n return request('save', data) as Promise<void>;\n },\n load: function () {\n return request('load') as Promise<string | null>;\n },\n getPlayer: getPlayer,\n onPlayerChange: function (handler) {\n if (typeof handler !== 'function') return function () {};\n playerChangeHandlers.push(handler);\n return function () {\n const idx = playerChangeHandlers.indexOf(handler);\n if (idx !== -1) playerChangeHandlers.splice(idx, 1);\n };\n },\n getVariant: function (key, variants) {\n const sorted = Array.isArray(variants) ? variants.slice().sort() : [];\n const fallback = sorted.length > 0 ? sorted[0] : null;\n if (typeof key !== 'string' || !key || sorted.length < 2) {\n return Promise.resolve(fallback);\n }\n return request('experiment', { key: key, variants: sorted }).then(\n function (variant) {\n return typeof variant === 'string' ? variant : fallback;\n },\n function () {\n return fallback;\n }\n );\n },\n version: VERSION,\n };\n\n // Internal, non-public hook used by the multiplayer module to ride the same\n // request/response transport (mp_ticket). Hidden behind a symbol-free\n // underscore name and excluded from BBArcadeSDK so it never becomes API.\n (sdk as BBArcadeSDK & { _request?: typeof request })._request = request;\n\n return sdk;\n}\n","/**\n * Inert BBArcade used when the module is evaluated without a `window` (SSR,\n * tests, bundler prerender). Mirrors the real SDK's standalone semantics:\n * fire-and-forget calls no-op, save/load reject fast with 'unsupported',\n * getPlayer resolves null, getVariant resolves its alphabetical control.\n */\nimport { VERSION } from './protocol';\nimport type {\n BBArcadeError,\n BBArcadeErrorCode,\n BBArcadeRewardedAdPreparation,\n BBArcadeRewardedAdResult,\n BBArcadeSDK,\n} from './types';\n\nfunction sdkError(code: BBArcadeErrorCode): BBArcadeError {\n const err = new Error(code) as BBArcadeError;\n err.code = code;\n return err;\n}\n\nexport function createInertBBArcade(): BBArcadeSDK {\n const noop = () => {};\n const unavailable = (): Promise<BBArcadeRewardedAdResult> =>\n Promise.resolve({\n status: 'unavailable',\n error: 'ad_break_unavailable',\n placement: 'rewarded',\n reward: 'reward',\n adBreakId: 'bbad_inert',\n });\n const prepareUnavailable = (): Promise<BBArcadeRewardedAdPreparation> =>\n Promise.resolve({\n status: 'unavailable',\n error: 'ad_break_unavailable',\n placement: 'rewarded',\n reward: 'reward',\n adBreakId: 'bbad_inert',\n });\n return {\n lockToHost: noop,\n init: () => Promise.resolve(),\n configure: noop,\n preloadRewardedAds: () => Promise.resolve(false),\n preloadRewardedAd: () => Promise.resolve(false),\n ready: noop,\n gameLoadingFinished: noop,\n gameplayStart: noop,\n gameplayStop: noop,\n submitScore: noop,\n gameOver: noop,\n xrSessionStart: noop,\n xrSessionEnd: noop,\n rewardedAd: unavailable,\n showRewardedAd: unavailable,\n prepareRewardedAd: prepareUnavailable,\n prepareRewardedBreak: prepareUnavailable,\n rewardedBreak: () => Promise.resolve(false),\n save: () => Promise.reject(sdkError('unsupported')),\n load: () => Promise.reject(sdkError('unsupported')),\n getPlayer: () => Promise.resolve(null),\n onPlayerChange: () => () => {},\n getVariant: (key, variants) => {\n const sorted = Array.isArray(variants) ? variants.slice().sort() : [];\n return Promise.resolve(sorted.length > 0 ? sorted[0] : null);\n },\n version: VERSION,\n };\n}\n","/**\n * @bountyboard/arcade-sdk — module entry.\n *\n * import { BBArcade } from '@bountyboard/arcade-sdk';\n *\n * Importing attaches the SDK's postMessage listeners immediately (the Bounty\n * Board host pushes its config at load time, so listener timing matters) but\n * does NOT install a window.BBArcade global — that's the script-tag build\n * (https://www.bountyboard.gg/arcade-sdk/v1.js, or the './global' subpath).\n * Evaluated without a window (SSR/prerender) it exports an inert instance with\n * standalone semantics, so importing is always safe.\n */\nimport { createBBArcade } from './core';\nimport { createInertBBArcade } from './inert';\nimport type { BBArcadeSDK } from './types';\n\nexport const BBArcade: BBArcadeSDK =\n typeof window === 'undefined' ? createInertBBArcade() : createBBArcade();\n\nexport default BBArcade;\n\nexport { VERSION as PROTOCOL_VERSION } from './protocol';\nexport type {\n BBArcadeConfig,\n BBArcadeError,\n BBArcadeErrorCode,\n BBArcadeLockToHostOptions,\n BBArcadePlayer,\n BBArcadePreparedRewardedAd,\n BBArcadePrepareRewardedAdOptions,\n BBArcadeRewardedAdOptions,\n BBArcadeRewardedAdPreparation,\n BBArcadeRewardedAdPrepareFailure,\n BBArcadeRewardedAdResult,\n BBArcadeRewardedAdStatus,\n BBArcadeRewardedAdsConfig,\n BBArcadeRewardedBreakOptions,\n BBArcadeSDK,\n} from './types';\n"],"mappings":"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","names":["ready","sdkError"]}
@@ -29,6 +29,8 @@ type BBArcadeErrorCode =
29
29
  /** Rejection reason for save()/load(): a real Error that also carries `code`. */
30
30
  interface BBArcadeError extends Error {
31
31
  code: BBArcadeErrorCode;
32
+ /** Optional server detail for 'rejected' errors (e.g. 'room_full'). */
33
+ detail?: string;
32
34
  }
33
35
 
34
36
  /** Rewarded-ads settings accepted by init()/configure(). */
@@ -85,7 +87,17 @@ interface BBArcadeLockToHostOptions {
85
87
  }
86
88
 
87
89
  /** Outcome of a rewarded placement. */
88
- type BBArcadeRewardedAdStatus = 'viewed' | 'dismissed' | 'ready' | 'unavailable' | 'error';
90
+ type BBArcadeRewardedAdStatus =
91
+ /** The player watched the ad to completion — the only status that grants. */
92
+ | 'viewed'
93
+ /** The player closed the ad early, or a prepared ad ended before it was ever shown. */
94
+ | 'dismissed'
95
+ /** An ad is ready to show (preparation result; terminal only in the legacy shown-but-unreported edge). */
96
+ | 'ready'
97
+ /** No ad could be offered. */
98
+ | 'unavailable'
99
+ /** The placement failed. */
100
+ | 'error';
89
101
 
90
102
  /** Resolution value of rewardedAd()/showRewardedAd(). */
91
103
  interface BBArcadeRewardedAdResult {
@@ -220,6 +232,22 @@ interface BBArcadeMpJoinOptions {
220
232
  code?: string;
221
233
  /** Create a new room (a share code is generated for you). */
222
234
  create?: boolean;
235
+ /**
236
+ * Public quick match: join your game's open public room, or start a fresh
237
+ * one when no seat is free. Mutually exclusive with code/create. Rooms fill
238
+ * to the game's player cap (at most 64), and a matched room still exposes
239
+ * room.code so the player can invite friends into the same room. If the
240
+ * last free seat is lost to a race, the SDK re-matchmakes automatically a
241
+ * couple of times before rejecting.
242
+ */
243
+ match?: boolean;
244
+ /**
245
+ * Game-defined, JSON-serializable data supplied to the authoritative module
246
+ * before it admits this player (avatar/loadout/mode selections, for example).
247
+ * The room server treats this as untrusted input and limits it to 1 KiB.
248
+ * Never put credentials or other secrets here.
249
+ */
250
+ joinData?: Readonly<Record<string, unknown>>;
223
251
  /**
224
252
  * Local development only: connect straight to a room server (e.g. wrangler
225
253
  * dev with DEV_ALLOW_UNSIGNED=1) instead of asking the Bounty Board host for
@@ -227,6 +255,12 @@ interface BBArcadeMpJoinOptions {
227
255
  */
228
256
  roomUrl?: string;
229
257
  ticket?: string;
258
+ /**
259
+ * How long to wait for the room server's welcome before the join rejects
260
+ * with code 'error', in ms (default 10000, clamped to 1000–60000). Applies
261
+ * to the initial join and to every automatic reconnect attempt.
262
+ */
263
+ timeoutMs?: number;
230
264
  }
231
265
 
232
266
  /** Events a multiplayer room emits. */
@@ -240,6 +274,8 @@ interface BBArcadeMpRoomEvents {
240
274
  /** The match finished; results were reported by the server, not by clients. */
241
275
  end: { results: BBArcadeMpResult[] };
242
276
  error: { code: string };
277
+ /** Connection state, including successful automatic reconnection. */
278
+ connection: { connected: boolean; reconnecting: boolean };
243
279
  /** Connection dropped. reconnecting: true means the SDK is retrying. */
244
280
  close: { reconnecting: boolean };
245
281
  }
@@ -254,8 +290,19 @@ interface BBArcadeMpRoom {
254
290
  players: BBArcadeMpPlayer[];
255
291
  /** Latest authoritative state snapshot (as filtered for you). */
256
292
  state: unknown;
293
+ /** Whether the room currently has an accepted WebSocket connection. */
294
+ connected: boolean;
295
+ /**
296
+ * Join-handshake latency in ms (socket open → server welcome, so ticket
297
+ * verification plus one round trip), refreshed on every successful
298
+ * (re)connect; null until the first welcome arrives. A tuning estimate for
299
+ * render-side smoothing, not a measured RTT.
300
+ */
301
+ latencyMs: number | null;
257
302
  /** Send an INPUT to the server (never authoritative state — the server simulates). */
258
303
  send(input: unknown): void;
304
+ /** Like send(), but reports false when disconnected or the socket raced closed. */
305
+ trySend(input: unknown): boolean;
259
306
  /** Subscribe to a room event; returns an unsubscribe function. */
260
307
  on<K extends keyof BBArcadeMpRoomEvents>(
261
308
  event: K,
@@ -288,6 +335,9 @@ interface BBArcadeSDK {
288
335
  /**
289
336
  * Refuse to run unless embedded on a Bounty Board host (anti
290
337
  * scrape-and-reupload deterrent — not DRM). Call once, early, before boot.
338
+ * When the embedder can't be determined from browser signals (opaque-origin
339
+ * sandboxed builds), the SDK asks the embedding page and blocks if nothing
340
+ * answers within ~5 seconds.
291
341
  */
292
342
  lockToHost(options?: BBArcadeLockToHostOptions): void;
293
343
  /**
@@ -355,12 +405,16 @@ interface BBArcadeSDK {
355
405
  * Save the player's progress (a string blob, ~1MB max) to their Bounty Board
356
406
  * account. Requires a Bounty-Board-hosted build upload and a logged-in
357
407
  * player; rejects with a BBArcadeError otherwise (code 'unsupported' /
358
- * 'unauthenticated' / 'too_large' / 'rejected' / 'error').
408
+ * 'unauthenticated' / 'too_large' / 'rejected' / 'error'). Oversized blobs
409
+ * reject 'too_large' immediately (client-side precheck against the same 1
410
+ * MiB cap the server enforces); a host that never answers rejects 'error'
411
+ * after a 15s request timeout.
359
412
  */
360
413
  save(blob: string): Promise<void>;
361
414
  /**
362
415
  * Load the player's saved blob. Resolves with the string, or null when there
363
- * is no save yet. Rejects like save() ('unauthenticated' when logged out).
416
+ * is no save yet. Rejects like save() ('unauthenticated' when logged out,
417
+ * 'error' after the same 15s request timeout when no host answers).
364
418
  */
365
419
  load(): Promise<string | null>;
366
420
  /**
@@ -371,6 +425,15 @@ interface BBArcadeSDK {
371
425
  * display name + avatar only, never ids, emails, or roles.
372
426
  */
373
427
  getPlayer(): Promise<BBArcadePlayer | null>;
428
+ /**
429
+ * Subscribe to CHANGES in the player's display identity (e.g. the player
430
+ * logs in or out mid-session and the host pushes a fresh config). The
431
+ * handler runs only when the identity actually changes — subscribing does
432
+ * not replay the current value; call getPlayer() for that. Receives the
433
+ * same { name, avatarUrl } | null shape as getPlayer(). Returns an
434
+ * unsubscribe function.
435
+ */
436
+ onPlayerChange(handler: (player: BBArcadePlayer | null) => void): () => void;
374
437
  /**
375
438
  * Deterministic A/B variant for this player (even split; stable per
376
439
  * player+game+key). Resolves the alphabetically-first variant as the