@indiegems/gem-web-sdk 16.1.0 → 16.3.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,32 @@ 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.3.0
20
+
21
+ Upgrading from 16.2.0. Impact: none.
22
+
23
+ ### New capability
24
+
25
+ - Games can now read the platform's branding manifest at run time through Gem.branding. A game can show the platform logo, the Arcade wordmark, the currency marks and the trophy, and a branding change on the platform reaches the game with no SDK update or republish. The manifest is read once on the first call and all callers share that read. A lookup by id and kind resolves to one of three outcomes: ok, with the URL, the chosen variant and the whole asset; not_found, when the manifest does not list that id or kind; or unavailable, when the manifest could not be read. In the unavailable case, draw your bundled brand copy instead. Only a bad argument rejects. A list call resolves every asset. You can pass the size you will draw and an optional theme, and the SDK picks the best-matching variant. Nothing is required of existing games.
26
+
27
+ ## 16.2.0
28
+
29
+ Upgrading from 16.1.0. Impact: required.
30
+
31
+ ### Must change
32
+
33
+ - 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.
34
+
35
+ ### Rebuild
36
+
37
+ - 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.
38
+
39
+ ### New capability
40
+
41
+ - 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.
42
+ - 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.
43
+ - 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.
44
+
19
45
  ## 16.1.0
20
46
 
21
47
  Upgrading from 16.0.1. Impact: required.
package/README.md CHANGED
@@ -31,6 +31,7 @@ Read them here rather than guessing from the type declarations, which carry sign
31
31
  | leaderboards | [`docs/leaderboards.md`](docs/leaderboards.md) |
32
32
  | currencies, items, shops and loot | [`docs/inventory-and-catalogs.md`](docs/inventory-and-catalogs.md), [`docs/game-economy-tutorial.md`](docs/game-economy-tutorial.md), [`docs/trading-and-gifting.md`](docs/trading-and-gifting.md) |
33
33
  | Gems, Coins and achievements | [`docs/arcade-economy.md`](docs/arcade-economy.md), [`docs/achievements.md`](docs/achievements.md), [`docs/brand-art.md`](docs/brand-art.md) |
34
+ | the Indie Gems logo, the Gem Arcade wordmark and the platform's currency marks, as the platform serves them now | [`docs/branding.md`](docs/branding.md) |
34
35
  | linking to the Arcade app or your game's app, opening another game, games like yours, and selling Gem packs, Boosts and passes for real money — the Arcade Store | [`docs/arcade-and-store.md`](docs/arcade-and-store.md) |
35
36
  | what to signal at every break in play — level complete, game over, back to menu | [`docs/platform-moments.md`](docs/platform-moments.md) |
36
37
  | daily challenges, async opponents, friends | [`docs/daily-challenges.md`](docs/daily-challenges.md), [`docs/async-opponents.md`](docs/async-opponents.md), [`docs/friends.md`](docs/friends.md) |
@@ -53,7 +54,7 @@ Every guide is one Markdown file, so a search over `docs/` finds the page that c
53
54
  | `@indiegems/gem-web-sdk/game`, `@indiegems/gem-web-sdk/server` | The browser half and the server half on their own, for a build that wants only one. |
54
55
  | `@indiegems/gem-web-sdk/function` | What a serverless function written in JavaScript imports. JavaScript functions are disabled for now, due to performance issues: a new one cannot be uploaded, and one already published keeps running. A function's build bundles it, and the starter's build resolves `/sdk` and `/game` to it as well. |
55
56
  | `@indiegems/gem-web-sdk/schema` | The wire schema between a game and the Arcade page that hosts it. |
56
- | `@indiegems/gem-web-sdk/brand/*` | The platform's Gems, Coins and trophy art — see [`docs/brand-art.md`](docs/brand-art.md). |
57
+ | `@indiegems/gem-web-sdk/brand/*` | The platform's Gems, Coins and trophy art, the fallback for when `gem.branding` cannot reach the platform — see [`docs/brand-art.md`](docs/brand-art.md). |
57
58
  | `gem-sdk-stamp <build-directory>` | Run after your bundler: records which SDK version a build carries, which a published web build has to say. |
58
59
  | `docs/` | The guides and the API reference. Not imported by anything, so none of it reaches a player's download. |
59
60
  | `docs/api/reference.json` | The API reference as data: every exported symbol, its signature and its documentation. |
@@ -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;
@@ -3186,6 +3210,54 @@ interface StoreApi {
3186
3210
  purchase(skuId: string): Promise<ArcadeOutcome>;
3187
3211
  onSettled(listener: (settlement: StoreSettlement) => void): () => void;
3188
3212
  }
