@indiegems/gem-web-sdk 24.0.2 → 24.1.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.
- package/CHANGELOG.md +24 -0
- package/dist/conformance.d.ts +1 -1
- package/dist/gem-web-sdk.conformance.js +3 -3
- package/dist/gem-web-sdk.esm.js +1 -1
- package/dist/gem-web-sdk.function.js +1 -1
- package/dist/gem-web-sdk.game.js +3 -3
- package/dist/gem-web-sdk.min.js +2 -2
- package/dist/gem-web-sdk.sdk.js +2 -2
- package/dist/gem-web-sdk.server.js +1 -1
- package/docs/api/api-reference-function.md +6 -1
- package/docs/api/api-reference-game.md +22 -10
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +6 -6
- package/docs/arcade-and-store.md +48 -22
- package/docs/changelog.md +24 -0
- package/docs/getting-started.md +1 -1
- package/docs/inventory-and-catalogs.md +28 -15
- package/docs/manifest.json +12 -12
- package/docs/platform-rules.md +3 -2
- package/docs/server-bundles.md +2 -2
- package/package.json +1 -1
- package/schema/bridge.v1.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,30 @@ A release opens with a short summary of what it is about, and its entries are gr
|
|
|
17
17
|
- **New capability**: something a game can now do. Nothing changes unless you use it.
|
|
18
18
|
- **Fixes and improvements**: bugs fixed and behaviour improved without a change to the API. Nothing to change in your game.
|
|
19
19
|
|
|
20
|
+
## 24.1.0
|
|
21
|
+
|
|
22
|
+
Upgrading from 24.0.2. Impact: required.
|
|
23
|
+
|
|
24
|
+
Declining a pass's sheet no longer takes the pass off sale, so a game may open a pass's sheet as often as it likes. A held pass may or may not be offered again, so a game should follow `passOnSale` and assume neither. The only API change is the parent conformance kit version, which moves from 9 to 10 and asks a game to change nothing beyond a rebuild.
|
|
25
|
+
|
|
26
|
+
### Rebuild
|
|
27
|
+
|
|
28
|
+
- `PARENT_CONFORMANCE_VERSION` is now `10` instead of `9`. This constant belongs to the parent conformance kit, which Arcade parents use. A game does not use it, and no message, field, outcome or offer kind was added. Recompiling is enough, and no adapter member is new. A parent that passed kit 9 passes kit 10 unchanged.
|
|
29
|
+
|
|
30
|
+
Only code that compares this constant to the literal `9` needs updating:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// before
|
|
34
|
+
if (PARENT_CONFORMANCE_VERSION === 9) { /* ... */ }
|
|
35
|
+
// after
|
|
36
|
+
if (PARENT_CONFORMANCE_VERSION === 10) { /* ... */ }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Fixes and improvements
|
|
40
|
+
|
|
41
|
+
- Declining a pass's sheet no longer takes the pass off sale. `passOnSale` and the reward preview's `upsells` no longer change after declines, and there is no daily limit on pass offers. The Arcade now records how often a sheet is opened and how long it stays open, for reporting only. Remove any game logic that assumed a pass would stop being offered after two declines.
|
|
42
|
+
- Whether a player who already holds a pass is offered it again is the parent's decision. A pass left out of the holder's listing answers `gem.requestStoreOfferSheet` with one `unavailable` and shows nothing. A pass in the listing opens exactly one sheet. Follow `passOnSale` for a holder as you would for anyone. Do not keep a "Get VIP" button up for a holder on the assumption that its sheet will open, and do not assume a pass lasts 30 days.
|
|
43
|
+
|
|
20
44
|
## 24.0.2
|
|
21
45
|
|
|
22
46
|
Upgrading from 24.0.1. Impact: none.
|
package/dist/conformance.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ interface ParentConformanceResult {
|
|
|
57
57
|
readonly ok: boolean;
|
|
58
58
|
readonly message?: string;
|
|
59
59
|
}
|
|
60
|
-
declare const PARENT_CONFORMANCE_VERSION =
|
|
60
|
+
declare const PARENT_CONFORMANCE_VERSION = 10;
|
|
61
61
|
declare const PARENT_CONFORMANCE_CASES: readonly ParentConformanceCase[];
|
|
62
62
|
declare function runParentConformance(factory: ParentFactory, options: ParentConformanceOptions): Promise<readonly ParentConformanceResult[]>;
|
|
63
63
|
export { KIT_PASS, PARENT_CONFORMANCE_CASES, PARENT_CONFORMANCE_VERSION, type ParentConformanceCase, ParentConformanceError, type ParentConformanceOptions, type ParentConformanceResult, type ParentFactory, type ParentFixture, type ParentState, type ParentUnderTest, type StoreScenario, runParentConformance };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var It=Object.defineProperty;var o=(n,t)=>It(n,"name",{value:t,configurable:true});var u="gem-web-bridge",x={min:1,max:1},Et=65536,Ke=8,fe=5e3,Je=1024,E=1e4,L=Object.create(null),ye="__malformed";var Oe=Object.create(null),Tt=Object.freeze([["gem.asyncOpponents.","asyncOpponents"],["gem.textScale.","textScale"],["gem.vibration.","vibration"],["gem.viewport.","reservedAreas"],["gem.moments.","moments"],["gem.prefs.","audioChannels"],["gem.share.","shareRemap"]]);function he(n){let t=Oe[n];if(t!==void 0)return t;for(let[e,r]of Tt)if(n.startsWith(e))return r}o(he,"capabilityFor");function Y(n){let t=/^([a-z][a-z0-9+.-]*):\/\/([a-z0-9.-]+|\[[0-9a-f:.]+\])(:([0-9]{1,5}))?$/i.exec(n);return t===null?false:t[4]===void 0||Number(t[4])<=65535}o(Y,"isOrigin");function Ct(n){let t;try{t=new URL(n);}catch{return false}return t.username!==""||t.password!==""?false:t.protocol==="https:"?true:t.protocol==="http:"&&(t.hostname==="localhost"||t.hostname==="127.0.0.1")}o(Ct,"isSafeHttpUrl");function Ot(n){return /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i.test(n)}o(Ot,"isBcp47");function Mt(n){return /^[a-z_]+$/.test(n)}o(Mt,"isCode");function Gt(n){return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(n)||/^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$/.test(n)&&!n.includes("--")}o(Gt,"isGameRef");function Te(n,t,e){if(t>Ke)return `nesting deeper than ${Ke}`;if(++e.nodes>fe)return `more than ${fe} values`;if(e.bytes>e.cap)return `over the ${e.cap} byte limit`;if(n==null)return e.bytes+=4,null;let r=typeof n;if(r==="boolean")return e.bytes+=5,null;if(r==="number")return e.bytes+=8,null;if(r==="string")return e.bytes+=n.length*3+2,null;if(r!=="object")return `${r} is not plain data`;let i=n;if(e.seen.has(i))return "a repeated or cyclic object reference";if(e.seen.add(i),Array.isArray(n)){if(n.length>Je)return `an array of ${n.length} entries, over the ${Je} limit`;e.bytes+=2;for(let l of n){let c=Te(l,t+1,e);if(c)return c}return null}let a=Object.getPrototypeOf(n);if(a!==Object.prototype&&a!==null)return "a value that is not a plain object";e.bytes+=2;let s=n;for(let l in s){if(!Object.prototype.hasOwnProperty.call(s,l))continue;if(++e.nodes>fe)return `more than ${fe} values`;if(e.bytes+=l.length*3+4,e.bytes>e.cap)return `over the ${e.cap} byte limit`;let c=Te(s[l],t+1,e);if(c)return c}return null}o(Te,"walk");function Lt(n,t=Et){let e={nodes:0,bytes:0,seen:new Set,cap:t},r=Te(n,0,e);return r?`message contains ${r}`:e.bytes>t?`message is over the ${t} byte limit`:null}o(Lt,"validateSize");var $t={origin:[Y,"not a well-formed origin"],url:[Ct,"not a safe absolute http(s) URL"],bcp47:[Ot,"not a well-formed BCP 47 tag"],code:[Mt,"not a code of lower-case letters and underscores"],gameRef:[Gt,"not a game id or web address"]};function ze(n,t,e){for(let r of t){let i=e===""?r[0]:`${e}.${r[0]}`,a=n[r[0]];if(a==null){if(r[1]<"a")continue;return `${i}: required`}let s=Ce(a,r,i);if(s)return s}return null}o(ze,"checkFields");function Ce(n,t,e){let r=t[1].toLowerCase();switch(r){case "u":return typeof n=="boolean"?null:Ce(n,typeof n=="string"?[t[0],"s",t[2]]:[t[0],"n",t[3],t[4]],e);case "s":{if(typeof n!="string")return `${e}: expected string`;if(n.length>t[2])return `${e}: exceeds ${t[2]} characters`;if(t[3]){let[i,a]=$t[t[3]];if(!i(n))return `${e}: ${a}`}return null}case "n":case "i":return typeof n!="number"||!(r=="i"?Number.isInteger:Number.isFinite)(n)?`${e}: expected ${r=="i"?"integer":"number"}`:n<t[2]||n>t[3]?`${e}: out of range ${t[2]}..${t[3]}`:null;case "b":return typeof n=="boolean"?null:`${e}: expected boolean`;case "e":{let i=t[2];return i.includes(n)?null:`${e}: not one of ${i.join("|")}`}case "o":return typeof n!="object"||n===null||Array.isArray(n)?`${e}: expected object`:ze(n,t[2],e);case "j":return typeof n!="object"||n===null||Array.isArray(n)?`${e}: expected a plain object`:null;case "a":{if(!Array.isArray(n))return `${e}: expected array`;if(n.length>t[2])return `${e}: exceeds ${t[2]} items`;let i=t[3],a=`${e}[]`;for(let s of n){if(s==null){if(i[1]<"a")continue;return `${a}: required`}let l=Ce(s,i,a);if(l)return l}return null}default:return `${e}: unknown kind ${String(r)}`}}o(Ce,"checkValue");var Xe=Object.create(null);function d(n,t,e,r,i){return L[n]=e,r&&(Oe[n]=r),Xe[n]=a=>{let s=Lt(a,i);if(s)return s;if(typeof a!="object"||a===null||Array.isArray(a))return "message: expected object";let l=a;return l.gem!==u?"gem: wrong protocol marker":l.type!==n?`type: expected ${n}`:ze(l,t,"")}}o(d,"makeValidator");var Q=["","s",64],m=["requestId","s",64],Z=["currencySlug","s",32],ee=["matchId","s",64],I=["reason","S",64,"code"],T=["outcome","e",["ok","declined","unavailable"]];d("gem.handshake",[["versions","o",[["min","i",1,1e3],["max","i",1,1e3]]],["capabilities","A",64,Q],["embedder","s",2048,"origin"],["unbidden","B"]],30);d("gem.handshake.accept",[["v","i",1,1e3],["capabilities","A",64,Q]],5);d("gem.token",[["token","s",4096],["requestId","S",64],["generation","i",0,2147483647],["apiBaseUrl","S",512,"url"],["playerId","s",64],["gameId","s",64],["channel","e",["dev","test","release"]],["gameVersion","S",64],["expiresAt","i",0,4102444800],["chainEndsAt","i",0,4102444800],["allowDevTools","B"]],30);d("gem.token.denied",[m,["retryable","b"],["retryAfterMs","I",0,36e5],["reason","e",["chain_exhausted","unavailable"]],["correlationId","s",64]],30);d("gem.player",[["untrusted","o",[["displayName","s",64],["displayTag","S",80],["displayHandle","s",80]]],["playerId","S",64],["discriminator","S",8],["avatarIconUrl","S",2048,"url"],["avatarIconId","S",64],["avatar3dId","S",64],["avatar3dModelUrl","S",2048,"url"],["avatar3dIconUrl","S",2048,"url"],["avatars3d","A",8,["","o",[["family","s",32],["id","s",64],["modelUrl","s",256,"url"],["iconUrl","s",256,"url"]]]],["badges","A",2,["","o",[["itemId","s",64],["iconUrl","s",2048,"url"]]]],["displayTag","s",8],["avatarUrl","S",2048,"url"],["boost","O",[["multipliers","a",8,["","o",[["currencyId","s",64],Z,["multiplierBp","i",1e4,1e6]]]],["expiresInDays","I",1,7]]],["signInAvailable","B"]],12);d("gem.pause",[["paused","b"]],120);d("gem.prefs",[["reducedMotion","b"],["muted","b"],["showFrameStats","B"],["music","B"],["sounds","B"],["locale","s",35,"bcp47"],["recommended","O",[["volume","i",0,100],["batterySaver","b"],["keyboardLayout","e",["wasd","arrows"]],["gamepadConfirm","e",["south","east"]],["vibration","b"],["lookSensitivity","I",20,400],["gamepadLookSensitivity","I",20,400],["invertLookY","B"],["touchMovement","S",16,"code"],["graphicsQuality","S",16,"code"],["graphicsQualityAuto","B"],["colorHelp","B"],["textScale","I",100,200]]]],60);d("gem.cohorts",[["cohorts","a",8,["","o",[["experimentId","s",36],["variantKey","s",32]]]]],12,"cohorts");d("gem.earnAllowanceChanged",[],30,"earnAllowance");d("gem.inventoryChanged",[],30,"inventoryChanged");d("gem.viewport",[["insets","o",[["top","i",0,4096],["right","i",0,4096],["bottom","i",0,4096],["left","i",0,4096]]],["reserved","A",4,["","o",[["x","i",0,16384],["y","i",0,16384],["width","i",0,16384],["height","i",0,16384],["control","O",[["x","i",0,16384],["y","i",0,16384],["width","i",0,16384],["height","i",0,16384]]]]]]],60,"viewport");d("gem.deviceSettings",[["settings","J"]],12,"deviceSettings");d("gem.willTerminate",[["reason","e",["grant_expiring","session_ended","navigating","kill_switch"]],["graceMs","i",0,6e4]],10);d("gem.roomEvent",[["event","e",["updated","match_started","match_ready","match_failed","version_stale"]],["roomId","s",64],["matchId","S",64],["version","I",0,2147483647],["reason","S",64],["platform","E",["universal","mobile"]]],120,"rooms");d("gem.socketState",[["connected","b"],["reason","E",["connecting","dropped","watchdog","terminal"]]],12,"socketState");d("gem.seatedEvent",[["roomId","s",64]],60,"partySeating");d("gem.autoStartFailedEvent",[["roomId","s",64],["code","s",64]],30,"autoStartFailure");d("gem.ticketEvent",[["event","e",["queued","matched","expired","cancelled","failed"]],["ticketId","s",64],["partySize","I",1,64],["queuedAt","I",0,4102444800],["expiresAt","I",0,4102444800],["reason","S",64],["roomId","S",64]],60,"ticketEvents");d("gem.signalEvent",[["event","e",["hello","offer","answer","ice","peerJoined","peerLeft","migration","matchEnded","error"]],ee,["data","j"]],1800,"p2p");d("gem.relayDraining",[ee,["clusterTargetId","s",64],["relayIps","a",16,Q],["drainingSince","s",40],["deadline","s",40]],60,"p2pRelayDrain");d("gem.dedicatedEvent",[["event","e",["answer","error","matchEnded"]],ee,["sdp","S",10240],["sessionId","S",64],["code","S",64],["message","S",256],["reason","S",64]],120,"dedicated");d("gem.partyResult",[m,["partyId","S",64]],30,"parties");d("gem.clusterTarget",[["clusterTargetId","s",64],["clusterTargetIds","A",3,Q]],12,"clusterTarget");d("gem.graphicsRoutes",[["entries","a",32,["","o",[["gpu","s",32],["route","S",32],["host","S",32]]]]],12,"graphicsRoutes");d("gem.devtools",[["action","e",["open","close","toggle"]]],30,"devtools");d("gem.screenshot",[["maxWidth","i",1,7680],["maxHeight","i",1,4320]],6,"screenshot");d("gem.screenshot.denied",[["reason","e",["not_wired","no_source","unsupported","unreadable","tainted","too_large","busy","declined","failed"]],["retryable","b"]],12,"screenshot");d("gem.requestContext",[],12,"feedbackContext");d("gem.gameContext",[["context","j"]],12,"feedbackContext");d("gem.signal",[["signal","e",["join","offer","answer","ice","migrationAck","hostUnreachable"]],ee,["data","j"]],1200,"p2p");d("gem.dedicatedOffer",[ee,["sdp","s",10240],["intent","e",["initial","ice_restart","full_reconnect"]]],60,"dedicated");d("gem.partyRequest",[m],12,"parties");d("gem.ready",[],5);d("gem.engaged",[],5,"engagement");d("gem.progress",[["loaded","i",0,1099511627776],["total","i",0,1099511627776]],600);d("gem.requestToken",[m,["reason","e",["initial","expiring","rejected"]]],12);d("gem.requestLocalServer",[m,["gameRoomId","s",64]],6,"localHost");d("gem.localServer",[m,["serverId","S",128]],30,"localHost");d("gem.localServer.denied",[m,["retryable","b"],["reason","e",["refused","unavailable"]]],30,"localHost");d("gem.exit",[["reason","S",64]],5);d("gem.openExternal",[["url","s",2048,"url"]],6);d("gem.requestFullscreen",[["enabled","b"]],30);d("gem.audioState",[["muted","b"]],120,"audioState");d("gem.rewardCue",[Z,["cueId","S",64],["expected","I",1,1e9],["origin","O",[["x","i",0,16384],["y","i",0,16384]]]],60,"rewardCue");d("gem.rewardPresentation",[["mode","e",["arcade","game"]]],12,"rewardPresentation");d("gem.walletDisplay",[["visible","b"],["currencies","A",8,Q],["score","B"]],60,"walletDisplay");d("gem.controlsDeclare",[["uses","o",[["lookSensitivity","S",16,"code"],["gamepadLookSensitivity","S",16,"code"],["invertLookY","S",16,"code"],["touchMovement","S",16,"code"]]]],60,"controls");d("gem.menu",[["settings","b"],["items","A",4,["","o",[["id","s",32],["label","s",40]]]]],60,"gameMenu");d("gem.menuSelect",[["entry","e",["settings","item"]],["id","S",32]],60,"gameMenu");d("gem.gemCorner",[["corner","e",["top-left","top-right","bottom-left","bottom-right"]]],12,"gemCorner");d("gem.menuStyle",[["ground","S",7],["accent","S",7]],12,"menuStyle");d("gem.settingsDeclare",[["settings","a",8,["","o",[["id","s",40],["label","s",40],["kind","e",["toggle","slider","choice"]],["value","u",40,-1e6,1e6],["quick","B"],["icon","S",16,"code"],["min","N",-1e6,1e6],["max","N",-1e6,1e6],["step","N",0,1e6],["options","A",4,["","o",[["id","s",40],["label","s",40]]]]]]]],120,"gameSettings");d("gem.settingChange",[["id","s",40],["value","u",40,-1e6,1e6]],120,"gameSettings");d("gem.requestShare",[["requestId","S",64],["params","a",16,["","o",[["key","s",64],["value","s",512]]]]],6,"share");d("gem.shareResult",[m,["outcome","e",["accepted","refused"]],I,["key","S",64]],30,"shareResult");d("gem.requestAvatarPicker",[["tab","E",["2d","3d"]]],6,"avatarPicker");d("gem.vibrate",[["pattern","a",99,["","i",0,1e4]]],600,"vibration");d("gem.frameStats",[["fps","i",0,1e3],["frameTimeP95Ms","i",0,1e5],["counts","E",["pageFrames","gameDraws"]]],120,"frameStats");d("gem.portraitBorders",[["name","S",40]],60,"portraitBorders");d("gem.error",[["code","e",["version_mismatch","handshake_timeout","handshake_duplicate","invalid_message","rate_limited","token_unavailable","unsupported","unsupported_capability","internal"]],["message","S",256]],60);d("gem.requestPurchase",[m,["purchaseKind","e",["item","recipe"]],["targetId","s",64],Z,["quantity","I",1,10],["catalogId","S",64],["origin","E",["sheet","direct_refused"]]],12,"purchases");d("gem.requestAvatar3dPurchase",[m,["itemId","s",64],Z],12,"avatar3dPurchases");d("gem.purchaseResult",[m,T,I,["purchaseId","S",64]],30,"purchases");d("gem.purchaseSettled",[["purchaseKind","e",["item","recipe"]],["targetId","s",64],["catalogId","S",64],["quantity","i",1,10],["purchaseId","S",64]],30,"purchaseSettled");d("gem.deviceSettings.put",[m,["settings","j"]],30,"deviceSettings");d("gem.deviceSettings.result",[m,["outcome","e",["stored","too_large","unavailable"]]],30,"deviceSettings");d("gem.requestAd",[m,["placement","s",32],["format","e",["interstitial","rewarded"]]],6,"ads");d("gem.adResult",[m,["outcome","e",["shown","no_ad","unavailable"]]],12,"ads");d("gem.signalMoment",[m,["kind","e",["level_end","run_end","match_end","menu_return","other_break"]],["placement","S",32],["wall","B"]],6,"moments");d("gem.momentResult",[m,["shown","b"]],12,"moments");d("gem.requestSignIn",[m],6,"signIn");d("gem.signInResult",[m,["outcome","e",["signed_in","declined","unavailable"]]],12,"signIn");d("gem.requestFriendChallenge",[m,["playerId","s",64],["template","s",64],["params","J"]],12,"friendChallenges");d("gem.friendChallengeResult",[m,["outcome","e",["accepted","cancelled","refused","unavailable","rate_limited","invalid"]],["code","S",64],["param","S",64],["channel","S",16],["configVersion","I",0,9007199254740991],["retryAfterMs","I",0,864e5],["detail","S",300]],30,"friendChallenges");d("gem.requestPlayInvite",[m,["params","A",16,["","o",[["key","s",64],["value","s",512]]]]],6,"playInvites");d("gem.playInviteResult",[m,["shown","b"]],12,"playInvites");d("gem.inviteParams",[["params","A",16,["","o",[["key","s",64],["value","s",512]]]]],30,"playInvites");d("gem.requestAppLinks",[m],12,"appLinks");d("gem.appLinksResult",[m,T,I,["arcade","O",[["store","e",["app_store","google_play"]]]],["game","O",[["store","e",["app_store","google_play"]]]]],30,"appLinks");d("gem.requestOpenApp",[m,["target","e",["arcade","game"]]],6,"appLinks");d("gem.openAppResult",[m,T,I],30,"appLinks");d("gem.requestOpenGame",[m,["ref","s",40,"gameRef"]],6,"openGame");d("gem.openGameResult",[m,T,I],30,"openGame");d("gem.requestSimilarGames",[m,["limit","I",1,24]],12,"openGame");d("gem.similarGamesResult",[m,T,I,["games","a",24,["","o",[["gameId","s",36,"gameRef"],["slug","S",40,"gameRef"],["iconUrl","s",1024,"url"],["capsuleLandscapeUrl","s",1024,"url"],["capsulePortraitUrl","s",1024,"url"],["previewVideoUrl","S",1024,"url"],["previewPosterUrl","S",1024,"url"],["untrusted","o",[["name","s",64],["tagline","s",64],["shortDescription","s",320],["tags","a",10,["","s",32]]]]]]]],30,"openGame",524288);d("gem.requestStoreOffers",[m],12,"store");d("gem.storeOffersResult",[m,T,I,["offers","a",32,["","o",[["skuId","s",64],["kind","e",["gem_pack","boost","pass","bundle"]],["name","s",80],["description","s",400],["imageUrl","S",1024,"url"],["priceDisplay","s",32],["grant","O",[["currency","s",32],["amount","i",1,1e9]]],["pass","S",64],["earnMultipliers","A",8,["","o",[Z,["multiplierBp","i",1e4,1e6]]]]]]]],30,"store",262144);d("gem.requestStorePurchase",[m,["skuId","s",64]],12,"store");d("gem.storePurchaseResult",[m,T,I],30,"store");d("gem.storePurchaseSettled",[["skuId","s",64]],30,"store");d("gem.requestStoreOfferSheet",[m,["skuId","s",64]],12,"storeOfferSheet");d("gem.storeOfferSheetResult",[m,T,I],30,"storeOfferSheet");var C=Object.freeze(Xe);L.__malformed=60;Object.freeze(L);Object.freeze(Oe);var Ft=/^[a-z0-9][a-z0-9_-]{0,39}$/;function qt(n){return typeof n=="string"&&Ft.test(n)}o(qt,"isBorderName");function Me(n){if(n===null||qt(n))return n;let t=typeof n=="string"?JSON.stringify(n.length>48?`${n.slice(0,48)}\u2026`:n):`a value of type ${typeof n}`;console.warn(`[gem] showPortraitBorders: ${t} is not a border name, so nothing was sent. A name is 1 to 40 of a-z, 0-9, _ and -, starting with a letter or digit, exactly as your store page declares it under media.portrait_borders. Pass null for no art.`);}o(Me,"checkBorderName");function Ye(n){return typeof n=="string"&&/^[\w-]{1,64}$/.test(n)}o(Ye,"isCueId");var O={now:o(()=>Date.now(),"now"),setTimeout:o((n,t)=>setTimeout(n,t),"setTimeout"),clearTimeout:o(n=>clearTimeout(n),"clearTimeout")},te=class{constructor(t=O){this.clock=t;}clock;static{o(this,"RateLimiter");}tokens=new Map;lastRefill=new Map;allow(t){let e=L[t];if(e===void 0)return false;let r=this.clock.now(),i=this.lastRefill.get(t)??r,a=this.tokens.get(t)??e,s=Math.max(0,r-i),l=Math.min(e,a+s*e/6e4);return l<1?(this.tokens.set(t,l),this.lastRefill.set(t,r),false):(this.tokens.set(t,l-1),this.lastRefill.set(t,r),true)}};function ne(n,t){return n.toLowerCase()===t.toLowerCase()}o(ne,"originsEqual");function q(n){let t=n.toLowerCase();return t==="localhost"||t.endsWith(".localhost")||t==="[::1]"||t==="::1"?true:/^127(\.\d{1,3}){3}$/.test(t)}o(q,"isLoopbackHostname");function Ge(n,t){if(typeof t!="string"||t==="")return false;let e,r;try{e=new URL(n),r=new URL(t);}catch{return false}return e.protocol!=="http:"&&e.protocol!=="https:"||r.protocol!=="http:"&&r.protocol!=="https:"||e.username!==""||e.password!==""||e.pathname!=="/"||e.search!==""||e.hash!==""||e.port===""?false:q(e.hostname)&&q(r.hostname)}o(Ge,"isLoopbackDevHostEmbedder");function Qe(n,t){if(n.min>n.max||t.min>t.max)return null;let e=Math.min(n.max,t.max),r=Math.max(n.min,t.min);return e>=r?e:null}o(Qe,"negotiateVersion");var tt="[gem: use gem.text.toPlain()]",Ze=new Set,et;function Nt(n){try{if(Ze.has(n))return;let t=globalThis.location;if(et??=!!t?.port&&q(t.hostname??""),!et)return;Ze.add(n),console.warn(`[gem] ${n} is PlayerText, turned into a string: players see ${tt}. Render text.toPlain(value) (text is in @indiegems/gem-web-sdk/sdk).
|
|
2
|
-
${new Error().stack}`);}catch{}}o(Nt,"warnStringified");var be=class{static{o(this,"PlayerText");}#e;#t;constructor(t,e="player text"){this.#e=t,this.#t=e,Object.freeze(this);}toString(){return Nt(this.#t),tt}toJSON(){return this.toString()}get[Symbol.toStringTag](){return "PlayerText"}static unwrap(t){return t.#e}};function $(n,t){return new be(n,t)}o($,"asPlayerText");function nt(n){return Math.min(16384,Math.max(0,Math.round(Number.isFinite(n)?n:0)))}o(nt,"cuePoint");function it(n,t){if(n!==true)return {visible:false};let e=[],r=false;for(let i of Array.isArray(t)?t:[])i==="score"?r=true:typeof i=="string"&&i.length>0&&i.length<=64&&!e.includes(i)&&e.push(i);return !r&&e.length===0?{visible:true}:{visible:true,currencies:e.slice(0,8),score:r}}o(it,"walletState");function at(n){let t=[];for(let e of Array.isArray(n)?n:[]){if(t.length===4)break;if(typeof e!="object"||e===null)continue;let{id:r,label:i}=e;typeof r!="string"||r.length===0||r.length>32||typeof i!="string"||i.trim().length===0||t.some(a=>a.id===r)||t.push({id:r,label:i.trim().slice(0,40)});}return t.length===0?{settings:false}:{settings:false,items:t}}o(at,"menuState");function rt(n,t){return Math.min(t,Math.max(0,Math.round(Number.isFinite(n)?n:0)))}o(rt,"clampStat");function jt(n){let t=n.untrusted,e=$(t.displayName,"gem.player.displayName"),r=t.displayTag||t.displayHandle,i=r?$(r,"gem.player.displayTag"):void 0,a=n.discriminator||n.displayTag,s=a&&/^[0-9]{1,8}$/.test(a)?a:void 0,l=n.avatarIconUrl||n.avatarUrl,c=/^[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$/i,h=n.avatarIconId,b=n.avatar3dId,v={displayName:e,displayHandle:i??e,badges:(n.badges??[]).map(w=>({itemId:w.itemId,iconUrl:w.iconUrl}))};n.playerId&&(v.playerId=n.playerId),s&&(v.discriminator=s),i&&(v.displayTag=i),l&&(v.avatarIconUrl=v.avatarUrl=l),h&&c.test(h)&&(v.avatarIconId=h),b&&c.test(b)&&(v.avatar3dId=b),n.avatar3dModelUrl&&(v.avatar3dModelUrl=n.avatar3dModelUrl),n.avatar3dIconUrl&&(v.avatar3dIconUrl=n.avatar3dIconUrl),n.avatars3d&&(v.avatars3d=n.avatars3d.filter(w=>c.test(w.id)&&/^[a-z][a-z0-9_]*$/.test(w.family)).filter((w,Pe,Ie)=>Ie.findIndex(Ee=>Ee.family==w.family)==Pe).map(({family:w,id:Pe,modelUrl:Ie,iconUrl:Ee})=>({family:w,id:Pe,modelUrl:Ie,iconUrl:Ee})));let A=n.boost,X=(A?.multipliers??[]).map(w=>({currencyId:w.currencyId,currencySlug:w.currencySlug,multiplier:w.multiplierBp/1e4})),xe=X.some(w=>w.multiplier>1);return v.boost=xe&&A?.expiresInDays?{active:xe,multipliers:X,expiresInDays:A.expiresInDays}:{active:xe,multipliers:X},v}o(jt,"playerFromWire");var Bt=1e4,Ut=["appLinks","openGame","store","storeOfferSheet"],Wt=["token","pause","menuSelect","settingChange","prefs","cohorts","viewport","willTerminate","localServer","roomEvent","seatedEvent","autoStartFailedEvent","ticketEvent","socketState","signalEvent","relayDraining","dedicatedEvent","clusterTarget","graphicsRoutes","partyResult","purchaseResult","purchaseSettled","inventoryChanged","shareResult","deviceSettings","adResult","momentResult","signInResult","friendChallengeResult","playInviteResult"],Ht=new WeakMap;var N=class{constructor(t){this.options=t;if(t.expectedSource===void 0||t.expectedSource===null)throw new Error("GemFrame: expectedSource is required and must not be null \u2014 pass window.parent. The handshake transfers the MessagePort, and origin alone does not say WHICH document on that origin sent it.");this.expectedSource=t.expectedSource,this.clock=t.clock??O,this.inbound=new te(this.clock),this.outbound=new te(this.clock);}options;static{o(this,"GemFrame");}state="waiting";port=null;version=null;peerCapabilities=[];unbidden=false;engagedSent=false;readyPending=false;readySent=false;settleWaiters=[];clock;inbound;outbound;expectedSource;get currentState(){return this.state}get parentCapabilities(){return this.peerCapabilities}parentSupports(t){return this.peerCapabilities.includes(t)}get protocolVersion(){return this.version}get unbiddenLaunch(){return this.unbidden}handleWindowMessage(t){if(t.source!==this.expectedSource)return this.refuse(t,"handshake did not come from the expected embedder window"),false;if(!(this.options.allowedEmbedders.some(l=>ne(l,t.origin))||Ge(t.origin,this.options.selfOrigin)||(Ht.get(this)??[]).some(l=>ne(l,t.origin))))return this.refuse(t,`embedder '${t.origin}' is not in allowedEmbedders`),this.state==="waiting"&&this.transition("refused"),false;if(this.state==="connected")return this.fail("handshake_duplicate","this document already has a port"),false;if(this.state!=="waiting")return false;let r=C["gem.handshake"]?.(t.data);if(r)return this.refuse(t,`malformed handshake: ${r}`),false;let i=t.data;if(!ne(i.embedder,t.origin))return this.refuse(t,`handshake claims embedder '${i.embedder}' but arrived from '${t.origin}'`),false;let a=t.ports?.[0];if(!a)return this.refuse(t,"handshake transferred no MessagePort"),false;let s=Qe(x,i.versions);return s===null?(a.onmessage=null,a.postMessage({gem:u,type:"gem.error",code:"version_mismatch",message:`frame supports v${x.min}..v${x.max}`}),a.close(),this.transition("refused"),this.options.onError?.("version_mismatch"),false):(this.port=a,this.version=s,a.onmessage=l=>this.receive(l.data,l.ports??[]),a.start?.(),this.peerCapabilities=i.capabilities??[],this.unbidden=i.unbidden===true,a.postMessage({gem:u,type:"gem.handshake.accept",v:s,...this.options.capabilities===void 0?{}:{capabilities:[...this.options.capabilities]}}),this.transition("connected"),this.readyPending&&(this.readyPending=false,this.ready()),true)}ready(){if(this.state!=="connected"||!this.port){this.readyPending=true;return}if(this.readySent){this.options.onDropped?.("internal","gem.ready was already sent; duplicate suppressed");return}this.readySent=true,this.send({gem:u,type:"gem.ready"});}settled(t=Bt){return this.state!=="waiting"?Promise.resolve(this.state):new Promise(e=>{let r=o(a=>{this.clock.clearTimeout(i),e(a);},"waiter"),i=this.clock.setTimeout(()=>{let a=this.settleWaiters.indexOf(r);a>=0&&this.settleWaiters.splice(a,1),e(this.state);},t);this.settleWaiters.push(r);})}progress(t,e){this.send({gem:u,type:"gem.progress",loaded:t,total:e});}requestToken(t,e){this.send({gem:u,type:"gem.requestToken",requestId:t,reason:e});}requestLocalServer(t,e){return this.parentSupports("localHost")?(this.send({gem:u,type:"gem.requestLocalServer",requestId:t,gameRoomId:e}),true):false}exit(t){this.send(t===void 0?{gem:u,type:"gem.exit"}:{gem:u,type:"gem.exit",reason:t});}openExternal(t){this.send({gem:u,type:"gem.openExternal",url:t});}requestFullscreen(t){this.send({gem:u,type:"gem.requestFullscreen",enabled:t});}reportMuted(t){return this.gated("audioState")?(this.send({gem:u,type:"gem.audioState",muted:t}),true):false}reportEngaged(){return this.engagedSent||!this.gated("engagement")||!this.send({gem:u,type:"gem.engaged"})?false:(this.engagedSent=true,true)}cueReward(t){if(!this.gated("rewardCue"))return false;let e={gem:u,type:"gem.rewardCue",currencySlug:String(t.currencySlug).slice(0,32)};return Ye(t.cueId)&&(e.cueId=t.cueId),t.expected!==void 0&&Number.isFinite(t.expected)&&t.expected>=1&&(e.expected=Math.min(1e9,Math.round(t.expected))),t.origin&&(e.origin={x:nt(t.origin.x),y:nt(t.origin.y)}),this.send(e),true}showPortraitBorders(t){let e=Me(t);return e===void 0||!this.gated("portraitBorders")?false:this.send(e===null?{gem:u,type:"gem.portraitBorders"}:{gem:u,type:"gem.portraitBorders",name:e})}setWalletVisible(t,e){return this.gated("walletDisplay")?this.send({gem:u,type:"gem.walletDisplay",...it(t,e?.only)}):false}setMenu(t){return this.gated("gameMenu")?this.send({gem:u,type:"gem.menu",...at(t)}):false}declareSettings(t){return this.gated("gameSettings")?this.send({gem:u,type:"gem.settingsDeclare",settings:t}):false}setGemCorner(t){return this.gated("gemCorner")?this.send({gem:u,type:"gem.gemCorner",corner:t}):false}setRewardPresentation(t){return this.gated("rewardPresentation")?this.send({gem:u,type:"gem.rewardPresentation",mode:t}):false}setMenuStyle(t){return this.gated("menuStyle")?this.send(t===null?{gem:u,type:"gem.menuStyle"}:{gem:u,type:"gem.menuStyle",ground:t.ground,accent:t.accent}):false}requestShare(t,e){return this.gated("share")?this.send({gem:u,type:"gem.requestShare",...e===void 0?{}:{requestId:e},params:t.map(({key:r,value:i})=>({key:r,value:i}))}):false}requestAvatarPicker(t){return this.gated("avatarPicker")?this.send({gem:u,type:"gem.requestAvatarPicker",...t==="2d"||t==="3d"?{tab:t}:{}}):false}requestVibrate(t){return this.gated("vibration")?this.send({gem:u,type:"gem.vibrate",pattern:[...t]}):false}reportFrameStats(t){return this.gated("frameStats")?(this.send({gem:u,type:"gem.frameStats",fps:rt(t.fps,1e3),frameTimeP95Ms:rt(t.frameTimeP95Ms,1e5),counts:t.counts==="gameDraws"?"gameDraws":"pageFrames"}),true):false}sendGameContext(t){return this.state!=="connected"?false:(this.send({gem:u,type:"gem.gameContext",context:t}),true)}signal(t,e,r){this.send({gem:u,type:"gem.signal",signal:t,matchId:e,data:r});}dedicatedOffer(t,e,r){this.send({gem:u,type:"gem.dedicatedOffer",matchId:t,sdp:e,intent:r});}requestParty(t){return this.send({gem:u,type:"gem.partyRequest",requestId:t})}requestPurchase(t,e,r,i,a,s,l){return this.send({gem:u,type:"gem.requestPurchase",requestId:t,purchaseKind:e,targetId:r,currencySlug:i,...a===void 0?{}:{quantity:a},...s===void 0?{}:{catalogId:s},...l===void 0?{}:{origin:l}})}requestAvatar3dPurchase(t,e,r){return this.parentSupports("avatar3dPurchases")?this.send({gem:u,type:"gem.requestAvatar3dPurchase",requestId:t,itemId:e,currencySlug:r}):false}putDeviceSettings(t,e){return this.send({gem:u,type:"gem.deviceSettings.put",requestId:t,settings:e})}requestAd(t,e,r){return this.send({gem:u,type:"gem.requestAd",requestId:t,placement:e,format:r})}signalMoment(t,e,r,i){return this.send({gem:u,type:"gem.signalMoment",requestId:t,kind:e,...r===void 0?{}:{placement:r},...i===true?{wall:true}:{}})}readyCalled(){return this.readySent||this.readyPending}requestSignIn(t){return this.send({gem:u,type:"gem.requestSignIn",requestId:t})}requestFriendChallenge(t,e,r,i){return this.send({gem:u,type:"gem.requestFriendChallenge",requestId:t,playerId:e,template:r,...i===void 0?{}:{params:i}})}requestArcade(t){return this.gated(he(t.type))&&this.send({gem:u,...t})}close(){this.port&&(this.port.onmessage=null,this.port.close(),this.port=null),this.state!=="refused"&&this.transition("closed");}transition(t){if(this.state!==t){this.state=t;for(let e of this.settleWaiters.splice(0))e(t);this.options.onStateChange?.(t);}}refuseMalformed(t,e){if(!this.inbound.allow(ye)){this.options.onDropped?.(t,e);return}this.fail(t,e);}dropUnknownType(t){this.inbound.allow(ye)&&this.options.onDropped?.("unsupported",`ignored unknown message type '${t}'`);}refuse(t,e){t.ports?.[0]?.close?.(),this.inbound.allow(ye)&&this.options.onRefused?.(e);}fail(t,e){this.options.onError?.(t,e),this.state==="connected"&&this.port&&this.send({gem:u,type:"gem.error",code:t,...e===void 0?{}:{message:e}});}gated(t){return this.state==="connected"&&this.parentSupports(t)}send(t){if(this.state!=="connected"||!this.port)return this.options.onDropped?.("internal",`dropped ${String(t.type)} before connect`),false;let e=t.type;if(!this.outbound.allow(e))return this.options.onError?.("rate_limited",`frame exceeded its own limit for ${e}`),false;let r=C[e]?.(t);return r?(this.options.onError?.("internal",`refusing to send malformed ${e}: ${r}`),false):(this.port.postMessage(this.version===null?t:{...t,v:this.version}),true)}receive(t,e=[]){let r=false;try{r=this.dispatchInbound(t,e);}finally{r||ot(e);}}dispatchInbound(t,e){if(this.state!=="connected")return false;let r=typeof t=="object"&&t!==null?t.type:void 0;if(typeof r!="string")return this.refuseMalformed("invalid_message","message has no type"),false;let i=C[r];if(!i)return this.dropUnknownType(r),false;if(!this.inbound.allow(r))return this.fail("rate_limited",`parent exceeded the limit for ${r}`),false;let a=he(r);if(a!==void 0&&!this.options.capabilities?.includes(a))return this.fail("unsupported_capability",`${r} requires the '${a}' capability, which this frame did not advertise`),false;let s=i(t);if(s)return this.fail("invalid_message",`${r}: ${s}`),false;let l=t;if(l.v!==this.version)return this.fail("invalid_message",`${r}: expected v ${String(this.version)}`),false;switch(r){case "gem.earnAllowanceChanged":this.options.onEarnAllowanceChanged?.();break;case "gem.devtools":this.options.onDevtools?.(l.action);break;case "gem.requestContext":this.options.onRequestContext?.();break;case "gem.screenshot":{let c=e[0];if(!c){this.options.onError?.("invalid_message","gem.screenshot transferred no MessagePort");break}let h={maxWidth:l.maxWidth,maxHeight:l.maxHeight};if(!this.options.onScreenshot){c.close(),this.screenshotDenied("not_wired",false);break}return ot(e.slice(1)),this.options.onScreenshot(h,c,(b,v)=>this.screenshotDenied(b,v)),true}case "gem.token.denied":this.options.onTokenDenied?.(l);break;case "gem.localServer.denied":this.options.onLocalServerDenied?.(l);break;case "gem.player":{let c=l;this.options.onPlayer?.(jt(c),c.signInAvailable);break}case "gem.deviceSettings.result":this.options.onDeviceSettingsResult?.(l);break;case "gem.error":this.options.onError?.(l.code,l.message);break;default:if(Wt.includes(r.slice(4))){this.options[`on${r[4].toUpperCase()}${r.slice(5)}`]?.(l);break}if(Ut.includes(he(r))){this.options.onArcadeAnswer?.(l);break}this.fail("unsupported",`no frame handler for '${r}'`);}return false}screenshotDenied(t,e){this.send({gem:u,type:"gem.screenshot.denied",reason:t,retryable:e});}};function ot(n){for(let t of n)try{t?.close?.();}catch{}}o(ot,"closeAll");globalThis.__gemWebSdkVersion="[gem-web-sdk:sdkVersion:24.0.2]";var st=["if-match","if-none-match"];var zt={unknown_currency:1,currency_not_in_game_payable:1,not_purchasable:1,no_price_in_currency:1,busy:1,cancelled:1,insufficient_funds:1,price_mismatch:1,config_version_stale:1,platform_currency_unavailable:1,not_available:1,recipe_locked:1,already_owned:1,guest_upgrade_required:1,recipe_on_cooldown:1,recipe_max_uses_exceeded:1,item_max_stack_exceeded:1,recipe_costs_unmet:1,quantity_invalid:1,error:1};new Set(Object.keys(zt));new Set(st);new TextEncoder;var ct=["explicit","stale_cleanup","game_room_closed","replaced","migration_failed","host_left","insufficient_players","all_left","expired","migration_rate_exceeded","game_room_gone","failed","idle","reclaimed"];new Set(ct);new TextEncoder;new TextDecoder;new TextEncoder;var On=8;(class{constructor(t=On){this.perMinute=t;}perMinute;static{o(this,"CredentialPacing");}sent=[];delayUntilAllowed(t){return this.prune(t),this.sent.length<this.perMinute?0:Math.max(0,(this.sent[0]??t)+6e4-t)}record(t){this.prune(t),this.sent.push(t);}prune(t){let e=0;for(let r of this.sent)r<=t&&t-r<6e4&&(this.sent[e++]=r);this.sent.length=e;}});function g(n){return t=>typeof t=="string"&&t.length>0&&t.length<=n?t:void 0}o(g,"text");function G(n,t){return e=>typeof e=="number"&&Number.isInteger(e)&&e>=n&&e<=t?e:void 0}o(G,"integer");function j(...n){return t=>typeof t=="string"&&n.includes(t)?t:void 0}o(j,"oneOf");function mt(n){return typeof n=="boolean"?n:void 0}o(mt,"flag");var S=g(64),ae=G(0,2147483647);function ve(n){if(!Array.isArray(n))return;let t=[];for(let e of n){if(t.length>=64)break;let r=S(e);typeof r=="string"&&t.push(r);}return t}o(ve,"ids");function f(n,t,e){return {to:n,from:t,read:e,required:true}}o(f,"required");function y(n,t,e){return {to:n,from:t,read:e,required:false}}o(y,"optional");var $n=o(n=>{if(typeof n!="object"||n===null||Array.isArray(n))return;let t=n,e=j("disconnected","failed","closed")(t.iceState),r=G(0,4102444800)(t.lastSeenAt);if(!(e===void 0&&r===void 0))return {...e===void 0?{}:{ice_state:e},...r===void 0?{}:{last_seen_timestamp:r}}},"evidence");Object.freeze(Object.assign(Object.create(null),{join:{type:"p2p_join",gameScoped:true,args:[]},offer:{type:"p2p_offer",gameScoped:false,args:[f("sdp","sdp",g(10240)),f("signaling_intent","intent",j("initial","ice_restart","full_reconnect")),y("sdp_hash","sdpHash",g(128))]},answer:{type:"p2p_answer",gameScoped:false,args:[f("peer_id","peerId",S),f("sdp","sdp",g(10240)),f("assigned_peer_id","assignedPeerId",G(0,4294967295)),y("sdp_hash","sdpHash",g(128))]},ice:{type:"p2p_ice_candidate",gameScoped:false,args:[f("peer_id","peerId",S),f("candidate","candidate",g(512)),y("sdp_mid","sdpMid",g(64)),y("sdp_mline_index","sdpMLineIndex",G(0,255))]},migrationAck:{type:"p2p_migration_ack",gameScoped:false,args:[f("host_epoch","hostEpoch",ae)]},hostUnreachable:{type:"p2p_report_host_unreachable",gameScoped:false,args:[f("host_epoch","hostEpoch",ae),y("evidence","evidence",$n)]}}));Object.freeze(Object.assign(Object.create(null),{p2p_hello:"hello",p2p_sdp_offer:"offer",p2p_sdp_answer:"answer",p2p_ice_candidate:"ice",p2p_peer_joined:"peerJoined",p2p_peer_left:"peerLeft",p2p_host_migration:"migration",p2p_match_ended:"matchEnded",p2p_error:"error",p2p_relay_draining:"relayDraining"}));var Dn=16,Fn=o(n=>ve(n)?.slice(0,Dn),"relayIps");Object.freeze(Object.assign(Object.create(null),{hello:[f("playerId","player_id",S),f("hostPlayerId","host_player_id",S),f("hostEpoch","host_epoch",ae),y("members","members",ve)],offer:[f("fromPlayerId","from_player_id",S),f("sdp","sdp",g(10240)),y("sessionId","signaling_session_id",g(128)),y("sdpHash","sdp_hash",g(128)),y("iceUfrag","ice_ufrag",g(64)),y("intent","signaling_intent",j("initial","ice_restart","full_reconnect"))],answer:[f("fromPlayerId","from_player_id",S),f("sdp","sdp",g(10240)),y("sessionId","signaling_session_id",g(128)),y("sdpHash","sdp_hash",g(128)),y("iceUfrag","ice_ufrag",g(64)),y("assignedPeerId","assigned_peer_id",G(0,4294967295))],ice:[f("fromPlayerId","from_player_id",S),f("candidate","candidate",g(512)),y("sdpMid","sdp_mid",g(64)),y("sdpMLineIndex","sdp_m_line_index",G(0,255)),y("sdpMLineIndexAlt","sdp_mline_index",G(0,255))],peerJoined:[f("playerId","player_id",S)],peerLeft:[f("playerId","player_id",S),y("reason","reason",g(64))],migration:[f("hostPlayerId","new_host_player_id",S),f("hostEpoch","host_epoch",ae),y("reason","reason",g(64)),y("kickedPlayerIds","kicked_player_ids",ve),y("migrationComplete","migration_complete",mt)],matchEnded:[y("reason","reason",g(64))],error:[y("code","code",g(64)),y("message","message",g(256))],relayDraining:[f("clusterTargetId","cluster_target_id",S),f("relayIps","relay_ips",Fn),f("drainingSince","draining_since",g(40)),f("deadline","deadline",g(40))]}));Object.freeze(Object.assign(Object.create(null),{party_invite_received:"inviteReceived",party_member_joined:"memberJoined",party_member_left:"memberLeft",party_member_kicked:"memberKicked",party_leader_transferred:"leaderTransferred",party_updated:"updated",party_disbanded:"disbanded",party_game_room_joined:"gameRoomJoined",party_game_room_left:"gameRoomLeft"}));function Wn(n,t){return Object.freeze({route:n,listed:t})}o(Wn,"answer");Object.freeze(Object.assign(Object.create(null),{webrtc_sdp_answer:"answer",webrtc_error:"error",match_ended:"matchEnded"}));Object.freeze(Object.assign(Object.create(null),{answer:[f("sdp","sdp",g(10240)),y("sessionId","session_id",g(64))],error:[y("code","code",g(64)),y("message","message",g(256))],matchEnded:[y("reason","reason",g(64))]}));var St=Object.freeze(["https://gemarcade.com","https://developer.gemarcade.com","http://gemarcade.localhost","gemarcade://localhost","https://app.gemarcade.com"]);var pe="vip",z=class extends Error{constructor(e,r){super(`[${e}] ${r}`);this.caseId=e;this.name="ParentConformanceError";}caseId;static{o(this,"ParentConformanceError");}},z_=9,kt="https://game.conformance.invalid",Rt="00000000-0000-4000-8000-0000000c0de7",At="release",Tr="https://stranger.conformance.invalid",Be=L["gem.handshake"]??1,ce=6e4,ke=class{static{o(this,"KitClock");}current=1e6;seq=0;pending=new Map;now(){return this.current}setTimeout(t,e){let r=++this.seq;return this.pending.set(r,{at:this.current+Math.max(0,e),fn:t}),r}clearTimeout(t){this.pending.delete(t);}get pendingTimers(){return this.pending.size}advance(t){let e=this.current+t;for(;;){let r;for(let i of this.pending)i[1].at<=e&&(r===void 0||i[1].at<r[1].at)&&(r=i);if(r===void 0)break;this.pending.delete(r[0]),this.current=r[1].at,r[1].fn();}this.current=e;}},xt=Object.freeze({conformance:"parent-window"}),Re=class{constructor(t,e,r,i){this.caseId=t;this.options=r;let a=i??r.frameOrigin??kt,s={gameId:Rt,channel:At,frame:{postMessage:o((l,c,h)=>{this.offers.push({data:l,targetOrigin:c,transfer:h??[],at:this.clock.now()});},"postMessage")},frameOrigin:a,embedderOrigin:r.embedderOrigin,clock:this.clock,makeChannel:o(()=>{let l=new MessageChannel,c={port1:l.port1,port2:l.port2,closed:false},h=l.port1.close.bind(l.port1);return l.port1.close=()=>{c.closed=true,h();},this.count(l.port1),this.count(l.port2),this.minted.push(c),l},"makeChannel"),reportError:o((l,c)=>{this.errors.push(c===void 0?{code:l}:{code:l,message:c});},"reportError")};Object.defineProperty(s,"userActivation",{enumerable:true,get:o(()=>this.activation===void 0?void 0:{...this.activation},"get")}),this.parent=e(s);}caseId;options;static{o(this,"Rig");}clock=new ke;frameClock=new ke;offers=[];minted=[];parent;activation={isActive:false};errors=[];inFlight=0;frames=[];get frameOrigin(){return this.options.frameOrigin??kt}get gameId(){return Rt}get channel(){return At}fail(t){throw new z(this.caseId,t)}count(t){let e=t.postMessage.bind(t);t.postMessage=((...r)=>{this.inFlight+=1,e(...r);}),t.addEventListener("message",()=>{this.inFlight-=1;});}async flush(){let t=o(()=>new Promise(e=>setTimeout(e,0)),"turn");await t(),await t();for(let e=0;this.inFlight>0&&e<200;e++)await t();}advance(t,e=250){for(let r=0;r<t;r+=e){let i=Math.min(e,t-r);this.clock.advance(i),this.frameClock.advance(i);}}start(){try{this.parent.start();}catch(t){this.fail(`start() threw: ${ge(t)}`);}this.offers.length===0&&this.fail("start() posted no handshake to the frame window. The offer must be posted at start, on the window the fixture names as `frame`.");}latest(){let t=this.offers[this.offers.length-1];return t===void 0&&this.fail("no handshake has been offered"),t}transferred(t){t.transfer.length!==1&&this.fail(`an offer transferred ${t.transfer.length} objects; it must transfer exactly one MessagePort.`);let e=this.minted.find(r=>r.port2===t.transfer[0]);return e===void 0&&this.fail("an offer transferred a port the fixture did not mint. Take every offer\u2019s channel from `fixture.makeChannel`, so the kit can see which ports the parent closes."),e}frame(t){let e=t??[...new Set([...St,...this.options.allowedEmbedders??[]])],r=[],i=new N({allowedEmbedders:e,expectedSource:xt,selfOrigin:this.frameOrigin,clock:this.frameClock,onRefused:o(a=>r.push(a),"onRefused")});return i.kitRefusals=r,this.frames.push(i),i}deliver(t,e){let r=this.transferred(e);return t.handleWindowMessage({data:e.data,origin:this.options.embedderOrigin,source:xt,ports:[r.port2]})}answer(t,e,r=[]){this.transferred(t).port2.postMessage({gem:u,type:"gem.handshake.accept",v:e,capabilities:[...r]});}advertised(){let t=this.latest().data.capabilities;return Array.isArray(t)?t.filter(e=>typeof e=="string"):[]}async raw(t){let e=this.latest(),r=this.transferred(e),i=[];r.port2.onmessage=l=>{l.data!==null&&typeof l.data=="object"&&i.push(l.data);};let a=Math.min(K(this).max,x.max);this.answer(e,a,t),await this.flush(),this.state()!=="connected"&&this.fail(`a hand-written frame accepted at v${a}, and the parent reports '${this.state()}'.`);let s=0;return {received:i,ask:o(async(l,c,h)=>{let b=`conformance-${++s}`;return this.activation=h==="missing"?void 0:{isActive:h==="pressed"},r.port2.postMessage({gem:u,type:l,v:a,requestId:b,...c}),await this.flush(),this.activation={isActive:false},b},"ask"),answers:o(l=>i.filter(c=>c.requestId===l),"answers")}}surfaces(t){let e=this.parent.surfacesShown;return typeof e!="number"&&this.fail(`${t}, so the kit must see what it shows: expose \`surfacesShown\` on the adapter (every checkout, card, QR code, toast or tab the parent opened for a game).`),e}offersInAnyMinute(){let t=0;for(let e=0;e<this.offers.length;e++){let r=this.offers[e].at,i=0;for(let a=e;a<this.offers.length&&this.offers[a].at<r+ce;a++)i++;t=Math.max(t,i);}return t}state(){return this.parent.state}dispose(){try{this.parent.close();}catch{}for(let t of this.frames)t.close();for(let t of this.minted)try{t.port1.close(),t.port2.close();}catch{}}};function ge(n){return n instanceof Error?n.message:String(n)}o(ge,"describe");function Cr(n){return n.kitRefusals??[]}o(Cr,"refusalsOf");function K(n){let t=n.latest().data.versions,e=t?.min,r=t?.max;return (typeof e!="number"||typeof r!="number")&&n.fail("the handshake carries no numeric `versions.min` / `versions.max`"),{min:e,max:r}}o(K,"offeredRange");async function p(n,t,e,r){let i=new Re(n,t,e);try{await r(i);}finally{i.dispose();}}o(p,"withRig");async function se(n){let t=n.frame();return n.deliver(t,n.latest())||n.fail(`a game built with this SDK refused the handshake from ${n.options.embedderOrigin}: ${Cr(t).join("; ")||"no reason recorded"}. A production origin the SDK does not carry needs an SDK that lists it; a non-production one goes in \`allowedEmbedders\` here, as a game would name it.`),await n.flush(),n.state()!=="connected"&&n.fail(`the frame accepted, but the parent reports '${n.state()}' rather than 'connected'.`),t}o(se,"connect");var P=["appLinks","openGame","store","storeOfferSheet"],Pt="00000000-0000-4000-8000-00000000c0de",Ae="conformance-unknown-sku",Ue={appLinks:{type:"gem.requestOpenApp",fields:{target:"arcade"}},openGame:{type:"gem.requestOpenGame",fields:{ref:Pt}},store:{type:"gem.requestStorePurchase",fields:{skuId:Ae}},storeOfferSheet:{type:"gem.requestStoreOfferSheet",fields:{skuId:Ae}}},Or=["appLinks","openGame"],Mr=[["store","gem.requestStorePurchase"],["storeOfferSheet","gem.requestStoreOfferSheet"]];function we(n,t,e,r,i){let a=t.answers(e);a.length!==1&&n.fail(`${i}: the parent sent ${a.length} answers; it owes exactly one.`);let[s]=a;(s.outcome!=="declined"||s.reason!=="busy")&&n.fail(`${i}: answered ${JSON.stringify(s.outcome)} / ${JSON.stringify(s.reason)}, not 'declined' / 'busy'.`);let l=n.surfaces(i);l!==r&&n.fail(`${i}: the parent showed ${l-r} surface(s); it must show nothing.`);}o(we,"expectBusy");async function W(n,t,e="gem.requestStorePurchase",r="pressed",i=""){let a=await t.ask("gem.requestStoreOffers",{},"unpressed"),[s]=t.answers(a),l=s?.offers??[],c=(l.find(A=>A.pass===void 0)??l[0])?.skuId;(s?.outcome!=="ok"||typeof c!="string")&&n.fail(`the parent advertises \`store\` but listed no offer to a signed-in player (${JSON.stringify(s?.outcome)} / ${JSON.stringify(s?.reason)}). Give the adapter's store a signed-in player and at least one SKU, so the kit can open a checkout.`);let h=n.surfaces("the parent advertises `store`"),b=await t.ask(e,{skuId:c},r),v=n.surfaces("the parent advertises `store`");if(v!==h+1){let A=r==="pressed"?"a pressed":r==="unpressed"?"an unpressed":"with no userActivation API, a",X=t.answers(b);n.fail(`${i}${A} ${e} of the listed SKU ${JSON.stringify(c)} showed ${v-h} surface(s) and was answered ${JSON.stringify(X)}; it must open exactly one. A store purchase needs no press, and nothing backs off after the player closes a checkout: a player who wants to pay can always pay.`);}return t.answers(b).length>0&&n.fail(`${e} was answered while what it opened was still open. Answer once the player has acted, so the kit can hold it open.`),{requestId:b,skuId:c,shown:v}}o(W,"openStoreCheckout");function J(n){typeof n.parent.dismiss!="function"&&n.fail("the parent advertises `store`, so the kit must be able to close its checkout: expose `dismiss()` on the adapter (the player closing the checkout without paying)."),n.parent.dismiss();}o(J,"dismiss");function Gr(n){let t={...n};return delete t.requestId,t}o(Gr,"listingOf");function Se(n){return (Array.isArray(n.offers)?n.offers:[]).filter(e=>e.pass===pe)}o(Se,"kitPassOffers");async function F(n,t,e,r,i){await p(n,t,e,async a=>{if(typeof a.parent.storeScenario!="function")return;try{a.parent.storeScenario(r);}catch(l){a.fail(`storeScenario('${r}') threw: ${ge(l)}`);}if(a.start(),!a.advertised().includes("storeOfferSheet"))return;let s=await a.raw(P);await i(a,s);});}o(F,"withPassRig");async function ue(n){let t=await n.ask("gem.requestStoreOffers",{},"unpressed");return Gr(n.answers(t)[0])}o(ue,"listStore");async function le(n,t,e){let r=await ue(t),[i]=Se(r);(r.outcome!=="ok"||typeof i?.skuId!="string")&&n.fail(`for ${e}, the store answered ${JSON.stringify(r.outcome)} / ${JSON.stringify(r.reason)} and listed no offer with pass '${pe}'. List the pass with its slug in \`pass\`, so a game can find it with gem.store.showPass.`);let a=n.surfaces("the parent sells a pass"),s=await t.ask("gem.requestStoreOfferSheet",{skuId:i.skuId},"pressed"),l=n.surfaces("the parent sells a pass");return (l!==a+1||t.answers(s).length>0)&&n.fail(`for ${e}, the sheet for the pass's SKU ${JSON.stringify(i.skuId)} showed ${l-a} surface(s) and was answered ${JSON.stringify(t.answers(s))}; it must open exactly one sheet and answer once the player has acted.`),J(n),await n.flush(),{skuId:i.skuId,answer:t.answers(s)[0]}}o(le,"openPassSheet");var Lr="game_inventory_changed",$r=1e4;function H(n,t){return {v:1,type:Lr,data:{game_id:n,channel:t,entity_id:"conformance-entity",balance_after:500},ts:179e7}}o(H,"inventoryEvent");function Dr(n,t){try{n.parent.deliverSocket(t);}catch(e){n.fail(`deliverSocket threw: ${ge(e)}`);}}o(Dr,"deliverSocket");function Fr(n,t){return n.received.filter(e=>e.type===t)}o(Fr,"sentOf");async function de(n,t,e=true){if(typeof n.parent.deliverSocket!="function")return null;n.start();let r=await n.raw(t);if(!e)return r;await r.ask("gem.requestToken",{reason:"initial"},"unpressed");let i=Fr(r,"gem.token"),a=i[i.length-1];return a===void 0&&n.fail("the adapter exposes `deliverSocket`, so the kit needs a session with a token: answer the frame's gem.requestToken with a gem.token naming `fixture.gameId` and `fixture.channel`."),(a.gameId!==n.gameId||a.channel!==n.channel)&&n.fail(`the parent's gem.token named game ${JSON.stringify(a.gameId)} on ${JSON.stringify(a.channel)}; the kit's session is ${n.gameId} on ${n.channel}. Name the game and channel from \`fixture.gameId\` and \`fixture.channel\`.`),r}o(de,"socketSession");async function V(n,t,e){let r=t.received.length;for(let i of e)Dr(n,i);return await n.flush(),n.advance($r),await n.flush(),t.received.slice(r)}o(V,"deliverAndSettle");var Ve=[100,115,130,150,180,200],qr=[250,99,137,137.5,Number.NaN];async function We(n,t){return typeof n.parent.setTextScale!="function"?null:(n.start(),n.raw(t))}o(We,"textScaleSession");async function _e(n,t,e){let r=t.received.length;try{n.parent.setTextScale(e);}catch(i){n.fail(`setTextScale(${e}) threw: ${ge(i)}`);}return await n.flush(),t.received.slice(r).filter(i=>i.type==="gem.prefs")}o(_e,"pushTextScale");function me(n){let t=n.recommended;return t===void 0?void 0:t.textScale}o(me,"textScaleOf");function He(n,t,e){for(let r of t){let i=C["gem.prefs"]?.(r);i&&n.fail(`after ${e}, gem.prefs fails the contract: ${i}`);let a=me(r);a!==void 0&&!Ve.includes(a)&&n.fail(`after ${e}, gem.prefs carried recommended.textScale ${JSON.stringify(a)}. Send only one of the six sizes (${Ve.join(", ")}), or leave it out.`);}}o(He,"checkTextScalePrefs");var Nr=["*","null","","https://game.conformance.invalid/","https://game.conformance.invalid/play"],jr=[{id:"origin.refuses-imprecise-target",title:"refuses a frame origin that is not one exact origin, before anything is posted",async run(n,t){for(let e of Nr){let r=JSON.stringify(e),i,a=false;try{i=new Re(this.id,n,{...t,frameOrigin:e},e),i.parent.start();}catch(s){if(s instanceof z)throw s;a=true;}try{if((i?.offers??[]).some(l=>l.targetOrigin==="*"||l.targetOrigin===e))throw new z(this.id,`with frame origin ${r} the parent posted the handshake anyway \u2014 the offer transfers the session port, so an imprecise target hands the session to whatever the frame has navigated to.`);if(!a)throw new z(this.id,`a frame origin of ${r} was accepted. Refuse it at construction or at start(), by throwing.`)}finally{i?.dispose();}}}},{id:"handshake.targets-frame-origin",title:"posts a valid handshake to exactly the frame origin, naming the page\u2019s own origin",async run(n,t){await p(this.id,n,t,e=>{e.start(),e.advance(E);for(let a of e.offers){a.targetOrigin!==e.frameOrigin&&e.fail(`an offer was posted with targetOrigin ${JSON.stringify(a.targetOrigin)}, not the frame origin ${e.frameOrigin}.`),e.transferred(a);let s=C["gem.handshake"]?.(a.data);s&&e.fail(`the handshake does not validate against the schema: ${s}`);let l=a.data;l.gem!==u&&e.fail(`the handshake's \`gem\` is not ${u}`),l.embedder!==t.embedderOrigin&&e.fail(`the handshake names embedder ${JSON.stringify(l.embedder)}, but the page is served from ${t.embedderOrigin}. Every game compares the two and refuses a mismatch: name the origin the page is actually served from.`);}let{min:r,max:i}=K(e);r>i&&e.fail(`the offered range is empty: min ${r} > max ${i}`),(i<x.min||r>x.max)&&e.fail(`the offered range v${r}\u2013v${i} shares no version with this SDK (v${x.min}\u2013v${x.max}).`);});}},{id:"handshake.default-game-connects",title:"connects to a game that names no embedders, at the highest common version",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=await se(e),{max:i}=K(e),a=Math.min(i,x.max);r.protocolVersion!==a&&e.fail(`the frame chose v${String(r.protocolVersion)}, expected v${a}`),e.parent.protocolVersion!==r.protocolVersion&&e.fail(`the parent reports v${String(e.parent.protocolVersion)} while the frame chose v${String(r.protocolVersion)}. Adopt the version on the accept.`);});}},{id:"handshake.refusal-is-not-a-verdict",title:"keeps offering, still handshaking, when a game refuses this page",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.frame([Tr]);e.deliver(r,e.latest())&&e.fail("a frame that does not allow this page accepted it \u2014 the kit is misconfigured"),await e.flush();let i=e.offers.length;e.advance(E*3),await e.flush(),e.state()!=="handshaking"&&e.fail(`after a refusal and ${E*3}ms the parent reports '${e.state()}'. A refusing game answers nothing, by design, so silence is not evidence: stay handshaking until the page closes the bridge.`),e.offers.length<=i&&e.fail("the parent stopped offering after a refusal; keep offering on the cadence.");});}},{id:"version.adopts-frame-choice",title:"adopts the version the frame chose from inside the offered range",async run(n,t){await p(this.id,n,t,async e=>{e.start();let{min:r}=K(e);e.answer(e.latest(),r),await e.flush(),(e.state()!=="connected"||e.parent.protocolVersion!==r)&&e.fail(`answered with v${r}, the bottom of the offered range, the parent reports '${e.state()}' at v${String(e.parent.protocolVersion)}.`);});}},{id:"version.refuses-out-of-range",title:"never connects at a version outside the range it offered",async run(n,t){for(let e of ["above","below"])await p(this.id,n,t,async r=>{r.start();let{min:i,max:a}=K(r),s=e==="above"?a+1:i-1;r.answer(r.latest(),s),await r.flush(),(r.state()==="connected"||r.parent.protocolVersion===s)&&r.fail(`answered with v${s}, outside the offered v${i}\u2013v${a}, the parent connected. A frame choosing a version this parent never offered is not a session.`);});}},{id:"lifecycle.reoffers-on-fresh-ports",title:"re-offers on a fresh port while unanswered, inside the contract\u2019s budget",async run(n,t){await p(this.id,n,t,e=>{e.start(),e.advance(E*2),e.offers.length<2&&e.fail(`one offer in ${E*2}ms. A message to a document that is not listening yet is lost, not queued: re-offer until something answers.`);let r=e.offers.map(a=>e.transferred(a).port2);new Set(r).size!==r.length&&e.fail("an offer re-used a port; each offer needs its own channel."),e.advance(ce*2);let i=e.offersInAnyMinute();i>Be&&e.fail(`${i} offers inside one minute; the contract budgets ${Be} \`gem.handshake\` per minute.`);});}},{id:"lifecycle.late-answer-adopts-and-closes-the-rest",title:"adopts the offer a late frame answers, and closes every other offered port",async run(n,t){await p(this.id,n,t,async e=>{e.start(),e.advance(E*2);let r=e.transferred(e.latest());await se(e),r.closed&&e.fail("the parent closed the port the frame answered on.");let i=e.minted.filter(a=>a!==r&&!a.closed).length;i>0&&e.fail(`${i} unanswered offer port(s) still open after connecting. Close every other offer when one is answered.`);});}},{id:"lifecycle.no-offers-after-connect",title:"stops offering once connected",async run(n,t){await p(this.id,n,t,async e=>{e.start(),await se(e);let r=e.offers.length;e.advance(ce*5,1e3),e.offers.length!==r&&e.fail(`${e.offers.length-r} offer(s) after the session connected.`);});}},{id:"lifecycle.duplicate-accept-keeps-session",title:"keeps the session when a second accept arrives, even an out-of-range one",async run(n,t){await p(this.id,n,t,async e=>{e.start(),await se(e);let r=e.parent.protocolVersion;e.answer(e.latest(),K(e).max+1),await e.flush(),(e.state()!=="connected"||e.parent.protocolVersion!==r)&&e.fail(`a second accept moved the parent to '${e.state()}' at v${String(e.parent.protocolVersion)}. One answer settles the handshake.`);});}},{id:"lifecycle.close-releases-everything",title:"on close, releases every port and timer and offers nothing more",async run(n,t){await p(this.id,n,t,e=>{e.start(),e.advance(E),e.parent.close(),e.state()!=="closed"&&e.fail(`after close() the parent reports '${e.state()}'`);let r=e.minted.filter(a=>!a.closed).length;r>0&&e.fail(`${r} offer port(s) left open after close().`),e.clock.pendingTimers>0&&e.fail(`${e.clock.pendingTimers} timer(s) still armed after close().`);let i=e.offers.length;e.advance(ce*2,1e3),e.offers.length!==i&&e.fail("the parent offered again after close().");}),await p(this.id,n,t,async e=>{e.start(),await se(e),e.parent.close(),e.state()!=="closed"&&e.fail(`after close() the parent reports '${e.state()}'`),e.minted.filter(i=>!i.closed).length>0&&e.fail("the session port is still open after close().");});}},{id:"lifecycle.unanswered-offers-are-bounded",title:"holds at most about a minute of unanswered offer ports, however long it runs",async run(n,t){await p(this.id,n,t,e=>{e.start(),e.advance(ce*10,1e3);let r=e.minted.filter(i=>!i.closed).length;r>Be+1&&e.fail(`${r} unanswered offer ports open after ten minutes. Close offers nothing answered within a grace (a minute is the reference), or the loop leaks a channel per tick for as long as the page holds the frame.`);});}},{id:"capability.unadvertised-is-refused",title:"refuses an arcade-feature or store ask under a capability either side did not advertise, answering nothing and showing nothing",async run(n,t){for(let e of P)await p(this.id,n,t,async r=>{r.start();let i=r.advertised().includes(e),a=P.filter(A=>!i||A!==e),s=await r.raw(a),l=r.parent.surfacesShown,{type:c,fields:h}=Ue[e],b=await s.ask(c,h,"pressed"),v=i?"the frame":"the parent";s.answers(b).length>0&&r.fail(`${c} under '${e}', which ${v} did not advertise, was answered. Refuse it before anything else and send nothing back.`),r.parent.surfacesShown!==l&&r.fail(`${c} under unadvertised '${e}' showed a surface.`),r.errors.some(A=>A.code==="unsupported_capability")||r.fail(`${c} under '${e}', which ${v} did not advertise, was not reported as 'unsupported_capability'. Report it through \`fixture.reportError\` (what GemHost calls onError).`);});}},{id:"acting.refuses-without-activation",title:"answers an ask to leave the game (open app, open game) with no press \u2014 or no activation API \u2014 declined / busy, showing nothing",async run(n,t){for(let e of Or)for(let r of ["unpressed","missing"])await p(this.id,n,t,async i=>{if(i.start(),!i.advertised().includes(e))return;let a=await i.raw(P),s=i.surfaces(`the parent advertises '${e}'`),{type:l,fields:c}=Ue[e],h=await a.ask(l,c,r);we(i,a,h,s,r==="missing"?`${l} with no userActivation API (it must fail closed)`:`${l} with no press`);});}},{id:"store.unknown-sku-is-unavailable",title:"answers a store purchase of a SKU it does not list unavailable, showing nothing",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let r=await e.raw(P),i=e.surfaces("the parent advertises `store`"),a=await r.ask("gem.requestStorePurchase",{skuId:Ae},"pressed"),s=r.answers(a);(s.length!==1||s[0].outcome!=="unavailable")&&e.fail(`a pressed purchase of an unknown SKU was answered ${JSON.stringify(s)}; it owes exactly one 'unavailable'.`),e.surfaces("the parent advertises `store`")!==i&&e.fail("a purchase of an unknown SKU showed a surface; it must show nothing.");});}},{id:"acting.second-ask-is-busy",title:"answers any acting ask declined / busy, showing nothing, while a store checkout is open",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let r=await e.raw(P),i=await W(e,r),a=await r.ask("gem.requestStorePurchase",{skuId:i.skuId},"pressed");we(e,r,a,i.shown,"a second store purchase while one is open");for(let l of ["appLinks","openGame","storeOfferSheet"]){if(!e.advertised().includes(l))continue;let{type:c,fields:h}=Ue[l],b=await r.ask(c,h,"pressed");we(e,r,b,i.shown,`${c} while a store checkout is open`);}J(e),await e.flush();let[s]=r.answers(i.requestId);(s?.outcome!=="declined"||s.reason!=="cancelled")&&e.fail(`the closed checkout was answered ${JSON.stringify(s?.outcome)} / ${JSON.stringify(s?.reason)}, not 'declined' / 'cancelled'.`);});}},{id:"store.no-press-needed",title:"opens a store checkout, or an offer sheet, for an ask with no press or no activation API",async run(n,t){for(let[e,r]of Mr)for(let i of ["unpressed","missing"])await p(this.id,n,t,async a=>{if(a.start(),!a.advertised().includes(e))return;let s=await a.raw(P),l=await W(a,s,r,i);J(a),await a.flush();let[c]=s.answers(l.requestId);(c?.outcome!=="declined"||c.reason!=="cancelled")&&a.fail(`the closed ${r} asked with no press was answered ${JSON.stringify(c?.outcome)} / ${JSON.stringify(c?.reason)}, not 'declined' / 'cancelled'.`);});}},{id:"store.no-back-off",title:"opens the checkout again for the next store purchase, however many times the player closed one",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let r=await e.raw(P);for(let i=1;i<=5;i+=1){let a=await W(e,r,"gem.requestStorePurchase","pressed",i===1?"":`straight after the player dismissed one (round ${i}), `);J(e),await e.flush();let[s]=r.answers(a.requestId);(s?.outcome!=="declined"||s.reason!=="cancelled")&&e.fail(`closed checkout ${i} was answered ${JSON.stringify(s?.outcome)} / ${JSON.stringify(s?.reason)}, not 'declined' / 'cancelled'.`);}e.advertised().includes("storeOfferSheet")&&await W(e,r,"gem.requestStoreOfferSheet","pressed","straight after the player dismissed a checkout, ");});}},{id:"sheet.needs-store",title:"advertises storeOfferSheet only beside store",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.advertised();r.includes("storeOfferSheet")&&!r.includes("store")&&e.fail("the parent advertises `storeOfferSheet` without `store`. A game takes its SKU from gem.storeOffersResult and hears a pending purchase settle through gem.storePurchaseSettled, and both are gated on `store`.");});}},{id:"settled.needs-purchases",title:"advertises purchaseSettled only beside purchases",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.advertised();r.includes("purchaseSettled")&&!r.includes("purchases")&&e.fail("the parent advertises `purchaseSettled` without `purchases`. gem.purchaseSettled reports a purchase the game asked for through gem.requestPurchase, which is gated on `purchases`.");});}},{id:"share.extras-need-share",title:"advertises shareResult and shareRemap only beside share",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.advertised();if(!r.includes("share"))for(let i of ["shareResult","shareRemap"])r.includes(i)&&e.fail(`the parent advertises \`${i}\` without \`share\`. Both describe what the parent does with gem.requestShare, which is gated on \`share\`.`);});}},{id:"share.refusal-is-answered",title:"answers a share naming embedder refused / reserved_key, naming the key, when it advertises shareResult",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.advertised();if(!r.includes("share")||!r.includes("shareResult"))return;let i=await e.raw(["share","shareResult"]),a=e.parent.surfacesShown,s=await i.ask("gem.requestShare",{params:[{key:"embedder",value:"x"}]},"pressed"),l=i.answers(s);l.length!==1&&e.fail(`a share naming \`embedder\` was answered ${l.length} times; a parent that advertises \`shareResult\` owes exactly one gem.shareResult for a request carrying a requestId.`);let[c]=l;(c.type!=="gem.shareResult"||c.outcome!=="refused"||c.reason!=="reserved_key"||c.key!=="embedder")&&e.fail(`a share naming \`embedder\` was answered ${JSON.stringify(c)}; it owes gem.shareResult 'refused' / 'reserved_key' with key 'embedder'. No parent carries embedder in a share link, remapping or not.`),typeof a=="number"&&e.parent.surfacesShown!==a&&e.fail("a refused share showed a surface; it must show nothing.");});}},{id:"sheet.unknown-sku-is-unavailable",title:"answers an offer sheet for a SKU it does not list unavailable, showing nothing",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("storeOfferSheet"))return;let r=await e.raw(P),i=e.surfaces("the parent advertises `storeOfferSheet`"),a=await r.ask("gem.requestStoreOfferSheet",{skuId:Ae},"pressed"),s=r.answers(a);(s.length!==1||s[0].outcome!=="unavailable")&&e.fail(`a pressed offer sheet for an unknown SKU was answered ${JSON.stringify(s)}; it owes exactly one 'unavailable'.`),e.surfaces("the parent advertises `storeOfferSheet`")!==i&&e.fail("an offer sheet for an unknown SKU showed a surface; it must show nothing.");});}},{id:"sheet.shares-the-slot",title:"holds the acting slot while an offer sheet is open, and opens either store ask again once the player leaves it",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("storeOfferSheet"))return;let r=await e.raw(P),i=await W(e,r,"gem.requestStoreOfferSheet"),a=await r.ask("gem.requestStorePurchase",{skuId:i.skuId},"pressed");we(e,r,a,i.shown,"a store purchase while an offer sheet is open"),J(e),await e.flush();let[s]=r.answers(i.requestId);(s?.outcome!=="declined"||s.reason!=="cancelled")&&e.fail(`the closed offer sheet was answered ${JSON.stringify(s?.outcome)} / ${JSON.stringify(s?.reason)}, not 'declined' / 'cancelled'.`);for(let l of ["gem.requestStoreOfferSheet","gem.requestStorePurchase"])await W(e,r,l,"pressed","straight after the player left a sheet, "),J(e),await e.flush();});}},{id:"pass.listed-by-slug",title:"lists a pass with its slug, and opens the pass's sheet, for an adult",async run(n,t){await F(this.id,n,t,"adult",async(e,r)=>{let{answer:i}=await le(e,r,"an adult");(i?.outcome!=="declined"||i.reason!=="cancelled")&&e.fail(`the closed pass sheet was answered ${JSON.stringify(i?.outcome)} / ${JSON.stringify(i?.reason)}, not 'declined' / 'cancelled'.`);});}},{id:"pass.not-on-sale",title:"lists no offer with the slug of a pass that is not on sale",async run(n,t){await F(this.id,n,t,"off_sale",async(e,r)=>{let i=await ue(r);Se(i).length>0&&e.fail(`the store listed pass '${pe}' where it is not on sale: a game would draw a button that opens nothing.`);});}},{id:"pass.guest-is-sold",title:"lists a pass to a guest and opens its sheet, rather than answering sign_in_required",async run(n,t){await F(this.id,n,t,"guest",async(e,r)=>{await le(e,r,"a guest");});}},{id:"pass.holder-is-sold",title:"lists a pass to a player who holds it, and opens its sheet",async run(n,t){await F(this.id,n,t,"holder",async(e,r)=>{await le(e,r,"a holder");});}},{id:"pass.declined-twice",title:"leaves a pass out, and opens nothing for it, once its sheet was declined twice today",async run(n,t){await F(this.id,n,t,"adult",async(e,r)=>{await le(e,r,"an adult");let{skuId:i}=await le(e,r,"an adult who declined it once"),a=await ue(r);Se(a).length>0&&e.fail(`the store still lists pass '${pe}' after its sheet was declined twice. After two declines in a day it is left out, so gem.store.passOnSale answers false.`);let s=e.surfaces("the parent sells a pass"),l=await r.ask("gem.requestStoreOfferSheet",{skuId:i},"pressed"),c=r.answers(l);(c.length!==1||c[0].outcome!=="unavailable")&&e.fail(`a third sheet for the pass was answered ${JSON.stringify(c)}; it owes exactly one 'unavailable', as for a SKU it does not list.`),e.surfaces("the parent sells a pass")!==s&&e.fail("a third sheet for the pass showed a surface; it must show nothing.");});}},{id:"pass.minor-looks-off-sale",title:"answers a known minor's listing exactly as it answers a player the pass is not sold to",async run(n,t){let e;await F(this.id,n,t,"off_sale",async(r,i)=>{e=await ue(i);}),await F(this.id,n,t,"minor",async(r,i)=>{let a=await ue(i);Se(a).length>0&&r.fail(`the store listed pass '${pe}' to a known minor.`),!(a.outcome==="unavailable"&&a.reason==="store_off")&&JSON.stringify(a)!==JSON.stringify(e)&&r.fail(`a known minor's listing ${JSON.stringify(a)} differs from the listing of an adult the pass is not on sale to, ${JSON.stringify(e)}. A game must not be able to tell a minor from a pass that is not sold here.`);});}},{id:"inventory.own-game-relayed",title:"relays this game's own game_inventory_changed as one field-free gem.inventoryChanged, and coalesces a burst",async run(n,t){await p(this.id,n,t,async e=>{let r=await de(e,["inventoryChanged"]);if(r===null)return;let i=await V(e,r,[H(e.gameId,e.channel)]),a=i.filter(b=>b.type==="gem.inventoryChanged");(a.length!==1||i.length!==1)&&e.fail(`one game_inventory_changed for this game and channel put ${a.length} gem.inventoryChanged and ${i.length-a.length} other message(s) on the frame; it owes exactly one gem.inventoryChanged.`);let s=Object.keys(a[0]).filter(b=>!["gem","type","v"].includes(b));s.length>0&&e.fail(`gem.inventoryChanged carried ${s.map(b=>JSON.stringify(b)).join(", ")}. It carries no field: copy nothing from the socket event.`);let l=C["gem.inventoryChanged"]?.(a[0]);l&&e.fail(`gem.inventoryChanged fails the contract: ${l}`);let h=(await V(e,r,Array.from({length:10},()=>H(e.gameId,e.channel)))).filter(b=>b.type==="gem.inventoryChanged").length;(h<1||h>2)&&e.fail(`a burst of 10 game_inventory_changed inside a second put ${h} gem.inventoryChanged on the frame; coalesce it to one, then at most one trailing send.`);});}},{id:"inventory.other-game-never-relayed",title:"never relays another game's, or another channel's, game_inventory_changed",async run(n,t){await p(this.id,n,t,async e=>{let r=await de(e,["inventoryChanged"]);if(r===null)return;let i=e.channel==="release"?"dev":"release";for(let[a,s]of [["another game",H(Pt,e.channel)],["this game on another channel",H(e.gameId,i)]]){let l=await V(e,r,[s]);l.length>0&&e.fail(`game_inventory_changed for ${a} put ${JSON.stringify(l.map(c=>c.type))} on the frame. Relay only the token's own game and channel: another game's change tells this game when another game paid the player.`);}});}},{id:"inventory.before-token-never-relayed",title:"relays no game_inventory_changed before a token has named the game",async run(n,t){await p(this.id,n,t,async e=>{let r=await de(e,["inventoryChanged"],!1);if(r===null)return;let i=await V(e,r,[H(e.gameId,e.channel)]);i.length>0&&e.fail(`before any token, game_inventory_changed put ${JSON.stringify(i.map(a=>a.type))} on the frame. Until a token has gone out there is no game to attribute it to.`);});}},{id:"inventory.unadvertised-not-sent",title:"sends no gem.inventoryChanged to a frame that did not advertise inventoryChanged",async run(n,t){await p(this.id,n,t,async e=>{let r=await de(e,[]);if(r===null)return;let i=await V(e,r,[H(e.gameId,e.channel)]);i.length>0&&e.fail(`to a frame that advertised nothing, game_inventory_changed put ${JSON.stringify(i.map(a=>a.type))} on it. Send gem.inventoryChanged only when the frame advertised \`inventoryChanged\`: an older SDK drops it unread.`);});}},{id:"inventory.wallet-events-never-relayed",title:"relays nothing from a platform wallet event, even one naming this game",async run(n,t){await p(this.id,n,t,async e=>{let r=await de(e,["inventoryChanged"]);if(r===null)return;let i={game_id:e.gameId,channel:e.channel};for(let[a,s]of [["platform_grant_fired",{...i,currency_id:"gems",granted:50,game_total:50,game_max:500,total:500}],["platform_funded_game_inventory_purchase_completed",{...i,purchase_id:"p",kind:"item",target:"t",qty:1,balance_after:450}],["platform_daily_maximum_reached",{...i,currency_id:"gems",limit:500}],["purchase_completed",{...i,purchase_id:"p",package_id:"k"}]]){let l=await V(e,r,[{v:1,type:a,data:s,ts:179e7}]);l.length>0&&e.fail(`${a} put ${JSON.stringify(l.map(c=>c.type))} on the frame. A wallet event describes the platform wallet across every game; relay nothing from it.`);}});}},{id:"textscale.unadvertised-not-sent",title:"sends no text size to a frame that did not advertise textScale",async run(n,t){await p(this.id,n,t,async e=>{let r=await We(e,[]);if(r!==null)for(let i of [150,200]){let a=await _e(e,r,i);for(let s of a)me(s)!==void 0&&e.fail(`to a frame that advertised nothing, gem.prefs carried recommended.textScale ${JSON.stringify(me(s))}. Send it only to a frame that advertised \`textScale\`: it reaches only the games that apply it.`);}});}},{id:"textscale.advertised-gets-each-size",title:"sends each Arcade text size to a frame that advertised textScale",async run(n,t){await p(this.id,n,t,async e=>{let r=await We(e,["textScale"]);if(r!==null)for(let i of [200,...Ve]){let a=await _e(e,r,i);He(e,a,`setTextScale(${i})`);let s=a[a.length-1];(s===void 0||me(s)!==i)&&e.fail(`setTextScale(${i}) to a frame that advertised \`textScale\` put ${s===void 0?"no gem.prefs":`recommended.textScale ${JSON.stringify(me(s))}`} on the frame. Push gem.prefs on the change, with recommended.textScale ${i}.`);}});}},{id:"textscale.bad-size-costs-nothing-else",title:"never sends a text size that is not one of the six, and never drops recommended for one",async run(n,t){await p(this.id,n,t,async e=>{let r=await We(e,["textScale"]);if(r!==null)for(let i of qr){He(e,await _e(e,r,130),"setTextScale(130)");let a=await _e(e,r,i);He(e,a,`setTextScale(${i})`);for(let s of a)s.recommended===void 0&&e.fail(`after setTextScale(${i}), gem.prefs carried no recommended. A text size the contract refuses is left out, and recommended sent without it: it must not cost the game the player's volume, Color help or graphics.`);}});}}],Br=Object.freeze(jr);async function X_(n,t){if(!Y(t.embedderOrigin))throw new Error(`runParentConformance: embedderOrigin must be one exact origin, got ${JSON.stringify(t.embedderOrigin)}`);let e=[];for(let r of Br)try{await r.run(n,t),e.push({id:r.id,title:r.title,ok:!0});}catch(i){e.push({id:r.id,title:r.title,ok:false,message:ge(i)});}return e}o(X_,"runParentConformance");
|
|
3
|
-
export{
|
|
1
|
+
var Tt=Object.defineProperty;var i=(n,t)=>Tt(n,"name",{value:t,configurable:true});var u="gem-web-bridge",x={min:1,max:1},Ct=65536,Ve=8,ge=5e3,Ke=1024,E=1e4,L=Object.create(null),fe="__malformed";var Ce=Object.create(null),Ot=Object.freeze([["gem.asyncOpponents.","asyncOpponents"],["gem.textScale.","textScale"],["gem.vibration.","vibration"],["gem.viewport.","reservedAreas"],["gem.moments.","moments"],["gem.prefs.","audioChannels"],["gem.share.","shareRemap"]]);function ye(n){let t=Ce[n];if(t!==void 0)return t;for(let[e,r]of Ot)if(n.startsWith(e))return r}i(ye,"capabilityFor");function Z(n){let t=/^([a-z][a-z0-9+.-]*):\/\/([a-z0-9.-]+|\[[0-9a-f:.]+\])(:([0-9]{1,5}))?$/i.exec(n);return t===null?false:t[4]===void 0||Number(t[4])<=65535}i(Z,"isOrigin");function Mt(n){let t;try{t=new URL(n);}catch{return false}return t.username!==""||t.password!==""?false:t.protocol==="https:"?true:t.protocol==="http:"&&(t.hostname==="localhost"||t.hostname==="127.0.0.1")}i(Mt,"isSafeHttpUrl");function Gt(n){return /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i.test(n)}i(Gt,"isBcp47");function Lt(n){return /^[a-z_]+$/.test(n)}i(Lt,"isCode");function $t(n){return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(n)||/^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$/.test(n)&&!n.includes("--")}i($t,"isGameRef");function Ee(n,t,e){if(t>Ve)return `nesting deeper than ${Ve}`;if(++e.nodes>ge)return `more than ${ge} values`;if(e.bytes>e.cap)return `over the ${e.cap} byte limit`;if(n==null)return e.bytes+=4,null;let r=typeof n;if(r==="boolean")return e.bytes+=5,null;if(r==="number")return e.bytes+=8,null;if(r==="string")return e.bytes+=n.length*3+2,null;if(r!=="object")return `${r} is not plain data`;let o=n;if(e.seen.has(o))return "a repeated or cyclic object reference";if(e.seen.add(o),Array.isArray(n)){if(n.length>Ke)return `an array of ${n.length} entries, over the ${Ke} limit`;e.bytes+=2;for(let l of n){let c=Ee(l,t+1,e);if(c)return c}return null}let a=Object.getPrototypeOf(n);if(a!==Object.prototype&&a!==null)return "a value that is not a plain object";e.bytes+=2;let s=n;for(let l in s){if(!Object.prototype.hasOwnProperty.call(s,l))continue;if(++e.nodes>ge)return `more than ${ge} values`;if(e.bytes+=l.length*3+4,e.bytes>e.cap)return `over the ${e.cap} byte limit`;let c=Ee(s[l],t+1,e);if(c)return c}return null}i(Ee,"walk");function Dt(n,t=Ct){let e={nodes:0,bytes:0,seen:new Set,cap:t},r=Ee(n,0,e);return r?`message contains ${r}`:e.bytes>t?`message is over the ${t} byte limit`:null}i(Dt,"validateSize");var Ft={origin:[Z,"not a well-formed origin"],url:[Mt,"not a safe absolute http(s) URL"],bcp47:[Gt,"not a well-formed BCP 47 tag"],code:[Lt,"not a code of lower-case letters and underscores"],gameRef:[$t,"not a game id or web address"]};function Je(n,t,e){for(let r of t){let o=e===""?r[0]:`${e}.${r[0]}`,a=n[r[0]];if(a==null){if(r[1]<"a")continue;return `${o}: required`}let s=Te(a,r,o);if(s)return s}return null}i(Je,"checkFields");function Te(n,t,e){let r=t[1].toLowerCase();switch(r){case "u":return typeof n=="boolean"?null:Te(n,typeof n=="string"?[t[0],"s",t[2]]:[t[0],"n",t[3],t[4]],e);case "s":{if(typeof n!="string")return `${e}: expected string`;if(n.length>t[2])return `${e}: exceeds ${t[2]} characters`;if(t[3]){let[o,a]=Ft[t[3]];if(!o(n))return `${e}: ${a}`}return null}case "n":case "i":return typeof n!="number"||!(r=="i"?Number.isInteger:Number.isFinite)(n)?`${e}: expected ${r=="i"?"integer":"number"}`:n<t[2]||n>t[3]?`${e}: out of range ${t[2]}..${t[3]}`:null;case "b":return typeof n=="boolean"?null:`${e}: expected boolean`;case "e":{let o=t[2];return o.includes(n)?null:`${e}: not one of ${o.join("|")}`}case "o":return typeof n!="object"||n===null||Array.isArray(n)?`${e}: expected object`:Je(n,t[2],e);case "j":return typeof n!="object"||n===null||Array.isArray(n)?`${e}: expected a plain object`:null;case "a":{if(!Array.isArray(n))return `${e}: expected array`;if(n.length>t[2])return `${e}: exceeds ${t[2]} items`;let o=t[3],a=`${e}[]`;for(let s of n){if(s==null){if(o[1]<"a")continue;return `${a}: required`}let l=Te(s,o,a);if(l)return l}return null}default:return `${e}: unknown kind ${String(r)}`}}i(Te,"checkValue");var ze=Object.create(null);function d(n,t,e,r,o){return L[n]=e,r&&(Ce[n]=r),ze[n]=a=>{let s=Dt(a,o);if(s)return s;if(typeof a!="object"||a===null||Array.isArray(a))return "message: expected object";let l=a;return l.gem!==u?"gem: wrong protocol marker":l.type!==n?`type: expected ${n}`:Je(l,t,"")}}i(d,"makeValidator");var ee=["","s",64],m=["requestId","s",64],te=["currencySlug","s",32],ne=["matchId","s",64],I=["reason","S",64,"code"],T=["outcome","e",["ok","declined","unavailable"]];d("gem.handshake",[["versions","o",[["min","i",1,1e3],["max","i",1,1e3]]],["capabilities","A",64,ee],["embedder","s",2048,"origin"],["unbidden","B"]],30);d("gem.handshake.accept",[["v","i",1,1e3],["capabilities","A",64,ee]],5);d("gem.token",[["token","s",4096],["requestId","S",64],["generation","i",0,2147483647],["apiBaseUrl","S",512,"url"],["playerId","s",64],["gameId","s",64],["channel","e",["dev","test","release"]],["gameVersion","S",64],["expiresAt","i",0,4102444800],["chainEndsAt","i",0,4102444800],["allowDevTools","B"]],30);d("gem.token.denied",[m,["retryable","b"],["retryAfterMs","I",0,36e5],["reason","e",["chain_exhausted","unavailable"]],["correlationId","s",64]],30);d("gem.player",[["untrusted","o",[["displayName","s",64],["displayTag","S",80],["displayHandle","s",80]]],["playerId","S",64],["discriminator","S",8],["avatarIconUrl","S",2048,"url"],["avatarIconId","S",64],["avatar3dId","S",64],["avatar3dModelUrl","S",2048,"url"],["avatar3dIconUrl","S",2048,"url"],["avatars3d","A",8,["","o",[["family","s",32],["id","s",64],["modelUrl","s",256,"url"],["iconUrl","s",256,"url"]]]],["badges","A",2,["","o",[["itemId","s",64],["iconUrl","s",2048,"url"]]]],["displayTag","s",8],["avatarUrl","S",2048,"url"],["boost","O",[["multipliers","a",8,["","o",[["currencyId","s",64],te,["multiplierBp","i",1e4,1e6]]]],["expiresInDays","I",1,7]]],["signInAvailable","B"]],12);d("gem.pause",[["paused","b"]],120);d("gem.prefs",[["reducedMotion","b"],["muted","b"],["showFrameStats","B"],["music","B"],["sounds","B"],["locale","s",35,"bcp47"],["recommended","O",[["volume","i",0,100],["batterySaver","b"],["keyboardLayout","e",["wasd","arrows"]],["gamepadConfirm","e",["south","east"]],["vibration","b"],["lookSensitivity","I",20,400],["gamepadLookSensitivity","I",20,400],["invertLookY","B"],["touchMovement","S",16,"code"],["graphicsQuality","S",16,"code"],["graphicsQualityAuto","B"],["colorHelp","B"],["textScale","I",100,200]]]],60);d("gem.cohorts",[["cohorts","a",8,["","o",[["experimentId","s",36],["variantKey","s",32]]]]],12,"cohorts");d("gem.earnAllowanceChanged",[],30,"earnAllowance");d("gem.inventoryChanged",[],30,"inventoryChanged");d("gem.viewport",[["insets","o",[["top","i",0,4096],["right","i",0,4096],["bottom","i",0,4096],["left","i",0,4096]]],["reserved","A",4,["","o",[["x","i",0,16384],["y","i",0,16384],["width","i",0,16384],["height","i",0,16384],["control","O",[["x","i",0,16384],["y","i",0,16384],["width","i",0,16384],["height","i",0,16384]]]]]]],60,"viewport");d("gem.deviceSettings",[["settings","J"]],12,"deviceSettings");d("gem.willTerminate",[["reason","e",["grant_expiring","session_ended","navigating","kill_switch"]],["graceMs","i",0,6e4]],10);d("gem.roomEvent",[["event","e",["updated","match_started","match_ready","match_failed","version_stale"]],["roomId","s",64],["matchId","S",64],["version","I",0,2147483647],["reason","S",64],["platform","E",["universal","mobile"]]],120,"rooms");d("gem.socketState",[["connected","b"],["reason","E",["connecting","dropped","watchdog","terminal"]]],12,"socketState");d("gem.seatedEvent",[["roomId","s",64]],60,"partySeating");d("gem.autoStartFailedEvent",[["roomId","s",64],["code","s",64]],30,"autoStartFailure");d("gem.ticketEvent",[["event","e",["queued","matched","expired","cancelled","failed"]],["ticketId","s",64],["partySize","I",1,64],["queuedAt","I",0,4102444800],["expiresAt","I",0,4102444800],["reason","S",64],["roomId","S",64]],60,"ticketEvents");d("gem.signalEvent",[["event","e",["hello","offer","answer","ice","peerJoined","peerLeft","migration","matchEnded","error"]],ne,["data","j"]],1800,"p2p");d("gem.relayDraining",[ne,["clusterTargetId","s",64],["relayIps","a",16,ee],["drainingSince","s",40],["deadline","s",40]],60,"p2pRelayDrain");d("gem.dedicatedEvent",[["event","e",["answer","error","matchEnded"]],ne,["sdp","S",10240],["sessionId","S",64],["code","S",64],["message","S",256],["reason","S",64]],120,"dedicated");d("gem.partyResult",[m,["partyId","S",64]],30,"parties");d("gem.clusterTarget",[["clusterTargetId","s",64],["clusterTargetIds","A",3,ee]],12,"clusterTarget");d("gem.graphicsRoutes",[["entries","a",32,["","o",[["gpu","s",32],["route","S",32],["host","S",32]]]]],12,"graphicsRoutes");d("gem.devtools",[["action","e",["open","close","toggle"]]],30,"devtools");d("gem.screenshot",[["maxWidth","i",1,7680],["maxHeight","i",1,4320]],6,"screenshot");d("gem.screenshot.denied",[["reason","e",["not_wired","no_source","unsupported","unreadable","tainted","too_large","busy","declined","failed"]],["retryable","b"]],12,"screenshot");d("gem.requestContext",[],12,"feedbackContext");d("gem.gameContext",[["context","j"]],12,"feedbackContext");d("gem.signal",[["signal","e",["join","offer","answer","ice","migrationAck","hostUnreachable"]],ne,["data","j"]],1200,"p2p");d("gem.dedicatedOffer",[ne,["sdp","s",10240],["intent","e",["initial","ice_restart","full_reconnect"]]],60,"dedicated");d("gem.partyRequest",[m],12,"parties");d("gem.ready",[],5);d("gem.engaged",[],5,"engagement");d("gem.progress",[["loaded","i",0,1099511627776],["total","i",0,1099511627776]],600);d("gem.requestToken",[m,["reason","e",["initial","expiring","rejected"]]],12);d("gem.requestLocalServer",[m,["gameRoomId","s",64]],6,"localHost");d("gem.localServer",[m,["serverId","S",128]],30,"localHost");d("gem.localServer.denied",[m,["retryable","b"],["reason","e",["refused","unavailable"]]],30,"localHost");d("gem.exit",[["reason","S",64]],5);d("gem.openExternal",[["url","s",2048,"url"]],6);d("gem.requestFullscreen",[["enabled","b"]],30);d("gem.audioState",[["muted","b"]],120,"audioState");d("gem.rewardCue",[te,["cueId","S",64],["expected","I",1,1e9],["origin","O",[["x","i",0,16384],["y","i",0,16384]]]],60,"rewardCue");d("gem.rewardPresentation",[["mode","e",["arcade","game"]]],12,"rewardPresentation");d("gem.walletDisplay",[["visible","b"],["currencies","A",8,ee],["score","B"]],60,"walletDisplay");d("gem.controlsDeclare",[["uses","o",[["lookSensitivity","S",16,"code"],["gamepadLookSensitivity","S",16,"code"],["invertLookY","S",16,"code"],["touchMovement","S",16,"code"]]]],60,"controls");d("gem.menu",[["settings","b"],["items","A",4,["","o",[["id","s",32],["label","s",40]]]]],60,"gameMenu");d("gem.menuSelect",[["entry","e",["settings","item"]],["id","S",32]],60,"gameMenu");d("gem.gemCorner",[["corner","e",["top-left","top-right","bottom-left","bottom-right"]]],12,"gemCorner");d("gem.menuStyle",[["ground","S",7],["accent","S",7]],12,"menuStyle");d("gem.settingsDeclare",[["settings","a",8,["","o",[["id","s",40],["label","s",40],["kind","e",["toggle","slider","choice"]],["value","u",40,-1e6,1e6],["quick","B"],["icon","S",16,"code"],["min","N",-1e6,1e6],["max","N",-1e6,1e6],["step","N",0,1e6],["options","A",4,["","o",[["id","s",40],["label","s",40]]]]]]]],120,"gameSettings");d("gem.settingChange",[["id","s",40],["value","u",40,-1e6,1e6]],120,"gameSettings");d("gem.requestShare",[["requestId","S",64],["params","a",16,["","o",[["key","s",64],["value","s",512]]]]],6,"share");d("gem.shareResult",[m,["outcome","e",["accepted","refused"]],I,["key","S",64]],30,"shareResult");d("gem.requestAvatarPicker",[["tab","E",["2d","3d"]]],6,"avatarPicker");d("gem.vibrate",[["pattern","a",99,["","i",0,1e4]]],600,"vibration");d("gem.frameStats",[["fps","i",0,1e3],["frameTimeP95Ms","i",0,1e5],["counts","E",["pageFrames","gameDraws"]]],120,"frameStats");d("gem.portraitBorders",[["name","S",40]],60,"portraitBorders");d("gem.error",[["code","e",["version_mismatch","handshake_timeout","handshake_duplicate","invalid_message","rate_limited","token_unavailable","unsupported","unsupported_capability","internal"]],["message","S",256]],60);d("gem.requestPurchase",[m,["purchaseKind","e",["item","recipe"]],["targetId","s",64],te,["quantity","I",1,10],["catalogId","S",64],["origin","E",["sheet","direct_refused"]]],12,"purchases");d("gem.requestAvatar3dPurchase",[m,["itemId","s",64],te],12,"avatar3dPurchases");d("gem.purchaseResult",[m,T,I,["purchaseId","S",64]],30,"purchases");d("gem.purchaseSettled",[["purchaseKind","e",["item","recipe"]],["targetId","s",64],["catalogId","S",64],["quantity","i",1,10],["purchaseId","S",64]],30,"purchaseSettled");d("gem.deviceSettings.put",[m,["settings","j"]],30,"deviceSettings");d("gem.deviceSettings.result",[m,["outcome","e",["stored","too_large","unavailable"]]],30,"deviceSettings");d("gem.requestAd",[m,["placement","s",32],["format","e",["interstitial","rewarded"]]],6,"ads");d("gem.adResult",[m,["outcome","e",["shown","no_ad","unavailable"]]],12,"ads");d("gem.signalMoment",[m,["kind","e",["level_end","run_end","match_end","menu_return","other_break"]],["placement","S",32],["wall","B"]],6,"moments");d("gem.momentResult",[m,["shown","b"]],12,"moments");d("gem.requestSignIn",[m],6,"signIn");d("gem.signInResult",[m,["outcome","e",["signed_in","declined","unavailable"]]],12,"signIn");d("gem.requestFriendChallenge",[m,["playerId","s",64],["template","s",64],["params","J"]],12,"friendChallenges");d("gem.friendChallengeResult",[m,["outcome","e",["accepted","cancelled","refused","unavailable","rate_limited","invalid"]],["code","S",64],["param","S",64],["channel","S",16],["configVersion","I",0,9007199254740991],["retryAfterMs","I",0,864e5],["detail","S",300]],30,"friendChallenges");d("gem.requestPlayInvite",[m,["params","A",16,["","o",[["key","s",64],["value","s",512]]]]],6,"playInvites");d("gem.playInviteResult",[m,["shown","b"]],12,"playInvites");d("gem.inviteParams",[["params","A",16,["","o",[["key","s",64],["value","s",512]]]]],30,"playInvites");d("gem.requestAppLinks",[m],12,"appLinks");d("gem.appLinksResult",[m,T,I,["arcade","O",[["store","e",["app_store","google_play"]]]],["game","O",[["store","e",["app_store","google_play"]]]]],30,"appLinks");d("gem.requestOpenApp",[m,["target","e",["arcade","game"]]],6,"appLinks");d("gem.openAppResult",[m,T,I],30,"appLinks");d("gem.requestOpenGame",[m,["ref","s",40,"gameRef"]],6,"openGame");d("gem.openGameResult",[m,T,I],30,"openGame");d("gem.requestSimilarGames",[m,["limit","I",1,24]],12,"openGame");d("gem.similarGamesResult",[m,T,I,["games","a",24,["","o",[["gameId","s",36,"gameRef"],["slug","S",40,"gameRef"],["iconUrl","s",1024,"url"],["capsuleLandscapeUrl","s",1024,"url"],["capsulePortraitUrl","s",1024,"url"],["previewVideoUrl","S",1024,"url"],["previewPosterUrl","S",1024,"url"],["untrusted","o",[["name","s",64],["tagline","s",64],["shortDescription","s",320],["tags","a",10,["","s",32]]]]]]]],30,"openGame",524288);d("gem.requestStoreOffers",[m],12,"store");d("gem.storeOffersResult",[m,T,I,["offers","a",32,["","o",[["skuId","s",64],["kind","e",["gem_pack","boost","pass","bundle"]],["name","s",80],["description","s",400],["imageUrl","S",1024,"url"],["priceDisplay","s",32],["grant","O",[["currency","s",32],["amount","i",1,1e9]]],["pass","S",64],["earnMultipliers","A",8,["","o",[te,["multiplierBp","i",1e4,1e6]]]]]]]],30,"store",262144);d("gem.requestStorePurchase",[m,["skuId","s",64]],12,"store");d("gem.storePurchaseResult",[m,T,I],30,"store");d("gem.storePurchaseSettled",[["skuId","s",64]],30,"store");d("gem.requestStoreOfferSheet",[m,["skuId","s",64]],12,"storeOfferSheet");d("gem.storeOfferSheetResult",[m,T,I],30,"storeOfferSheet");var C=Object.freeze(ze);L.__malformed=60;Object.freeze(L);Object.freeze(Ce);var Nt=/^[a-z0-9][a-z0-9_-]{0,39}$/;function jt(n){return typeof n=="string"&&Nt.test(n)}i(jt,"isBorderName");function Oe(n){if(n===null||jt(n))return n;let t=typeof n=="string"?JSON.stringify(n.length>48?`${n.slice(0,48)}\u2026`:n):`a value of type ${typeof n}`;console.warn(`[gem] showPortraitBorders: ${t} is not a border name, so nothing was sent. A name is 1 to 40 of a-z, 0-9, _ and -, starting with a letter or digit, exactly as your store page declares it under media.portrait_borders. Pass null for no art.`);}i(Oe,"checkBorderName");function Xe(n){return typeof n=="string"&&/^[\w-]{1,64}$/.test(n)}i(Xe,"isCueId");var O={now:i(()=>Date.now(),"now"),setTimeout:i((n,t)=>setTimeout(n,t),"setTimeout"),clearTimeout:i(n=>clearTimeout(n),"clearTimeout")},re=class{constructor(t=O){this.clock=t;}clock;static{i(this,"RateLimiter");}tokens=new Map;lastRefill=new Map;allow(t){let e=L[t];if(e===void 0)return false;let r=this.clock.now(),o=this.lastRefill.get(t)??r,a=this.tokens.get(t)??e,s=Math.max(0,r-o),l=Math.min(e,a+s*e/6e4);return l<1?(this.tokens.set(t,l),this.lastRefill.set(t,r),false):(this.tokens.set(t,l-1),this.lastRefill.set(t,r),true)}};function oe(n,t){return n.toLowerCase()===t.toLowerCase()}i(oe,"originsEqual");function q(n){let t=n.toLowerCase();return t==="localhost"||t.endsWith(".localhost")||t==="[::1]"||t==="::1"?true:/^127(\.\d{1,3}){3}$/.test(t)}i(q,"isLoopbackHostname");function Me(n,t){if(typeof t!="string"||t==="")return false;let e,r;try{e=new URL(n),r=new URL(t);}catch{return false}return e.protocol!=="http:"&&e.protocol!=="https:"||r.protocol!=="http:"&&r.protocol!=="https:"||e.username!==""||e.password!==""||e.pathname!=="/"||e.search!==""||e.hash!==""||e.port===""?false:q(e.hostname)&&q(r.hostname)}i(Me,"isLoopbackDevHostEmbedder");function Ye(n,t){if(n.min>n.max||t.min>t.max)return null;let e=Math.min(n.max,t.max),r=Math.max(n.min,t.min);return e>=r?e:null}i(Ye,"negotiateVersion");var et="[gem: use gem.text.toPlain()]",Qe=new Set,Ze;function Bt(n){try{if(Qe.has(n))return;let t=globalThis.location;if(Ze??=!!t?.port&&q(t.hostname??""),!Ze)return;Qe.add(n),console.warn(`[gem] ${n} is PlayerText, turned into a string: players see ${et}. Render text.toPlain(value) (text is in @indiegems/gem-web-sdk/sdk).
|
|
2
|
+
${new Error().stack}`);}catch{}}i(Bt,"warnStringified");var he=class{static{i(this,"PlayerText");}#e;#t;constructor(t,e="player text"){this.#e=t,this.#t=e,Object.freeze(this);}toString(){return Bt(this.#t),et}toJSON(){return this.toString()}get[Symbol.toStringTag](){return "PlayerText"}static unwrap(t){return t.#e}};function $(n,t){return new he(n,t)}i($,"asPlayerText");function tt(n){return Math.min(16384,Math.max(0,Math.round(Number.isFinite(n)?n:0)))}i(tt,"cuePoint");function ot(n,t){if(n!==true)return {visible:false};let e=[],r=false;for(let o of Array.isArray(t)?t:[])o==="score"?r=true:typeof o=="string"&&o.length>0&&o.length<=64&&!e.includes(o)&&e.push(o);return !r&&e.length===0?{visible:true}:{visible:true,currencies:e.slice(0,8),score:r}}i(ot,"walletState");function it(n){let t=[];for(let e of Array.isArray(n)?n:[]){if(t.length===4)break;if(typeof e!="object"||e===null)continue;let{id:r,label:o}=e;typeof r!="string"||r.length===0||r.length>32||typeof o!="string"||o.trim().length===0||t.some(a=>a.id===r)||t.push({id:r,label:o.trim().slice(0,40)});}return t.length===0?{settings:false}:{settings:false,items:t}}i(it,"menuState");function nt(n,t){return Math.min(t,Math.max(0,Math.round(Number.isFinite(n)?n:0)))}i(nt,"clampStat");function Ut(n){let t=n.untrusted,e=$(t.displayName,"gem.player.displayName"),r=t.displayTag||t.displayHandle,o=r?$(r,"gem.player.displayTag"):void 0,a=n.discriminator||n.displayTag,s=a&&/^[0-9]{1,8}$/.test(a)?a:void 0,l=n.avatarIconUrl||n.avatarUrl,c=/^[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$/i,h=n.avatarIconId,b=n.avatar3dId,v={displayName:e,displayHandle:o??e,badges:(n.badges??[]).map(w=>({itemId:w.itemId,iconUrl:w.iconUrl}))};n.playerId&&(v.playerId=n.playerId),s&&(v.discriminator=s),o&&(v.displayTag=o),l&&(v.avatarIconUrl=v.avatarUrl=l),h&&c.test(h)&&(v.avatarIconId=h),b&&c.test(b)&&(v.avatar3dId=b),n.avatar3dModelUrl&&(v.avatar3dModelUrl=n.avatar3dModelUrl),n.avatar3dIconUrl&&(v.avatar3dIconUrl=n.avatar3dIconUrl),n.avatars3d&&(v.avatars3d=n.avatars3d.filter(w=>c.test(w.id)&&/^[a-z][a-z0-9_]*$/.test(w.family)).filter((w,xe,Pe)=>Pe.findIndex(Ie=>Ie.family==w.family)==xe).map(({family:w,id:xe,modelUrl:Pe,iconUrl:Ie})=>({family:w,id:xe,modelUrl:Pe,iconUrl:Ie})));let A=n.boost,Q=(A?.multipliers??[]).map(w=>({currencyId:w.currencyId,currencySlug:w.currencySlug,multiplier:w.multiplierBp/1e4})),Ae=Q.some(w=>w.multiplier>1);return v.boost=Ae&&A?.expiresInDays?{active:Ae,multipliers:Q,expiresInDays:A.expiresInDays}:{active:Ae,multipliers:Q},v}i(Ut,"playerFromWire");var Wt=1e4,Ht=["appLinks","openGame","store","storeOfferSheet"],Vt=["token","pause","menuSelect","settingChange","prefs","cohorts","viewport","willTerminate","localServer","roomEvent","seatedEvent","autoStartFailedEvent","ticketEvent","socketState","signalEvent","relayDraining","dedicatedEvent","clusterTarget","graphicsRoutes","partyResult","purchaseResult","purchaseSettled","inventoryChanged","shareResult","deviceSettings","adResult","momentResult","signInResult","friendChallengeResult","playInviteResult"],Kt=new WeakMap;var N=class{constructor(t){this.options=t;if(t.expectedSource===void 0||t.expectedSource===null)throw new Error("GemFrame: expectedSource is required and must not be null \u2014 pass window.parent. The handshake transfers the MessagePort, and origin alone does not say WHICH document on that origin sent it.");this.expectedSource=t.expectedSource,this.clock=t.clock??O,this.inbound=new re(this.clock),this.outbound=new re(this.clock);}options;static{i(this,"GemFrame");}state="waiting";port=null;version=null;peerCapabilities=[];unbidden=false;engagedSent=false;readyPending=false;readySent=false;settleWaiters=[];clock;inbound;outbound;expectedSource;get currentState(){return this.state}get parentCapabilities(){return this.peerCapabilities}parentSupports(t){return this.peerCapabilities.includes(t)}get protocolVersion(){return this.version}get unbiddenLaunch(){return this.unbidden}handleWindowMessage(t){if(t.source!==this.expectedSource)return this.refuse(t,"handshake did not come from the expected embedder window"),false;if(!(this.options.allowedEmbedders.some(l=>oe(l,t.origin))||Me(t.origin,this.options.selfOrigin)||(Kt.get(this)??[]).some(l=>oe(l,t.origin))))return this.refuse(t,`embedder '${t.origin}' is not in allowedEmbedders`),this.state==="waiting"&&this.transition("refused"),false;if(this.state==="connected")return this.fail("handshake_duplicate","this document already has a port"),false;if(this.state!=="waiting")return false;let r=C["gem.handshake"]?.(t.data);if(r)return this.refuse(t,`malformed handshake: ${r}`),false;let o=t.data;if(!oe(o.embedder,t.origin))return this.refuse(t,`handshake claims embedder '${o.embedder}' but arrived from '${t.origin}'`),false;let a=t.ports?.[0];if(!a)return this.refuse(t,"handshake transferred no MessagePort"),false;let s=Ye(x,o.versions);return s===null?(a.onmessage=null,a.postMessage({gem:u,type:"gem.error",code:"version_mismatch",message:`frame supports v${x.min}..v${x.max}`}),a.close(),this.transition("refused"),this.options.onError?.("version_mismatch"),false):(this.port=a,this.version=s,a.onmessage=l=>this.receive(l.data,l.ports??[]),a.start?.(),this.peerCapabilities=o.capabilities??[],this.unbidden=o.unbidden===true,a.postMessage({gem:u,type:"gem.handshake.accept",v:s,...this.options.capabilities===void 0?{}:{capabilities:[...this.options.capabilities]}}),this.transition("connected"),this.readyPending&&(this.readyPending=false,this.ready()),true)}ready(){if(this.state!=="connected"||!this.port){this.readyPending=true;return}if(this.readySent){this.options.onDropped?.("internal","gem.ready was already sent; duplicate suppressed");return}this.readySent=true,this.send({gem:u,type:"gem.ready"});}settled(t=Wt){return this.state!=="waiting"?Promise.resolve(this.state):new Promise(e=>{let r=i(a=>{this.clock.clearTimeout(o),e(a);},"waiter"),o=this.clock.setTimeout(()=>{let a=this.settleWaiters.indexOf(r);a>=0&&this.settleWaiters.splice(a,1),e(this.state);},t);this.settleWaiters.push(r);})}progress(t,e){this.send({gem:u,type:"gem.progress",loaded:t,total:e});}requestToken(t,e){this.send({gem:u,type:"gem.requestToken",requestId:t,reason:e});}requestLocalServer(t,e){return this.parentSupports("localHost")?(this.send({gem:u,type:"gem.requestLocalServer",requestId:t,gameRoomId:e}),true):false}exit(t){this.send(t===void 0?{gem:u,type:"gem.exit"}:{gem:u,type:"gem.exit",reason:t});}openExternal(t){this.send({gem:u,type:"gem.openExternal",url:t});}requestFullscreen(t){this.send({gem:u,type:"gem.requestFullscreen",enabled:t});}reportMuted(t){return this.gated("audioState")?(this.send({gem:u,type:"gem.audioState",muted:t}),true):false}reportEngaged(){return this.engagedSent||!this.gated("engagement")||!this.send({gem:u,type:"gem.engaged"})?false:(this.engagedSent=true,true)}cueReward(t){if(!this.gated("rewardCue"))return false;let e={gem:u,type:"gem.rewardCue",currencySlug:String(t.currencySlug).slice(0,32)};return Xe(t.cueId)&&(e.cueId=t.cueId),t.expected!==void 0&&Number.isFinite(t.expected)&&t.expected>=1&&(e.expected=Math.min(1e9,Math.round(t.expected))),t.origin&&(e.origin={x:tt(t.origin.x),y:tt(t.origin.y)}),this.send(e),true}showPortraitBorders(t){let e=Oe(t);return e===void 0||!this.gated("portraitBorders")?false:this.send(e===null?{gem:u,type:"gem.portraitBorders"}:{gem:u,type:"gem.portraitBorders",name:e})}setWalletVisible(t,e){return this.gated("walletDisplay")?this.send({gem:u,type:"gem.walletDisplay",...ot(t,e?.only)}):false}setMenu(t){return this.gated("gameMenu")?this.send({gem:u,type:"gem.menu",...it(t)}):false}declareSettings(t){return this.gated("gameSettings")?this.send({gem:u,type:"gem.settingsDeclare",settings:t}):false}setGemCorner(t){return this.gated("gemCorner")?this.send({gem:u,type:"gem.gemCorner",corner:t}):false}setRewardPresentation(t){return this.gated("rewardPresentation")?this.send({gem:u,type:"gem.rewardPresentation",mode:t}):false}setMenuStyle(t){return this.gated("menuStyle")?this.send(t===null?{gem:u,type:"gem.menuStyle"}:{gem:u,type:"gem.menuStyle",ground:t.ground,accent:t.accent}):false}requestShare(t,e){return this.gated("share")?this.send({gem:u,type:"gem.requestShare",...e===void 0?{}:{requestId:e},params:t.map(({key:r,value:o})=>({key:r,value:o}))}):false}requestAvatarPicker(t){return this.gated("avatarPicker")?this.send({gem:u,type:"gem.requestAvatarPicker",...t==="2d"||t==="3d"?{tab:t}:{}}):false}requestVibrate(t){return this.gated("vibration")?this.send({gem:u,type:"gem.vibrate",pattern:[...t]}):false}reportFrameStats(t){return this.gated("frameStats")?(this.send({gem:u,type:"gem.frameStats",fps:nt(t.fps,1e3),frameTimeP95Ms:nt(t.frameTimeP95Ms,1e5),counts:t.counts==="gameDraws"?"gameDraws":"pageFrames"}),true):false}sendGameContext(t){return this.state!=="connected"?false:(this.send({gem:u,type:"gem.gameContext",context:t}),true)}signal(t,e,r){this.send({gem:u,type:"gem.signal",signal:t,matchId:e,data:r});}dedicatedOffer(t,e,r){this.send({gem:u,type:"gem.dedicatedOffer",matchId:t,sdp:e,intent:r});}requestParty(t){return this.send({gem:u,type:"gem.partyRequest",requestId:t})}requestPurchase(t,e,r,o,a,s,l){return this.send({gem:u,type:"gem.requestPurchase",requestId:t,purchaseKind:e,targetId:r,currencySlug:o,...a===void 0?{}:{quantity:a},...s===void 0?{}:{catalogId:s},...l===void 0?{}:{origin:l}})}requestAvatar3dPurchase(t,e,r){return this.parentSupports("avatar3dPurchases")?this.send({gem:u,type:"gem.requestAvatar3dPurchase",requestId:t,itemId:e,currencySlug:r}):false}putDeviceSettings(t,e){return this.send({gem:u,type:"gem.deviceSettings.put",requestId:t,settings:e})}requestAd(t,e,r){return this.send({gem:u,type:"gem.requestAd",requestId:t,placement:e,format:r})}signalMoment(t,e,r,o){return this.send({gem:u,type:"gem.signalMoment",requestId:t,kind:e,...r===void 0?{}:{placement:r},...o===true?{wall:true}:{}})}readyCalled(){return this.readySent||this.readyPending}requestSignIn(t){return this.send({gem:u,type:"gem.requestSignIn",requestId:t})}requestFriendChallenge(t,e,r,o){return this.send({gem:u,type:"gem.requestFriendChallenge",requestId:t,playerId:e,template:r,...o===void 0?{}:{params:o}})}requestArcade(t){return this.gated(ye(t.type))&&this.send({gem:u,...t})}close(){this.port&&(this.port.onmessage=null,this.port.close(),this.port=null),this.state!=="refused"&&this.transition("closed");}transition(t){if(this.state!==t){this.state=t;for(let e of this.settleWaiters.splice(0))e(t);this.options.onStateChange?.(t);}}refuseMalformed(t,e){if(!this.inbound.allow(fe)){this.options.onDropped?.(t,e);return}this.fail(t,e);}dropUnknownType(t){this.inbound.allow(fe)&&this.options.onDropped?.("unsupported",`ignored unknown message type '${t}'`);}refuse(t,e){t.ports?.[0]?.close?.(),this.inbound.allow(fe)&&this.options.onRefused?.(e);}fail(t,e){this.options.onError?.(t,e),this.state==="connected"&&this.port&&this.send({gem:u,type:"gem.error",code:t,...e===void 0?{}:{message:e}});}gated(t){return this.state==="connected"&&this.parentSupports(t)}send(t){if(this.state!=="connected"||!this.port)return this.options.onDropped?.("internal",`dropped ${String(t.type)} before connect`),false;let e=t.type;if(!this.outbound.allow(e))return this.options.onError?.("rate_limited",`frame exceeded its own limit for ${e}`),false;let r=C[e]?.(t);return r?(this.options.onError?.("internal",`refusing to send malformed ${e}: ${r}`),false):(this.port.postMessage(this.version===null?t:{...t,v:this.version}),true)}receive(t,e=[]){let r=false;try{r=this.dispatchInbound(t,e);}finally{r||rt(e);}}dispatchInbound(t,e){if(this.state!=="connected")return false;let r=typeof t=="object"&&t!==null?t.type:void 0;if(typeof r!="string")return this.refuseMalformed("invalid_message","message has no type"),false;let o=C[r];if(!o)return this.dropUnknownType(r),false;if(!this.inbound.allow(r))return this.fail("rate_limited",`parent exceeded the limit for ${r}`),false;let a=ye(r);if(a!==void 0&&!this.options.capabilities?.includes(a))return this.fail("unsupported_capability",`${r} requires the '${a}' capability, which this frame did not advertise`),false;let s=o(t);if(s)return this.fail("invalid_message",`${r}: ${s}`),false;let l=t;if(l.v!==this.version)return this.fail("invalid_message",`${r}: expected v ${String(this.version)}`),false;switch(r){case "gem.earnAllowanceChanged":this.options.onEarnAllowanceChanged?.();break;case "gem.devtools":this.options.onDevtools?.(l.action);break;case "gem.requestContext":this.options.onRequestContext?.();break;case "gem.screenshot":{let c=e[0];if(!c){this.options.onError?.("invalid_message","gem.screenshot transferred no MessagePort");break}let h={maxWidth:l.maxWidth,maxHeight:l.maxHeight};if(!this.options.onScreenshot){c.close(),this.screenshotDenied("not_wired",false);break}return rt(e.slice(1)),this.options.onScreenshot(h,c,(b,v)=>this.screenshotDenied(b,v)),true}case "gem.token.denied":this.options.onTokenDenied?.(l);break;case "gem.localServer.denied":this.options.onLocalServerDenied?.(l);break;case "gem.player":{let c=l;this.options.onPlayer?.(Ut(c),c.signInAvailable);break}case "gem.deviceSettings.result":this.options.onDeviceSettingsResult?.(l);break;case "gem.error":this.options.onError?.(l.code,l.message);break;default:if(Vt.includes(r.slice(4))){this.options[`on${r[4].toUpperCase()}${r.slice(5)}`]?.(l);break}if(Ht.includes(ye(r))){this.options.onArcadeAnswer?.(l);break}this.fail("unsupported",`no frame handler for '${r}'`);}return false}screenshotDenied(t,e){this.send({gem:u,type:"gem.screenshot.denied",reason:t,retryable:e});}};function rt(n){for(let t of n)try{t?.close?.();}catch{}}i(rt,"closeAll");globalThis.__gemWebSdkVersion="[gem-web-sdk:sdkVersion:24.1.0]";var at=["if-match","if-none-match"];var Yt={unknown_currency:1,currency_not_in_game_payable:1,not_purchasable:1,no_price_in_currency:1,busy:1,cancelled:1,insufficient_funds:1,price_mismatch:1,config_version_stale:1,platform_currency_unavailable:1,not_available:1,recipe_locked:1,already_owned:1,guest_upgrade_required:1,recipe_on_cooldown:1,recipe_max_uses_exceeded:1,item_max_stack_exceeded:1,recipe_costs_unmet:1,quantity_invalid:1,error:1};new Set(Object.keys(Yt));new Set(at);new TextEncoder;var dt=["explicit","stale_cleanup","game_room_closed","replaced","migration_failed","host_left","insufficient_players","all_left","expired","migration_rate_exceeded","game_room_gone","failed","idle","reclaimed"];new Set(dt);new TextEncoder;new TextDecoder;new TextEncoder;var Gn=8;(class{constructor(t=Gn){this.perMinute=t;}perMinute;static{i(this,"CredentialPacing");}sent=[];delayUntilAllowed(t){return this.prune(t),this.sent.length<this.perMinute?0:Math.max(0,(this.sent[0]??t)+6e4-t)}record(t){this.prune(t),this.sent.push(t);}prune(t){let e=0;for(let r of this.sent)r<=t&&t-r<6e4&&(this.sent[e++]=r);this.sent.length=e;}});function g(n){return t=>typeof t=="string"&&t.length>0&&t.length<=n?t:void 0}i(g,"text");function G(n,t){return e=>typeof e=="number"&&Number.isInteger(e)&&e>=n&&e<=t?e:void 0}i(G,"integer");function j(...n){return t=>typeof t=="string"&&n.includes(t)?t:void 0}i(j,"oneOf");function ut(n){return typeof n=="boolean"?n:void 0}i(ut,"flag");var S=g(64),le=G(0,2147483647);function be(n){if(!Array.isArray(n))return;let t=[];for(let e of n){if(t.length>=64)break;let r=S(e);typeof r=="string"&&t.push(r);}return t}i(be,"ids");function f(n,t,e){return {to:n,from:t,read:e,required:true}}i(f,"required");function y(n,t,e){return {to:n,from:t,read:e,required:false}}i(y,"optional");var Fn=i(n=>{if(typeof n!="object"||n===null||Array.isArray(n))return;let t=n,e=j("disconnected","failed","closed")(t.iceState),r=G(0,4102444800)(t.lastSeenAt);if(!(e===void 0&&r===void 0))return {...e===void 0?{}:{ice_state:e},...r===void 0?{}:{last_seen_timestamp:r}}},"evidence");Object.freeze(Object.assign(Object.create(null),{join:{type:"p2p_join",gameScoped:true,args:[]},offer:{type:"p2p_offer",gameScoped:false,args:[f("sdp","sdp",g(10240)),f("signaling_intent","intent",j("initial","ice_restart","full_reconnect")),y("sdp_hash","sdpHash",g(128))]},answer:{type:"p2p_answer",gameScoped:false,args:[f("peer_id","peerId",S),f("sdp","sdp",g(10240)),f("assigned_peer_id","assignedPeerId",G(0,4294967295)),y("sdp_hash","sdpHash",g(128))]},ice:{type:"p2p_ice_candidate",gameScoped:false,args:[f("peer_id","peerId",S),f("candidate","candidate",g(512)),y("sdp_mid","sdpMid",g(64)),y("sdp_mline_index","sdpMLineIndex",G(0,255))]},migrationAck:{type:"p2p_migration_ack",gameScoped:false,args:[f("host_epoch","hostEpoch",le)]},hostUnreachable:{type:"p2p_report_host_unreachable",gameScoped:false,args:[f("host_epoch","hostEpoch",le),y("evidence","evidence",Fn)]}}));Object.freeze(Object.assign(Object.create(null),{p2p_hello:"hello",p2p_sdp_offer:"offer",p2p_sdp_answer:"answer",p2p_ice_candidate:"ice",p2p_peer_joined:"peerJoined",p2p_peer_left:"peerLeft",p2p_host_migration:"migration",p2p_match_ended:"matchEnded",p2p_error:"error",p2p_relay_draining:"relayDraining"}));var qn=16,Nn=i(n=>be(n)?.slice(0,qn),"relayIps");Object.freeze(Object.assign(Object.create(null),{hello:[f("playerId","player_id",S),f("hostPlayerId","host_player_id",S),f("hostEpoch","host_epoch",le),y("members","members",be)],offer:[f("fromPlayerId","from_player_id",S),f("sdp","sdp",g(10240)),y("sessionId","signaling_session_id",g(128)),y("sdpHash","sdp_hash",g(128)),y("iceUfrag","ice_ufrag",g(64)),y("intent","signaling_intent",j("initial","ice_restart","full_reconnect"))],answer:[f("fromPlayerId","from_player_id",S),f("sdp","sdp",g(10240)),y("sessionId","signaling_session_id",g(128)),y("sdpHash","sdp_hash",g(128)),y("iceUfrag","ice_ufrag",g(64)),y("assignedPeerId","assigned_peer_id",G(0,4294967295))],ice:[f("fromPlayerId","from_player_id",S),f("candidate","candidate",g(512)),y("sdpMid","sdp_mid",g(64)),y("sdpMLineIndex","sdp_m_line_index",G(0,255)),y("sdpMLineIndexAlt","sdp_mline_index",G(0,255))],peerJoined:[f("playerId","player_id",S)],peerLeft:[f("playerId","player_id",S),y("reason","reason",g(64))],migration:[f("hostPlayerId","new_host_player_id",S),f("hostEpoch","host_epoch",le),y("reason","reason",g(64)),y("kickedPlayerIds","kicked_player_ids",be),y("migrationComplete","migration_complete",ut)],matchEnded:[y("reason","reason",g(64))],error:[y("code","code",g(64)),y("message","message",g(256))],relayDraining:[f("clusterTargetId","cluster_target_id",S),f("relayIps","relay_ips",Nn),f("drainingSince","draining_since",g(40)),f("deadline","deadline",g(40))]}));Object.freeze(Object.assign(Object.create(null),{party_invite_received:"inviteReceived",party_member_joined:"memberJoined",party_member_left:"memberLeft",party_member_kicked:"memberKicked",party_leader_transferred:"leaderTransferred",party_updated:"updated",party_disbanded:"disbanded",party_game_room_joined:"gameRoomJoined",party_game_room_left:"gameRoomLeft"}));function Vn(n,t){return Object.freeze({route:n,listed:t})}i(Vn,"answer");Object.freeze(Object.assign(Object.create(null),{webrtc_sdp_answer:"answer",webrtc_error:"error",match_ended:"matchEnded"}));Object.freeze(Object.assign(Object.create(null),{answer:[f("sdp","sdp",g(10240)),y("sessionId","session_id",g(64))],error:[y("code","code",g(64)),y("message","message",g(256))],matchEnded:[y("reason","reason",g(64))]}));var _t=Object.freeze(["https://gemarcade.com","https://developer.gemarcade.com","http://gemarcade.localhost","gemarcade://localhost","https://app.gemarcade.com"]);var Y="vip",X=class extends Error{constructor(e,r){super(`[${e}] ${r}`);this.caseId=e;this.name="ParentConformanceError";}caseId;static{i(this,"ParentConformanceError");}},Q_=10,St="https://game.conformance.invalid",kt="00000000-0000-4000-8000-0000000c0de7",Rt="release",Or="https://stranger.conformance.invalid",je=L["gem.handshake"]??1,ue=6e4,_e=class{static{i(this,"KitClock");}current=1e6;seq=0;pending=new Map;now(){return this.current}setTimeout(t,e){let r=++this.seq;return this.pending.set(r,{at:this.current+Math.max(0,e),fn:t}),r}clearTimeout(t){this.pending.delete(t);}get pendingTimers(){return this.pending.size}advance(t){let e=this.current+t;for(;;){let r;for(let o of this.pending)o[1].at<=e&&(r===void 0||o[1].at<r[1].at)&&(r=o);if(r===void 0)break;this.pending.delete(r[0]),this.current=r[1].at,r[1].fn();}this.current=e;}},At=Object.freeze({conformance:"parent-window"}),Se=class{constructor(t,e,r,o){this.caseId=t;this.options=r;let a=o??r.frameOrigin??St,s={gameId:kt,channel:Rt,frame:{postMessage:i((l,c,h)=>{this.offers.push({data:l,targetOrigin:c,transfer:h??[],at:this.clock.now()});},"postMessage")},frameOrigin:a,embedderOrigin:r.embedderOrigin,clock:this.clock,makeChannel:i(()=>{let l=new MessageChannel,c={port1:l.port1,port2:l.port2,closed:false},h=l.port1.close.bind(l.port1);return l.port1.close=()=>{c.closed=true,h();},this.count(l.port1),this.count(l.port2),this.minted.push(c),l},"makeChannel"),reportError:i((l,c)=>{this.errors.push(c===void 0?{code:l}:{code:l,message:c});},"reportError")};Object.defineProperty(s,"userActivation",{enumerable:true,get:i(()=>this.activation===void 0?void 0:{...this.activation},"get")}),this.parent=e(s);}caseId;options;static{i(this,"Rig");}clock=new _e;frameClock=new _e;offers=[];minted=[];parent;activation={isActive:false};errors=[];inFlight=0;frames=[];get frameOrigin(){return this.options.frameOrigin??St}get gameId(){return kt}get channel(){return Rt}fail(t){throw new X(this.caseId,t)}count(t){let e=t.postMessage.bind(t);t.postMessage=((...r)=>{this.inFlight+=1,e(...r);}),t.addEventListener("message",()=>{this.inFlight-=1;});}async flush(){let t=i(()=>new Promise(e=>setTimeout(e,0)),"turn");await t(),await t();for(let e=0;this.inFlight>0&&e<200;e++)await t();}advance(t,e=250){for(let r=0;r<t;r+=e){let o=Math.min(e,t-r);this.clock.advance(o),this.frameClock.advance(o);}}start(){try{this.parent.start();}catch(t){this.fail(`start() threw: ${pe(t)}`);}this.offers.length===0&&this.fail("start() posted no handshake to the frame window. The offer must be posted at start, on the window the fixture names as `frame`.");}latest(){let t=this.offers[this.offers.length-1];return t===void 0&&this.fail("no handshake has been offered"),t}transferred(t){t.transfer.length!==1&&this.fail(`an offer transferred ${t.transfer.length} objects; it must transfer exactly one MessagePort.`);let e=this.minted.find(r=>r.port2===t.transfer[0]);return e===void 0&&this.fail("an offer transferred a port the fixture did not mint. Take every offer\u2019s channel from `fixture.makeChannel`, so the kit can see which ports the parent closes."),e}frame(t){let e=t??[...new Set([..._t,...this.options.allowedEmbedders??[]])],r=[],o=new N({allowedEmbedders:e,expectedSource:At,selfOrigin:this.frameOrigin,clock:this.frameClock,onRefused:i(a=>r.push(a),"onRefused")});return o.kitRefusals=r,this.frames.push(o),o}deliver(t,e){let r=this.transferred(e);return t.handleWindowMessage({data:e.data,origin:this.options.embedderOrigin,source:At,ports:[r.port2]})}answer(t,e,r=[]){this.transferred(t).port2.postMessage({gem:u,type:"gem.handshake.accept",v:e,capabilities:[...r]});}advertised(){let t=this.latest().data.capabilities;return Array.isArray(t)?t.filter(e=>typeof e=="string"):[]}async raw(t){let e=this.latest(),r=this.transferred(e),o=[];r.port2.onmessage=l=>{l.data!==null&&typeof l.data=="object"&&o.push(l.data);};let a=Math.min(K(this).max,x.max);this.answer(e,a,t),await this.flush(),this.state()!=="connected"&&this.fail(`a hand-written frame accepted at v${a}, and the parent reports '${this.state()}'.`);let s=0;return {received:o,ask:i(async(l,c,h)=>{let b=`conformance-${++s}`;return this.activation=h==="missing"?void 0:{isActive:h==="pressed"},r.port2.postMessage({gem:u,type:l,v:a,requestId:b,...c}),await this.flush(),this.activation={isActive:false},b},"ask"),answers:i(l=>o.filter(c=>c.requestId===l),"answers")}}surfaces(t){let e=this.parent.surfacesShown;return typeof e!="number"&&this.fail(`${t}, so the kit must see what it shows: expose \`surfacesShown\` on the adapter (every checkout, card, QR code, toast or tab the parent opened for a game).`),e}offersInAnyMinute(){let t=0;for(let e=0;e<this.offers.length;e++){let r=this.offers[e].at,o=0;for(let a=e;a<this.offers.length&&this.offers[a].at<r+ue;a++)o++;t=Math.max(t,o);}return t}state(){return this.parent.state}dispose(){try{this.parent.close();}catch{}for(let t of this.frames)t.close();for(let t of this.minted)try{t.port1.close(),t.port2.close();}catch{}}};function pe(n){return n instanceof Error?n.message:String(n)}i(pe,"describe");function Mr(n){return n.kitRefusals??[]}i(Mr,"refusalsOf");function K(n){let t=n.latest().data.versions,e=t?.min,r=t?.max;return (typeof e!="number"||typeof r!="number")&&n.fail("the handshake carries no numeric `versions.min` / `versions.max`"),{min:e,max:r}}i(K,"offeredRange");async function p(n,t,e,r){let o=new Se(n,t,e);try{await r(o);}finally{o.dispose();}}i(p,"withRig");async function de(n){let t=n.frame();return n.deliver(t,n.latest())||n.fail(`a game built with this SDK refused the handshake from ${n.options.embedderOrigin}: ${Mr(t).join("; ")||"no reason recorded"}. A production origin the SDK does not carry needs an SDK that lists it; a non-production one goes in \`allowedEmbedders\` here, as a game would name it.`),await n.flush(),n.state()!=="connected"&&n.fail(`the frame accepted, but the parent reports '${n.state()}' rather than 'connected'.`),t}i(de,"connect");var P=["appLinks","openGame","store","storeOfferSheet"],It="00000000-0000-4000-8000-00000000c0de",ke="conformance-unknown-sku",Be={appLinks:{type:"gem.requestOpenApp",fields:{target:"arcade"}},openGame:{type:"gem.requestOpenGame",fields:{ref:It}},store:{type:"gem.requestStorePurchase",fields:{skuId:ke}},storeOfferSheet:{type:"gem.requestStoreOfferSheet",fields:{skuId:ke}}},Gr=["appLinks","openGame"],Lr=[["store","gem.requestStorePurchase"],["storeOfferSheet","gem.requestStoreOfferSheet"]];function ve(n,t,e,r,o){let a=t.answers(e);a.length!==1&&n.fail(`${o}: the parent sent ${a.length} answers; it owes exactly one.`);let[s]=a;(s.outcome!=="declined"||s.reason!=="busy")&&n.fail(`${o}: answered ${JSON.stringify(s.outcome)} / ${JSON.stringify(s.reason)}, not 'declined' / 'busy'.`);let l=n.surfaces(o);l!==r&&n.fail(`${o}: the parent showed ${l-r} surface(s); it must show nothing.`);}i(ve,"expectBusy");async function W(n,t,e="gem.requestStorePurchase",r="pressed",o=""){let a=await t.ask("gem.requestStoreOffers",{},"unpressed"),[s]=t.answers(a),l=s?.offers??[],c=(l.find(A=>A.pass===void 0)??l[0])?.skuId;(s?.outcome!=="ok"||typeof c!="string")&&n.fail(`the parent advertises \`store\` but listed no offer to a signed-in player (${JSON.stringify(s?.outcome)} / ${JSON.stringify(s?.reason)}). Give the adapter's store a signed-in player and at least one SKU, so the kit can open a checkout.`);let h=n.surfaces("the parent advertises `store`"),b=await t.ask(e,{skuId:c},r),v=n.surfaces("the parent advertises `store`");if(v!==h+1){let A=r==="pressed"?"a pressed":r==="unpressed"?"an unpressed":"with no userActivation API, a",Q=t.answers(b);n.fail(`${o}${A} ${e} of the listed SKU ${JSON.stringify(c)} showed ${v-h} surface(s) and was answered ${JSON.stringify(Q)}; it must open exactly one. A store purchase needs no press, and nothing backs off after the player closes a checkout: a player who wants to pay can always pay.`);}return t.answers(b).length>0&&n.fail(`${e} was answered while what it opened was still open. Answer once the player has acted, so the kit can hold it open.`),{requestId:b,skuId:c,shown:v}}i(W,"openStoreCheckout");function J(n){typeof n.parent.dismiss!="function"&&n.fail("the parent advertises `store`, so the kit must be able to close its checkout: expose `dismiss()` on the adapter (the player closing the checkout without paying)."),n.parent.dismiss();}i(J,"dismiss");function $r(n){let t={...n};return delete t.requestId,t}i($r,"listingOf");function Re(n){return (Array.isArray(n.offers)?n.offers:[]).filter(e=>e.pass===Y)}i(Re,"kitPassOffers");function xt(n){if(n.outcome!=="ok")return;let t=Re(n)[0]?.skuId;return typeof t=="string"?t:void 0}i(xt,"listedPassSku");async function F(n,t,e,r,o){await p(n,t,e,async a=>{if(typeof a.parent.storeScenario!="function")return;try{a.parent.storeScenario(r);}catch(l){a.fail(`storeScenario('${r}') threw: ${pe(l)}`);}if(a.start(),!a.advertised().includes("storeOfferSheet"))return;let s=await a.raw(P);await o(a,s);});}i(F,"withPassRig");async function z(n){let t=await n.ask("gem.requestStoreOffers",{},"unpressed");return $r(n.answers(t)[0])}i(z,"listStore");async function Pt(n,t,e){let r=await z(t),[o]=Re(r);return (r.outcome!=="ok"||typeof o?.skuId!="string")&&n.fail(`for ${e}, the store answered ${JSON.stringify(r.outcome)} / ${JSON.stringify(r.reason)} and listed no offer with pass '${Y}'. List the pass with its slug in \`pass\`, so a game can find it with gem.store.showPass.`),{skuId:o.skuId,answer:await Et(n,t,o.skuId,e)}}i(Pt,"openPassSheet");async function Et(n,t,e,r,o=""){let a=n.surfaces("the parent sells a pass"),s=await t.ask("gem.requestStoreOfferSheet",{skuId:e},"pressed"),l=n.surfaces("the parent sells a pass");return (l!==a+1||t.answers(s).length>0)&&n.fail(`for ${r}, the sheet for the pass's SKU ${JSON.stringify(e)} showed ${l-a} surface(s) and was answered ${JSON.stringify(t.answers(s))}; it must open exactly one sheet and answer once the player has acted.${o}`),J(n),await n.flush(),t.answers(s)[0]}i(Et,"openListedSheet");async function Dr(n,t,e){e===void 0&&n.fail(`the store listed no offer with pass '${Y}' to a player who holds it, and none to an adult either, so the kit has no SKU to ask a sheet for. List the pass to an adult with its slug in \`pass\`.`);let r=n.surfaces("the parent sells a pass"),o=await t.ask("gem.requestStoreOfferSheet",{skuId:e},"pressed"),a=t.answers(o),s=n.surfaces("the parent sells a pass")-r;(s!==0||a.length!==1||a[0].outcome!=="unavailable")&&n.fail(`the store listed no offer with pass '${Y}' to a player who holds it, yet a sheet for the pass's SKU ${JSON.stringify(e)} showed ${s} surface(s) and was answered ${JSON.stringify(a)}; it owes exactly one 'unavailable' and must show nothing, as for a SKU it does not list. List the pass, or refuse its sheet: never one without the other.`);}i(Dr,"expectHolderSheetRefused");var Fr="game_inventory_changed",qr=1e4;function H(n,t){return {v:1,type:Fr,data:{game_id:n,channel:t,entity_id:"conformance-entity",balance_after:500},ts:179e7}}i(H,"inventoryEvent");function Nr(n,t){try{n.parent.deliverSocket(t);}catch(e){n.fail(`deliverSocket threw: ${pe(e)}`);}}i(Nr,"deliverSocket");function jr(n,t){return n.received.filter(e=>e.type===t)}i(jr,"sentOf");async function ce(n,t,e=true){if(typeof n.parent.deliverSocket!="function")return null;n.start();let r=await n.raw(t);if(!e)return r;await r.ask("gem.requestToken",{reason:"initial"},"unpressed");let o=jr(r,"gem.token"),a=o[o.length-1];return a===void 0&&n.fail("the adapter exposes `deliverSocket`, so the kit needs a session with a token: answer the frame's gem.requestToken with a gem.token naming `fixture.gameId` and `fixture.channel`."),(a.gameId!==n.gameId||a.channel!==n.channel)&&n.fail(`the parent's gem.token named game ${JSON.stringify(a.gameId)} on ${JSON.stringify(a.channel)}; the kit's session is ${n.gameId} on ${n.channel}. Name the game and channel from \`fixture.gameId\` and \`fixture.channel\`.`),r}i(ce,"socketSession");async function V(n,t,e){let r=t.received.length;for(let o of e)Nr(n,o);return await n.flush(),n.advance(qr),await n.flush(),t.received.slice(r)}i(V,"deliverAndSettle");var He=[100,115,130,150,180,200],Br=[250,99,137,137.5,Number.NaN];async function Ue(n,t){return typeof n.parent.setTextScale!="function"?null:(n.start(),n.raw(t))}i(Ue,"textScaleSession");async function we(n,t,e){let r=t.received.length;try{n.parent.setTextScale(e);}catch(o){n.fail(`setTextScale(${e}) threw: ${pe(o)}`);}return await n.flush(),t.received.slice(r).filter(o=>o.type==="gem.prefs")}i(we,"pushTextScale");function me(n){let t=n.recommended;return t===void 0?void 0:t.textScale}i(me,"textScaleOf");function We(n,t,e){for(let r of t){let o=C["gem.prefs"]?.(r);o&&n.fail(`after ${e}, gem.prefs fails the contract: ${o}`);let a=me(r);a!==void 0&&!He.includes(a)&&n.fail(`after ${e}, gem.prefs carried recommended.textScale ${JSON.stringify(a)}. Send only one of the six sizes (${He.join(", ")}), or leave it out.`);}}i(We,"checkTextScalePrefs");var Ur=["*","null","","https://game.conformance.invalid/","https://game.conformance.invalid/play"],Wr=[{id:"origin.refuses-imprecise-target",title:"refuses a frame origin that is not one exact origin, before anything is posted",async run(n,t){for(let e of Ur){let r=JSON.stringify(e),o,a=false;try{o=new Se(this.id,n,{...t,frameOrigin:e},e),o.parent.start();}catch(s){if(s instanceof X)throw s;a=true;}try{if((o?.offers??[]).some(l=>l.targetOrigin==="*"||l.targetOrigin===e))throw new X(this.id,`with frame origin ${r} the parent posted the handshake anyway \u2014 the offer transfers the session port, so an imprecise target hands the session to whatever the frame has navigated to.`);if(!a)throw new X(this.id,`a frame origin of ${r} was accepted. Refuse it at construction or at start(), by throwing.`)}finally{o?.dispose();}}}},{id:"handshake.targets-frame-origin",title:"posts a valid handshake to exactly the frame origin, naming the page\u2019s own origin",async run(n,t){await p(this.id,n,t,e=>{e.start(),e.advance(E);for(let a of e.offers){a.targetOrigin!==e.frameOrigin&&e.fail(`an offer was posted with targetOrigin ${JSON.stringify(a.targetOrigin)}, not the frame origin ${e.frameOrigin}.`),e.transferred(a);let s=C["gem.handshake"]?.(a.data);s&&e.fail(`the handshake does not validate against the schema: ${s}`);let l=a.data;l.gem!==u&&e.fail(`the handshake's \`gem\` is not ${u}`),l.embedder!==t.embedderOrigin&&e.fail(`the handshake names embedder ${JSON.stringify(l.embedder)}, but the page is served from ${t.embedderOrigin}. Every game compares the two and refuses a mismatch: name the origin the page is actually served from.`);}let{min:r,max:o}=K(e);r>o&&e.fail(`the offered range is empty: min ${r} > max ${o}`),(o<x.min||r>x.max)&&e.fail(`the offered range v${r}\u2013v${o} shares no version with this SDK (v${x.min}\u2013v${x.max}).`);});}},{id:"handshake.default-game-connects",title:"connects to a game that names no embedders, at the highest common version",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=await de(e),{max:o}=K(e),a=Math.min(o,x.max);r.protocolVersion!==a&&e.fail(`the frame chose v${String(r.protocolVersion)}, expected v${a}`),e.parent.protocolVersion!==r.protocolVersion&&e.fail(`the parent reports v${String(e.parent.protocolVersion)} while the frame chose v${String(r.protocolVersion)}. Adopt the version on the accept.`);});}},{id:"handshake.refusal-is-not-a-verdict",title:"keeps offering, still handshaking, when a game refuses this page",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.frame([Or]);e.deliver(r,e.latest())&&e.fail("a frame that does not allow this page accepted it \u2014 the kit is misconfigured"),await e.flush();let o=e.offers.length;e.advance(E*3),await e.flush(),e.state()!=="handshaking"&&e.fail(`after a refusal and ${E*3}ms the parent reports '${e.state()}'. A refusing game answers nothing, by design, so silence is not evidence: stay handshaking until the page closes the bridge.`),e.offers.length<=o&&e.fail("the parent stopped offering after a refusal; keep offering on the cadence.");});}},{id:"version.adopts-frame-choice",title:"adopts the version the frame chose from inside the offered range",async run(n,t){await p(this.id,n,t,async e=>{e.start();let{min:r}=K(e);e.answer(e.latest(),r),await e.flush(),(e.state()!=="connected"||e.parent.protocolVersion!==r)&&e.fail(`answered with v${r}, the bottom of the offered range, the parent reports '${e.state()}' at v${String(e.parent.protocolVersion)}.`);});}},{id:"version.refuses-out-of-range",title:"never connects at a version outside the range it offered",async run(n,t){for(let e of ["above","below"])await p(this.id,n,t,async r=>{r.start();let{min:o,max:a}=K(r),s=e==="above"?a+1:o-1;r.answer(r.latest(),s),await r.flush(),(r.state()==="connected"||r.parent.protocolVersion===s)&&r.fail(`answered with v${s}, outside the offered v${o}\u2013v${a}, the parent connected. A frame choosing a version this parent never offered is not a session.`);});}},{id:"lifecycle.reoffers-on-fresh-ports",title:"re-offers on a fresh port while unanswered, inside the contract\u2019s budget",async run(n,t){await p(this.id,n,t,e=>{e.start(),e.advance(E*2),e.offers.length<2&&e.fail(`one offer in ${E*2}ms. A message to a document that is not listening yet is lost, not queued: re-offer until something answers.`);let r=e.offers.map(a=>e.transferred(a).port2);new Set(r).size!==r.length&&e.fail("an offer re-used a port; each offer needs its own channel."),e.advance(ue*2);let o=e.offersInAnyMinute();o>je&&e.fail(`${o} offers inside one minute; the contract budgets ${je} \`gem.handshake\` per minute.`);});}},{id:"lifecycle.late-answer-adopts-and-closes-the-rest",title:"adopts the offer a late frame answers, and closes every other offered port",async run(n,t){await p(this.id,n,t,async e=>{e.start(),e.advance(E*2);let r=e.transferred(e.latest());await de(e),r.closed&&e.fail("the parent closed the port the frame answered on.");let o=e.minted.filter(a=>a!==r&&!a.closed).length;o>0&&e.fail(`${o} unanswered offer port(s) still open after connecting. Close every other offer when one is answered.`);});}},{id:"lifecycle.no-offers-after-connect",title:"stops offering once connected",async run(n,t){await p(this.id,n,t,async e=>{e.start(),await de(e);let r=e.offers.length;e.advance(ue*5,1e3),e.offers.length!==r&&e.fail(`${e.offers.length-r} offer(s) after the session connected.`);});}},{id:"lifecycle.duplicate-accept-keeps-session",title:"keeps the session when a second accept arrives, even an out-of-range one",async run(n,t){await p(this.id,n,t,async e=>{e.start(),await de(e);let r=e.parent.protocolVersion;e.answer(e.latest(),K(e).max+1),await e.flush(),(e.state()!=="connected"||e.parent.protocolVersion!==r)&&e.fail(`a second accept moved the parent to '${e.state()}' at v${String(e.parent.protocolVersion)}. One answer settles the handshake.`);});}},{id:"lifecycle.close-releases-everything",title:"on close, releases every port and timer and offers nothing more",async run(n,t){await p(this.id,n,t,e=>{e.start(),e.advance(E),e.parent.close(),e.state()!=="closed"&&e.fail(`after close() the parent reports '${e.state()}'`);let r=e.minted.filter(a=>!a.closed).length;r>0&&e.fail(`${r} offer port(s) left open after close().`),e.clock.pendingTimers>0&&e.fail(`${e.clock.pendingTimers} timer(s) still armed after close().`);let o=e.offers.length;e.advance(ue*2,1e3),e.offers.length!==o&&e.fail("the parent offered again after close().");}),await p(this.id,n,t,async e=>{e.start(),await de(e),e.parent.close(),e.state()!=="closed"&&e.fail(`after close() the parent reports '${e.state()}'`),e.minted.filter(o=>!o.closed).length>0&&e.fail("the session port is still open after close().");});}},{id:"lifecycle.unanswered-offers-are-bounded",title:"holds at most about a minute of unanswered offer ports, however long it runs",async run(n,t){await p(this.id,n,t,e=>{e.start(),e.advance(ue*10,1e3);let r=e.minted.filter(o=>!o.closed).length;r>je+1&&e.fail(`${r} unanswered offer ports open after ten minutes. Close offers nothing answered within a grace (a minute is the reference), or the loop leaks a channel per tick for as long as the page holds the frame.`);});}},{id:"capability.unadvertised-is-refused",title:"refuses an arcade-feature or store ask under a capability either side did not advertise, answering nothing and showing nothing",async run(n,t){for(let e of P)await p(this.id,n,t,async r=>{r.start();let o=r.advertised().includes(e),a=P.filter(A=>!o||A!==e),s=await r.raw(a),l=r.parent.surfacesShown,{type:c,fields:h}=Be[e],b=await s.ask(c,h,"pressed"),v=o?"the frame":"the parent";s.answers(b).length>0&&r.fail(`${c} under '${e}', which ${v} did not advertise, was answered. Refuse it before anything else and send nothing back.`),r.parent.surfacesShown!==l&&r.fail(`${c} under unadvertised '${e}' showed a surface.`),r.errors.some(A=>A.code==="unsupported_capability")||r.fail(`${c} under '${e}', which ${v} did not advertise, was not reported as 'unsupported_capability'. Report it through \`fixture.reportError\` (what GemHost calls onError).`);});}},{id:"acting.refuses-without-activation",title:"answers an ask to leave the game (open app, open game) with no press \u2014 or no activation API \u2014 declined / busy, showing nothing",async run(n,t){for(let e of Gr)for(let r of ["unpressed","missing"])await p(this.id,n,t,async o=>{if(o.start(),!o.advertised().includes(e))return;let a=await o.raw(P),s=o.surfaces(`the parent advertises '${e}'`),{type:l,fields:c}=Be[e],h=await a.ask(l,c,r);ve(o,a,h,s,r==="missing"?`${l} with no userActivation API (it must fail closed)`:`${l} with no press`);});}},{id:"store.unknown-sku-is-unavailable",title:"answers a store purchase of a SKU it does not list unavailable, showing nothing",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let r=await e.raw(P),o=e.surfaces("the parent advertises `store`"),a=await r.ask("gem.requestStorePurchase",{skuId:ke},"pressed"),s=r.answers(a);(s.length!==1||s[0].outcome!=="unavailable")&&e.fail(`a pressed purchase of an unknown SKU was answered ${JSON.stringify(s)}; it owes exactly one 'unavailable'.`),e.surfaces("the parent advertises `store`")!==o&&e.fail("a purchase of an unknown SKU showed a surface; it must show nothing.");});}},{id:"acting.second-ask-is-busy",title:"answers any acting ask declined / busy, showing nothing, while a store checkout is open",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let r=await e.raw(P),o=await W(e,r),a=await r.ask("gem.requestStorePurchase",{skuId:o.skuId},"pressed");ve(e,r,a,o.shown,"a second store purchase while one is open");for(let l of ["appLinks","openGame","storeOfferSheet"]){if(!e.advertised().includes(l))continue;let{type:c,fields:h}=Be[l],b=await r.ask(c,h,"pressed");ve(e,r,b,o.shown,`${c} while a store checkout is open`);}J(e),await e.flush();let[s]=r.answers(o.requestId);(s?.outcome!=="declined"||s.reason!=="cancelled")&&e.fail(`the closed checkout was answered ${JSON.stringify(s?.outcome)} / ${JSON.stringify(s?.reason)}, not 'declined' / 'cancelled'.`);});}},{id:"store.no-press-needed",title:"opens a store checkout, or an offer sheet, for an ask with no press or no activation API",async run(n,t){for(let[e,r]of Lr)for(let o of ["unpressed","missing"])await p(this.id,n,t,async a=>{if(a.start(),!a.advertised().includes(e))return;let s=await a.raw(P),l=await W(a,s,r,o);J(a),await a.flush();let[c]=s.answers(l.requestId);(c?.outcome!=="declined"||c.reason!=="cancelled")&&a.fail(`the closed ${r} asked with no press was answered ${JSON.stringify(c?.outcome)} / ${JSON.stringify(c?.reason)}, not 'declined' / 'cancelled'.`);});}},{id:"store.no-back-off",title:"opens the checkout again for the next store purchase, however many times the player closed one",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let r=await e.raw(P);for(let o=1;o<=5;o+=1){let a=await W(e,r,"gem.requestStorePurchase","pressed",o===1?"":`straight after the player dismissed one (round ${o}), `);J(e),await e.flush();let[s]=r.answers(a.requestId);(s?.outcome!=="declined"||s.reason!=="cancelled")&&e.fail(`closed checkout ${o} was answered ${JSON.stringify(s?.outcome)} / ${JSON.stringify(s?.reason)}, not 'declined' / 'cancelled'.`);}e.advertised().includes("storeOfferSheet")&&await W(e,r,"gem.requestStoreOfferSheet","pressed","straight after the player dismissed a checkout, ");});}},{id:"sheet.needs-store",title:"advertises storeOfferSheet only beside store",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.advertised();r.includes("storeOfferSheet")&&!r.includes("store")&&e.fail("the parent advertises `storeOfferSheet` without `store`. A game takes its SKU from gem.storeOffersResult and hears a pending purchase settle through gem.storePurchaseSettled, and both are gated on `store`.");});}},{id:"settled.needs-purchases",title:"advertises purchaseSettled only beside purchases",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.advertised();r.includes("purchaseSettled")&&!r.includes("purchases")&&e.fail("the parent advertises `purchaseSettled` without `purchases`. gem.purchaseSettled reports a purchase the game asked for through gem.requestPurchase, which is gated on `purchases`.");});}},{id:"share.extras-need-share",title:"advertises shareResult and shareRemap only beside share",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.advertised();if(!r.includes("share"))for(let o of ["shareResult","shareRemap"])r.includes(o)&&e.fail(`the parent advertises \`${o}\` without \`share\`. Both describe what the parent does with gem.requestShare, which is gated on \`share\`.`);});}},{id:"share.refusal-is-answered",title:"answers a share naming embedder refused / reserved_key, naming the key, when it advertises shareResult",async run(n,t){await p(this.id,n,t,async e=>{e.start();let r=e.advertised();if(!r.includes("share")||!r.includes("shareResult"))return;let o=await e.raw(["share","shareResult"]),a=e.parent.surfacesShown,s=await o.ask("gem.requestShare",{params:[{key:"embedder",value:"x"}]},"pressed"),l=o.answers(s);l.length!==1&&e.fail(`a share naming \`embedder\` was answered ${l.length} times; a parent that advertises \`shareResult\` owes exactly one gem.shareResult for a request carrying a requestId.`);let[c]=l;(c.type!=="gem.shareResult"||c.outcome!=="refused"||c.reason!=="reserved_key"||c.key!=="embedder")&&e.fail(`a share naming \`embedder\` was answered ${JSON.stringify(c)}; it owes gem.shareResult 'refused' / 'reserved_key' with key 'embedder'. No parent carries embedder in a share link, remapping or not.`),typeof a=="number"&&e.parent.surfacesShown!==a&&e.fail("a refused share showed a surface; it must show nothing.");});}},{id:"sheet.unknown-sku-is-unavailable",title:"answers an offer sheet for a SKU it does not list unavailable, showing nothing",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("storeOfferSheet"))return;let r=await e.raw(P),o=e.surfaces("the parent advertises `storeOfferSheet`"),a=await r.ask("gem.requestStoreOfferSheet",{skuId:ke},"pressed"),s=r.answers(a);(s.length!==1||s[0].outcome!=="unavailable")&&e.fail(`a pressed offer sheet for an unknown SKU was answered ${JSON.stringify(s)}; it owes exactly one 'unavailable'.`),e.surfaces("the parent advertises `storeOfferSheet`")!==o&&e.fail("an offer sheet for an unknown SKU showed a surface; it must show nothing.");});}},{id:"sheet.shares-the-slot",title:"holds the acting slot while an offer sheet is open, and opens either store ask again once the player leaves it",async run(n,t){await p(this.id,n,t,async e=>{if(e.start(),!e.advertised().includes("storeOfferSheet"))return;let r=await e.raw(P),o=await W(e,r,"gem.requestStoreOfferSheet"),a=await r.ask("gem.requestStorePurchase",{skuId:o.skuId},"pressed");ve(e,r,a,o.shown,"a store purchase while an offer sheet is open"),J(e),await e.flush();let[s]=r.answers(o.requestId);(s?.outcome!=="declined"||s.reason!=="cancelled")&&e.fail(`the closed offer sheet was answered ${JSON.stringify(s?.outcome)} / ${JSON.stringify(s?.reason)}, not 'declined' / 'cancelled'.`);for(let l of ["gem.requestStoreOfferSheet","gem.requestStorePurchase"])await W(e,r,l,"pressed","straight after the player left a sheet, "),J(e),await e.flush();});}},{id:"pass.listed-by-slug",title:"lists a pass with its slug, and opens the pass's sheet, for an adult",async run(n,t){await F(this.id,n,t,"adult",async(e,r)=>{let{answer:o}=await Pt(e,r,"an adult");(o?.outcome!=="declined"||o.reason!=="cancelled")&&e.fail(`the closed pass sheet was answered ${JSON.stringify(o?.outcome)} / ${JSON.stringify(o?.reason)}, not 'declined' / 'cancelled'.`);});}},{id:"pass.not-on-sale",title:"lists no offer with the slug of a pass that is not on sale",async run(n,t){await F(this.id,n,t,"off_sale",async(e,r)=>{let o=await z(r);Re(o).length>0&&e.fail(`the store listed pass '${Y}' where it is not on sale: a game would draw a button that opens nothing.`);});}},{id:"pass.guest-is-sold",title:"lists a pass to a guest and opens its sheet, rather than answering sign_in_required",async run(n,t){await F(this.id,n,t,"guest",async(e,r)=>{await Pt(e,r,"a guest");});}},{id:"pass.holder-sheet-follows-listing",title:"for a player who holds a pass, opens its sheet when it lists the pass, and answers unavailable, showing nothing, when it does not",async run(n,t){let e;await F(this.id,n,t,"adult",async(r,o)=>{e=xt(await z(o));}),await F(this.id,n,t,"holder",async(r,o)=>{let a=xt(await z(o));a===void 0?await Dr(r,o,e):await Et(r,o,a,"a player who holds the pass, and is listed it"," A pass in the listing is one a game draws a button for: open its sheet, or leave the pass out of the listing.");});}},{id:"pass.minor-looks-off-sale",title:"answers a known minor's listing exactly as it answers a player the pass is not sold to",async run(n,t){let e;await F(this.id,n,t,"off_sale",async(r,o)=>{e=await z(o);}),await F(this.id,n,t,"minor",async(r,o)=>{let a=await z(o);Re(a).length>0&&r.fail(`the store listed pass '${Y}' to a known minor.`),!(a.outcome==="unavailable"&&a.reason==="store_off")&&JSON.stringify(a)!==JSON.stringify(e)&&r.fail(`a known minor's listing ${JSON.stringify(a)} differs from the listing of an adult the pass is not on sale to, ${JSON.stringify(e)}. A game must not be able to tell a minor from a pass that is not sold here.`);});}},{id:"inventory.own-game-relayed",title:"relays this game's own game_inventory_changed as one field-free gem.inventoryChanged, and coalesces a burst",async run(n,t){await p(this.id,n,t,async e=>{let r=await ce(e,["inventoryChanged"]);if(r===null)return;let o=await V(e,r,[H(e.gameId,e.channel)]),a=o.filter(b=>b.type==="gem.inventoryChanged");(a.length!==1||o.length!==1)&&e.fail(`one game_inventory_changed for this game and channel put ${a.length} gem.inventoryChanged and ${o.length-a.length} other message(s) on the frame; it owes exactly one gem.inventoryChanged.`);let s=Object.keys(a[0]).filter(b=>!["gem","type","v"].includes(b));s.length>0&&e.fail(`gem.inventoryChanged carried ${s.map(b=>JSON.stringify(b)).join(", ")}. It carries no field: copy nothing from the socket event.`);let l=C["gem.inventoryChanged"]?.(a[0]);l&&e.fail(`gem.inventoryChanged fails the contract: ${l}`);let h=(await V(e,r,Array.from({length:10},()=>H(e.gameId,e.channel)))).filter(b=>b.type==="gem.inventoryChanged").length;(h<1||h>2)&&e.fail(`a burst of 10 game_inventory_changed inside a second put ${h} gem.inventoryChanged on the frame; coalesce it to one, then at most one trailing send.`);});}},{id:"inventory.other-game-never-relayed",title:"never relays another game's, or another channel's, game_inventory_changed",async run(n,t){await p(this.id,n,t,async e=>{let r=await ce(e,["inventoryChanged"]);if(r===null)return;let o=e.channel==="release"?"dev":"release";for(let[a,s]of [["another game",H(It,e.channel)],["this game on another channel",H(e.gameId,o)]]){let l=await V(e,r,[s]);l.length>0&&e.fail(`game_inventory_changed for ${a} put ${JSON.stringify(l.map(c=>c.type))} on the frame. Relay only the token's own game and channel: another game's change tells this game when another game paid the player.`);}});}},{id:"inventory.before-token-never-relayed",title:"relays no game_inventory_changed before a token has named the game",async run(n,t){await p(this.id,n,t,async e=>{let r=await ce(e,["inventoryChanged"],!1);if(r===null)return;let o=await V(e,r,[H(e.gameId,e.channel)]);o.length>0&&e.fail(`before any token, game_inventory_changed put ${JSON.stringify(o.map(a=>a.type))} on the frame. Until a token has gone out there is no game to attribute it to.`);});}},{id:"inventory.unadvertised-not-sent",title:"sends no gem.inventoryChanged to a frame that did not advertise inventoryChanged",async run(n,t){await p(this.id,n,t,async e=>{let r=await ce(e,[]);if(r===null)return;let o=await V(e,r,[H(e.gameId,e.channel)]);o.length>0&&e.fail(`to a frame that advertised nothing, game_inventory_changed put ${JSON.stringify(o.map(a=>a.type))} on it. Send gem.inventoryChanged only when the frame advertised \`inventoryChanged\`: an older SDK drops it unread.`);});}},{id:"inventory.wallet-events-never-relayed",title:"relays nothing from a platform wallet event, even one naming this game",async run(n,t){await p(this.id,n,t,async e=>{let r=await ce(e,["inventoryChanged"]);if(r===null)return;let o={game_id:e.gameId,channel:e.channel};for(let[a,s]of [["platform_grant_fired",{...o,currency_id:"gems",granted:50,game_total:50,game_max:500,total:500}],["platform_funded_game_inventory_purchase_completed",{...o,purchase_id:"p",kind:"item",target:"t",qty:1,balance_after:450}],["platform_daily_maximum_reached",{...o,currency_id:"gems",limit:500}],["purchase_completed",{...o,purchase_id:"p",package_id:"k"}]]){let l=await V(e,r,[{v:1,type:a,data:s,ts:179e7}]);l.length>0&&e.fail(`${a} put ${JSON.stringify(l.map(c=>c.type))} on the frame. A wallet event describes the platform wallet across every game; relay nothing from it.`);}});}},{id:"textscale.unadvertised-not-sent",title:"sends no text size to a frame that did not advertise textScale",async run(n,t){await p(this.id,n,t,async e=>{let r=await Ue(e,[]);if(r!==null)for(let o of [150,200]){let a=await we(e,r,o);for(let s of a)me(s)!==void 0&&e.fail(`to a frame that advertised nothing, gem.prefs carried recommended.textScale ${JSON.stringify(me(s))}. Send it only to a frame that advertised \`textScale\`: it reaches only the games that apply it.`);}});}},{id:"textscale.advertised-gets-each-size",title:"sends each Arcade text size to a frame that advertised textScale",async run(n,t){await p(this.id,n,t,async e=>{let r=await Ue(e,["textScale"]);if(r!==null)for(let o of [200,...He]){let a=await we(e,r,o);We(e,a,`setTextScale(${o})`);let s=a[a.length-1];(s===void 0||me(s)!==o)&&e.fail(`setTextScale(${o}) to a frame that advertised \`textScale\` put ${s===void 0?"no gem.prefs":`recommended.textScale ${JSON.stringify(me(s))}`} on the frame. Push gem.prefs on the change, with recommended.textScale ${o}.`);}});}},{id:"textscale.bad-size-costs-nothing-else",title:"never sends a text size that is not one of the six, and never drops recommended for one",async run(n,t){await p(this.id,n,t,async e=>{let r=await Ue(e,["textScale"]);if(r!==null)for(let o of Br){We(e,await we(e,r,130),"setTextScale(130)");let a=await we(e,r,o);We(e,a,`setTextScale(${o})`);for(let s of a)s.recommended===void 0&&e.fail(`after setTextScale(${o}), gem.prefs carried no recommended. A text size the contract refuses is left out, and recommended sent without it: it must not cost the game the player's volume, Color help or graphics.`);}});}}],Hr=Object.freeze(Wr);async function Z_(n,t){if(!Z(t.embedderOrigin))throw new Error(`runParentConformance: embedderOrigin must be one exact origin, got ${JSON.stringify(t.embedderOrigin)}`);let e=[];for(let r of Hr)try{await r.run(n,t),e.push({id:r.id,title:r.title,ok:!0});}catch(o){e.push({id:r.id,title:r.title,ok:false,message:pe(o)});}return e}i(Z_,"runParentConformance");
|
|
3
|
+
export{Y as KIT_PASS,Hr as PARENT_CONFORMANCE_CASES,Q_ as PARENT_CONFORMANCE_VERSION,X as ParentConformanceError,Z_ as runParentConformance};//# sourceMappingURL=gem-web-sdk.conformance.js.map
|
|
4
4
|
//# sourceMappingURL=gem-web-sdk.conformance.js.map
|