@indiegems/gem-web-sdk 16.0.1 → 16.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,36 @@ Its entries are grouped under up to five headings:
16
16
  - **Rebuild**: a type changed. Recompile against the new version; code that compiles today keeps compiling unless the entry says otherwise.
17
17
  - **New capability**: something a game can now do. Nothing changes unless you use it.
18
18
 
19
+ ## 16.2.0
20
+
21
+ Upgrading from 16.1.0. Impact: required.
22
+
23
+ ### Must change
24
+
25
+ - The reason on a save error can now also be save_conflict or load_failed. Any code that switches over the reason exhaustively, or maps it to a message, will stop compiling or fall through, so handle both new values. save_conflict means the write was refused because the saved data changed underneath it, or because the player changed while the write was waiting. Reload the save and decide whether to retry, or pass overwrite when replacing the stored data is intended. load_failed means a save could not be loaded.
26
+
27
+ ### Rebuild
28
+
29
+ - The save error constructor now accepts the two new reasons, and the saves API constructor takes an optional trailing identity function. Recompiling is enough; existing calls stay valid.
30
+
31
+ ### New capability
32
+
33
+ - Save and byte writes accept an overwrite option. Set it when a write should replace whatever is stored without the conflict check; otherwise a write that collides with newer saved data is refused as save_conflict.
34
+ - Save and byte writes accept a teardown option. Set it on a final write, such as when the game is closing, so the write skips the spacing between writes and goes out right away.
35
+ - The saves options accept minWriteIntervalMs, which sets the minimum spacing between writes. Tune it if the default spacing does not suit how often your game saves.
36
+
37
+ ## 16.1.0
38
+
39
+ Upgrading from 16.0.1. Impact: required.
40
+
41
+ ### Rebuild
42
+
43
+ - The signalMoment method on the frame now accepts an optional fourth parameter, wall, in the game, root and SDK entry points. Existing calls with three arguments keep working unchanged, so recompiling your game against the new types is enough.
44
+
45
+ ### New capability
46
+
47
+ - A game can now mark a platform moment with wall set to true to say it accepts that the Arcade may show its app wall at that break. Leaving it out or passing false keeps the break ordinary. An older Arcade ignores the field, so it is safe to send everywhere. Only set it where showing the wall at that break is acceptable for your game.
48
+
19
49
  ## 16.0.1
20
50
 
21
51
  Upgrading from 16.0.0. Impact: none.
@@ -98,6 +98,7 @@ declare class GemClient {
98
98
  delete<T>(path: string, signal?: AbortSignal, options?: RequestOptions): Promise<T>;
99
99
  private send;
100
100
  private sendRaw;
101
+ private backoffMs;
101
102
  private backoff;
102
103
  }
103
104
  type SessionEndReason = "grant_expiring" | "session_ended" | "navigating" | "kill_switch" | "pagehide";
@@ -520,17 +521,20 @@ interface SavePage {
520
521
  readonly nextCursor?: string;
521
522
  }
522
523
  declare class GemSaveError extends Error {
523
- readonly reason: "upload_failed" | "download_failed" | "save_timeout" | "save_malformed";
524
- constructor(reason: "upload_failed" | "download_failed" | "save_timeout" | "save_malformed", message: string);
524
+ readonly reason: "upload_failed" | "download_failed" | "save_timeout" | "save_malformed" | "save_conflict" | "load_failed";
525
+ constructor(reason: "upload_failed" | "download_failed" | "save_timeout" | "save_malformed" | "save_conflict" | "load_failed", message: string);
525
526
  }
526
527
  interface SavesOptions {
527
528
  readonly fetch?: typeof globalThis.fetch;
528
529
  readonly timeoutMs?: number;
530
+ readonly minWriteIntervalMs?: number;
529
531
  }
530
532
  interface PutSaveOptions {
531
533
  readonly metadata?: Record<string, unknown>;
532
534
  readonly signal?: AbortSignal;
533
535
  readonly timeoutMs?: number;
536
+ readonly teardown?: boolean;
537
+ readonly overwrite?: boolean;
534
538
  }