3213
+ type BrandingTheme = "light" | "dark";
3214
+ interface BrandingVariant {
3215
+ readonly url: string;
3216
+ readonly type: string;
3217
+ readonly bytes: number;
3218
+ readonly width?: number;
3219
+ readonly height?: number;
3220
+ readonly size?: number;
3221
+ readonly density?: number;
3222
+ readonly vector?: true;
3223
+ readonly theme?: BrandingTheme;
3224
+ }
3225
+ interface BrandingAsset {
3226
+ readonly id: string;
3227
+ readonly kind: string;
3228
+ readonly variants: readonly BrandingVariant[];
3229
+ }
3230
+ type BrandingLookup = {
3231
+ readonly outcome: "ok";
3232
+ readonly url: string;
3233
+ readonly variant: BrandingVariant;
3234
+ readonly asset: BrandingAsset;
3235
+ } | {
3236
+ readonly outcome: "not_found" | "unavailable";
3237
+ };
3238
+ type BrandingList = {
3239
+ readonly outcome: "ok";
3240
+ readonly assets: readonly BrandingAsset[];
3241
+ } | {
3242
+ readonly outcome: "unavailable";
3243
+ };
3244
+ interface BrandingImageOptions {
3245
+ readonly size?: number;
3246
+ readonly density?: number;
3247
+ readonly theme?: BrandingTheme;
3248
+ }
3249
+ interface BrandingAssetOptions extends BrandingImageOptions {
3250
+ readonly kind: string;
3251
+ }
3252
+ type BrandingCurrency = "GEMS" | "ARCADE_TICKETS";
3253
+ interface BrandingApi {
3254
+ get(id: string, options: BrandingAssetOptions): Promise<BrandingLookup>;
3255
+ logo(options?: BrandingImageOptions): Promise<BrandingLookup>;
3256
+ wordmark(options?: BrandingImageOptions): Promise<BrandingLookup>;
3257
+ currencyMark(currency: BrandingCurrency, options?: BrandingImageOptions): Promise<BrandingLookup>;
3258
+ trophy(options?: BrandingImageOptions): Promise<BrandingLookup>;
3259
+ list(): Promise<BrandingList>;
3260
+ }
3189
3261
  interface VibrationApi {
3190
3262
  readonly available: boolean;
3191
3263
  vibrate(pattern: number | readonly number[]): boolean;
@@ -3448,6 +3520,7 @@ interface Gem {
3448
3520
  readonly share: ShareApi;
3449
3521
  readonly arcade: ArcadeApi;
3450
3522
  readonly store: StoreApi;
3523
+ readonly branding: BrandingApi;
3451
3524
  readonly avatarPickerAvailable: boolean;
3452
3525
  openAvatarPicker(options?: {
3453
3526
  readonly tab?: "2d" | "3d";
@@ -3502,4 +3575,4 @@ interface ExitAnalytics {
3502
3575
  }
3503
3576
  declare function leaveAndExit(rooms: ExitRooms, bridge: ExitBridge | null, reason?: string, analytics?: ExitAnalytics): Promise<void>;
3504
3577
  declare function createGem(options?: GemOptions): Gem;
3505
- export { ARCADE_EMBEDDERS, AVATAR_RIGS, type AccountApi, type AdFormat, type AdOutcome, type AdOutcomeKind, type AdRequest, type AdsApi, AnalyticsApi, type AnalyticsFlushOptions, type AnalyticsFlushResult, type AnalyticsOptions, type AnalyticsRejection, type AnalyticsValue, type AppLinksOutcome, type AppListing, type AppStore, type ArcadeApi, type ArcadeAvailability, type ArcadeOffer, type ArcadeOutcome, type ArcadeOutcomeKind, AuthError, type AuthFailure, type AuthState, type Avatar3dFamily, type Avatar3dOffer, type Avatar3dPrice, type Avatar3dPurchaseRequest, type AvatarRigNode, type AvatarRigs, type Avatars3dApi, type BootMark, type BootTimeline, type Challenge, type ChallengeWindow, ChallengesApi, type ConfiguredPurchasePayment, type ConfiguredPurchaseRequest, type ConfiguredPurchaseResult, type ControlsApi, DEVICE_SETTINGS_MAX_BYTES, DedicatedServerError, type DedicatedServerFailure, type DedicatedServerSession, type DevToolsOpener, type DeviceSettings, type DeviceSettingsApi, type DeviceSettingsPutOutcome, type DirectPurchaseRequest, type DirectPurchaseResult, type EarnAllowance, type EarnAllowanceEvent, type EarnAllowancePeriod, type EarnAllowanceReadOptions, type EntityLeaderboard, type EntityLeaderboardEntry, type EntityLeaderboardOptions, type EntityType, type ExitAnalytics, type ExitBridge, type ExitRooms, type FrameState, type Friend, type FriendChallenge, FriendChallengeError, type FriendChallengeErrorCode, type FriendChallengeOutcome, type FriendChallengeOutcomeKind, type FriendChallengeRequest, type FriendChallengesPage, FriendsApi, type FriendsPage, type FunctionFailure, type FunctionInvokeOptions, type FunctionResult, FunctionsApi, type GameCard, type GameConfig, type GameMenu, type GameMenuItem, type GameServerLink, type GameSettingsApi, type Gem, GemApiError, type GemAudioChannels, GemClient, type GemCohort, type GemCohorts, GemConfigError, type GemControlBox, type GemCorner, type GemDevTools, GemFrame, GemFunctionError, type GemGameChoiceSetting, type GemGameSetting, type GemGameSettingBase, type GemGameSettingChange, type GemGameSettingIcon, type GemGameSettingOption, type GemGameSettingValue, type GemGameSliderSetting, type GemGameToggleSetting, type GemGraphicsQuality, type GemIdentity, type GemInsets, type GemLook, type GemLookBase, type GemLookDelta, type GemLookOptions, type GemLookSpeed, type GemMatchApi, type GemMatchServerModule, type GemOptions, type GemPlayer, type GemPrefs, type GemRecommendedSettings, type GemReservedArea, GemSaveError, type GemServerBundle, type GemServerFactory, type GemServerHost, type GemServerHostProfile, type GemServerLifecycle, type GemTerminationNotice, GemUnframedError, type GemViewport, type GetLeaderboardOptions, type JoinWelcome, type Leaderboard, type LeaderboardEntry, type LeaderboardPeriod, LeaderboardsApi, type ListChallengesOptions, type ListFriendChallengesOptions, type ListFriendsOptions, MAX_PERIODS_BACK, type MatchConnectOptions, type MatchConnection, type MatchHosting, type MatchHostingMode, type MatchServeOptions, type MatchTransport, type MessageMeta, type MomentKind, type MomentResult, type MomentSignal, type MomentsApi, type MultiTransportSession, PLATFORM_EVENTS, type PeriodOptions, PlatformApi, PlatformCurrenciesApi, type PlatformCurrency, type PlatformCurrencyAccess, type PlatformCurrencyPurchasePayment, type PlatformCurrencyPurchaseRequest, type PlatformCurrencyPurchaseResult, type PlatformCurrencyReadOptions, PlatformEarnAllowanceApi, type PlatformEventName, type PlayerBadge, type PlayerBoost, type PlayerBoostMultiplier, type PlayerIdentity, type PlayerProfile, PlayerText, type ProfileCallOptions, type ProfileShowcaseEntry, ProfilesApi, type PurchaseApi, type PurchaseOptions, type PurchaseOutcome, PurchaseRefusal, type PurchaseRefusalCode, type PurchaseRequest, type PutBytesOptions, type PutSaveOptions, type RankedRow, type RecipeAvailability, type RecipeLock, type RewardCue, type RewardedStatus, type SavePage, type SaveRecord, type SendOptions$1 as SendOptions, type ServerTokenGrant, type SessionEndReason, type ShareApi, type ShareRequest, ShowcaseApi, type ShowcaseCallOptions, type ShowcaseFriendEntry, type ShowcaseFriends, type ShowcaseValue, type SignInOutcome, type SignInOutcomeKind, type SimilarGamesOutcome, type SnapshotCurrency, type SnapshotItem, type StoreApi, type StoreOffersOutcome, type StoreSettlement, type TransportAdvert, type TransportKind, TransportRefusal, UNRELIABLE_CHANNEL_LABEL, type VibrationApi, type WalletVisibleOptions, type WornAvatar3d, asPlayerText, createBootTimeline, createGem, experimentSelection, leaveAndExit, mapIdentity, markOnResolve, platformIconUrl, presentJoinCredential, readTemplateTransports, readTransportAdvert, reportOnResolve, text, webTransportAvailable };
3578
+ export { ARCADE_EMBEDDERS, AVATAR_RIGS, type AccountApi, type AdFormat, type AdOutcome, type AdOutcomeKind, type AdRequest, type AdsApi, AnalyticsApi, type AnalyticsFlushOptions, type AnalyticsFlushResult, type AnalyticsOptions, type AnalyticsRejection, type AnalyticsValue, type AppLinksOutcome, type AppListing, type AppStore, type ArcadeApi, type ArcadeAvailability, type ArcadeOffer, type ArcadeOutcome, type ArcadeOutcomeKind, AuthError, type AuthFailure, type AuthState, type Avatar3dFamily, type Avatar3dOffer, type Avatar3dPrice, type Avatar3dPurchaseRequest, type AvatarRigNode, type AvatarRigs, type Avatars3dApi, type BootMark, type BootTimeline, type BrandingApi, type BrandingAsset, type BrandingAssetOptions, type BrandingCurrency, type BrandingImageOptions, type BrandingList, type BrandingLookup, type BrandingTheme, type BrandingVariant, type Challenge, type ChallengeWindow, ChallengesApi, type ConfiguredPurchasePayment, type ConfiguredPurchaseRequest, type ConfiguredPurchaseResult, type ControlsApi, DEVICE_SETTINGS_MAX_BYTES, DedicatedServerError, type DedicatedServerFailure, type DedicatedServerSession, type DevToolsOpener, type DeviceSettings, type DeviceSettingsApi, type DeviceSettingsPutOutcome, type DirectPurchaseRequest, type DirectPurchaseResult, type EarnAllowance, type EarnAllowanceEvent, type EarnAllowancePeriod, type EarnAllowanceReadOptions, type EntityLeaderboard, type EntityLeaderboardEntry, type EntityLeaderboardOptions, type EntityType, type ExitAnalytics, type ExitBridge, type ExitRooms, type FrameState, type Friend, type FriendChallenge, FriendChallengeError, type FriendChallengeErrorCode, type FriendChallengeOutcome, type FriendChallengeOutcomeKind, type FriendChallengeRequest, type FriendChallengesPage, FriendsApi, type FriendsPage, type FunctionFailure, type FunctionInvokeOptions, type FunctionResult, FunctionsApi, type GameCard, type GameConfig, type GameMenu, type GameMenuItem, type GameServerLink, type GameSettingsApi, type Gem, GemApiError, type GemAudioChannels, GemClient, type GemCohort, type GemCohorts, GemConfigError, type GemControlBox, type GemCorner, type GemDevTools, GemFrame, GemFunctionError, type GemGameChoiceSetting, type GemGameSetting, type GemGameSettingBase, type GemGameSettingChange, type GemGameSettingIcon, type GemGameSettingOption, type GemGameSettingValue, type GemGameSliderSetting, type GemGameToggleSetting, type GemGraphicsQuality, type GemIdentity, type GemInsets, type GemLook, type GemLookBase, type GemLookDelta, type GemLookOptions, type GemLookSpeed, type GemMatchApi, type GemMatchServerModule, type GemOptions, type GemPlayer, type GemPrefs, type GemRecommendedSettings, type GemReservedArea, GemSaveError, type GemServerBundle, type GemServerFactory, type GemServerHost, type GemServerHostProfile, type GemServerLifecycle, type GemTerminationNotice, GemUnframedError, type GemViewport, type GetLeaderboardOptions, type JoinWelcome, type Leaderboard, type LeaderboardEntry, type LeaderboardPeriod, LeaderboardsApi, type ListChallengesOptions, type ListFriendChallengesOptions, type ListFriendsOptions, MAX_PERIODS_BACK, type MatchConnectOptions, type MatchConnection, type MatchHosting, type MatchHostingMode, type MatchServeOptions, type MatchTransport, type MessageMeta, type MomentKind, type MomentResult, type MomentSignal, type MomentsApi, type MultiTransportSession, PLATFORM_EVENTS, type PeriodOptions, PlatformApi, PlatformCurrenciesApi, type PlatformCurrency, type PlatformCurrencyAccess, type PlatformCurrencyPurchasePayment, type PlatformCurrencyPurchaseRequest, type PlatformCurrencyPurchaseResult, type PlatformCurrencyReadOptions, PlatformEarnAllowanceApi, type PlatformEventName, type PlayerBadge, type PlayerBoost, type PlayerBoostMultiplier, type PlayerIdentity, type PlayerProfile, PlayerText, type ProfileCallOptions, type ProfileShowcaseEntry, ProfilesApi, type PurchaseApi, type PurchaseOptions, type PurchaseOutcome, PurchaseRefusal, type PurchaseRefusalCode, type PurchaseRequest, type PutBytesOptions, type PutSaveOptions, type RankedRow, type RecipeAvailability, type RecipeLock, type RewardCue, type RewardedStatus, type SavePage, type SaveRecord, type SendOptions$1 as SendOptions, type ServerTokenGrant, type SessionEndReason, type ShareApi, type ShareRequest, ShowcaseApi, type ShowcaseCallOptions, type ShowcaseFriendEntry, type ShowcaseFriends, type ShowcaseValue, type SignInOutcome, type SignInOutcomeKind, type SimilarGamesOutcome, type SnapshotCurrency, type SnapshotItem, type StoreApi, type StoreOffersOutcome, type StoreSettlement, type TransportAdvert, type TransportKind, TransportRefusal, UNRELIABLE_CHANNEL_LABEL, type VibrationApi, type WalletVisibleOptions, type WornAvatar3d, asPlayerText, createBootTimeline, createGem, experimentSelection, leaveAndExit, mapIdentity, markOnResolve, platformIconUrl, presentJoinCredential, readTemplateTransports, readTransportAdvert, reportOnResolve, text, webTransportAvailable };
@@ -1,4 +1,4 @@
1
- var at=Object.defineProperty;var o=(r,t)=>at(r,"name",{value:t,configurable:true});var c="gem-web-bridge",R={min:1,max:1},st=65536,xe=8,ee=5e3,Pe=1024,x=1e4,I=Object.create(null),te="__malformed";var fe=Object.create(null),lt=Object.freeze([["gem.asyncOpponents.","asyncOpponents"],["gem.vibration.","vibration"],["gem.viewport.","reservedAreas"],["gem.moments.","moments"],["gem.prefs.","audioChannels"]]);function ne(r){let t=fe[r];if(t!==void 0)return t;for(let[e,n]of lt)if(r.startsWith(e))return n}o(ne,"capabilityFor");function V(r){let t=/^([a-z][a-z0-9+.-]*):\/\/([a-z0-9.-]+|\[[0-9a-f:.]+\])(:([0-9]{1,5}))?$/i.exec(r);return t===null?false:t[4]===void 0||Number(t[4])<=65535}o(V,"isOrigin");function dt(r){let t;try{t=new URL(r);}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(dt,"isSafeHttpUrl");function ct(r){return /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i.test(r)}o(ct,"isBcp47");function ut(r){return /^[a-z_]+$/.test(r)}o(ut,"isCode");function mt(r){return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(r)||/^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$/.test(r)&&!r.includes("--")}o(mt,"isGameRef");function pe(r,t,e){if(t>xe)return `nesting deeper than ${xe}`;if(++e.nodes>ee)return `more than ${ee} values`;if(e.bytes>e.cap)return `over the ${e.cap} byte limit`;if(r==null)return e.bytes+=4,null;let n=typeof r;if(n==="boolean")return e.bytes+=5,null;if(n==="number")return e.bytes+=8,null;if(n==="string")return e.bytes+=r.length*3+2,null;if(n!=="object")return `${n} is not plain data`;let i=r;if(e.seen.has(i))return "a repeated or cyclic object reference";if(e.seen.add(i),Array.isArray(r)){if(r.length>Pe)return `an array of ${r.length} entries, over the ${Pe} limit`;e.bytes+=2;for(let a of r){let u=pe(a,t+1,e);if(u)return u}return null}let s=Object.getPrototypeOf(r);if(s!==Object.prototype&&s!==null)return "a value that is not a plain object";e.bytes+=2;let d=r;for(let a in d){if(!Object.prototype.hasOwnProperty.call(d,a))continue;if(++e.nodes>ee)return `more than ${ee} values`;if(e.bytes+=a.length*3+4,e.bytes>e.cap)return `over the ${e.cap} byte limit`;let u=pe(d[a],t+1,e);if(u)return u}return null}o(pe,"walk");function pt(r,t=st){let e={nodes:0,bytes:0,seen:new Set,cap:t},n=pe(r,0,e);return n?`message contains ${n}`:e.bytes>t?`message is over the ${t} byte limit`:null}o(pt,"validateSize");var gt={origin:[V,"not a well-formed origin"],url:[dt,"not a safe absolute http(s) URL"],bcp47:[ct,"not a well-formed BCP 47 tag"],code:[ut,"not a code of lower-case letters and underscores"],gameRef:[mt,"not a game id or web address"]};function Ee(r,t,e){for(let n of t){let i=e===""?n[0]:`${e}.${n[0]}`,s=r[n[0]];if(s==null){if(n[1]<"a")continue;return `${i}: required`}let d=ge(s,n,i);if(d)return d}return null}o(Ee,"checkFields");function ge(r,t,e){let n=t[1].toLowerCase();switch(n){case "u":return typeof r=="boolean"?null:ge(r,typeof r=="string"?[t[0],"s",t[2]]:[t[0],"n",t[3],t[4]],e);case "s":{if(typeof r!="string")return `${e}: expected string`;if(r.length>t[2])return `${e}: exceeds ${t[2]} characters`;if(t[3]){let[i,s]=gt[t[3]];if(!i(r))return `${e}: ${s}`}return null}case "n":case "i":return typeof r!="number"||!(n=="i"?Number.isInteger:Number.isFinite)(r)?`${e}: expected ${n=="i"?"integer":"number"}`:r<t[2]||r>t[3]?`${e}: out of range ${t[2]}..${t[3]}`:null;case "b":return typeof r=="boolean"?null:`${e}: expected boolean`;case "e":{let i=t[2];return i.includes(r)?null:`${e}: not one of ${i.join("|")}`}case "o":return typeof r!="object"||r===null||Array.isArray(r)?`${e}: expected object`:Ee(r,t[2],e);case "j":return typeof r!="object"||r===null||Array.isArray(r)?`${e}: expected a plain object`:null;case "a":{if(!Array.isArray(r))return `${e}: expected array`;if(r.length>t[2])return `${e}: exceeds ${t[2]} items`;let i=t[3],s=`${e}[]`;for(let d of r){if(d==null){if(i[1]<"a")continue;return `${s}: required`}let a=ge(d,i,s);if(a)return a}return null}default:return `${e}: unknown kind ${String(n)}`}}o(ge,"checkValue");var Te=Object.create(null);function l(r,t,e,n,i){return I[r]=e,n&&(fe[r]=n),Te[r]=s=>{let d=pt(s,i);if(d)return d;if(typeof s!="object"||s===null||Array.isArray(s))return "message: expected object";let a=s;return a.gem!==c?"gem: wrong protocol marker":a.type!==r?`type: expected ${r}`:Ee(a,t,"")}}o(l,"makeValidator");var m=["requestId","s",64],C=["outcome","e",["ok","declined","unavailable"]],M=["reason","S",64,"code"];l("gem.handshake",[["versions","o",[["min","i",1,1e3],["max","i",1,1e3]]],["capabilities","A",64,["","s",64]],["embedder","s",2048,"origin"],["unbidden","B"]],30);l("gem.handshake.accept",[["v","i",1,1e3],["capabilities","A",64,["","s",64]]],5);l("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);l("gem.token.denied",[m,["retryable","b"],["retryAfterMs","I",0,36e5],["reason","e",["chain_exhausted","unavailable"]],["correlationId","s",64]],30);l("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);l("gem.pause",[["paused","b"]],120);l("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);l("gem.cohorts",[["cohorts","a",8,["","o",[["experimentId","s",36],["variantKey","s",32]]]]],12,"cohorts");l("gem.earnAllowanceChanged",[],30,"earnAllowance");l("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");l("gem.deviceSettings",[["settings","J"]],12,"deviceSettings");l("gem.willTerminate",[["reason","e",["grant_expiring","session_ended","navigating","kill_switch"]],["graceMs","i",0,6e4]],10);l("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");l("gem.socketState",[["connected","b"],["reason","E",["connecting","dropped","watchdog","terminal"]]],12,"socketState");l("gem.seatedEvent",[["roomId","s",64]],60,"partySeating");l("gem.autoStartFailedEvent",[["roomId","s",64],["code","s",64]],30,"autoStartFailure");l("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");l("gem.signalEvent",[["event","e",["hello","offer","answer","ice","peerJoined","peerLeft","migration","matchEnded","error"]],["matchId","s",64],["data","j"]],1800,"p2p");l("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");l("gem.partyResult",[m,["partyId","S",64]],30,"parties");l("gem.clusterTarget",[["clusterTargetId","s",64],["clusterTargetIds","A",3,["","s",64]]],12,"clusterTarget");l("gem.devtools",[["action","e",["open","close","toggle"]]],30,"devtools");l("gem.screenshot",[["maxWidth","i",1,7680],["maxHeight","i",1,4320]],6,"screenshot");l("gem.screenshot.denied",[["reason","e",["not_wired","no_source","unsupported","unreadable","tainted","too_large","busy","declined","failed"]],["retryable","b"]],12,"screenshot");l("gem.requestContext",[],12,"feedbackContext");l("gem.gameContext",[["context","j"]],12,"feedbackContext");l("gem.signal",[["signal","e",["join","offer","answer","ice","migrationAck","hostUnreachable"]],["matchId","s",64],["data","j"]],1200,"p2p");l("gem.dedicatedOffer",[["matchId","s",64],["sdp","s",10240],["intent","e",["initial","ice_restart","full_reconnect"]]],60,"dedicated");l("gem.partyRequest",[m],12,"parties");l("gem.ready",[],5);l("gem.engaged",[],5,"engagement");l("gem.progress",[["loaded","i",0,1099511627776],["total","i",0,1099511627776]],600);l("gem.requestToken",[m,["reason","e",["initial","expiring","rejected"]]],12);l("gem.requestLocalServer",[m,["gameRoomId","s",64]],6,"localHost");l("gem.localServer",[m,["serverId","S",128]],30,"localHost");l("gem.localServer.denied",[m,["retryable","b"],["reason","e",["refused","unavailable"]]],30,"localHost");l("gem.exit",[["reason","S",64]],5);l("gem.openExternal",[["url","s",2048,"url"]],6);l("gem.requestFullscreen",[["enabled","b"]],30);l("gem.audioState",[["muted","b"]],120,"audioState");l("gem.rewardCue",[["currencySlug","s",32],["expected","I",1,1e9],["origin","O",[["x","i",0,16384],["y","i",0,16384]]]],60,"rewardCue");l("gem.walletDisplay",[["visible","b"],["currencies","A",8,["","s",64]],["score","B"]],60,"walletDisplay");l("gem.controlsDeclare",[["uses","o",[["lookSensitivity","S",16,"code"],["gamepadLookSensitivity","S",16,"code"],["invertLookY","S",16,"code"],["touchMovement","S",16,"code"]]]],60,"controls");l("gem.menu",[["settings","b"],["items","A",4,["","o",[["id","s",32],["label","s",40]]]]],60,"gameMenu");l("gem.menuSelect",[["entry","e",["settings","item"]],["id","S",32]],60,"gameMenu");l("gem.gemCorner",[["corner","e",["top-left","top-right","bottom-left","bottom-right"]]],12,"gemCorner");l("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");l("gem.settingChange",[["id","s",40],["value","u",40,-1e6,1e6]],120,"gameSettings");l("gem.requestShare",[["params","a",16,["","o",[["key","s",64],["value","s",512]]]]],6,"share");l("gem.requestAvatarPicker",[["tab","E",["2d","3d"]]],6,"avatarPicker");l("gem.vibrate",[["pattern","a",99,["","i",0,1e4]]],600,"vibration");l("gem.frameStats",[["fps","i",0,1e3],["frameTimeP95Ms","i",0,1e5],["counts","E",["pageFrames","gameDraws"]]],120,"frameStats");l("gem.portraitBorders",[["name","S",40]],60,"portraitBorders");l("gem.error",[["code","e",["version_mismatch","handshake_timeout","handshake_duplicate","invalid_message","rate_limited","token_unavailable","unsupported","unsupported_capability","internal"]],["message","S",256]],60);l("gem.requestPurchase",[m,["purchaseKind","e",["item","recipe"]],["targetId","s",64],["currencySlug","s",32],["quantity","I",1,10],["catalogId","S",64]],12,"purchases");l("gem.requestAvatar3dPurchase",[m,["itemId","s",64],["currencySlug","s",32]],12,"avatar3dPurchases");l("gem.purchaseResult",[m,C,M,["purchaseId","S",64]],30,"purchases");l("gem.deviceSettings.put",[m,["settings","j"]],30,"deviceSettings");l("gem.deviceSettings.result",[m,["outcome","e",["stored","too_large","unavailable"]]],30,"deviceSettings");l("gem.requestAd",[m,["placement","s",32],["format","e",["interstitial","rewarded"]]],6,"ads");l("gem.adResult",[m,["outcome","e",["shown","no_ad","unavailable"]]],12,"ads");l("gem.signalMoment",[m,["kind","e",["level_end","run_end","match_end","menu_return","other_break"]],["placement","S",32],["wall","B"]],6,"moments");l("gem.momentResult",[m,["shown","b"]],12,"moments");l("gem.requestSignIn",[m],6,"signIn");l("gem.signInResult",[m,["outcome","e",["signed_in","declined","unavailable"]]],12,"signIn");l("gem.requestFriendChallenge",[m,["playerId","s",64],["template","s",64],["params","J"]],12,"friendChallenges");l("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");l("gem.requestAppLinks",[m],12,"appLinks");l("gem.appLinksResult",[m,C,M,["arcade","O",[["store","e",["app_store","google_play"]]]],["game","O",[["store","e",["app_store","google_play"]]]]],30,"appLinks");l("gem.requestOpenApp",[m,["target","e",["arcade","game"]]],6,"appLinks");l("gem.openAppResult",[m,C,M],30,"appLinks");l("gem.requestOpenGame",[m,["ref","s",40,"gameRef"]],6,"openGame");l("gem.openGameResult",[m,C,M],30,"openGame");l("gem.requestSimilarGames",[m,["limit","I",1,24]],12,"openGame");l("gem.similarGamesResult",[m,C,M,["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);l("gem.requestStoreOffers",[m],12,"store");l("gem.storeOffersResult",[m,C,M,["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);l("gem.requestStorePurchase",[m,["skuId","s",64]],12,"store");l("gem.storePurchaseResult",[m,C,M],30,"store");l("gem.storePurchaseSettled",[["skuId","s",64]],30,"store");var $=Object.freeze(Te);I.__malformed=60;Object.freeze(I);Object.freeze(fe);var yt=/^[a-z0-9][a-z0-9_-]{0,39}$/;function ht(r){return typeof r=="string"&&yt.test(r)}o(ht,"isBorderName");function ye(r){if(r===null||ht(r))return r;let t=typeof r=="string"?JSON.stringify(r.length>48?`${r.slice(0,48)}\u2026`:r):`a value of type ${typeof r}`;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(ye,"checkBorderName");var P={now:o(()=>Date.now(),"now"),setTimeout:o((r,t)=>setTimeout(r,t),"setTimeout"),clearTimeout:o(r=>clearTimeout(r),"clearTimeout")},H=class{constructor(t=P){this.clock=t;}clock;static{o(this,"RateLimiter");}tokens=new Map;lastRefill=new Map;allow(t){let e=I[t];if(e===void 0)return false;let n=this.clock.now(),i=this.lastRefill.get(t)??n,s=this.tokens.get(t)??e,d=Math.max(0,n-i),a=Math.min(e,s+d*e/6e4);return a<1?(this.tokens.set(t,a),this.lastRefill.set(t,n),false):(this.tokens.set(t,a-1),this.lastRefill.set(t,n),true)}};function K(r,t){return r.toLowerCase()===t.toLowerCase()}o(K,"originsEqual");function D(r){let t=r.toLowerCase();return t==="localhost"||t.endsWith(".localhost")||t==="[::1]"||t==="::1"?true:/^127(\.\d{1,3}){3}$/.test(t)}o(D,"isLoopbackHostname");function he(r,t){if(typeof t!="string"||t==="")return false;let e,n;try{e=new URL(r),n=new URL(t);}catch{return false}return e.protocol!=="http:"&&e.protocol!=="https:"||n.protocol!=="http:"&&n.protocol!=="https:"||e.username!==""||e.password!==""||e.pathname!=="/"||e.search!==""||e.hash!==""||e.port===""?false:D(e.hostname)&&D(n.hostname)}o(he,"isLoopbackDevHostEmbedder");function Ie(r,t){if(r.min>r.max||t.min>t.max)return null;let e=Math.min(r.max,t.max),n=Math.max(r.min,t.min);return e>=n?e:null}o(Ie,"negotiateVersion");var Oe="[gem: use gem.text.toPlain()]",Ce=new Set,Me;function bt(r){try{if(Ce.has(r))return;let t=globalThis.location;if(Me??=!!t?.port&&D(t.hostname??""),!Me)return;Ce.add(r),console.warn(`[gem] ${r} is PlayerText, turned into a string: players see ${Oe}. Render text.toPlain(value) (text is in @indiegems/gem-web-sdk/sdk).
2
- ${new Error().stack}`);}catch{}}o(bt,"warnStringified");var re=class{static{o(this,"PlayerText");}#e;#t;constructor(t,e="player text"){this.#e=t,this.#t=e,Object.freeze(this);}toString(){return bt(this.#t),Oe}toJSON(){return this.toString()}get[Symbol.toStringTag](){return "PlayerText"}static unwrap(t){return t.#e}};function O(r,t){return new re(r,t)}o(O,"asPlayerText");function Ge(r){return Math.min(16384,Math.max(0,Math.round(Number.isFinite(r)?r:0)))}o(Ge,"cuePoint");function De(r,t){if(r!==true)return {visible:false};let e=[],n=false;for(let i of Array.isArray(t)?t:[])i==="score"?n=true:typeof i=="string"&&i.length>0&&i.length<=64&&!e.includes(i)&&e.push(i);return !n&&e.length===0?{visible:true}:{visible:true,currencies:e.slice(0,8),score:n}}o(De,"walletState");function Fe(r){let t=[];for(let e of Array.isArray(r)?r:[]){if(t.length===4)break;if(typeof e!="object"||e===null)continue;let{id:n,label:i}=e;typeof n!="string"||n.length===0||n.length>32||typeof i!="string"||i.trim().length===0||t.some(s=>s.id===n)||t.push({id:n,label:i.trim().slice(0,40)});}return t.length===0?{settings:false}:{settings:false,items:t}}o(Fe,"menuState");function Le(r,t){return Math.min(t,Math.max(0,Math.round(Number.isFinite(r)?r:0)))}o(Le,"clampStat");function vt(r){let t=r.untrusted,e=O(t.displayName,"gem.player.displayName"),n=t.displayTag||t.displayHandle,i=n?O(n,"gem.player.displayTag"):void 0,s=r.discriminator||r.displayTag,d=s&&/^[0-9]{1,8}$/.test(s)?s:void 0,a=r.avatarIconUrl||r.avatarUrl,u=/^[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$/i,y=r.avatarIconId,w=r.avatar3dId,h={displayName:e,displayHandle:i??e,badges:(r.badges??[]).map(v=>({itemId:v.itemId,iconUrl:v.iconUrl}))};r.playerId&&(h.playerId=r.playerId),d&&(h.discriminator=d),i&&(h.displayTag=i),a&&(h.avatarIconUrl=h.avatarUrl=a),y&&u.test(y)&&(h.avatarIconId=y),w&&u.test(w)&&(h.avatar3dId=w),r.avatar3dModelUrl&&(h.avatar3dModelUrl=r.avatar3dModelUrl),r.avatar3dIconUrl&&(h.avatar3dIconUrl=r.avatar3dIconUrl),r.avatars3d&&(h.avatars3d=r.avatars3d.filter(v=>u.test(v.id)&&/^[a-z][a-z0-9_]*$/.test(v.family)).filter((v,ce,ue)=>ue.findIndex(me=>me.family==v.family)==ce).map(({family:v,id:ce,modelUrl:ue,iconUrl:me})=>({family:v,id:ce,modelUrl:ue,iconUrl:me})));let T=r.boost,le=(T?.multipliers??[]).map(v=>({currencyId:v.currencyId,currencySlug:v.currencySlug,multiplier:v.multiplierBp/1e4})),de=le.some(v=>v.multiplier>1);return h.boost=de&&T?.expiresInDays?{active:de,multipliers:le,expiresInDays:T.expiresInDays}:{active:de,multipliers:le},h}o(vt,"playerFromWire");var _t=1e4,wt=["appLinks","openGame","store"],St=new WeakMap;var F=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??P,this.inbound=new H(this.clock),this.outbound=new H(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(a=>K(a,t.origin))||he(t.origin,this.options.selfOrigin)||(St.get(this)??[]).some(a=>K(a,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 n=$["gem.handshake"]?.(t.data);if(n)return this.refuse(t,`malformed handshake: ${n}`),false;let i=t.data;if(!K(i.embedder,t.origin))return this.refuse(t,`handshake claims embedder '${i.embedder}' but arrived from '${t.origin}'`),false;let s=t.ports?.[0];if(!s)return this.refuse(t,"handshake transferred no MessagePort"),false;let d=Ie(R,i.versions);return d===null?(s.onmessage=null,s.postMessage({gem:c,type:"gem.error",code:"version_mismatch",message:`frame supports v${R.min}..v${R.max}`}),s.close(),this.transition("refused"),this.options.onError?.("version_mismatch"),false):(this.port=s,this.version=d,s.onmessage=a=>this.receive(a.data,a.ports??[]),s.start?.(),this.peerCapabilities=i.capabilities??[],this.unbidden=i.unbidden===true,s.postMessage({gem:c,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:c,type:"gem.ready"});}settled(t=_t){return this.state!=="waiting"?Promise.resolve(this.state):new Promise(e=>{let n=o(s=>{this.clock.clearTimeout(i),e(s);},"waiter"),i=this.clock.setTimeout(()=>{let s=this.settleWaiters.indexOf(n);s>=0&&this.settleWaiters.splice(s,1),e(this.state);},t);this.settleWaiters.push(n);})}progress(t,e){this.send({gem:c,type:"gem.progress",loaded:t,total:e});}requestToken(t,e){this.send({gem:c,type:"gem.requestToken",requestId:t,reason:e});}requestLocalServer(t,e){return this.parentSupports("localHost")?(this.send({gem:c,type:"gem.requestLocalServer",requestId:t,gameRoomId:e}),true):false}exit(t){this.send(t===void 0?{gem:c,type:"gem.exit"}:{gem:c,type:"gem.exit",reason:t});}openExternal(t){this.send({gem:c,type:"gem.openExternal",url:t});}requestFullscreen(t){this.send({gem:c,type:"gem.requestFullscreen",enabled:t});}reportMuted(t){return this.gated("audioState")?(this.send({gem:c,type:"gem.audioState",muted:t}),true):false}reportEngaged(){return this.engagedSent||!this.gated("engagement")||!this.send({gem:c,type:"gem.engaged"})?false:(this.engagedSent=true,true)}cueReward(t){if(!this.gated("rewardCue"))return false;let e={gem:c,type:"gem.rewardCue",currencySlug:String(t.currencySlug).slice(0,32)};return 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:Ge(t.origin.x),y:Ge(t.origin.y)}),this.send(e),true}showPortraitBorders(t){let e=ye(t);return e===void 0||!this.gated("portraitBorders")?false:this.send(e===null?{gem:c,type:"gem.portraitBorders"}:{gem:c,type:"gem.portraitBorders",name:e})}setWalletVisible(t,e){return this.gated("walletDisplay")?this.send({gem:c,type:"gem.walletDisplay",...De(t,e?.only)}):false}setMenu(t){return this.gated("gameMenu")?this.send({gem:c,type:"gem.menu",...Fe(t)}):false}declareSettings(t){return this.gated("gameSettings")?this.send({gem:c,type:"gem.settingsDeclare",settings:t}):false}setGemCorner(t){return this.gated("gemCorner")?this.send({gem:c,type:"gem.gemCorner",corner:t}):false}requestShare(t){return this.gated("share")?this.send({gem:c,type:"gem.requestShare",params:t.map(({key:e,value:n})=>({key:e,value:n}))}):false}requestAvatarPicker(t){return this.gated("avatarPicker")?this.send({gem:c,type:"gem.requestAvatarPicker",...t==="2d"||t==="3d"?{tab:t}:{}}):false}requestVibrate(t){return this.gated("vibration")?this.send({gem:c,type:"gem.vibrate",pattern:[...t]}):false}reportFrameStats(t){return this.gated("frameStats")?(this.send({gem:c,type:"gem.frameStats",fps:Le(t.fps,1e3),frameTimeP95Ms:Le(t.frameTimeP95Ms,1e5),counts:t.counts==="gameDraws"?"gameDraws":"pageFrames"}),true):false}sendGameContext(t){return this.state!=="connected"?false:(this.send({gem:c,type:"gem.gameContext",context:t}),true)}signal(t,e,n){this.send({gem:c,type:"gem.signal",signal:t,matchId:e,data:n});}dedicatedOffer(t,e,n){this.send({gem:c,type:"gem.dedicatedOffer",matchId:t,sdp:e,intent:n});}requestParty(t){return this.send({gem:c,type:"gem.partyRequest",requestId:t})}requestPurchase(t,e,n,i,s,d){return this.send({gem:c,type:"gem.requestPurchase",requestId:t,purchaseKind:e,targetId:n,currencySlug:i,...s===void 0?{}:{quantity:s},...d===void 0?{}:{catalogId:d}})}requestAvatar3dPurchase(t,e,n){return this.parentSupports("avatar3dPurchases")?this.send({gem:c,type:"gem.requestAvatar3dPurchase",requestId:t,itemId:e,currencySlug:n}):false}putDeviceSettings(t,e){return this.send({gem:c,type:"gem.deviceSettings.put",requestId:t,settings:e})}requestAd(t,e,n){return this.send({gem:c,type:"gem.requestAd",requestId:t,placement:e,format:n})}signalMoment(t,e,n,i){return this.send({gem:c,type:"gem.signalMoment",requestId:t,kind:e,...n===void 0?{}:{placement:n},...i===true?{wall:true}:{}})}readyCalled(){return this.readySent||this.readyPending}requestSignIn(t){return this.send({gem:c,type:"gem.requestSignIn",requestId:t})}requestFriendChallenge(t,e,n,i){return this.send({gem:c,type:"gem.requestFriendChallenge",requestId:t,playerId:e,template:n,...i===void 0?{}:{params:i}})}requestArcade(t){return this.gated(ne(t.type))&&this.send({gem:c,...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(te)){this.options.onDropped?.(t,e);return}this.fail(t,e);}dropUnknownType(t){this.inbound.allow(te)&&this.options.onDropped?.("unsupported",`ignored unknown message type '${t}'`);}refuse(t,e){t.ports?.[0]?.close?.(),this.inbound.allow(te)&&this.options.onRefused?.(e);}fail(t,e){this.options.onError?.(t,e),this.state==="connected"&&this.port&&this.send({gem:c,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 n=$[e]?.(t);return n?(this.options.onError?.("internal",`refusing to send malformed ${e}: ${n}`),false):(this.port.postMessage(this.version===null?t:{...t,v:this.version}),true)}receive(t,e=[]){let n=false;try{n=this.dispatchInbound(t,e);}finally{n||$e(e);}}dispatchInbound(t,e){if(this.state!=="connected")return false;let n=typeof t=="object"&&t!==null?t.type:void 0;if(typeof n!="string")return this.refuseMalformed("invalid_message","message has no type"),false;let i=$[n];if(!i)return this.dropUnknownType(n),false;if(!this.inbound.allow(n))return this.fail("rate_limited",`parent exceeded the limit for ${n}`),false;let s=ne(n);if(s!==void 0&&!this.options.capabilities?.includes(s))return this.fail("unsupported_capability",`${n} requires the '${s}' capability, which this frame did not advertise`),false;let d=i(t);if(d)return this.fail("invalid_message",`${n}: ${d}`),false;let a=t;if(a.v!==this.version)return this.fail("invalid_message",`${n}: expected v ${String(this.version)}`),false;switch(n){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=e[0];if(!u){this.options.onError?.("invalid_message","gem.screenshot transferred no MessagePort");break}let y={maxWidth:a.maxWidth,maxHeight:a.maxHeight};if(!this.options.onScreenshot){u.close(),this.screenshotDenied("not_wired",false);break}return $e(e.slice(1)),this.options.onScreenshot(y,u,(w,h)=>this.screenshotDenied(w,h)),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?.(vt(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(wt.includes(ne(n))){this.options.onArcadeAnswer?.(a);break}this.fail("unsupported",`no frame handler for '${n}'`);}return false}screenshotDenied(t,e){this.send({gem:c,type:"gem.screenshot.denied",reason:t,retryable:e});}};function $e(r){for(let t of r)try{t?.close?.();}catch{}}o($e,"closeAll");globalThis.__gemWebSdkVersion="[gem-web-sdk:sdkVersion:16.1.0]";var qe=["if-match","if-none-match"];var xt={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,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(xt));new TextEncoder;var je=["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(je);new TextEncoder;new TextDecoder;new TextEncoder;var rn=8;(class{constructor(t=rn){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 n of this.sent)n<=t&&t-n<6e4&&(this.sent[e++]=n);this.sent.length=e;}});function p(r){return t=>typeof t=="string"&&t.length>0&&t.length<=r?t:void 0}o(p,"text");function E(r,t){return e=>typeof e=="number"&&Number.isInteger(e)&&e>=r&&e<=t?e:void 0}o(E,"integer");function N(...r){return t=>typeof t=="string"&&r.includes(t)?t:void 0}o(N,"oneOf");function Be(r){return typeof r=="boolean"?r:void 0}o(Be,"flag");var _=p(64),X=E(0,2147483647);function ke(r){if(!Array.isArray(r))return;let t=[];for(let e of r){if(t.length>=64)break;let n=_(e);typeof n=="string"&&t.push(n);}return t}o(ke,"ids");function f(r,t,e){return {to:r,from:t,read:e,required:true}}o(f,"required");function g(r,t,e){return {to:r,from:t,read:e,required:false}}o(g,"optional");var sn=o(r=>{if(typeof r!="object"||r===null||Array.isArray(r))return;let t=r,e=N("disconnected","failed","closed")(t.iceState),n=E(0,4102444800)(t.lastSeenAt);if(!(e===void 0&&n===void 0))return {...e===void 0?{}:{ice_state:e},...n===void 0?{}:{last_seen_timestamp:n}}},"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",p(10240)),f("signaling_intent","intent",N("initial","ice_restart","full_reconnect")),g("sdp_hash","sdpHash",p(128))]},answer:{type:"p2p_answer",gameScoped:false,args:[f("peer_id","peerId",_),f("sdp","sdp",p(10240)),f("assigned_peer_id","assignedPeerId",E(0,4294967295)),g("sdp_hash","sdpHash",p(128))]},ice:{type:"p2p_ice_candidate",gameScoped:false,args:[f("peer_id","peerId",_),f("candidate","candidate",p(512)),g("sdp_mid","sdpMid",p(64)),g("sdp_mline_index","sdpMLineIndex",E(0,255))]},migrationAck:{type:"p2p_migration_ack",gameScoped:false,args:[f("host_epoch","hostEpoch",X)]},hostUnreachable:{type:"p2p_report_host_unreachable",gameScoped:false,args:[f("host_epoch","hostEpoch",X),g("evidence","evidence",sn)]}}));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"}));Object.freeze(Object.assign(Object.create(null),{hello:[f("playerId","player_id",_),f("hostPlayerId","host_player_id",_),f("hostEpoch","host_epoch",X),g("members","members",ke)],offer:[f("fromPlayerId","from_player_id",_),f("sdp","sdp",p(10240)),g("sessionId","signaling_session_id",p(128)),g("sdpHash","sdp_hash",p(128)),g("iceUfrag","ice_ufrag",p(64)),g("intent","signaling_intent",N("initial","ice_restart","full_reconnect"))],answer:[f("fromPlayerId","from_player_id",_),f("sdp","sdp",p(10240)),g("sessionId","signaling_session_id",p(128)),g("sdpHash","sdp_hash",p(128)),g("iceUfrag","ice_ufrag",p(64)),g("assignedPeerId","assigned_peer_id",E(0,4294967295))],ice:[f("fromPlayerId","from_player_id",_),f("candidate","candidate",p(512)),g("sdpMid","sdp_mid",p(64)),g("sdpMLineIndex","sdp_m_line_index",E(0,255)),g("sdpMLineIndexAlt","sdp_mline_index",E(0,255))],peerJoined:[f("playerId","player_id",_)],peerLeft:[f("playerId","player_id",_),g("reason","reason",p(64))],migration:[f("hostPlayerId","new_host_player_id",_),f("hostEpoch","host_epoch",X),g("reason","reason",p(64)),g("kickedPlayerIds","kicked_player_ids",ke),g("migrationComplete","migration_complete",Be)],matchEnded:[g("reason","reason",p(64))],error:[g("code","code",p(64)),g("message","message",p(256))]}));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"}));new Set(qe);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",p(10240)),g("sessionId","session_id",p(64))],error:[g("code","code",p(64)),g("message","message",p(256))],matchEnded:[g("reason","reason",p(64))]}));var Ze=Object.freeze(["https://gemarcade.com","https://developer.gemarcade.com","http://gemarcade.localhost","gemarcade://localhost"]);var W=class extends Error{constructor(e,n){super(`[${e}] ${n}`);this.caseId=e;this.name="ParentConformanceError";}caseId;static{o(this,"ParentConformanceError");}},hv=2,et="https://game.conformance.invalid",Jn="https://stranger.conformance.invalid",Re=I["gem.handshake"]??1,Z=6e4,ae=class{static{o(this,"KitClock");}current=1e6;seq=0;pending=new Map;now(){return this.current}setTimeout(t,e){let n=++this.seq;return this.pending.set(n,{at:this.current+Math.max(0,e),fn:t}),n}clearTimeout(t){this.pending.delete(t);}get pendingTimers(){return this.pending.size}advance(t){let e=this.current+t;for(;;){let n;for(let i of this.pending)i[1].at<=e&&(n===void 0||i[1].at<n[1].at)&&(n=i);if(n===void 0)break;this.pending.delete(n[0]),this.current=n[1].at,n[1].fn();}this.current=e;}},tt=Object.freeze({conformance:"parent-window"}),se=class{constructor(t,e,n,i){this.caseId=t;this.options=n;let s=i??n.frameOrigin??et,d={frame:{postMessage:o((a,u,y)=>{this.offers.push({data:a,targetOrigin:u,transfer:y??[],at:this.clock.now()});},"postMessage")},frameOrigin:s,embedderOrigin:n.embedderOrigin,clock:this.clock,makeChannel:o(()=>{let a=new MessageChannel,u={port1:a.port1,port2:a.port2,closed:false},y=a.port1.close.bind(a.port1);return a.port1.close=()=>{u.closed=true,y();},this.count(a.port1),this.count(a.port2),this.minted.push(u),a},"makeChannel"),reportError:o((a,u)=>{this.errors.push(u===void 0?{code:a}:{code:a,message:u});},"reportError")};Object.defineProperty(d,"userActivation",{enumerable:true,get:o(()=>this.activation===void 0?void 0:{...this.activation},"get")}),this.parent=e(d);}caseId;options;static{o(this,"Rig");}clock=new ae;frameClock=new ae;offers=[];minted=[];parent;activation={isActive:false};errors=[];inFlight=0;frames=[];get frameOrigin(){return this.options.frameOrigin??et}fail(t){throw new W(this.caseId,t)}count(t){let e=t.postMessage.bind(t);t.postMessage=((...n)=>{this.inFlight+=1,e(...n);}),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 n=0;n<t;n+=e){let i=Math.min(e,t-n);this.clock.advance(i),this.frameClock.advance(i);}}start(){try{this.parent.start();}catch(t){this.fail(`start() threw: ${ot(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(n=>n.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([...Ze,...this.options.allowedEmbedders??[]])],n=[],i=new F({allowedEmbedders:e,expectedSource:tt,selfOrigin:this.frameOrigin,clock:this.frameClock,onRefused:o(s=>n.push(s),"onRefused")});return i.kitRefusals=n,this.frames.push(i),i}deliver(t,e){let n=this.transferred(e);return t.handleWindowMessage({data:e.data,origin:this.options.embedderOrigin,source:tt,ports:[n.port2]})}answer(t,e,n=[]){this.transferred(t).port2.postMessage({gem:c,type:"gem.handshake.accept",v:e,capabilities:[...n]});}advertised(){let t=this.latest().data.capabilities;return Array.isArray(t)?t.filter(e=>typeof e=="string"):[]}async raw(t){let e=this.latest(),n=this.transferred(e),i=[];n.port2.onmessage=a=>{a.data!==null&&typeof a.data=="object"&&i.push(a.data);};let s=Math.min(B(this).max,R.max);this.answer(e,s,t),await this.flush(),this.state()!=="connected"&&this.fail(`a hand-written frame accepted at v${s}, and the parent reports '${this.state()}'.`);let d=0;return {received:i,ask:o(async(a,u,y)=>{let w=`conformance-${++d}`;return this.activation=y==="missing"?void 0:{isActive:y==="pressed"},n.port2.postMessage({gem:c,type:a,v:s,requestId:w,...u}),await this.flush(),this.activation={isActive:false},w},"ask"),answers:o(a=>i.filter(u=>u.requestId===a),"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 sheet, 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 n=this.offers[e].at,i=0;for(let s=e;s<this.offers.length&&this.offers[s].at<n+Z;s++)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 ot(r){return r instanceof Error?r.message:String(r)}o(ot,"describe");function zn(r){return r.kitRefusals??[]}o(zn,"refusalsOf");function B(r){let t=r.latest().data.versions,e=t?.min,n=t?.max;return (typeof e!="number"||typeof n!="number")&&r.fail("the handshake carries no numeric `versions.min` / `versions.max`"),{min:e,max:n}}o(B,"offeredRange");async function b(r,t,e,n){let i=new se(r,t,e);try{await n(i);}finally{i.dispose();}}o(b,"withRig");async function Q(r){let t=r.frame();return r.deliver(t,r.latest())||r.fail(`a game built with this SDK refused the handshake from ${r.options.embedderOrigin}: ${zn(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 r.flush(),r.state()!=="connected"&&r.fail(`the frame accepted, but the parent reports '${r.state()}' rather than 'connected'.`),t}o(Q,"connect");var L=["appLinks","openGame","store"],Yn="00000000-0000-4000-8000-00000000c0de",it="conformance-unknown-sku",Ae={appLinks:{type:"gem.requestOpenApp",fields:{target:"arcade"}},openGame:{type:"gem.requestOpenGame",fields:{ref:Yn}},store:{type:"gem.requestStorePurchase",fields:{skuId:it}}};function ie(r,t,e,n,i){let s=t.answers(e);s.length!==1&&r.fail(`${i}: the parent sent ${s.length} answers; it owes exactly one.`);let[d]=s;(d.outcome!=="declined"||d.reason!=="busy")&&r.fail(`${i}: answered ${JSON.stringify(d.outcome)} / ${JSON.stringify(d.reason)}, not 'declined' / 'busy'.`);let a=r.surfaces(i);a!==n&&r.fail(`${i}: the parent showed ${a-n} surface(s); it must show nothing.`);}o(ie,"expectBusy");async function nt(r,t){let e=await t.ask("gem.requestStoreOffers",{},"unpressed"),[n]=t.answers(e),s=(n?.offers??[])[0]?.skuId;(n?.outcome!=="ok"||typeof s!="string")&&r.fail(`the parent advertises \`store\` but listed no offer to a signed-in player (${JSON.stringify(n?.outcome)} / ${JSON.stringify(n?.reason)}). Give the adapter's store a signed-in player and at least one SKU, so the kit can open a sheet.`);let d=r.surfaces("the parent advertises `store`"),a=await t.ask("gem.requestStorePurchase",{skuId:s},"pressed"),u=r.surfaces("the parent advertises `store`");return u!==d+1&&r.fail(`a pressed purchase of the listed SKU ${JSON.stringify(s)} showed ${u-d} surface(s); it must open exactly one sheet.`),t.answers(a).length>0&&r.fail("the store purchase was answered while its sheet was open. Answer once the player has acted, so the kit can hold a sheet open."),{requestId:a,skuId:s,shown:u}}o(nt,"openStoreSheet");function rt(r){typeof r.parent.dismiss!="function"&&r.fail('the parent advertises `store`, so the kit must be able to dismiss its sheet: expose `dismiss()` on the adapter (the player pressing Escape, Back or "Not now").'),r.parent.dismiss();}o(rt,"dismiss");var Xn=["*","null","","https://game.conformance.invalid/","https://game.conformance.invalid/play"],Qn=[{id:"origin.refuses-imprecise-target",title:"refuses a frame origin that is not one exact origin, before anything is posted",async run(r,t){for(let e of Xn){let n=JSON.stringify(e),i,s=false;try{i=new se(this.id,r,{...t,frameOrigin:e},e),i.parent.start();}catch(d){if(d instanceof W)throw d;s=true;}try{if((i?.offers??[]).some(a=>a.targetOrigin==="*"||a.targetOrigin===e))throw new W(this.id,`with frame origin ${n} 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(!s)throw new W(this.id,`a frame origin of ${n} 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(r,t){await b(this.id,r,t,e=>{e.start(),e.advance(x);for(let s of e.offers){s.targetOrigin!==e.frameOrigin&&e.fail(`an offer was posted with targetOrigin ${JSON.stringify(s.targetOrigin)}, not the frame origin ${e.frameOrigin}.`),e.transferred(s);let d=$["gem.handshake"]?.(s.data);d&&e.fail(`the handshake does not validate against the schema: ${d}`);let a=s.data;a.gem!==c&&e.fail(`the handshake's \`gem\` is not ${c}`),a.embedder!==t.embedderOrigin&&e.fail(`the handshake names embedder ${JSON.stringify(a.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:n,max:i}=B(e);n>i&&e.fail(`the offered range is empty: min ${n} > max ${i}`),(i<R.min||n>R.max)&&e.fail(`the offered range v${n}\u2013v${i} shares no version with this SDK (v${R.min}\u2013v${R.max}).`);});}},{id:"handshake.default-game-connects",title:"connects to a game that names no embedders, at the highest common version",async run(r,t){await b(this.id,r,t,async e=>{e.start();let n=await Q(e),{max:i}=B(e),s=Math.min(i,R.max);n.protocolVersion!==s&&e.fail(`the frame chose v${String(n.protocolVersion)}, expected v${s}`),e.parent.protocolVersion!==n.protocolVersion&&e.fail(`the parent reports v${String(e.parent.protocolVersion)} while the frame chose v${String(n.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(r,t){await b(this.id,r,t,async e=>{e.start();let n=e.frame([Jn]);e.deliver(n,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(x*3),await e.flush(),e.state()!=="handshaking"&&e.fail(`after a refusal and ${x*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(r,t){await b(this.id,r,t,async e=>{e.start();let{min:n}=B(e);e.answer(e.latest(),n),await e.flush(),(e.state()!=="connected"||e.parent.protocolVersion!==n)&&e.fail(`answered with v${n}, 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(r,t){for(let e of ["above","below"])await b(this.id,r,t,async n=>{n.start();let{min:i,max:s}=B(n),d=e==="above"?s+1:i-1;n.answer(n.latest(),d),await n.flush(),(n.state()==="connected"||n.parent.protocolVersion===d)&&n.fail(`answered with v${d}, outside the offered v${i}\u2013v${s}, 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(r,t){await b(this.id,r,t,e=>{e.start(),e.advance(x*2),e.offers.length<2&&e.fail(`one offer in ${x*2}ms. A message to a document that is not listening yet is lost, not queued: re-offer until something answers.`);let n=e.offers.map(s=>e.transferred(s).port2);new Set(n).size!==n.length&&e.fail("an offer re-used a port; each offer needs its own channel."),e.advance(Z*2);let i=e.offersInAnyMinute();i>Re&&e.fail(`${i} offers inside one minute; the contract budgets ${Re} \`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(r,t){await b(this.id,r,t,async e=>{e.start(),e.advance(x*2);let n=e.transferred(e.latest());await Q(e),n.closed&&e.fail("the parent closed the port the frame answered on.");let i=e.minted.filter(s=>s!==n&&!s.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(r,t){await b(this.id,r,t,async e=>{e.start(),await Q(e);let n=e.offers.length;e.advance(Z*5,1e3),e.offers.length!==n&&e.fail(`${e.offers.length-n} 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(r,t){await b(this.id,r,t,async e=>{e.start(),await Q(e);let n=e.parent.protocolVersion;e.answer(e.latest(),B(e).max+1),await e.flush(),(e.state()!=="connected"||e.parent.protocolVersion!==n)&&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(r,t){await b(this.id,r,t,e=>{e.start(),e.advance(x),e.parent.close(),e.state()!=="closed"&&e.fail(`after close() the parent reports '${e.state()}'`);let n=e.minted.filter(s=>!s.closed).length;n>0&&e.fail(`${n} 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(Z*2,1e3),e.offers.length!==i&&e.fail("the parent offered again after close().");}),await b(this.id,r,t,async e=>{e.start(),await Q(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(r,t){await b(this.id,r,t,e=>{e.start(),e.advance(Z*10,1e3);let n=e.minted.filter(i=>!i.closed).length;n>Re+1&&e.fail(`${n} 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(r,t){for(let e of L)await b(this.id,r,t,async n=>{n.start();let i=n.advertised().includes(e),s=L.filter(T=>!i||T!==e),d=await n.raw(s),a=n.parent.surfacesShown,{type:u,fields:y}=Ae[e],w=await d.ask(u,y,"pressed"),h=i?"the frame":"the parent";d.answers(w).length>0&&n.fail(`${u} under '${e}', which ${h} did not advertise, was answered. Refuse it before anything else and send nothing back.`),n.parent.surfacesShown!==a&&n.fail(`${u} under unadvertised '${e}' showed a surface.`),n.errors.some(T=>T.code==="unsupported_capability")||n.fail(`${u} under '${e}', which ${h} 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 acting ask with no press \u2014 or no activation API \u2014 declined / busy, showing nothing",async run(r,t){for(let e of L)for(let n of ["unpressed","missing"])await b(this.id,r,t,async i=>{if(i.start(),!i.advertised().includes(e))return;let s=await i.raw(L),d=i.surfaces(`the parent advertises '${e}'`),{type:a,fields:u}=Ae[e],y=await s.ask(a,u,n);ie(i,s,y,d,n==="missing"?`${a} with no userActivation API (it must fail closed)`:`${a} 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(r,t){await b(this.id,r,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let n=await e.raw(L),i=e.surfaces("the parent advertises `store`"),s=await n.ask("gem.requestStorePurchase",{skuId:it},"pressed"),d=n.answers(s);(d.length!==1||d[0].outcome!=="unavailable")&&e.fail(`a pressed purchase of an unknown SKU was answered ${JSON.stringify(d)}; 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 sheet is open",async run(r,t){await b(this.id,r,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let n=await e.raw(L),i=await nt(e,n),s=await n.ask("gem.requestStorePurchase",{skuId:i.skuId},"pressed");ie(e,n,s,i.shown,"a second store purchase while one is open");for(let a of ["appLinks","openGame"]){if(!e.advertised().includes(a))continue;let{type:u,fields:y}=Ae[a],w=await n.ask(u,y,"pressed");ie(e,n,w,i.shown,`${u} while a store sheet is open`);}rt(e),await e.flush();let[d]=n.answers(i.requestId);(d?.outcome!=="declined"||d.reason!=="cancelled")&&e.fail(`the dismissed sheet was answered ${JSON.stringify(d?.outcome)} / ${JSON.stringify(d?.reason)}, not 'declined' / 'cancelled'.`);});}},{id:"store.backs-off-after-cancel",title:"answers the next store purchase declined / busy, showing nothing, after a dismissal",async run(r,t){await b(this.id,r,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let n=await e.raw(L),i=await nt(e,n);rt(e),await e.flush();let s=e.surfaces("the parent advertises `store`"),d=await n.ask("gem.requestStorePurchase",{skuId:i.skuId},"pressed");ie(e,n,d,s,"a store purchase straight after the player dismissed one");});}}],Zn=Object.freeze(Qn);async function bv(r,t){if(!V(t.embedderOrigin))throw new Error(`runParentConformance: embedderOrigin must be one exact origin, got ${JSON.stringify(t.embedderOrigin)}`);let e=[];for(let n of Zn)try{await n.run(r,t),e.push({id:n.id,title:n.title,ok:!0});}catch(i){e.push({id:n.id,title:n.title,ok:false,message:ot(i)});}return e}o(bv,"runParentConformance");
3
- export{Zn as PARENT_CONFORMANCE_CASES,hv as PARENT_CONFORMANCE_VERSION,W as ParentConformanceError,bv as runParentConformance};//# sourceMappingURL=gem-web-sdk.conformance.js.map
1
+ var at=Object.defineProperty;var i=(r,t)=>at(r,"name",{value:t,configurable:true});var c="gem-web-bridge",R={min:1,max:1},st=65536,xe=8,ee=5e3,Pe=1024,x=1e4,I=Object.create(null),te="__malformed";var fe=Object.create(null),lt=Object.freeze([["gem.asyncOpponents.","asyncOpponents"],["gem.vibration.","vibration"],["gem.viewport.","reservedAreas"],["gem.moments.","moments"],["gem.prefs.","audioChannels"]]);function ne(r){let t=fe[r];if(t!==void 0)return t;for(let[e,n]of lt)if(r.startsWith(e))return n}i(ne,"capabilityFor");function V(r){let t=/^([a-z][a-z0-9+.-]*):\/\/([a-z0-9.-]+|\[[0-9a-f:.]+\])(:([0-9]{1,5}))?$/i.exec(r);return t===null?false:t[4]===void 0||Number(t[4])<=65535}i(V,"isOrigin");function dt(r){let t;try{t=new URL(r);}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(dt,"isSafeHttpUrl");function ct(r){return /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i.test(r)}i(ct,"isBcp47");function ut(r){return /^[a-z_]+$/.test(r)}i(ut,"isCode");function mt(r){return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(r)||/^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$/.test(r)&&!r.includes("--")}i(mt,"isGameRef");function pe(r,t,e){if(t>xe)return `nesting deeper than ${xe}`;if(++e.nodes>ee)return `more than ${ee} values`;if(e.bytes>e.cap)return `over the ${e.cap} byte limit`;if(r==null)return e.bytes+=4,null;let n=typeof r;if(n==="boolean")return e.bytes+=5,null;if(n==="number")return e.bytes+=8,null;if(n==="string")return e.bytes+=r.length*3+2,null;if(n!=="object")return `${n} is not plain data`;let o=r;if(e.seen.has(o))return "a repeated or cyclic object reference";if(e.seen.add(o),Array.isArray(r)){if(r.length>Pe)return `an array of ${r.length} entries, over the ${Pe} limit`;e.bytes+=2;for(let a of r){let u=pe(a,t+1,e);if(u)return u}return null}let s=Object.getPrototypeOf(r);if(s!==Object.prototype&&s!==null)return "a value that is not a plain object";e.bytes+=2;let d=r;for(let a in d){if(!Object.prototype.hasOwnProperty.call(d,a))continue;if(++e.nodes>ee)return `more than ${ee} values`;if(e.bytes+=a.length*3+4,e.bytes>e.cap)return `over the ${e.cap} byte limit`;let u=pe(d[a],t+1,e);if(u)return u}return null}i(pe,"walk");function pt(r,t=st){let e={nodes:0,bytes:0,seen:new Set,cap:t},n=pe(r,0,e);return n?`message contains ${n}`:e.bytes>t?`message is over the ${t} byte limit`:null}i(pt,"validateSize");var gt={origin:[V,"not a well-formed origin"],url:[dt,"not a safe absolute http(s) URL"],bcp47:[ct,"not a well-formed BCP 47 tag"],code:[ut,"not a code of lower-case letters and underscores"],gameRef:[mt,"not a game id or web address"]};function Ee(r,t,e){for(let n of t){let o=e===""?n[0]:`${e}.${n[0]}`,s=r[n[0]];if(s==null){if(n[1]<"a")continue;return `${o}: required`}let d=ge(s,n,o);if(d)return d}return null}i(Ee,"checkFields");function ge(r,t,e){let n=t[1].toLowerCase();switch(n){case "u":return typeof r=="boolean"?null:ge(r,typeof r=="string"?[t[0],"s",t[2]]:[t[0],"n",t[3],t[4]],e);case "s":{if(typeof r!="string")return `${e}: expected string`;if(r.length>t[2])return `${e}: exceeds ${t[2]} characters`;if(t[3]){let[o,s]=gt[t[3]];if(!o(r))return `${e}: ${s}`}return null}case "n":case "i":return typeof r!="number"||!(n=="i"?Number.isInteger:Number.isFinite)(r)?`${e}: expected ${n=="i"?"integer":"number"}`:r<t[2]||r>t[3]?`${e}: out of range ${t[2]}..${t[3]}`:null;case "b":return typeof r=="boolean"?null:`${e}: expected boolean`;case "e":{let o=t[2];return o.includes(r)?null:`${e}: not one of ${o.join("|")}`}case "o":return typeof r!="object"||r===null||Array.isArray(r)?`${e}: expected object`:Ee(r,t[2],e);case "j":return typeof r!="object"||r===null||Array.isArray(r)?`${e}: expected a plain object`:null;case "a":{if(!Array.isArray(r))return `${e}: expected array`;if(r.length>t[2])return `${e}: exceeds ${t[2]} items`;let o=t[3],s=`${e}[]`;for(let d of r){if(d==null){if(o[1]<"a")continue;return `${s}: required`}let a=ge(d,o,s);if(a)return a}return null}default:return `${e}: unknown kind ${String(n)}`}}i(ge,"checkValue");var Te=Object.create(null);function l(r,t,e,n,o){return I[r]=e,n&&(fe[r]=n),Te[r]=s=>{let d=pt(s,o);if(d)return d;if(typeof s!="object"||s===null||Array.isArray(s))return "message: expected object";let a=s;return a.gem!==c?"gem: wrong protocol marker":a.type!==r?`type: expected ${r}`:Ee(a,t,"")}}i(l,"makeValidator");var m=["requestId","s",64],C=["outcome","e",["ok","declined","unavailable"]],M=["reason","S",64,"code"];l("gem.handshake",[["versions","o",[["min","i",1,1e3],["max","i",1,1e3]]],["capabilities","A",64,["","s",64]],["embedder","s",2048,"origin"],["unbidden","B"]],30);l("gem.handshake.accept",[["v","i",1,1e3],["capabilities","A",64,["","s",64]]],5);l("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);l("gem.token.denied",[m,["retryable","b"],["retryAfterMs","I",0,36e5],["reason","e",["chain_exhausted","unavailable"]],["correlationId","s",64]],30);l("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);l("gem.pause",[["paused","b"]],120);l("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);l("gem.cohorts",[["cohorts","a",8,["","o",[["experimentId","s",36],["variantKey","s",32]]]]],12,"cohorts");l("gem.earnAllowanceChanged",[],30,"earnAllowance");l("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");l("gem.deviceSettings",[["settings","J"]],12,"deviceSettings");l("gem.willTerminate",[["reason","e",["grant_expiring","session_ended","navigating","kill_switch"]],["graceMs","i",0,6e4]],10);l("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");l("gem.socketState",[["connected","b"],["reason","E",["connecting","dropped","watchdog","terminal"]]],12,"socketState");l("gem.seatedEvent",[["roomId","s",64]],60,"partySeating");l("gem.autoStartFailedEvent",[["roomId","s",64],["code","s",64]],30,"autoStartFailure");l("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");l("gem.signalEvent",[["event","e",["hello","offer","answer","ice","peerJoined","peerLeft","migration","matchEnded","error"]],["matchId","s",64],["data","j"]],1800,"p2p");l("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");l("gem.partyResult",[m,["partyId","S",64]],30,"parties");l("gem.clusterTarget",[["clusterTargetId","s",64],["clusterTargetIds","A",3,["","s",64]]],12,"clusterTarget");l("gem.devtools",[["action","e",["open","close","toggle"]]],30,"devtools");l("gem.screenshot",[["maxWidth","i",1,7680],["maxHeight","i",1,4320]],6,"screenshot");l("gem.screenshot.denied",[["reason","e",["not_wired","no_source","unsupported","unreadable","tainted","too_large","busy","declined","failed"]],["retryable","b"]],12,"screenshot");l("gem.requestContext",[],12,"feedbackContext");l("gem.gameContext",[["context","j"]],12,"feedbackContext");l("gem.signal",[["signal","e",["join","offer","answer","ice","migrationAck","hostUnreachable"]],["matchId","s",64],["data","j"]],1200,"p2p");l("gem.dedicatedOffer",[["matchId","s",64],["sdp","s",10240],["intent","e",["initial","ice_restart","full_reconnect"]]],60,"dedicated");l("gem.partyRequest",[m],12,"parties");l("gem.ready",[],5);l("gem.engaged",[],5,"engagement");l("gem.progress",[["loaded","i",0,1099511627776],["total","i",0,1099511627776]],600);l("gem.requestToken",[m,["reason","e",["initial","expiring","rejected"]]],12);l("gem.requestLocalServer",[m,["gameRoomId","s",64]],6,"localHost");l("gem.localServer",[m,["serverId","S",128]],30,"localHost");l("gem.localServer.denied",[m,["retryable","b"],["reason","e",["refused","unavailable"]]],30,"localHost");l("gem.exit",[["reason","S",64]],5);l("gem.openExternal",[["url","s",2048,"url"]],6);l("gem.requestFullscreen",[["enabled","b"]],30);l("gem.audioState",[["muted","b"]],120,"audioState");l("gem.rewardCue",[["currencySlug","s",32],["expected","I",1,1e9],["origin","O",[["x","i",0,16384],["y","i",0,16384]]]],60,"rewardCue");l("gem.walletDisplay",[["visible","b"],["currencies","A",8,["","s",64]],["score","B"]],60,"walletDisplay");l("gem.controlsDeclare",[["uses","o",[["lookSensitivity","S",16,"code"],["gamepadLookSensitivity","S",16,"code"],["invertLookY","S",16,"code"],["touchMovement","S",16,"code"]]]],60,"controls");l("gem.menu",[["settings","b"],["items","A",4,["","o",[["id","s",32],["label","s",40]]]]],60,"gameMenu");l("gem.menuSelect",[["entry","e",["settings","item"]],["id","S",32]],60,"gameMenu");l("gem.gemCorner",[["corner","e",["top-left","top-right","bottom-left","bottom-right"]]],12,"gemCorner");l("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");l("gem.settingChange",[["id","s",40],["value","u",40,-1e6,1e6]],120,"gameSettings");l("gem.requestShare",[["params","a",16,["","o",[["key","s",64],["value","s",512]]]]],6,"share");l("gem.requestAvatarPicker",[["tab","E",["2d","3d"]]],6,"avatarPicker");l("gem.vibrate",[["pattern","a",99,["","i",0,1e4]]],600,"vibration");l("gem.frameStats",[["fps","i",0,1e3],["frameTimeP95Ms","i",0,1e5],["counts","E",["pageFrames","gameDraws"]]],120,"frameStats");l("gem.portraitBorders",[["name","S",40]],60,"portraitBorders");l("gem.error",[["code","e",["version_mismatch","handshake_timeout","handshake_duplicate","invalid_message","rate_limited","token_unavailable","unsupported","unsupported_capability","internal"]],["message","S",256]],60);l("gem.requestPurchase",[m,["purchaseKind","e",["item","recipe"]],["targetId","s",64],["currencySlug","s",32],["quantity","I",1,10],["catalogId","S",64]],12,"purchases");l("gem.requestAvatar3dPurchase",[m,["itemId","s",64],["currencySlug","s",32]],12,"avatar3dPurchases");l("gem.purchaseResult",[m,C,M,["purchaseId","S",64]],30,"purchases");l("gem.deviceSettings.put",[m,["settings","j"]],30,"deviceSettings");l("gem.deviceSettings.result",[m,["outcome","e",["stored","too_large","unavailable"]]],30,"deviceSettings");l("gem.requestAd",[m,["placement","s",32],["format","e",["interstitial","rewarded"]]],6,"ads");l("gem.adResult",[m,["outcome","e",["shown","no_ad","unavailable"]]],12,"ads");l("gem.signalMoment",[m,["kind","e",["level_end","run_end","match_end","menu_return","other_break"]],["placement","S",32],["wall","B"]],6,"moments");l("gem.momentResult",[m,["shown","b"]],12,"moments");l("gem.requestSignIn",[m],6,"signIn");l("gem.signInResult",[m,["outcome","e",["signed_in","declined","unavailable"]]],12,"signIn");l("gem.requestFriendChallenge",[m,["playerId","s",64],["template","s",64],["params","J"]],12,"friendChallenges");l("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");l("gem.requestAppLinks",[m],12,"appLinks");l("gem.appLinksResult",[m,C,M,["arcade","O",[["store","e",["app_store","google_play"]]]],["game","O",[["store","e",["app_store","google_play"]]]]],30,"appLinks");l("gem.requestOpenApp",[m,["target","e",["arcade","game"]]],6,"appLinks");l("gem.openAppResult",[m,C,M],30,"appLinks");l("gem.requestOpenGame",[m,["ref","s",40,"gameRef"]],6,"openGame");l("gem.openGameResult",[m,C,M],30,"openGame");l("gem.requestSimilarGames",[m,["limit","I",1,24]],12,"openGame");l("gem.similarGamesResult",[m,C,M,["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);l("gem.requestStoreOffers",[m],12,"store");l("gem.storeOffersResult",[m,C,M,["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);l("gem.requestStorePurchase",[m,["skuId","s",64]],12,"store");l("gem.storePurchaseResult",[m,C,M],30,"store");l("gem.storePurchaseSettled",[["skuId","s",64]],30,"store");var $=Object.freeze(Te);I.__malformed=60;Object.freeze(I);Object.freeze(fe);var yt=/^[a-z0-9][a-z0-9_-]{0,39}$/;function ht(r){return typeof r=="string"&&yt.test(r)}i(ht,"isBorderName");function ye(r){if(r===null||ht(r))return r;let t=typeof r=="string"?JSON.stringify(r.length>48?`${r.slice(0,48)}\u2026`:r):`a value of type ${typeof r}`;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(ye,"checkBorderName");var P={now:i(()=>Date.now(),"now"),setTimeout:i((r,t)=>setTimeout(r,t),"setTimeout"),clearTimeout:i(r=>clearTimeout(r),"clearTimeout")},H=class{constructor(t=P){this.clock=t;}clock;static{i(this,"RateLimiter");}tokens=new Map;lastRefill=new Map;allow(t){let e=I[t];if(e===void 0)return false;let n=this.clock.now(),o=this.lastRefill.get(t)??n,s=this.tokens.get(t)??e,d=Math.max(0,n-o),a=Math.min(e,s+d*e/6e4);return a<1?(this.tokens.set(t,a),this.lastRefill.set(t,n),false):(this.tokens.set(t,a-1),this.lastRefill.set(t,n),true)}};function K(r,t){return r.toLowerCase()===t.toLowerCase()}i(K,"originsEqual");function D(r){let t=r.toLowerCase();return t==="localhost"||t.endsWith(".localhost")||t==="[::1]"||t==="::1"?true:/^127(\.\d{1,3}){3}$/.test(t)}i(D,"isLoopbackHostname");function he(r,t){if(typeof t!="string"||t==="")return false;let e,n;try{e=new URL(r),n=new URL(t);}catch{return false}return e.protocol!=="http:"&&e.protocol!=="https:"||n.protocol!=="http:"&&n.protocol!=="https:"||e.username!==""||e.password!==""||e.pathname!=="/"||e.search!==""||e.hash!==""||e.port===""?false:D(e.hostname)&&D(n.hostname)}i(he,"isLoopbackDevHostEmbedder");function Ie(r,t){if(r.min>r.max||t.min>t.max)return null;let e=Math.min(r.max,t.max),n=Math.max(r.min,t.min);return e>=n?e:null}i(Ie,"negotiateVersion");var Oe="[gem: use gem.text.toPlain()]",Ce=new Set,Me;function bt(r){try{if(Ce.has(r))return;let t=globalThis.location;if(Me??=!!t?.port&&D(t.hostname??""),!Me)return;Ce.add(r),console.warn(`[gem] ${r} is PlayerText, turned into a string: players see ${Oe}. Render text.toPlain(value) (text is in @indiegems/gem-web-sdk/sdk).
2
+ ${new Error().stack}`);}catch{}}i(bt,"warnStringified");var re=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),Oe}toJSON(){return this.toString()}get[Symbol.toStringTag](){return "PlayerText"}static unwrap(t){return t.#e}};function O(r,t){return new re(r,t)}i(O,"asPlayerText");function Ge(r){return Math.min(16384,Math.max(0,Math.round(Number.isFinite(r)?r:0)))}i(Ge,"cuePoint");function De(r,t){if(r!==true)return {visible:false};let e=[],n=false;for(let o of Array.isArray(t)?t:[])o==="score"?n=true:typeof o=="string"&&o.length>0&&o.length<=64&&!e.includes(o)&&e.push(o);return !n&&e.length===0?{visible:true}:{visible:true,currencies:e.slice(0,8),score:n}}i(De,"walletState");function Fe(r){let t=[];for(let e of Array.isArray(r)?r:[]){if(t.length===4)break;if(typeof e!="object"||e===null)continue;let{id:n,label:o}=e;typeof n!="string"||n.length===0||n.length>32||typeof o!="string"||o.trim().length===0||t.some(s=>s.id===n)||t.push({id:n,label:o.trim().slice(0,40)});}return t.length===0?{settings:false}:{settings:false,items:t}}i(Fe,"menuState");function Le(r,t){return Math.min(t,Math.max(0,Math.round(Number.isFinite(r)?r:0)))}i(Le,"clampStat");function vt(r){let t=r.untrusted,e=O(t.displayName,"gem.player.displayName"),n=t.displayTag||t.displayHandle,o=n?O(n,"gem.player.displayTag"):void 0,s=r.discriminator||r.displayTag,d=s&&/^[0-9]{1,8}$/.test(s)?s:void 0,a=r.avatarIconUrl||r.avatarUrl,u=/^[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$/i,y=r.avatarIconId,w=r.avatar3dId,h={displayName:e,displayHandle:o??e,badges:(r.badges??[]).map(v=>({itemId:v.itemId,iconUrl:v.iconUrl}))};r.playerId&&(h.playerId=r.playerId),d&&(h.discriminator=d),o&&(h.displayTag=o),a&&(h.avatarIconUrl=h.avatarUrl=a),y&&u.test(y)&&(h.avatarIconId=y),w&&u.test(w)&&(h.avatar3dId=w),r.avatar3dModelUrl&&(h.avatar3dModelUrl=r.avatar3dModelUrl),r.avatar3dIconUrl&&(h.avatar3dIconUrl=r.avatar3dIconUrl),r.avatars3d&&(h.avatars3d=r.avatars3d.filter(v=>u.test(v.id)&&/^[a-z][a-z0-9_]*$/.test(v.family)).filter((v,ce,ue)=>ue.findIndex(me=>me.family==v.family)==ce).map(({family:v,id:ce,modelUrl:ue,iconUrl:me})=>({family:v,id:ce,modelUrl:ue,iconUrl:me})));let T=r.boost,le=(T?.multipliers??[]).map(v=>({currencyId:v.currencyId,currencySlug:v.currencySlug,multiplier:v.multiplierBp/1e4})),de=le.some(v=>v.multiplier>1);return h.boost=de&&T?.expiresInDays?{active:de,multipliers:le,expiresInDays:T.expiresInDays}:{active:de,multipliers:le},h}i(vt,"playerFromWire");var _t=1e4,wt=["appLinks","openGame","store"],St=new WeakMap;var F=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??P,this.inbound=new H(this.clock),this.outbound=new H(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(a=>K(a,t.origin))||he(t.origin,this.options.selfOrigin)||(St.get(this)??[]).some(a=>K(a,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 n=$["gem.handshake"]?.(t.data);if(n)return this.refuse(t,`malformed handshake: ${n}`),false;let o=t.data;if(!K(o.embedder,t.origin))return this.refuse(t,`handshake claims embedder '${o.embedder}' but arrived from '${t.origin}'`),false;let s=t.ports?.[0];if(!s)return this.refuse(t,"handshake transferred no MessagePort"),false;let d=Ie(R,o.versions);return d===null?(s.onmessage=null,s.postMessage({gem:c,type:"gem.error",code:"version_mismatch",message:`frame supports v${R.min}..v${R.max}`}),s.close(),this.transition("refused"),this.options.onError?.("version_mismatch"),false):(this.port=s,this.version=d,s.onmessage=a=>this.receive(a.data,a.ports??[]),s.start?.(),this.peerCapabilities=o.capabilities??[],this.unbidden=o.unbidden===true,s.postMessage({gem:c,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:c,type:"gem.ready"});}settled(t=_t){return this.state!=="waiting"?Promise.resolve(this.state):new Promise(e=>{let n=i(s=>{this.clock.clearTimeout(o),e(s);},"waiter"),o=this.clock.setTimeout(()=>{let s=this.settleWaiters.indexOf(n);s>=0&&this.settleWaiters.splice(s,1),e(this.state);},t);this.settleWaiters.push(n);})}progress(t,e){this.send({gem:c,type:"gem.progress",loaded:t,total:e});}requestToken(t,e){this.send({gem:c,type:"gem.requestToken",requestId:t,reason:e});}requestLocalServer(t,e){return this.parentSupports("localHost")?(this.send({gem:c,type:"gem.requestLocalServer",requestId:t,gameRoomId:e}),true):false}exit(t){this.send(t===void 0?{gem:c,type:"gem.exit"}:{gem:c,type:"gem.exit",reason:t});}openExternal(t){this.send({gem:c,type:"gem.openExternal",url:t});}requestFullscreen(t){this.send({gem:c,type:"gem.requestFullscreen",enabled:t});}reportMuted(t){return this.gated("audioState")?(this.send({gem:c,type:"gem.audioState",muted:t}),true):false}reportEngaged(){return this.engagedSent||!this.gated("engagement")||!this.send({gem:c,type:"gem.engaged"})?false:(this.engagedSent=true,true)}cueReward(t){if(!this.gated("rewardCue"))return false;let e={gem:c,type:"gem.rewardCue",currencySlug:String(t.currencySlug).slice(0,32)};return 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:Ge(t.origin.x),y:Ge(t.origin.y)}),this.send(e),true}showPortraitBorders(t){let e=ye(t);return e===void 0||!this.gated("portraitBorders")?false:this.send(e===null?{gem:c,type:"gem.portraitBorders"}:{gem:c,type:"gem.portraitBorders",name:e})}setWalletVisible(t,e){return this.gated("walletDisplay")?this.send({gem:c,type:"gem.walletDisplay",...De(t,e?.only)}):false}setMenu(t){return this.gated("gameMenu")?this.send({gem:c,type:"gem.menu",...Fe(t)}):false}declareSettings(t){return this.gated("gameSettings")?this.send({gem:c,type:"gem.settingsDeclare",settings:t}):false}setGemCorner(t){return this.gated("gemCorner")?this.send({gem:c,type:"gem.gemCorner",corner:t}):false}requestShare(t){return this.gated("share")?this.send({gem:c,type:"gem.requestShare",params:t.map(({key:e,value:n})=>({key:e,value:n}))}):false}requestAvatarPicker(t){return this.gated("avatarPicker")?this.send({gem:c,type:"gem.requestAvatarPicker",...t==="2d"||t==="3d"?{tab:t}:{}}):false}requestVibrate(t){return this.gated("vibration")?this.send({gem:c,type:"gem.vibrate",pattern:[...t]}):false}reportFrameStats(t){return this.gated("frameStats")?(this.send({gem:c,type:"gem.frameStats",fps:Le(t.fps,1e3),frameTimeP95Ms:Le(t.frameTimeP95Ms,1e5),counts:t.counts==="gameDraws"?"gameDraws":"pageFrames"}),true):false}sendGameContext(t){return this.state!=="connected"?false:(this.send({gem:c,type:"gem.gameContext",context:t}),true)}signal(t,e,n){this.send({gem:c,type:"gem.signal",signal:t,matchId:e,data:n});}dedicatedOffer(t,e,n){this.send({gem:c,type:"gem.dedicatedOffer",matchId:t,sdp:e,intent:n});}requestParty(t){return this.send({gem:c,type:"gem.partyRequest",requestId:t})}requestPurchase(t,e,n,o,s,d){return this.send({gem:c,type:"gem.requestPurchase",requestId:t,purchaseKind:e,targetId:n,currencySlug:o,...s===void 0?{}:{quantity:s},...d===void 0?{}:{catalogId:d}})}requestAvatar3dPurchase(t,e,n){return this.parentSupports("avatar3dPurchases")?this.send({gem:c,type:"gem.requestAvatar3dPurchase",requestId:t,itemId:e,currencySlug:n}):false}putDeviceSettings(t,e){return this.send({gem:c,type:"gem.deviceSettings.put",requestId:t,settings:e})}requestAd(t,e,n){return this.send({gem:c,type:"gem.requestAd",requestId:t,placement:e,format:n})}signalMoment(t,e,n,o){return this.send({gem:c,type:"gem.signalMoment",requestId:t,kind:e,...n===void 0?{}:{placement:n},...o===true?{wall:true}:{}})}readyCalled(){return this.readySent||this.readyPending}requestSignIn(t){return this.send({gem:c,type:"gem.requestSignIn",requestId:t})}requestFriendChallenge(t,e,n,o){return this.send({gem:c,type:"gem.requestFriendChallenge",requestId:t,playerId:e,template:n,...o===void 0?{}:{params:o}})}requestArcade(t){return this.gated(ne(t.type))&&this.send({gem:c,...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(te)){this.options.onDropped?.(t,e);return}this.fail(t,e);}dropUnknownType(t){this.inbound.allow(te)&&this.options.onDropped?.("unsupported",`ignored unknown message type '${t}'`);}refuse(t,e){t.ports?.[0]?.close?.(),this.inbound.allow(te)&&this.options.onRefused?.(e);}fail(t,e){this.options.onError?.(t,e),this.state==="connected"&&this.port&&this.send({gem:c,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 n=$[e]?.(t);return n?(this.options.onError?.("internal",`refusing to send malformed ${e}: ${n}`),false):(this.port.postMessage(this.version===null?t:{...t,v:this.version}),true)}receive(t,e=[]){let n=false;try{n=this.dispatchInbound(t,e);}finally{n||$e(e);}}dispatchInbound(t,e){if(this.state!=="connected")return false;let n=typeof t=="object"&&t!==null?t.type:void 0;if(typeof n!="string")return this.refuseMalformed("invalid_message","message has no type"),false;let o=$[n];if(!o)return this.dropUnknownType(n),false;if(!this.inbound.allow(n))return this.fail("rate_limited",`parent exceeded the limit for ${n}`),false;let s=ne(n);if(s!==void 0&&!this.options.capabilities?.includes(s))return this.fail("unsupported_capability",`${n} requires the '${s}' capability, which this frame did not advertise`),false;let d=o(t);if(d)return this.fail("invalid_message",`${n}: ${d}`),false;let a=t;if(a.v!==this.version)return this.fail("invalid_message",`${n}: expected v ${String(this.version)}`),false;switch(n){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=e[0];if(!u){this.options.onError?.("invalid_message","gem.screenshot transferred no MessagePort");break}let y={maxWidth:a.maxWidth,maxHeight:a.maxHeight};if(!this.options.onScreenshot){u.close(),this.screenshotDenied("not_wired",false);break}return $e(e.slice(1)),this.options.onScreenshot(y,u,(w,h)=>this.screenshotDenied(w,h)),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?.(vt(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(wt.includes(ne(n))){this.options.onArcadeAnswer?.(a);break}this.fail("unsupported",`no frame handler for '${n}'`);}return false}screenshotDenied(t,e){this.send({gem:c,type:"gem.screenshot.denied",reason:t,retryable:e});}};function $e(r){for(let t of r)try{t?.close?.();}catch{}}i($e,"closeAll");globalThis.__gemWebSdkVersion="[gem-web-sdk:sdkVersion:16.3.0]";var qe=["if-match","if-none-match"];var xt={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,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(xt));new Set(qe);new TextEncoder;var je=["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(je);new TextEncoder;new TextDecoder;new TextEncoder;var rn=8;(class{constructor(t=rn){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 n of this.sent)n<=t&&t-n<6e4&&(this.sent[e++]=n);this.sent.length=e;}});function p(r){return t=>typeof t=="string"&&t.length>0&&t.length<=r?t:void 0}i(p,"text");function E(r,t){return e=>typeof e=="number"&&Number.isInteger(e)&&e>=r&&e<=t?e:void 0}i(E,"integer");function N(...r){return t=>typeof t=="string"&&r.includes(t)?t:void 0}i(N,"oneOf");function Ue(r){return typeof r=="boolean"?r:void 0}i(Ue,"flag");var _=p(64),Y=E(0,2147483647);function ke(r){if(!Array.isArray(r))return;let t=[];for(let e of r){if(t.length>=64)break;let n=_(e);typeof n=="string"&&t.push(n);}return t}i(ke,"ids");function f(r,t,e){return {to:r,from:t,read:e,required:true}}i(f,"required");function g(r,t,e){return {to:r,from:t,read:e,required:false}}i(g,"optional");var sn=i(r=>{if(typeof r!="object"||r===null||Array.isArray(r))return;let t=r,e=N("disconnected","failed","closed")(t.iceState),n=E(0,4102444800)(t.lastSeenAt);if(!(e===void 0&&n===void 0))return {...e===void 0?{}:{ice_state:e},...n===void 0?{}:{last_seen_timestamp:n}}},"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",p(10240)),f("signaling_intent","intent",N("initial","ice_restart","full_reconnect")),g("sdp_hash","sdpHash",p(128))]},answer:{type:"p2p_answer",gameScoped:false,args:[f("peer_id","peerId",_),f("sdp","sdp",p(10240)),f("assigned_peer_id","assignedPeerId",E(0,4294967295)),g("sdp_hash","sdpHash",p(128))]},ice:{type:"p2p_ice_candidate",gameScoped:false,args:[f("peer_id","peerId",_),f("candidate","candidate",p(512)),g("sdp_mid","sdpMid",p(64)),g("sdp_mline_index","sdpMLineIndex",E(0,255))]},migrationAck:{type:"p2p_migration_ack",gameScoped:false,args:[f("host_epoch","hostEpoch",Y)]},hostUnreachable:{type:"p2p_report_host_unreachable",gameScoped:false,args:[f("host_epoch","hostEpoch",Y),g("evidence","evidence",sn)]}}));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"}));Object.freeze(Object.assign(Object.create(null),{hello:[f("playerId","player_id",_),f("hostPlayerId","host_player_id",_),f("hostEpoch","host_epoch",Y),g("members","members",ke)],offer:[f("fromPlayerId","from_player_id",_),f("sdp","sdp",p(10240)),g("sessionId","signaling_session_id",p(128)),g("sdpHash","sdp_hash",p(128)),g("iceUfrag","ice_ufrag",p(64)),g("intent","signaling_intent",N("initial","ice_restart","full_reconnect"))],answer:[f("fromPlayerId","from_player_id",_),f("sdp","sdp",p(10240)),g("sessionId","signaling_session_id",p(128)),g("sdpHash","sdp_hash",p(128)),g("iceUfrag","ice_ufrag",p(64)),g("assignedPeerId","assigned_peer_id",E(0,4294967295))],ice:[f("fromPlayerId","from_player_id",_),f("candidate","candidate",p(512)),g("sdpMid","sdp_mid",p(64)),g("sdpMLineIndex","sdp_m_line_index",E(0,255)),g("sdpMLineIndexAlt","sdp_mline_index",E(0,255))],peerJoined:[f("playerId","player_id",_)],peerLeft:[f("playerId","player_id",_),g("reason","reason",p(64))],migration:[f("hostPlayerId","new_host_player_id",_),f("hostEpoch","host_epoch",Y),g("reason","reason",p(64)),g("kickedPlayerIds","kicked_player_ids",ke),g("migrationComplete","migration_complete",Ue)],matchEnded:[g("reason","reason",p(64))],error:[g("code","code",p(64)),g("message","message",p(256))]}));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"}));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",p(10240)),g("sessionId","session_id",p(64))],error:[g("code","code",p(64)),g("message","message",p(256))],matchEnded:[g("reason","reason",p(64))]}));var Ze=Object.freeze(["https://gemarcade.com","https://developer.gemarcade.com","http://gemarcade.localhost","gemarcade://localhost"]);var W=class extends Error{constructor(e,n){super(`[${e}] ${n}`);this.caseId=e;this.name="ParentConformanceError";}caseId;static{i(this,"ParentConformanceError");}},Iv=2,et="https://game.conformance.invalid",Xn="https://stranger.conformance.invalid",Re=I["gem.handshake"]??1,Z=6e4,ae=class{static{i(this,"KitClock");}current=1e6;seq=0;pending=new Map;now(){return this.current}setTimeout(t,e){let n=++this.seq;return this.pending.set(n,{at:this.current+Math.max(0,e),fn:t}),n}clearTimeout(t){this.pending.delete(t);}get pendingTimers(){return this.pending.size}advance(t){let e=this.current+t;for(;;){let n;for(let o of this.pending)o[1].at<=e&&(n===void 0||o[1].at<n[1].at)&&(n=o);if(n===void 0)break;this.pending.delete(n[0]),this.current=n[1].at,n[1].fn();}this.current=e;}},tt=Object.freeze({conformance:"parent-window"}),se=class{constructor(t,e,n,o){this.caseId=t;this.options=n;let s=o??n.frameOrigin??et,d={frame:{postMessage:i((a,u,y)=>{this.offers.push({data:a,targetOrigin:u,transfer:y??[],at:this.clock.now()});},"postMessage")},frameOrigin:s,embedderOrigin:n.embedderOrigin,clock:this.clock,makeChannel:i(()=>{let a=new MessageChannel,u={port1:a.port1,port2:a.port2,closed:false},y=a.port1.close.bind(a.port1);return a.port1.close=()=>{u.closed=true,y();},this.count(a.port1),this.count(a.port2),this.minted.push(u),a},"makeChannel"),reportError:i((a,u)=>{this.errors.push(u===void 0?{code:a}:{code:a,message:u});},"reportError")};Object.defineProperty(d,"userActivation",{enumerable:true,get:i(()=>this.activation===void 0?void 0:{...this.activation},"get")}),this.parent=e(d);}caseId;options;static{i(this,"Rig");}clock=new ae;frameClock=new ae;offers=[];minted=[];parent;activation={isActive:false};errors=[];inFlight=0;frames=[];get frameOrigin(){return this.options.frameOrigin??et}fail(t){throw new W(this.caseId,t)}count(t){let e=t.postMessage.bind(t);t.postMessage=((...n)=>{this.inFlight+=1,e(...n);}),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 n=0;n<t;n+=e){let o=Math.min(e,t-n);this.clock.advance(o),this.frameClock.advance(o);}}start(){try{this.parent.start();}catch(t){this.fail(`start() threw: ${it(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(n=>n.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([...Ze,...this.options.allowedEmbedders??[]])],n=[],o=new F({allowedEmbedders:e,expectedSource:tt,selfOrigin:this.frameOrigin,clock:this.frameClock,onRefused:i(s=>n.push(s),"onRefused")});return o.kitRefusals=n,this.frames.push(o),o}deliver(t,e){let n=this.transferred(e);return t.handleWindowMessage({data:e.data,origin:this.options.embedderOrigin,source:tt,ports:[n.port2]})}answer(t,e,n=[]){this.transferred(t).port2.postMessage({gem:c,type:"gem.handshake.accept",v:e,capabilities:[...n]});}advertised(){let t=this.latest().data.capabilities;return Array.isArray(t)?t.filter(e=>typeof e=="string"):[]}async raw(t){let e=this.latest(),n=this.transferred(e),o=[];n.port2.onmessage=a=>{a.data!==null&&typeof a.data=="object"&&o.push(a.data);};let s=Math.min(U(this).max,R.max);this.answer(e,s,t),await this.flush(),this.state()!=="connected"&&this.fail(`a hand-written frame accepted at v${s}, and the parent reports '${this.state()}'.`);let d=0;return {received:o,ask:i(async(a,u,y)=>{let w=`conformance-${++d}`;return this.activation=y==="missing"?void 0:{isActive:y==="pressed"},n.port2.postMessage({gem:c,type:a,v:s,requestId:w,...u}),await this.flush(),this.activation={isActive:false},w},"ask"),answers:i(a=>o.filter(u=>u.requestId===a),"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 sheet, 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 n=this.offers[e].at,o=0;for(let s=e;s<this.offers.length&&this.offers[s].at<n+Z;s++)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 it(r){return r instanceof Error?r.message:String(r)}i(it,"describe");function Yn(r){return r.kitRefusals??[]}i(Yn,"refusalsOf");function U(r){let t=r.latest().data.versions,e=t?.min,n=t?.max;return (typeof e!="number"||typeof n!="number")&&r.fail("the handshake carries no numeric `versions.min` / `versions.max`"),{min:e,max:n}}i(U,"offeredRange");async function b(r,t,e,n){let o=new se(r,t,e);try{await n(o);}finally{o.dispose();}}i(b,"withRig");async function Q(r){let t=r.frame();return r.deliver(t,r.latest())||r.fail(`a game built with this SDK refused the handshake from ${r.options.embedderOrigin}: ${Yn(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 r.flush(),r.state()!=="connected"&&r.fail(`the frame accepted, but the parent reports '${r.state()}' rather than 'connected'.`),t}i(Q,"connect");var L=["appLinks","openGame","store"],Qn="00000000-0000-4000-8000-00000000c0de",ot="conformance-unknown-sku",Ae={appLinks:{type:"gem.requestOpenApp",fields:{target:"arcade"}},openGame:{type:"gem.requestOpenGame",fields:{ref:Qn}},store:{type:"gem.requestStorePurchase",fields:{skuId:ot}}};function oe(r,t,e,n,o){let s=t.answers(e);s.length!==1&&r.fail(`${o}: the parent sent ${s.length} answers; it owes exactly one.`);let[d]=s;(d.outcome!=="declined"||d.reason!=="busy")&&r.fail(`${o}: answered ${JSON.stringify(d.outcome)} / ${JSON.stringify(d.reason)}, not 'declined' / 'busy'.`);let a=r.surfaces(o);a!==n&&r.fail(`${o}: the parent showed ${a-n} surface(s); it must show nothing.`);}i(oe,"expectBusy");async function nt(r,t){let e=await t.ask("gem.requestStoreOffers",{},"unpressed"),[n]=t.answers(e),s=(n?.offers??[])[0]?.skuId;(n?.outcome!=="ok"||typeof s!="string")&&r.fail(`the parent advertises \`store\` but listed no offer to a signed-in player (${JSON.stringify(n?.outcome)} / ${JSON.stringify(n?.reason)}). Give the adapter's store a signed-in player and at least one SKU, so the kit can open a sheet.`);let d=r.surfaces("the parent advertises `store`"),a=await t.ask("gem.requestStorePurchase",{skuId:s},"pressed"),u=r.surfaces("the parent advertises `store`");return u!==d+1&&r.fail(`a pressed purchase of the listed SKU ${JSON.stringify(s)} showed ${u-d} surface(s); it must open exactly one sheet.`),t.answers(a).length>0&&r.fail("the store purchase was answered while its sheet was open. Answer once the player has acted, so the kit can hold a sheet open."),{requestId:a,skuId:s,shown:u}}i(nt,"openStoreSheet");function rt(r){typeof r.parent.dismiss!="function"&&r.fail('the parent advertises `store`, so the kit must be able to dismiss its sheet: expose `dismiss()` on the adapter (the player pressing Escape, Back or "Not now").'),r.parent.dismiss();}i(rt,"dismiss");var Zn=["*","null","","https://game.conformance.invalid/","https://game.conformance.invalid/play"],er=[{id:"origin.refuses-imprecise-target",title:"refuses a frame origin that is not one exact origin, before anything is posted",async run(r,t){for(let e of Zn){let n=JSON.stringify(e),o,s=false;try{o=new se(this.id,r,{...t,frameOrigin:e},e),o.parent.start();}catch(d){if(d instanceof W)throw d;s=true;}try{if((o?.offers??[]).some(a=>a.targetOrigin==="*"||a.targetOrigin===e))throw new W(this.id,`with frame origin ${n} 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(!s)throw new W(this.id,`a frame origin of ${n} 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(r,t){await b(this.id,r,t,e=>{e.start(),e.advance(x);for(let s of e.offers){s.targetOrigin!==e.frameOrigin&&e.fail(`an offer was posted with targetOrigin ${JSON.stringify(s.targetOrigin)}, not the frame origin ${e.frameOrigin}.`),e.transferred(s);let d=$["gem.handshake"]?.(s.data);d&&e.fail(`the handshake does not validate against the schema: ${d}`);let a=s.data;a.gem!==c&&e.fail(`the handshake's \`gem\` is not ${c}`),a.embedder!==t.embedderOrigin&&e.fail(`the handshake names embedder ${JSON.stringify(a.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:n,max:o}=U(e);n>o&&e.fail(`the offered range is empty: min ${n} > max ${o}`),(o<R.min||n>R.max)&&e.fail(`the offered range v${n}\u2013v${o} shares no version with this SDK (v${R.min}\u2013v${R.max}).`);});}},{id:"handshake.default-game-connects",title:"connects to a game that names no embedders, at the highest common version",async run(r,t){await b(this.id,r,t,async e=>{e.start();let n=await Q(e),{max:o}=U(e),s=Math.min(o,R.max);n.protocolVersion!==s&&e.fail(`the frame chose v${String(n.protocolVersion)}, expected v${s}`),e.parent.protocolVersion!==n.protocolVersion&&e.fail(`the parent reports v${String(e.parent.protocolVersion)} while the frame chose v${String(n.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(r,t){await b(this.id,r,t,async e=>{e.start();let n=e.frame([Xn]);e.deliver(n,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(x*3),await e.flush(),e.state()!=="handshaking"&&e.fail(`after a refusal and ${x*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(r,t){await b(this.id,r,t,async e=>{e.start();let{min:n}=U(e);e.answer(e.latest(),n),await e.flush(),(e.state()!=="connected"||e.parent.protocolVersion!==n)&&e.fail(`answered with v${n}, 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(r,t){for(let e of ["above","below"])await b(this.id,r,t,async n=>{n.start();let{min:o,max:s}=U(n),d=e==="above"?s+1:o-1;n.answer(n.latest(),d),await n.flush(),(n.state()==="connected"||n.parent.protocolVersion===d)&&n.fail(`answered with v${d}, outside the offered v${o}\u2013v${s}, 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(r,t){await b(this.id,r,t,e=>{e.start(),e.advance(x*2),e.offers.length<2&&e.fail(`one offer in ${x*2}ms. A message to a document that is not listening yet is lost, not queued: re-offer until something answers.`);let n=e.offers.map(s=>e.transferred(s).port2);new Set(n).size!==n.length&&e.fail("an offer re-used a port; each offer needs its own channel."),e.advance(Z*2);let o=e.offersInAnyMinute();o>Re&&e.fail(`${o} offers inside one minute; the contract budgets ${Re} \`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(r,t){await b(this.id,r,t,async e=>{e.start(),e.advance(x*2);let n=e.transferred(e.latest());await Q(e),n.closed&&e.fail("the parent closed the port the frame answered on.");let o=e.minted.filter(s=>s!==n&&!s.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(r,t){await b(this.id,r,t,async e=>{e.start(),await Q(e);let n=e.offers.length;e.advance(Z*5,1e3),e.offers.length!==n&&e.fail(`${e.offers.length-n} 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(r,t){await b(this.id,r,t,async e=>{e.start(),await Q(e);let n=e.parent.protocolVersion;e.answer(e.latest(),U(e).max+1),await e.flush(),(e.state()!=="connected"||e.parent.protocolVersion!==n)&&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(r,t){await b(this.id,r,t,e=>{e.start(),e.advance(x),e.parent.close(),e.state()!=="closed"&&e.fail(`after close() the parent reports '${e.state()}'`);let n=e.minted.filter(s=>!s.closed).length;n>0&&e.fail(`${n} 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(Z*2,1e3),e.offers.length!==o&&e.fail("the parent offered again after close().");}),await b(this.id,r,t,async e=>{e.start(),await Q(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(r,t){await b(this.id,r,t,e=>{e.start(),e.advance(Z*10,1e3);let n=e.minted.filter(o=>!o.closed).length;n>Re+1&&e.fail(`${n} 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(r,t){for(let e of L)await b(this.id,r,t,async n=>{n.start();let o=n.advertised().includes(e),s=L.filter(T=>!o||T!==e),d=await n.raw(s),a=n.parent.surfacesShown,{type:u,fields:y}=Ae[e],w=await d.ask(u,y,"pressed"),h=o?"the frame":"the parent";d.answers(w).length>0&&n.fail(`${u} under '${e}', which ${h} did not advertise, was answered. Refuse it before anything else and send nothing back.`),n.parent.surfacesShown!==a&&n.fail(`${u} under unadvertised '${e}' showed a surface.`),n.errors.some(T=>T.code==="unsupported_capability")||n.fail(`${u} under '${e}', which ${h} 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 acting ask with no press \u2014 or no activation API \u2014 declined / busy, showing nothing",async run(r,t){for(let e of L)for(let n of ["unpressed","missing"])await b(this.id,r,t,async o=>{if(o.start(),!o.advertised().includes(e))return;let s=await o.raw(L),d=o.surfaces(`the parent advertises '${e}'`),{type:a,fields:u}=Ae[e],y=await s.ask(a,u,n);oe(o,s,y,d,n==="missing"?`${a} with no userActivation API (it must fail closed)`:`${a} 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(r,t){await b(this.id,r,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let n=await e.raw(L),o=e.surfaces("the parent advertises `store`"),s=await n.ask("gem.requestStorePurchase",{skuId:ot},"pressed"),d=n.answers(s);(d.length!==1||d[0].outcome!=="unavailable")&&e.fail(`a pressed purchase of an unknown SKU was answered ${JSON.stringify(d)}; 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 sheet is open",async run(r,t){await b(this.id,r,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let n=await e.raw(L),o=await nt(e,n),s=await n.ask("gem.requestStorePurchase",{skuId:o.skuId},"pressed");oe(e,n,s,o.shown,"a second store purchase while one is open");for(let a of ["appLinks","openGame"]){if(!e.advertised().includes(a))continue;let{type:u,fields:y}=Ae[a],w=await n.ask(u,y,"pressed");oe(e,n,w,o.shown,`${u} while a store sheet is open`);}rt(e),await e.flush();let[d]=n.answers(o.requestId);(d?.outcome!=="declined"||d.reason!=="cancelled")&&e.fail(`the dismissed sheet was answered ${JSON.stringify(d?.outcome)} / ${JSON.stringify(d?.reason)}, not 'declined' / 'cancelled'.`);});}},{id:"store.backs-off-after-cancel",title:"answers the next store purchase declined / busy, showing nothing, after a dismissal",async run(r,t){await b(this.id,r,t,async e=>{if(e.start(),!e.advertised().includes("store"))return;let n=await e.raw(L),o=await nt(e,n);rt(e),await e.flush();let s=e.surfaces("the parent advertises `store`"),d=await n.ask("gem.requestStorePurchase",{skuId:o.skuId},"pressed");oe(e,n,d,s,"a store purchase straight after the player dismissed one");});}}],tr=Object.freeze(er);async function Cv(r,t){if(!V(t.embedderOrigin))throw new Error(`runParentConformance: embedderOrigin must be one exact origin, got ${JSON.stringify(t.embedderOrigin)}`);let e=[];for(let n of tr)try{await n.run(r,t),e.push({id:n.id,title:n.title,ok:!0});}catch(o){e.push({id:n.id,title:n.title,ok:false,message:it(o)});}return e}i(Cv,"runParentConformance");
3
+ export{tr as PARENT_CONFORMANCE_CASES,Iv as PARENT_CONFORMANCE_VERSION,W as ParentConformanceError,Cv as runParentConformance};//# sourceMappingURL=gem-web-sdk.conformance.js.map
4
4
  //# sourceMappingURL=gem-web-sdk.conformance.js.map