535
539
  interface PutBytesOptions extends PutSaveOptions {
536
540
  readonly contentType?: string;
@@ -552,14 +556,34 @@ declare class SavesApi {
552
556
  private readonly api;
553
557
  private readonly entity;
554
558
  private readonly options;
555
- constructor(api: GameApi, entity: EntityRef, options?: SavesOptions);
559
+ private readonly identity?;
560
+ private readonly versions;
561
+ private seq;
562
+ private versionsOf;
563
+ private readonly writes;
564
+ private holdUntil;
565
+ constructor(api: GameApi, entity: EntityRef, options?: SavesOptions, identity?: (() => string | null) | undefined);
556
566
  put(key: string, value: unknown, options?: PutSaveOptions): Promise<SaveRecord>;
557
567
  putBytes(key: string, bytes: Uint8Array, options?: PutBytesOptions): Promise<SaveRecord>;
568
+ private holdOwn;
569
+ private drop;
570
+ private queue;
571
+ private pump;
572
+ private spacing;
573
+ private pause;
574
+ private take;
575
+ private slotWrites;
576
+ private noteRateLimit;
577
+ private held;
578
+ private send;
558
579
  get<T = unknown>(key: string, options?: GetSaveOptions): Promise<T | null>;
559
580
  getBytes(key: string, options?: GetSaveOptions): Promise<Uint8Array | null>;
560
581
  describe(key: string, options?: DescribeSaveOptions): Promise<SaveRecord | null>;
561
582
  list(options?: ListSavesOptions): Promise<SavePage>;
562
583
  delete(key: string, signal?: AbortSignal): Promise<void>;
584
+ private precondition;
585
+ private remember;
586
+ private slots;
563
587
  private budget;
564
588
  private timeout;
565
589
  private rethrow;
package/dist/game.d.ts CHANGED
@@ -107,6 +107,7 @@ declare class GemClient {
107
107
  delete<T>(path: string, signal?: AbortSignal, options?: RequestOptions): Promise<T>;
108
108
  private send;
109
109
  private sendRaw;
110
+ private backoffMs;
110
111
  private backoff;
111
112
  }
112
113
  interface GameApi {
@@ -913,17 +914,20 @@ interface SavePage {
913
914
  readonly nextCursor?: string;
914
915
  }
915
916
  declare class GemSaveError extends Error {
916
- readonly reason: "upload_failed" | "download_failed" | "save_timeout" | "save_malformed";
917
- constructor(reason: "upload_failed" | "download_failed" | "save_timeout" | "save_malformed", message: string);
917
+ readonly reason: "upload_failed" | "download_failed" | "save_timeout" | "save_malformed" | "save_conflict" | "load_failed";
918
+ constructor(reason: "upload_failed" | "download_failed" | "save_timeout" | "save_malformed" | "save_conflict" | "load_failed", message: string);
918
919
  }
919
920
  interface SavesOptions {
920
921
  readonly fetch?: typeof globalThis.fetch;
921
922
  readonly timeoutMs?: number;
923
+ readonly minWriteIntervalMs?: number;
922
924
  }
923
925
  interface PutSaveOptions {
924
926
  readonly metadata?: Record<string, unknown>;
925
927
  readonly signal?: AbortSignal;
926
928
  readonly timeoutMs?: number;
929
+ readonly teardown?: boolean;
930
+ readonly overwrite?: boolean;
927
931
  }
928
932
  interface PutBytesOptions extends PutSaveOptions {
929
933
  readonly contentType?: string;
@@ -945,14 +949,34 @@ declare class SavesApi {
945
949
  private readonly api;
946
950
  private readonly entity;
947
951
  private readonly options;
948
- constructor(api: GameApi, entity: EntityRef, options?: SavesOptions);
952
+ private readonly identity?;
953
+ private readonly versions;
954
+ private seq;
955
+ private versionsOf;
956
+ private readonly writes;
957
+ private holdUntil;
958
+ constructor(api: GameApi, entity: EntityRef, options?: SavesOptions, identity?: (() => string | null) | undefined);
949
959
  put(key: string, value: unknown, options?: PutSaveOptions): Promise<SaveRecord>;
950
960
  putBytes(key: string, bytes: Uint8Array, options?: PutBytesOptions): Promise<SaveRecord>;
961
+ private holdOwn;
962
+ private drop;
963
+ private queue;
964
+ private pump;
965
+ private spacing;
966
+ private pause;
967
+ private take;
968
+ private slotWrites;
969
+ private noteRateLimit;
970
+ private held;
971
+ private send;
951
972
  get<T = unknown>(key: string, options?: GetSaveOptions): Promise<T | null>;
952
973
  getBytes(key: string, options?: GetSaveOptions): Promise<Uint8Array | null>;
953
974
  describe(key: string, options?: DescribeSaveOptions): Promise<SaveRecord | null>;
954
975
  list(options?: ListSavesOptions): Promise<SavePage>;
955
976
  delete(key: string, signal?: AbortSignal): Promise<void>;
977
+ private precondition;
978
+ private remember;
979
+ private slots;
956
980
  private budget;
957
981
  private timeout;
958
982
  private rethrow;
@@ -3096,7 +3120,7 @@ declare class GemFrame {
3096
3120
  requestAvatar3dPurchase(requestId: string, itemId: string, currencySlug: string): boolean;
3097
3121
  putDeviceSettings(requestId: string, settings: Readonly<Record<string, unknown>>): boolean;
3098
3122
  requestAd(requestId: string, placement: string, format: "interstitial" | "rewarded"): boolean;
3099
- signalMoment(requestId: string, kind: string, placement?: string): boolean;
3123
+ signalMoment(requestId: string, kind: string, placement?: string, wall?: boolean): boolean;
3100
3124
  readyCalled(): boolean;
3101
3125
  requestSignIn(requestId: string): boolean;
3102
3126
  requestFriendChallenge(requestId: string, playerId: string, template: string, params?: Readonly<Record<string, string | number>>): boolean;
@@ -3283,6 +3307,7 @@ type MomentKind = "level_end" | "run_end" | "match_end" | "menu_return" | "other
3283
3307
  interface MomentSignal {
3284
3308
  kind: MomentKind;
3285
3309
  placement?: string;
3310
+ wall?: boolean;
3286
3311
  }
3287
3312
  interface MomentResult {
3288
3313
  readonly shown: boolean;
@@ -1,2 +1,2 @@
1
- var GemWebSdkBridgeBundle=(function(){'use strict';var L="GemWebSdkHandshake";function oe(){return typeof document>"u"?void 0:document.readyState}var U=()=>Date.now();function H(n={}){let e=n.win??window,t=e,r=t[L];if(r!==void 0)return r;let s=n.readyState??oe,i=n.now??U,d=[],a=[],u=y=>{let c=y.data;!c||c.type!=="gem.handshake"||(d.length>=4&&(d.shift(),a.shift(),p.dropped+=1),d.push(y),a.push(i()));};e.addEventListener("message",u);let p={events:d,times:a,dropped:0,late:s()==="complete",uninstall(){e.removeEventListener("message",u);}};return t[L]=p,p}function B(n,e,t={}){let r=n[L];if(r===void 0)return null;let s=t.ttlMs??9500,i=t.now??U,d=r.events.splice(0),a=r.times?.splice(0),u=0,p=0,y=false,c=i()-s;for(let f=d.length-1;f>=0;f--){let b=a?.[f];if(b!==void 0&&b<c){p+=1;continue}if(u+=1,e(d[f])===true){y=true;break}}return r.uninstall(),{capture:r,delivered:u,stale:p,accepted:y}}var l="gem-web-bridge",R={min:1,max:1},ae=65536,N=8,G=5e3,W=1024;var w=Object.create(null),x="__malformed";var j=Object.create(null),se=Object.freeze([["gem.asyncOpponents.","asyncOpponents"],["gem.vibration.","vibration"],["gem.viewport.","reservedAreas"],["gem.moments.","moments"],["gem.prefs.","audioChannels"]]);function O(n){let e=j[n];if(e!==void 0)return e;for(let[t,r]of se)if(n.startsWith(t))return r}function ie(n){let e=/^([a-z][a-z0-9+.-]*):\/\/([a-z0-9.-]+|\[[0-9a-f:.]+\])(:([0-9]{1,5}))?$/i.exec(n);return e===null?false:e[4]===void 0||Number(e[4])<=65535}function le(n){let e;try{e=new URL(n);}catch{return false}return e.username!==""||e.password!==""?false:e.protocol==="https:"?true:e.protocol==="http:"&&(e.hostname==="localhost"||e.hostname==="127.0.0.1")}function de(n){return /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i.test(n)}function me(n){return /^[a-z_]+$/.test(n)}function ue(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("--")}function _(n,e,t){if(e>N)return `nesting deeper than ${N}`;if(++t.nodes>G)return `more than ${G} values`;if(t.bytes>t.cap)return `over the ${t.cap} byte limit`;if(n==null)return t.bytes+=4,null;let r=typeof n;if(r==="boolean")return t.bytes+=5,null;if(r==="number")return t.bytes+=8,null;if(r==="string")return t.bytes+=n.length*3+2,null;if(r!=="object")return `${r} is not plain data`;let s=n;if(t.seen.has(s))return "a repeated or cyclic object reference";if(t.seen.add(s),Array.isArray(n)){if(n.length>W)return `an array of ${n.length} entries, over the ${W} limit`;t.bytes+=2;for(let a of n){let u=_(a,e+1,t);if(u)return u}return null}let i=Object.getPrototypeOf(n);if(i!==Object.prototype&&i!==null)return "a value that is not a plain object";t.bytes+=2;let d=n;for(let a in d){if(!Object.prototype.hasOwnProperty.call(d,a))continue;if(++t.nodes>G)return `more than ${G} values`;if(t.bytes+=a.length*3+4,t.bytes>t.cap)return `over the ${t.cap} byte limit`;let u=_(d[a],e+1,t);if(u)return u}return null}function ce(n,e=ae){let t={nodes:0,bytes:0,seen:new Set,cap:e},r=_(n,0,t);return r?`message contains ${r}`:t.bytes>e?`message is over the ${e} byte limit`:null}var ge={origin:[ie,"not a well-formed origin"],url:[le,"not a safe absolute http(s) URL"],bcp47:[de,"not a well-formed BCP 47 tag"],code:[me,"not a code of lower-case letters and underscores"],gameRef:[ue,"not a game id or web address"]};function V(n,e,t){for(let r of e){let s=t===""?r[0]:`${t}.${r[0]}`,i=n[r[0]];if(i==null){if(r[1]<"a")continue;return `${s}: required`}let d=M(i,r,s);if(d)return d}return null}function M(n,e,t){let r=e[1].toLowerCase();switch(r){case "u":return typeof n=="boolean"?null:M(n,typeof n=="string"?[e[0],"s",e[2]]:[e[0],"n",e[3],e[4]],t);case "s":{if(typeof n!="string")return `${t}: expected string`;if(n.length>e[2])return `${t}: exceeds ${e[2]} characters`;if(e[3]){let[s,i]=ge[e[3]];if(!s(n))return `${t}: ${i}`}return null}case "n":case "i":return typeof n!="number"||!(r=="i"?Number.isInteger:Number.isFinite)(n)?`${t}: expected ${r=="i"?"integer":"number"}`:n<e[2]||n>e[3]?`${t}: out of range ${e[2]}..${e[3]}`:null;case "b":return typeof n=="boolean"?null:`${t}: expected boolean`;case "e":{let s=e[2];return s.includes(n)?null:`${t}: not one of ${s.join("|")}`}case "o":return typeof n!="object"||n===null||Array.isArray(n)?`${t}: expected object`:V(n,e[2],t);case "j":return typeof n!="object"||n===null||Array.isArray(n)?`${t}: expected a plain object`:null;case "a":{if(!Array.isArray(n))return `${t}: expected array`;if(n.length>e[2])return `${t}: exceeds ${e[2]} items`;let s=e[3],i=`${t}[]`;for(let d of n){if(d==null){if(s[1]<"a")continue;return `${i}: required`}let a=M(d,s,i);if(a)return a}return null}default:return `${t}: unknown kind ${String(r)}`}}var z=Object.create(null);function o(n,e,t,r,s){return w[n]=t,r&&(j[n]=r),z[n]=i=>{let d=ce(i,s);if(d)return d;if(typeof i!="object"||i===null||Array.isArray(i))return "message: expected object";let a=i;return a.gem!==l?"gem: wrong protocol marker":a.type!==n?`type: expected ${n}`:V(a,e,"")}}var m=["requestId","s",64],h=["outcome","e",["ok","declined","unavailable"]],v=["reason","S",64,"code"];o("gem.handshake",[["versions","o",[["min","i",1,1e3],["max","i",1,1e3]]],["capabilities","A",64,["","s",64]],["embedder","s",2048,"origin"],["unbidden","B"]],30);o("gem.handshake.accept",[["v","i",1,1e3],["capabilities","A",64,["","s",64]]],5);o("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);o("gem.token.denied",[m,["retryable","b"],["retryAfterMs","I",0,36e5],["reason","e",["chain_exhausted","unavailable"]],["correlationId","s",64]],30);o("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],["currencySlug","s",32],["multiplierBp","i",1e4,1e6]]]],["expiresInDays","I",1,7]]]],12);o("gem.pause",[["paused","b"]],120);o("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"]]]],60);o("gem.cohorts",[["cohorts","a",8,["","o",[["experimentId","s",36],["variantKey","s",32]]]]],12,"cohorts");o("gem.earnAllowanceChanged",[],30,"earnAllowance");o("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");o("gem.deviceSettings",[["settings","J"]],12,"deviceSettings");o("gem.willTerminate",[["reason","e",["grant_expiring","session_ended","navigating","kill_switch"]],["graceMs","i",0,6e4]],10);o("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");o("gem.socketState",[["connected","b"],["reason","E",["connecting","dropped","watchdog","terminal"]]],12,"socketState");o("gem.seatedEvent",[["roomId","s",64]],60,"partySeating");o("gem.autoStartFailedEvent",[["roomId","s",64],["code","s",64]],30,"autoStartFailure");o("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");o("gem.signalEvent",[["event","e",["hello","offer","answer","ice","peerJoined","peerLeft","migration","matchEnded","error"]],["matchId","s",64],["data","j"]],1800,"p2p");o("gem.dedicatedEvent",[["event","e",["answer","error","matchEnded"]],["matchId","s",64],["sdp","S",10240],["sessionId","S",64],["code","S",64],["message","S",256],["reason","S",64]],120,"dedicated");o("gem.partyResult",[m,["partyId","S",64]],30,"parties");o("gem.clusterTarget",[["clusterTargetId","s",64],["clusterTargetIds","A",3,["","s",64]]],12,"clusterTarget");o("gem.devtools",[["action","e",["open","close","toggle"]]],30,"devtools");o("gem.screenshot",[["maxWidth","i",1,7680],["maxHeight","i",1,4320]],6,"screenshot");o("gem.screenshot.denied",[["reason","e",["not_wired","no_source","unsupported","unreadable","tainted","too_large","busy","declined","failed"]],["retryable","b"]],12,"screenshot");o("gem.requestContext",[],12,"feedbackContext");o("gem.gameContext",[["context","j"]],12,"feedbackContext");o("gem.signal",[["signal","e",["join","offer","answer","ice","migrationAck","hostUnreachable"]],["matchId","s",64],["data","j"]],1200,"p2p");o("gem.dedicatedOffer",[["matchId","s",64],["sdp","s",10240],["intent","e",["initial","ice_restart","full_reconnect"]]],60,"dedicated");o("gem.partyRequest",[m],12,"parties");o("gem.ready",[],5);o("gem.engaged",[],5,"engagement");o("gem.progress",[["loaded","i",0,1099511627776],["total","i",0,1099511627776]],600);o("gem.requestToken",[m,["reason","e",["initial","expiring","rejected"]]],12);o("gem.requestLocalServer",[m,["gameRoomId","s",64]],6,"localHost");o("gem.localServer",[m,["serverId","S",128]],30,"localHost");o("gem.localServer.denied",[m,["retryable","b"],["reason","e",["refused","unavailable"]]],30,"localHost");o("gem.exit",[["reason","S",64]],5);o("gem.openExternal",[["url","s",2048,"url"]],6);o("gem.requestFullscreen",[["enabled","b"]],30);o("gem.audioState",[["muted","b"]],120,"audioState");o("gem.rewardCue",[["currencySlug","s",32],["expected","I",1,1e9],["origin","O",[["x","i",0,16384],["y","i",0,16384]]]],60,"rewardCue");o("gem.walletDisplay",[["visible","b"],["currencies","A",8,["","s",64]],["score","B"]],60,"walletDisplay");o("gem.controlsDeclare",[["uses","o",[["lookSensitivity","S",16,"code"],["gamepadLookSensitivity","S",16,"code"],["invertLookY","S",16,"code"],["touchMovement","S",16,"code"]]]],60,"controls");o("gem.menu",[["settings","b"],["items","A",4,["","o",[["id","s",32],["label","s",40]]]]],60,"gameMenu");o("gem.menuSelect",[["entry","e",["settings","item"]],["id","S",32]],60,"gameMenu");o("gem.gemCorner",[["corner","e",["top-left","top-right","bottom-left","bottom-right"]]],12,"gemCorner");o("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");o("gem.settingChange",[["id","s",40],["value","u",40,-1e6,1e6]],120,"gameSettings");o("gem.requestShare",[["params","a",16,["","o",[["key","s",64],["value","s",512]]]]],6,"share");o("gem.requestAvatarPicker",[["tab","E",["2d","3d"]]],6,"avatarPicker");o("gem.vibrate",[["pattern","a",99,["","i",0,1e4]]],600,"vibration");o("gem.frameStats",[["fps","i",0,1e3],["frameTimeP95Ms","i",0,1e5],["counts","E",["pageFrames","gameDraws"]]],120,"frameStats");o("gem.portraitBorders",[["name","S",40]],60,"portraitBorders");o("gem.error",[["code","e",["version_mismatch","handshake_timeout","handshake_duplicate","invalid_message","rate_limited","token_unavailable","unsupported","unsupported_capability","internal"]],["message","S",256]],60);o("gem.requestPurchase",[m,["purchaseKind","e",["item","recipe"]],["targetId","s",64],["currencySlug","s",32],["quantity","I",1,10],["catalogId","S",64]],12,"purchases");o("gem.requestAvatar3dPurchase",[m,["itemId","s",64],["currencySlug","s",32]],12,"avatar3dPurchases");o("gem.purchaseResult",[m,h,v,["purchaseId","S",64]],30,"purchases");o("gem.deviceSettings.put",[m,["settings","j"]],30,"deviceSettings");o("gem.deviceSettings.result",[m,["outcome","e",["stored","too_large","unavailable"]]],30,"deviceSettings");o("gem.requestAd",[m,["placement","s",32],["format","e",["interstitial","rewarded"]]],6,"ads");o("gem.adResult",[m,["outcome","e",["shown","no_ad","unavailable"]]],12,"ads");o("gem.signalMoment",[m,["kind","e",["level_end","run_end","match_end","menu_return","other_break"]],["placement","S",32]],6,"moments");o("gem.momentResult",[m,["shown","b"]],12,"moments");o("gem.requestSignIn",[m],6,"signIn");o("gem.signInResult",[m,["outcome","e",["signed_in","declined","unavailable"]]],12,"signIn");o("gem.requestFriendChallenge",[m,["playerId","s",64],["template","s",64],["params","J"]],12,"friendChallenges");o("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");o("gem.requestAppLinks",[m],12,"appLinks");o("gem.appLinksResult",[m,h,v,["arcade","O",[["store","e",["app_store","google_play"]]]],["game","O",[["store","e",["app_store","google_play"]]]]],30,"appLinks");o("gem.requestOpenApp",[m,["target","e",["arcade","game"]]],6,"appLinks");o("gem.openAppResult",[m,h,v],30,"appLinks");o("gem.requestOpenGame",[m,["ref","s",40,"gameRef"]],6,"openGame");o("gem.openGameResult",[m,h,v],30,"openGame");o("gem.requestSimilarGames",[m,["limit","I",1,24]],12,"openGame");o("gem.similarGamesResult",[m,h,v,["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);o("gem.requestStoreOffers",[m],12,"store");o("gem.storeOffersResult",[m,h,v,["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]]]]]]],30,"store",262144);o("gem.requestStorePurchase",[m,["skuId","s",64]],12,"store");o("gem.storePurchaseResult",[m,h,v],30,"store");o("gem.storePurchaseSettled",[["skuId","s",64]],30,"store");var P=Object.freeze(z);w.__malformed=60;Object.freeze(w);Object.freeze(j);var pe=/^[a-z0-9][a-z0-9_-]{0,39}$/;function ye(n){return typeof n=="string"&&pe.test(n)}function K(n){if(n===null||ye(n))return n;let e=typeof n=="string"?JSON.stringify(n.length>48?`${n.slice(0,48)}\u2026`:n):`a value of type ${typeof n}`;console.warn(`[gem] showPortraitBorders: ${e} 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.`);}var D={now:()=>Date.now(),setTimeout:(n,e)=>setTimeout(n,e),clearTimeout:n=>clearTimeout(n)},k=class{constructor(e=D){this.clock=e;}clock;tokens=new Map;lastRefill=new Map;allow(e){let t=w[e];if(t===void 0)return false;let r=this.clock.now(),s=this.lastRefill.get(e)??r,i=this.tokens.get(e)??t,d=Math.max(0,r-s),a=Math.min(t,i+d*t/6e4);return a<1?(this.tokens.set(e,a),this.lastRefill.set(e,r),false):(this.tokens.set(e,a-1),this.lastRefill.set(e,r),true)}};function T(n,e){return n.toLowerCase()===e.toLowerCase()}function C(n){let e=n.toLowerCase();return e==="localhost"||e.endsWith(".localhost")||e==="[::1]"||e==="::1"?true:/^127(\.\d{1,3}){3}$/.test(e)}function Y(n,e){if(typeof e!="string"||e==="")return false;let t,r;try{t=new URL(n),r=new URL(e);}catch{return false}return t.protocol!=="http:"&&t.protocol!=="https:"||r.protocol!=="http:"&&r.protocol!=="https:"||t.username!==""||t.password!==""||t.pathname!=="/"||t.search!==""||t.hash!==""||t.port===""?false:C(t.hostname)&&C(r.hostname)}function J(n,e){if(n.min>n.max||e.min>e.max)return null;let t=Math.min(n.max,e.max),r=Math.max(n.min,e.min);return t>=r?t:null}var Z="[gem: use gem.text.toPlain()]",Q=new Set,X;function fe(n){try{if(Q.has(n))return;let e=globalThis.location;if(X??=!!e?.port&&C(e.hostname??""),!X)return;Q.add(n),console.warn(`[gem] ${n} is PlayerText, turned into a string: players see ${Z}. Render text.toPlain(value) (text is in @indiegems/gem-web-sdk/sdk).
2
- ${new Error().stack}`);}catch{}}var F=class{#e;#t;constructor(e,t="player text"){this.#e=e,this.#t=t,Object.freeze(this);}toString(){return fe(this.#t),Z}toJSON(){return this.toString()}get[Symbol.toStringTag](){return "PlayerText"}static unwrap(e){return e.#e}};function $(n,e){return new F(n,e)}function ee(n){return Math.min(16384,Math.max(0,Math.round(Number.isFinite(n)?n:0)))}function he(n,e){if(n!==true)return {visible:false};let t=[],r=false;for(let s of Array.isArray(e)?e:[])s==="score"?r=true:typeof s=="string"&&s.length>0&&s.length<=64&&!t.includes(s)&&t.push(s);return !r&&t.length===0?{visible:true}:{visible:true,currencies:t.slice(0,8),score:r}}function ve(n){let e=[];for(let t of Array.isArray(n)?n:[]){if(e.length===4)break;if(typeof t!="object"||t===null)continue;let{id:r,label:s}=t;typeof r!="string"||r.length===0||r.length>32||typeof s!="string"||s.trim().length===0||e.some(i=>i.id===r)||e.push({id:r,label:s.trim().slice(0,40)});}return e.length===0?{settings:false}:{settings:false,items:e}}function te(n,e){return Math.min(e,Math.max(0,Math.round(Number.isFinite(n)?n:0)))}function be(n){let e=n.untrusted,t=$(e.displayName,"gem.player.displayName"),r=e.displayTag||e.displayHandle,s=r?$(r,"gem.player.displayTag"):void 0,i=n.discriminator||n.displayTag,d=i&&/^[0-9]{1,8}$/.test(i)?i:void 0,a=n.avatarIconUrl||n.avatarUrl,u=/^[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$/i,p=n.avatarIconId,y=n.avatar3dId,c={displayName:t,displayHandle:s??t,badges:(n.badges??[]).map(g=>({itemId:g.itemId,iconUrl:g.iconUrl}))};n.playerId&&(c.playerId=n.playerId),d&&(c.discriminator=d),s&&(c.displayTag=s),a&&(c.avatarIconUrl=c.avatarUrl=a),p&&u.test(p)&&(c.avatarIconId=p),y&&u.test(y)&&(c.avatar3dId=y),n.avatar3dModelUrl&&(c.avatar3dModelUrl=n.avatar3dModelUrl),n.avatar3dIconUrl&&(c.avatar3dIconUrl=n.avatar3dIconUrl),n.avatars3d&&(c.avatars3d=n.avatars3d.filter(g=>u.test(g.id)&&/^[a-z][a-z0-9_]*$/.test(g.family)).filter((g,A,I)=>I.findIndex(q=>q.family==g.family)==A).map(({family:g,id:A,modelUrl:I,iconUrl:q})=>({family:g,id:A,modelUrl:I,iconUrl:q})));let f=n.boost,b=(f?.multipliers??[]).map(g=>({currencyId:g.currencyId,currencySlug:g.currencySlug,multiplier:g.multiplierBp/1e4})),E=b.some(g=>g.multiplier>1);return c.boost=E&&f?.expiresInDays?{active:E,multipliers:b,expiresInDays:f.expiresInDays}:{active:E,multipliers:b},c}var Re=1e4,we=["appLinks","openGame","store"],ke=new WeakMap;var S=class{constructor(e){this.options=e;if(e.expectedSource===void 0||e.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=e.expectedSource,this.clock=e.clock??D,this.inbound=new k(this.clock),this.outbound=new k(this.clock);}options;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(e){return this.peerCapabilities.includes(e)}get protocolVersion(){return this.version}get unbiddenLaunch(){return this.unbidden}handleWindowMessage(e){if(e.source!==this.expectedSource)return this.refuse(e,"handshake did not come from the expected embedder window"),false;if(!(this.options.allowedEmbedders.some(a=>T(a,e.origin))||Y(e.origin,this.options.selfOrigin)||(ke.get(this)??[]).some(a=>T(a,e.origin))))return this.refuse(e,`embedder '${e.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=P["gem.handshake"]?.(e.data);if(r)return this.refuse(e,`malformed handshake: ${r}`),false;let s=e.data;if(!T(s.embedder,e.origin))return this.refuse(e,`handshake claims embedder '${s.embedder}' but arrived from '${e.origin}'`),false;let i=e.ports?.[0];if(!i)return this.refuse(e,"handshake transferred no MessagePort"),false;let d=J(R,s.versions);return d===null?(i.onmessage=null,i.postMessage({gem:l,type:"gem.error",code:"version_mismatch",message:`frame supports v${R.min}..v${R.max}`}),i.close(),this.transition("refused"),this.options.onError?.("version_mismatch"),false):(this.port=i,this.version=d,i.onmessage=a=>this.receive(a.data,a.ports??[]),i.start?.(),this.peerCapabilities=s.capabilities??[],this.unbidden=s.unbidden===true,i.postMessage({gem:l,type:"gem.handshake.accept",v:d,...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:l,type:"gem.ready"});}settled(e=Re){return this.state!=="waiting"?Promise.resolve(this.state):new Promise(t=>{let r=i=>{this.clock.clearTimeout(s),t(i);},s=this.clock.setTimeout(()=>{let i=this.settleWaiters.indexOf(r);i>=0&&this.settleWaiters.splice(i,1),t(this.state);},e);this.settleWaiters.push(r);})}progress(e,t){this.send({gem:l,type:"gem.progress",loaded:e,total:t});}requestToken(e,t){this.send({gem:l,type:"gem.requestToken",requestId:e,reason:t});}requestLocalServer(e,t){return this.parentSupports("localHost")?(this.send({gem:l,type:"gem.requestLocalServer",requestId:e,gameRoomId:t}),true):false}exit(e){this.send(e===void 0?{gem:l,type:"gem.exit"}:{gem:l,type:"gem.exit",reason:e});}openExternal(e){this.send({gem:l,type:"gem.openExternal",url:e});}requestFullscreen(e){this.send({gem:l,type:"gem.requestFullscreen",enabled:e});}reportMuted(e){return this.gated("audioState")?(this.send({gem:l,type:"gem.audioState",muted:e}),true):false}reportEngaged(){return this.engagedSent||!this.gated("engagement")||!this.send({gem:l,type:"gem.engaged"})?false:(this.engagedSent=true,true)}cueReward(e){if(!this.gated("rewardCue"))return false;let t={gem:l,type:"gem.rewardCue",currencySlug:String(e.currencySlug).slice(0,32)};return e.expected!==void 0&&Number.isFinite(e.expected)&&e.expected>=1&&(t.expected=Math.min(1e9,Math.round(e.expected))),e.origin&&(t.origin={x:ee(e.origin.x),y:ee(e.origin.y)}),this.send(t),true}showPortraitBorders(e){let t=K(e);return t===void 0||!this.gated("portraitBorders")?false:this.send(t===null?{gem:l,type:"gem.portraitBorders"}:{gem:l,type:"gem.portraitBorders",name:t})}setWalletVisible(e,t){return this.gated("walletDisplay")?this.send({gem:l,type:"gem.walletDisplay",...he(e,t?.only)}):false}setMenu(e){return this.gated("gameMenu")?this.send({gem:l,type:"gem.menu",...ve(e)}):false}declareSettings(e){return this.gated("gameSettings")?this.send({gem:l,type:"gem.settingsDeclare",settings:e}):false}setGemCorner(e){return this.gated("gemCorner")?this.send({gem:l,type:"gem.gemCorner",corner:e}):false}requestShare(e){return this.gated("share")?this.send({gem:l,type:"gem.requestShare",params:e.map(({key:t,value:r})=>({key:t,value:r}))}):false}requestAvatarPicker(e){return this.gated("avatarPicker")?this.send({gem:l,type:"gem.requestAvatarPicker",...e==="2d"||e==="3d"?{tab:e}:{}}):false}requestVibrate(e){return this.gated("vibration")?this.send({gem:l,type:"gem.vibrate",pattern:[...e]}):false}reportFrameStats(e){return this.gated("frameStats")?(this.send({gem:l,type:"gem.frameStats",fps:te(e.fps,1e3),frameTimeP95Ms:te(e.frameTimeP95Ms,1e5),counts:e.counts==="gameDraws"?"gameDraws":"pageFrames"}),true):false}sendGameContext(e){return this.state!=="connected"?false:(this.send({gem:l,type:"gem.gameContext",context:e}),true)}signal(e,t,r){this.send({gem:l,type:"gem.signal",signal:e,matchId:t,data:r});}dedicatedOffer(e,t,r){this.send({gem:l,type:"gem.dedicatedOffer",matchId:e,sdp:t,intent:r});}requestParty(e){return this.send({gem:l,type:"gem.partyRequest",requestId:e})}requestPurchase(e,t,r,s,i,d){return this.send({gem:l,type:"gem.requestPurchase",requestId:e,purchaseKind:t,targetId:r,currencySlug:s,...i===void 0?{}:{quantity:i},...d===void 0?{}:{catalogId:d}})}requestAvatar3dPurchase(e,t,r){return this.parentSupports("avatar3dPurchases")?this.send({gem:l,type:"gem.requestAvatar3dPurchase",requestId:e,itemId:t,currencySlug:r}):false}putDeviceSettings(e,t){return this.send({gem:l,type:"gem.deviceSettings.put",requestId:e,settings:t})}requestAd(e,t,r){return this.send({gem:l,type:"gem.requestAd",requestId:e,placement:t,format:r})}signalMoment(e,t,r){return this.send({gem:l,type:"gem.signalMoment",requestId:e,kind:t,...r===void 0?{}:{placement:r}})}readyCalled(){return this.readySent||this.readyPending}requestSignIn(e){return this.send({gem:l,type:"gem.requestSignIn",requestId:e})}requestFriendChallenge(e,t,r,s){return this.send({gem:l,type:"gem.requestFriendChallenge",requestId:e,playerId:t,template:r,...s===void 0?{}:{params:s}})}requestArcade(e){return this.gated(O(e.type))&&this.send({gem:l,...e})}close(){this.port&&(this.port.onmessage=null,this.port.close(),this.port=null),this.state!=="refused"&&this.transition("closed");}transition(e){if(this.state!==e){this.state=e;for(let t of this.settleWaiters.splice(0))t(e);this.options.onStateChange?.(e);}}refuseMalformed(e,t){if(!this.inbound.allow(x)){this.options.onDropped?.(e,t);return}this.fail(e,t);}dropUnknownType(e){this.inbound.allow(x)&&this.options.onDropped?.("unsupported",`ignored unknown message type '${e}'`);}refuse(e,t){e.ports?.[0]?.close?.(),this.inbound.allow(x)&&this.options.onRefused?.(t);}fail(e,t){this.options.onError?.(e,t),this.state==="connected"&&this.port&&this.send({gem:l,type:"gem.error",code:e,...t===void 0?{}:{message:t}});}gated(e){return this.state==="connected"&&this.parentSupports(e)}send(e){if(this.state!=="connected"||!this.port)return this.options.onDropped?.("internal",`dropped ${String(e.type)} before connect`),false;let t=e.type;if(!this.outbound.allow(t))return this.options.onError?.("rate_limited",`frame exceeded its own limit for ${t}`),false;let r=P[t]?.(e);return r?(this.options.onError?.("internal",`refusing to send malformed ${t}: ${r}`),false):(this.port.postMessage(this.version===null?e:{...e,v:this.version}),true)}receive(e,t=[]){let r=false;try{r=this.dispatchInbound(e,t);}finally{r||ne(t);}}dispatchInbound(e,t){if(this.state!=="connected")return false;let r=typeof e=="object"&&e!==null?e.type:void 0;if(typeof r!="string")return this.refuseMalformed("invalid_message","message has no type"),false;let s=P[r];if(!s)return this.dropUnknownType(r),false;if(!this.inbound.allow(r))return this.fail("rate_limited",`parent exceeded the limit for ${r}`),false;let i=O(r);if(i!==void 0&&!this.options.capabilities?.includes(i))return this.fail("unsupported_capability",`${r} requires the '${i}' capability, which this frame did not advertise`),false;let d=s(e);if(d)return this.fail("invalid_message",`${r}: ${d}`),false;let a=e;if(a.v!==this.version)return this.fail("invalid_message",`${r}: expected v ${String(this.version)}`),false;switch(r){case "gem.token":this.options.onToken?.(a);break;case "gem.pause":this.options.onPause?.(a);break;case "gem.menuSelect":this.options.onMenuSelect?.(a);break;case "gem.settingChange":this.options.onSettingChange?.(a);break;case "gem.prefs":this.options.onPrefs?.(a);break;case "gem.cohorts":this.options.onCohorts?.(a);break;case "gem.earnAllowanceChanged":this.options.onEarnAllowanceChanged?.();break;case "gem.viewport":this.options.onViewport?.(a);break;case "gem.willTerminate":this.options.onWillTerminate?.(a);break;case "gem.devtools":this.options.onDevtools?.(a.action);break;case "gem.requestContext":this.options.onRequestContext?.();break;case "gem.screenshot":{let u=t[0];if(!u){this.options.onError?.("invalid_message","gem.screenshot transferred no MessagePort");break}let p={maxWidth:a.maxWidth,maxHeight:a.maxHeight};if(!this.options.onScreenshot){u.close(),this.screenshotDenied("not_wired",false);break}return ne(t.slice(1)),this.options.onScreenshot(p,u,(y,c)=>this.screenshotDenied(y,c)),true}case "gem.token.denied":this.options.onTokenDenied?.(a);break;case "gem.localServer":this.options.onLocalServer?.(a);break;case "gem.localServer.denied":this.options.onLocalServerDenied?.(a);break;case "gem.player":{this.options.onPlayer?.(be(a));break}case "gem.roomEvent":this.options.onRoomEvent?.(a);break;case "gem.seatedEvent":this.options.onSeatedEvent?.(a);break;case "gem.autoStartFailedEvent":this.options.onAutoStartFailedEvent?.(a);break;case "gem.ticketEvent":this.options.onTicketEvent?.(a);break;case "gem.socketState":this.options.onSocketState?.(a);break;case "gem.signalEvent":this.options.onSignalEvent?.(a);break;case "gem.dedicatedEvent":this.options.onDedicatedEvent?.(a);break;case "gem.clusterTarget":this.options.onClusterTarget?.(a);break;case "gem.partyResult":this.options.onPartyResult?.(a);break;case "gem.purchaseResult":this.options.onPurchaseResult?.(a);break;case "gem.deviceSettings":this.options.onDeviceSettings?.(a);break;case "gem.deviceSettings.result":this.options.onDeviceSettingsResult?.(a);break;case "gem.adResult":this.options.onAdResult?.(a);break;case "gem.momentResult":this.options.onMomentResult?.(a);break;case "gem.signInResult":this.options.onSignInResult?.(a);break;case "gem.friendChallengeResult":this.options.onFriendChallengeResult?.(a);break;case "gem.error":this.options.onError?.(a.code,a.message);break;default:if(we.includes(O(r))){this.options.onArcadeAnswer?.(a);break}this.fail("unsupported",`no frame handler for '${r}'`);}return false}screenshotDenied(e,t){this.send({gem:l,type:"gem.screenshot.denied",reason:e,retryable:t});}};function ne(n){for(let e of n)try{e?.close?.();}catch{}}H();function Se(n){if((n.expectedSource??window.parent)===window)throw new Error("GemWebSdkBridge.connect: this document is not framed (window.parent === window), so there is no embedder to hand it a port. A game runs inside the Gem Arcade iframe; opening it directly will never complete a handshake.");let e=new S({...n,expectedSource:n.expectedSource??window.parent}),t=r=>{let s=e.handleWindowMessage({data:r.data,origin:r.origin,source:r.source,ports:r.ports});return s&&window.removeEventListener("message",t),s};return window.addEventListener("message",t),B(window,t),e}var re={connect:Se,GemFrame:S,PROTOCOL:l,SUPPORTED_VERSIONS:R};window.GemWebSdkBridge=re;var cn=re;return cn;})();
1
+ var GemWebSdkBridgeBundle=(function(){'use strict';var L="GemWebSdkHandshake";function oe(){return typeof document>"u"?void 0:document.readyState}var U=()=>Date.now();function H(n={}){let e=n.win??window,t=e,r=t[L];if(r!==void 0)return r;let s=n.readyState??oe,i=n.now??U,d=[],a=[],u=y=>{let c=y.data;!c||c.type!=="gem.handshake"||(d.length>=4&&(d.shift(),a.shift(),p.dropped+=1),d.push(y),a.push(i()));};e.addEventListener("message",u);let p={events:d,times:a,dropped:0,late:s()==="complete",uninstall(){e.removeEventListener("message",u);}};return t[L]=p,p}function B(n,e,t={}){let r=n[L];if(r===void 0)return null;let s=t.ttlMs??9500,i=t.now??U,d=r.events.splice(0),a=r.times?.splice(0),u=0,p=0,y=false,c=i()-s;for(let f=d.length-1;f>=0;f--){let b=a?.[f];if(b!==void 0&&b<c){p+=1;continue}if(u+=1,e(d[f])===true){y=true;break}}return r.uninstall(),{capture:r,delivered:u,stale:p,accepted:y}}var l="gem-web-bridge",R={min:1,max:1},ae=65536,N=8,G=5e3,W=1024;var w=Object.create(null),x="__malformed";var j=Object.create(null),se=Object.freeze([["gem.asyncOpponents.","asyncOpponents"],["gem.vibration.","vibration"],["gem.viewport.","reservedAreas"],["gem.moments.","moments"],["gem.prefs.","audioChannels"]]);function O(n){let e=j[n];if(e!==void 0)return e;for(let[t,r]of se)if(n.startsWith(t))return r}function ie(n){let e=/^([a-z][a-z0-9+.-]*):\/\/([a-z0-9.-]+|\[[0-9a-f:.]+\])(:([0-9]{1,5}))?$/i.exec(n);return e===null?false:e[4]===void 0||Number(e[4])<=65535}function le(n){let e;try{e=new URL(n);}catch{return false}return e.username!==""||e.password!==""?false:e.protocol==="https:"?true:e.protocol==="http:"&&(e.hostname==="localhost"||e.hostname==="127.0.0.1")}function de(n){return /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i.test(n)}function me(n){return /^[a-z_]+$/.test(n)}function ue(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("--")}function _(n,e,t){if(e>N)return `nesting deeper than ${N}`;if(++t.nodes>G)return `more than ${G} values`;if(t.bytes>t.cap)return `over the ${t.cap} byte limit`;if(n==null)return t.bytes+=4,null;let r=typeof n;if(r==="boolean")return t.bytes+=5,null;if(r==="number")return t.bytes+=8,null;if(r==="string")return t.bytes+=n.length*3+2,null;if(r!=="object")return `${r} is not plain data`;let s=n;if(t.seen.has(s))return "a repeated or cyclic object reference";if(t.seen.add(s),Array.isArray(n)){if(n.length>W)return `an array of ${n.length} entries, over the ${W} limit`;t.bytes+=2;for(let a of n){let u=_(a,e+1,t);if(u)return u}return null}let i=Object.getPrototypeOf(n);if(i!==Object.prototype&&i!==null)return "a value that is not a plain object";t.bytes+=2;let d=n;for(let a in d){if(!Object.prototype.hasOwnProperty.call(d,a))continue;if(++t.nodes>G)return `more than ${G} values`;if(t.bytes+=a.length*3+4,t.bytes>t.cap)return `over the ${t.cap} byte limit`;let u=_(d[a],e+1,t);if(u)return u}return null}function ce(n,e=ae){let t={nodes:0,bytes:0,seen:new Set,cap:e},r=_(n,0,t);return r?`message contains ${r}`:t.bytes>e?`message is over the ${e} byte limit`:null}var ge={origin:[ie,"not a well-formed origin"],url:[le,"not a safe absolute http(s) URL"],bcp47:[de,"not a well-formed BCP 47 tag"],code:[me,"not a code of lower-case letters and underscores"],gameRef:[ue,"not a game id or web address"]};function V(n,e,t){for(let r of e){let s=t===""?r[0]:`${t}.${r[0]}`,i=n[r[0]];if(i==null){if(r[1]<"a")continue;return `${s}: required`}let d=M(i,r,s);if(d)return d}return null}function M(n,e,t){let r=e[1].toLowerCase();switch(r){case "u":return typeof n=="boolean"?null:M(n,typeof n=="string"?[e[0],"s",e[2]]:[e[0],"n",e[3],e[4]],t);case "s":{if(typeof n!="string")return `${t}: expected string`;if(n.length>e[2])return `${t}: exceeds ${e[2]} characters`;if(e[3]){let[s,i]=ge[e[3]];if(!s(n))return `${t}: ${i}`}return null}case "n":case "i":return typeof n!="number"||!(r=="i"?Number.isInteger:Number.isFinite)(n)?`${t}: expected ${r=="i"?"integer":"number"}`:n<e[2]||n>e[3]?`${t}: out of range ${e[2]}..${e[3]}`:null;case "b":return typeof n=="boolean"?null:`${t}: expected boolean`;case "e":{let s=e[2];return s.includes(n)?null:`${t}: not one of ${s.join("|")}`}case "o":return typeof n!="object"||n===null||Array.isArray(n)?`${t}: expected object`:V(n,e[2],t);case "j":return typeof n!="object"||n===null||Array.isArray(n)?`${t}: expected a plain object`:null;case "a":{if(!Array.isArray(n))return `${t}: expected array`;if(n.length>e[2])return `${t}: exceeds ${e[2]} items`;let s=e[3],i=`${t}[]`;for(let d of n){if(d==null){if(s[1]<"a")continue;return `${i}: required`}let a=M(d,s,i);if(a)return a}return null}default:return `${t}: unknown kind ${String(r)}`}}var z=Object.create(null);function o(n,e,t,r,s){return w[n]=t,r&&(j[n]=r),z[n]=i=>{let d=ce(i,s);if(d)return d;if(typeof i!="object"||i===null||Array.isArray(i))return "message: expected object";let a=i;return a.gem!==l?"gem: wrong protocol marker":a.type!==n?`type: expected ${n}`:V(a,e,"")}}var m=["requestId","s",64],h=["outcome","e",["ok","declined","unavailable"]],v=["reason","S",64,"code"];o("gem.handshake",[["versions","o",[["min","i",1,1e3],["max","i",1,1e3]]],["capabilities","A",64,["","s",64]],["embedder","s",2048,"origin"],["unbidden","B"]],30);o("gem.handshake.accept",[["v","i",1,1e3],["capabilities","A",64,["","s",64]]],5);o("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);o("gem.token.denied",[m,["retryable","b"],["retryAfterMs","I",0,36e5],["reason","e",["chain_exhausted","unavailable"]],["correlationId","s",64]],30);o("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],["currencySlug","s",32],["multiplierBp","i",1e4,1e6]]]],["expiresInDays","I",1,7]]]],12);o("gem.pause",[["paused","b"]],120);o("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"]]]],60);o("gem.cohorts",[["cohorts","a",8,["","o",[["experimentId","s",36],["variantKey","s",32]]]]],12,"cohorts");o("gem.earnAllowanceChanged",[],30,"earnAllowance");o("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");o("gem.deviceSettings",[["settings","J"]],12,"deviceSettings");o("gem.willTerminate",[["reason","e",["grant_expiring","session_ended","navigating","kill_switch"]],["graceMs","i",0,6e4]],10);o("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");o("gem.socketState",[["connected","b"],["reason","E",["connecting","dropped","watchdog","terminal"]]],12,"socketState");o("gem.seatedEvent",[["roomId","s",64]],60,"partySeating");o("gem.autoStartFailedEvent",[["roomId","s",64],["code","s",64]],30,"autoStartFailure");o("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");o("gem.signalEvent",[["event","e",["hello","offer","answer","ice","peerJoined","peerLeft","migration","matchEnded","error"]],["matchId","s",64],["data","j"]],1800,"p2p");o("gem.dedicatedEvent",[["event","e",["answer","error","matchEnded"]],["matchId","s",64],["sdp","S",10240],["sessionId","S",64],["code","S",64],["message","S",256],["reason","S",64]],120,"dedicated");o("gem.partyResult",[m,["partyId","S",64]],30,"parties");o("gem.clusterTarget",[["clusterTargetId","s",64],["clusterTargetIds","A",3,["","s",64]]],12,"clusterTarget");o("gem.devtools",[["action","e",["open","close","toggle"]]],30,"devtools");o("gem.screenshot",[["maxWidth","i",1,7680],["maxHeight","i",1,4320]],6,"screenshot");o("gem.screenshot.denied",[["reason","e",["not_wired","no_source","unsupported","unreadable","tainted","too_large","busy","declined","failed"]],["retryable","b"]],12,"screenshot");o("gem.requestContext",[],12,"feedbackContext");o("gem.gameContext",[["context","j"]],12,"feedbackContext");o("gem.signal",[["signal","e",["join","offer","answer","ice","migrationAck","hostUnreachable"]],["matchId","s",64],["data","j"]],1200,"p2p");o("gem.dedicatedOffer",[["matchId","s",64],["sdp","s",10240],["intent","e",["initial","ice_restart","full_reconnect"]]],60,"dedicated");o("gem.partyRequest",[m],12,"parties");o("gem.ready",[],5);o("gem.engaged",[],5,"engagement");o("gem.progress",[["loaded","i",0,1099511627776],["total","i",0,1099511627776]],600);o("gem.requestToken",[m,["reason","e",["initial","expiring","rejected"]]],12);o("gem.requestLocalServer",[m,["gameRoomId","s",64]],6,"localHost");o("gem.localServer",[m,["serverId","S",128]],30,"localHost");o("gem.localServer.denied",[m,["retryable","b"],["reason","e",["refused","unavailable"]]],30,"localHost");o("gem.exit",[["reason","S",64]],5);o("gem.openExternal",[["url","s",2048,"url"]],6);o("gem.requestFullscreen",[["enabled","b"]],30);o("gem.audioState",[["muted","b"]],120,"audioState");o("gem.rewardCue",[["currencySlug","s",32],["expected","I",1,1e9],["origin","O",[["x","i",0,16384],["y","i",0,16384]]]],60,"rewardCue");o("gem.walletDisplay",[["visible","b"],["currencies","A",8,["","s",64]],["score","B"]],60,"walletDisplay");o("gem.controlsDeclare",[["uses","o",[["lookSensitivity","S",16,"code"],["gamepadLookSensitivity","S",16,"code"],["invertLookY","S",16,"code"],["touchMovement","S",16,"code"]]]],60,"controls");o("gem.menu",[["settings","b"],["items","A",4,["","o",[["id","s",32],["label","s",40]]]]],60,"gameMenu");o("gem.menuSelect",[["entry","e",["settings","item"]],["id","S",32]],60,"gameMenu");o("gem.gemCorner",[["corner","e",["top-left","top-right","bottom-left","bottom-right"]]],12,"gemCorner");o("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");o("gem.settingChange",[["id","s",40],["value","u",40,-1e6,1e6]],120,"gameSettings");o("gem.requestShare",[["params","a",16,["","o",[["key","s",64],["value","s",512]]]]],6,"share");o("gem.requestAvatarPicker",[["tab","E",["2d","3d"]]],6,"avatarPicker");o("gem.vibrate",[["pattern","a",99,["","i",0,1e4]]],600,"vibration");o("gem.frameStats",[["fps","i",0,1e3],["frameTimeP95Ms","i",0,1e5],["counts","E",["pageFrames","gameDraws"]]],120,"frameStats");o("gem.portraitBorders",[["name","S",40]],60,"portraitBorders");o("gem.error",[["code","e",["version_mismatch","handshake_timeout","handshake_duplicate","invalid_message","rate_limited","token_unavailable","unsupported","unsupported_capability","internal"]],["message","S",256]],60);o("gem.requestPurchase",[m,["purchaseKind","e",["item","recipe"]],["targetId","s",64],["currencySlug","s",32],["quantity","I",1,10],["catalogId","S",64]],12,"purchases");o("gem.requestAvatar3dPurchase",[m,["itemId","s",64],["currencySlug","s",32]],12,"avatar3dPurchases");o("gem.purchaseResult",[m,h,v,["purchaseId","S",64]],30,"purchases");o("gem.deviceSettings.put",[m,["settings","j"]],30,"deviceSettings");o("gem.deviceSettings.result",[m,["outcome","e",["stored","too_large","unavailable"]]],30,"deviceSettings");o("gem.requestAd",[m,["placement","s",32],["format","e",["interstitial","rewarded"]]],6,"ads");o("gem.adResult",[m,["outcome","e",["shown","no_ad","unavailable"]]],12,"ads");o("gem.signalMoment",[m,["kind","e",["level_end","run_end","match_end","menu_return","other_break"]],["placement","S",32],["wall","B"]],6,"moments");o("gem.momentResult",[m,["shown","b"]],12,"moments");o("gem.requestSignIn",[m],6,"signIn");o("gem.signInResult",[m,["outcome","e",["signed_in","declined","unavailable"]]],12,"signIn");o("gem.requestFriendChallenge",[m,["playerId","s",64],["template","s",64],["params","J"]],12,"friendChallenges");o("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");o("gem.requestAppLinks",[m],12,"appLinks");o("gem.appLinksResult",[m,h,v,["arcade","O",[["store","e",["app_store","google_play"]]]],["game","O",[["store","e",["app_store","google_play"]]]]],30,"appLinks");o("gem.requestOpenApp",[m,["target","e",["arcade","game"]]],6,"appLinks");o("gem.openAppResult",[m,h,v],30,"appLinks");o("gem.requestOpenGame",[m,["ref","s",40,"gameRef"]],6,"openGame");o("gem.openGameResult",[m,h,v],30,"openGame");o("gem.requestSimilarGames",[m,["limit","I",1,24]],12,"openGame");o("gem.similarGamesResult",[m,h,v,["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);o("gem.requestStoreOffers",[m],12,"store");o("gem.storeOffersResult",[m,h,v,["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]]]]]]],30,"store",262144);o("gem.requestStorePurchase",[m,["skuId","s",64]],12,"store");o("gem.storePurchaseResult",[m,h,v],30,"store");o("gem.storePurchaseSettled",[["skuId","s",64]],30,"store");var P=Object.freeze(z);w.__malformed=60;Object.freeze(w);Object.freeze(j);var pe=/^[a-z0-9][a-z0-9_-]{0,39}$/;function ye(n){return typeof n=="string"&&pe.test(n)}function K(n){if(n===null||ye(n))return n;let e=typeof n=="string"?JSON.stringify(n.length>48?`${n.slice(0,48)}\u2026`:n):`a value of type ${typeof n}`;console.warn(`[gem] showPortraitBorders: ${e} 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.`);}var D={now:()=>Date.now(),setTimeout:(n,e)=>setTimeout(n,e),clearTimeout:n=>clearTimeout(n)},k=class{constructor(e=D){this.clock=e;}clock;tokens=new Map;lastRefill=new Map;allow(e){let t=w[e];if(t===void 0)return false;let r=this.clock.now(),s=this.lastRefill.get(e)??r,i=this.tokens.get(e)??t,d=Math.max(0,r-s),a=Math.min(t,i+d*t/6e4);return a<1?(this.tokens.set(e,a),this.lastRefill.set(e,r),false):(this.tokens.set(e,a-1),this.lastRefill.set(e,r),true)}};function T(n,e){return n.toLowerCase()===e.toLowerCase()}function C(n){let e=n.toLowerCase();return e==="localhost"||e.endsWith(".localhost")||e==="[::1]"||e==="::1"?true:/^127(\.\d{1,3}){3}$/.test(e)}function Y(n,e){if(typeof e!="string"||e==="")return false;let t,r;try{t=new URL(n),r=new URL(e);}catch{return false}return t.protocol!=="http:"&&t.protocol!=="https:"||r.protocol!=="http:"&&r.protocol!=="https:"||t.username!==""||t.password!==""||t.pathname!=="/"||t.search!==""||t.hash!==""||t.port===""?false:C(t.hostname)&&C(r.hostname)}function J(n,e){if(n.min>n.max||e.min>e.max)return null;let t=Math.min(n.max,e.max),r=Math.max(n.min,e.min);return t>=r?t:null}var Z="[gem: use gem.text.toPlain()]",Q=new Set,X;function fe(n){try{if(Q.has(n))return;let e=globalThis.location;if(X??=!!e?.port&&C(e.hostname??""),!X)return;Q.add(n),console.warn(`[gem] ${n} is PlayerText, turned into a string: players see ${Z}. Render text.toPlain(value) (text is in @indiegems/gem-web-sdk/sdk).
2
+ ${new Error().stack}`);}catch{}}var F=class{#e;#t;constructor(e,t="player text"){this.#e=e,this.#t=t,Object.freeze(this);}toString(){return fe(this.#t),Z}toJSON(){return this.toString()}get[Symbol.toStringTag](){return "PlayerText"}static unwrap(e){return e.#e}};function $(n,e){return new F(n,e)}function ee(n){return Math.min(16384,Math.max(0,Math.round(Number.isFinite(n)?n:0)))}function he(n,e){if(n!==true)return {visible:false};let t=[],r=false;for(let s of Array.isArray(e)?e:[])s==="score"?r=true:typeof s=="string"&&s.length>0&&s.length<=64&&!t.includes(s)&&t.push(s);return !r&&t.length===0?{visible:true}:{visible:true,currencies:t.slice(0,8),score:r}}function ve(n){let e=[];for(let t of Array.isArray(n)?n:[]){if(e.length===4)break;if(typeof t!="object"||t===null)continue;let{id:r,label:s}=t;typeof r!="string"||r.length===0||r.length>32||typeof s!="string"||s.trim().length===0||e.some(i=>i.id===r)||e.push({id:r,label:s.trim().slice(0,40)});}return e.length===0?{settings:false}:{settings:false,items:e}}function te(n,e){return Math.min(e,Math.max(0,Math.round(Number.isFinite(n)?n:0)))}function be(n){let e=n.untrusted,t=$(e.displayName,"gem.player.displayName"),r=e.displayTag||e.displayHandle,s=r?$(r,"gem.player.displayTag"):void 0,i=n.discriminator||n.displayTag,d=i&&/^[0-9]{1,8}$/.test(i)?i:void 0,a=n.avatarIconUrl||n.avatarUrl,u=/^[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$/i,p=n.avatarIconId,y=n.avatar3dId,c={displayName:t,displayHandle:s??t,badges:(n.badges??[]).map(g=>({itemId:g.itemId,iconUrl:g.iconUrl}))};n.playerId&&(c.playerId=n.playerId),d&&(c.discriminator=d),s&&(c.displayTag=s),a&&(c.avatarIconUrl=c.avatarUrl=a),p&&u.test(p)&&(c.avatarIconId=p),y&&u.test(y)&&(c.avatar3dId=y),n.avatar3dModelUrl&&(c.avatar3dModelUrl=n.avatar3dModelUrl),n.avatar3dIconUrl&&(c.avatar3dIconUrl=n.avatar3dIconUrl),n.avatars3d&&(c.avatars3d=n.avatars3d.filter(g=>u.test(g.id)&&/^[a-z][a-z0-9_]*$/.test(g.family)).filter((g,A,I)=>I.findIndex(q=>q.family==g.family)==A).map(({family:g,id:A,modelUrl:I,iconUrl:q})=>({family:g,id:A,modelUrl:I,iconUrl:q})));let f=n.boost,b=(f?.multipliers??[]).map(g=>({currencyId:g.currencyId,currencySlug:g.currencySlug,multiplier:g.multiplierBp/1e4})),E=b.some(g=>g.multiplier>1);return c.boost=E&&f?.expiresInDays?{active:E,multipliers:b,expiresInDays:f.expiresInDays}:{active:E,multipliers:b},c}var Re=1e4,we=["appLinks","openGame","store"],ke=new WeakMap;var S=class{constructor(e){this.options=e;if(e.expectedSource===void 0||e.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=e.expectedSource,this.clock=e.clock??D,this.inbound=new k(this.clock),this.outbound=new k(this.clock);}options;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(e){return this.peerCapabilities.includes(e)}get protocolVersion(){return this.version}get unbiddenLaunch(){return this.unbidden}handleWindowMessage(e){if(e.source!==this.expectedSource)return this.refuse(e,"handshake did not come from the expected embedder window"),false;if(!(this.options.allowedEmbedders.some(a=>T(a,e.origin))||Y(e.origin,this.options.selfOrigin)||(ke.get(this)??[]).some(a=>T(a,e.origin))))return this.refuse(e,`embedder '${e.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=P["gem.handshake"]?.(e.data);if(r)return this.refuse(e,`malformed handshake: ${r}`),false;let s=e.data;if(!T(s.embedder,e.origin))return this.refuse(e,`handshake claims embedder '${s.embedder}' but arrived from '${e.origin}'`),false;let i=e.ports?.[0];if(!i)return this.refuse(e,"handshake transferred no MessagePort"),false;let d=J(R,s.versions);return d===null?(i.onmessage=null,i.postMessage({gem:l,type:"gem.error",code:"version_mismatch",message:`frame supports v${R.min}..v${R.max}`}),i.close(),this.transition("refused"),this.options.onError?.("version_mismatch"),false):(this.port=i,this.version=d,i.onmessage=a=>this.receive(a.data,a.ports??[]),i.start?.(),this.peerCapabilities=s.capabilities??[],this.unbidden=s.unbidden===true,i.postMessage({gem:l,type:"gem.handshake.accept",v:d,...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:l,type:"gem.ready"});}settled(e=Re){return this.state!=="waiting"?Promise.resolve(this.state):new Promise(t=>{let r=i=>{this.clock.clearTimeout(s),t(i);},s=this.clock.setTimeout(()=>{let i=this.settleWaiters.indexOf(r);i>=0&&this.settleWaiters.splice(i,1),t(this.state);},e);this.settleWaiters.push(r);})}progress(e,t){this.send({gem:l,type:"gem.progress",loaded:e,total:t});}requestToken(e,t){this.send({gem:l,type:"gem.requestToken",requestId:e,reason:t});}requestLocalServer(e,t){return this.parentSupports("localHost")?(this.send({gem:l,type:"gem.requestLocalServer",requestId:e,gameRoomId:t}),true):false}exit(e){this.send(e===void 0?{gem:l,type:"gem.exit"}:{gem:l,type:"gem.exit",reason:e});}openExternal(e){this.send({gem:l,type:"gem.openExternal",url:e});}requestFullscreen(e){this.send({gem:l,type:"gem.requestFullscreen",enabled:e});}reportMuted(e){return this.gated("audioState")?(this.send({gem:l,type:"gem.audioState",muted:e}),true):false}reportEngaged(){return this.engagedSent||!this.gated("engagement")||!this.send({gem:l,type:"gem.engaged"})?false:(this.engagedSent=true,true)}cueReward(e){if(!this.gated("rewardCue"))return false;let t={gem:l,type:"gem.rewardCue",currencySlug:String(e.currencySlug).slice(0,32)};return e.expected!==void 0&&Number.isFinite(e.expected)&&e.expected>=1&&(t.expected=Math.min(1e9,Math.round(e.expected))),e.origin&&(t.origin={x:ee(e.origin.x),y:ee(e.origin.y)}),this.send(t),true}showPortraitBorders(e){let t=K(e);return t===void 0||!this.gated("portraitBorders")?false:this.send(t===null?{gem:l,type:"gem.portraitBorders"}:{gem:l,type:"gem.portraitBorders",name:t})}setWalletVisible(e,t){return this.gated("walletDisplay")?this.send({gem:l,type:"gem.walletDisplay",...he(e,t?.only)}):false}setMenu(e){return this.gated("gameMenu")?this.send({gem:l,type:"gem.menu",...ve(e)}):false}declareSettings(e){return this.gated("gameSettings")?this.send({gem:l,type:"gem.settingsDeclare",settings:e}):false}setGemCorner(e){return this.gated("gemCorner")?this.send({gem:l,type:"gem.gemCorner",corner:e}):false}requestShare(e){return this.gated("share")?this.send({gem:l,type:"gem.requestShare",params:e.map(({key:t,value:r})=>({key:t,value:r}))}):false}requestAvatarPicker(e){return this.gated("avatarPicker")?this.send({gem:l,type:"gem.requestAvatarPicker",...e==="2d"||e==="3d"?{tab:e}:{}}):false}requestVibrate(e){return this.gated("vibration")?this.send({gem:l,type:"gem.vibrate",pattern:[...e]}):false}reportFrameStats(e){return this.gated("frameStats")?(this.send({gem:l,type:"gem.frameStats",fps:te(e.fps,1e3),frameTimeP95Ms:te(e.frameTimeP95Ms,1e5),counts:e.counts==="gameDraws"?"gameDraws":"pageFrames"}),true):false}sendGameContext(e){return this.state!=="connected"?false:(this.send({gem:l,type:"gem.gameContext",context:e}),true)}signal(e,t,r){this.send({gem:l,type:"gem.signal",signal:e,matchId:t,data:r});}dedicatedOffer(e,t,r){this.send({gem:l,type:"gem.dedicatedOffer",matchId:e,sdp:t,intent:r});}requestParty(e){return this.send({gem:l,type:"gem.partyRequest",requestId:e})}requestPurchase(e,t,r,s,i,d){return this.send({gem:l,type:"gem.requestPurchase",requestId:e,purchaseKind:t,targetId:r,currencySlug:s,...i===void 0?{}:{quantity:i},...d===void 0?{}:{catalogId:d}})}requestAvatar3dPurchase(e,t,r){return this.parentSupports("avatar3dPurchases")?this.send({gem:l,type:"gem.requestAvatar3dPurchase",requestId:e,itemId:t,currencySlug:r}):false}putDeviceSettings(e,t){return this.send({gem:l,type:"gem.deviceSettings.put",requestId:e,settings:t})}requestAd(e,t,r){return this.send({gem:l,type:"gem.requestAd",requestId:e,placement:t,format:r})}signalMoment(e,t,r,s){return this.send({gem:l,type:"gem.signalMoment",requestId:e,kind:t,...r===void 0?{}:{placement:r},...s===true?{wall:true}:{}})}readyCalled(){return this.readySent||this.readyPending}requestSignIn(e){return this.send({gem:l,type:"gem.requestSignIn",requestId:e})}requestFriendChallenge(e,t,r,s){return this.send({gem:l,type:"gem.requestFriendChallenge",requestId:e,playerId:t,template:r,...s===void 0?{}:{params:s}})}requestArcade(e){return this.gated(O(e.type))&&this.send({gem:l,...e})}close(){this.port&&(this.port.onmessage=null,this.port.close(),this.port=null),this.state!=="refused"&&this.transition("closed");}transition(e){if(this.state!==e){this.state=e;for(let t of this.settleWaiters.splice(0))t(e);this.options.onStateChange?.(e);}}refuseMalformed(e,t){if(!this.inbound.allow(x)){this.options.onDropped?.(e,t);return}this.fail(e,t);}dropUnknownType(e){this.inbound.allow(x)&&this.options.onDropped?.("unsupported",`ignored unknown message type '${e}'`);}refuse(e,t){e.ports?.[0]?.close?.(),this.inbound.allow(x)&&this.options.onRefused?.(t);}fail(e,t){this.options.onError?.(e,t),this.state==="connected"&&this.port&&this.send({gem:l,type:"gem.error",code:e,...t===void 0?{}:{message:t}});}gated(e){return this.state==="connected"&&this.parentSupports(e)}send(e){if(this.state!=="connected"||!this.port)return this.options.onDropped?.("internal",`dropped ${String(e.type)} before connect`),false;let t=e.type;if(!this.outbound.allow(t))return this.options.onError?.("rate_limited",`frame exceeded its own limit for ${t}`),false;let r=P[t]?.(e);return r?(this.options.onError?.("internal",`refusing to send malformed ${t}: ${r}`),false):(this.port.postMessage(this.version===null?e:{...e,v:this.version}),true)}receive(e,t=[]){let r=false;try{r=this.dispatchInbound(e,t);}finally{r||ne(t);}}dispatchInbound(e,t){if(this.state!=="connected")return false;let r=typeof e=="object"&&e!==null?e.type:void 0;if(typeof r!="string")return this.refuseMalformed("invalid_message","message has no type"),false;let s=P[r];if(!s)return this.dropUnknownType(r),false;if(!this.inbound.allow(r))return this.fail("rate_limited",`parent exceeded the limit for ${r}`),false;let i=O(r);if(i!==void 0&&!this.options.capabilities?.includes(i))return this.fail("unsupported_capability",`${r} requires the '${i}' capability, which this frame did not advertise`),false;let d=s(e);if(d)return this.fail("invalid_message",`${r}: ${d}`),false;let a=e;if(a.v!==this.version)return this.fail("invalid_message",`${r}: expected v ${String(this.version)}`),false;switch(r){case "gem.token":this.options.onToken?.(a);break;case "gem.pause":this.options.onPause?.(a);break;case "gem.menuSelect":this.options.onMenuSelect?.(a);break;case "gem.settingChange":this.options.onSettingChange?.(a);break;case "gem.prefs":this.options.onPrefs?.(a);break;case "gem.cohorts":this.options.onCohorts?.(a);break;case "gem.earnAllowanceChanged":this.options.onEarnAllowanceChanged?.();break;case "gem.viewport":this.options.onViewport?.(a);break;case "gem.willTerminate":this.options.onWillTerminate?.(a);break;case "gem.devtools":this.options.onDevtools?.(a.action);break;case "gem.requestContext":this.options.onRequestContext?.();break;case "gem.screenshot":{let u=t[0];if(!u){this.options.onError?.("invalid_message","gem.screenshot transferred no MessagePort");break}let p={maxWidth:a.maxWidth,maxHeight:a.maxHeight};if(!this.options.onScreenshot){u.close(),this.screenshotDenied("not_wired",false);break}return ne(t.slice(1)),this.options.onScreenshot(p,u,(y,c)=>this.screenshotDenied(y,c)),true}case "gem.token.denied":this.options.onTokenDenied?.(a);break;case "gem.localServer":this.options.onLocalServer?.(a);break;case "gem.localServer.denied":this.options.onLocalServerDenied?.(a);break;case "gem.player":{this.options.onPlayer?.(be(a));break}case "gem.roomEvent":this.options.onRoomEvent?.(a);break;case "gem.seatedEvent":this.options.onSeatedEvent?.(a);break;case "gem.autoStartFailedEvent":this.options.onAutoStartFailedEvent?.(a);break;case "gem.ticketEvent":this.options.onTicketEvent?.(a);break;case "gem.socketState":this.options.onSocketState?.(a);break;case "gem.signalEvent":this.options.onSignalEvent?.(a);break;case "gem.dedicatedEvent":this.options.onDedicatedEvent?.(a);break;case "gem.clusterTarget":this.options.onClusterTarget?.(a);break;case "gem.partyResult":this.options.onPartyResult?.(a);break;case "gem.purchaseResult":this.options.onPurchaseResult?.(a);break;case "gem.deviceSettings":this.options.onDeviceSettings?.(a);break;case "gem.deviceSettings.result":this.options.onDeviceSettingsResult?.(a);break;case "gem.adResult":this.options.onAdResult?.(a);break;case "gem.momentResult":this.options.onMomentResult?.(a);break;case "gem.signInResult":this.options.onSignInResult?.(a);break;case "gem.friendChallengeResult":this.options.onFriendChallengeResult?.(a);break;case "gem.error":this.options.onError?.(a.code,a.message);break;default:if(we.includes(O(r))){this.options.onArcadeAnswer?.(a);break}this.fail("unsupported",`no frame handler for '${r}'`);}return false}screenshotDenied(e,t){this.send({gem:l,type:"gem.screenshot.denied",reason:e,retryable:t});}};function ne(n){for(let e of n)try{e?.close?.();}catch{}}H();function Se(n){if((n.expectedSource??window.parent)===window)throw new Error("GemWebSdkBridge.connect: this document is not framed (window.parent === window), so there is no embedder to hand it a port. A game runs inside the Gem Arcade iframe; opening it directly will never complete a handshake.");let e=new S({...n,expectedSource:n.expectedSource??window.parent}),t=r=>{let s=e.handleWindowMessage({data:r.data,origin:r.origin,source:r.source,ports:r.ports});return s&&window.removeEventListener("message",t),s};return window.addEventListener("message",t),B(window,t),e}var re={connect:Se,GemFrame:S,PROTOCOL:l,SUPPORTED_VERSIONS:R};window.GemWebSdkBridge=re;var cn=re;return cn;})();