@series-inc/rundot-game-sdk 5.7.0 → 5.8.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.
@@ -946,11 +946,9 @@ declare class RundotGameTransport implements RpcTransport {
946
946
  }
947
947
 
948
948
  /** Constraint for user-defined message unions shared between client and server. */
949
- type BaseMessage = {
949
+ type Protocol = {
950
950
  type: string;
951
951
  };
952
- /** Constraint for user-defined state objects shared between client and server. */
953
- type BaseState = Record<string, unknown>;
954
952
 
955
953
  /**
956
954
  * Server-authoritative room types used by the client multiplayer layer.
@@ -963,13 +961,11 @@ interface ServerPlayer {
963
961
  avatarUrl?: string | null;
964
962
  }
965
963
  /** Public interface for a server-authoritative room. */
966
- interface ServerRoom<M extends BaseMessage, S extends BaseState> {
964
+ interface ServerRoom<P extends Protocol> {
967
965
  /** The shareable room code (e.g. "HX9KWR") */
968
966
  readonly roomCode: string;
969
967
  /** The current player's ID */
970
968
  readonly playerId: string;
971
- /** Current room state (synced from the server) */
972
- readonly state: S;
973
969
  /** Whether the room is locked */
974
970
  readonly locked: boolean;
975
971
  /** Current latency in ms (round-trip / 2) */
@@ -977,21 +973,19 @@ interface ServerRoom<M extends BaseMessage, S extends BaseState> {
977
973
  /** Connection state */
978
974
  readonly connectionState: ConnectionState;
979
975
  /** Register event handlers. */
980
- on(events: RoomEvents<M, S>): void;
976
+ on(events: RoomEvents<P>): void;
981
977
  /** Send a typed message to the server room. */
982
- send(message: M): void;
978
+ send(message: P): void;
983
979
  /** Leave the room and close the connection. */
984
980
  leave(): void;
985
981
  /** Get estimated server time (local time + offset). */
986
982
  getServerTime(): number;
987
983
  }
988
- interface RoomEvents<M extends BaseMessage, S extends BaseState> {
989
- /** Called when the synced state changes */
990
- onStateChange?: (state: S) => void;
984
+ interface RoomEvents<P extends Protocol> {
991
985
  /** Called when a broadcast message is received from the server */
992
- onMessage?: (message: M) => void;
986
+ onMessage?: (message: P) => void;
993
987
  /** Called when a targeted message is received */
994
- onPrivateMessage?: (message: M) => void;
988
+ onPrivateMessage?: (message: P) => void;
995
989
  /** Called when a player joins the room */
996
990
  onPlayerJoined?: (player: ServerPlayer) => void;
997
991
  /** Called when a player leaves the room */
@@ -1019,15 +1013,15 @@ interface MultiplayerApi {
1019
1013
  /**
1020
1014
  * Create a new room of the given type.
1021
1015
  */
1022
- createRoom<M extends BaseMessage, S extends BaseState>(roomType: string): Promise<ServerRoom<M, S>>;
1016
+ createRoom<P extends Protocol>(roomType: string): Promise<ServerRoom<P>>;
1023
1017
  /**
1024
1018
  * Join a room by its 6-char code.
1025
1019
  */
1026
- joinRoomByCode<M extends BaseMessage, S extends BaseState>(code: string): Promise<ServerRoom<M, S>>;
1020
+ joinRoomByCode<P extends Protocol>(code: string): Promise<ServerRoom<P>>;
1027
1021
  /**
1028
1022
  * Join an existing room or create a new one (matchmaking by room type).
1029
1023
  */
1030
- joinOrCreateRoom<M extends BaseMessage, S extends BaseState>(roomType: string): Promise<ServerRoom<M, S>>;
1024
+ joinOrCreateRoom<P extends Protocol>(roomType: string): Promise<ServerRoom<P>>;
1031
1025
  }
1032
1026
 
1033
1027
  interface LoggingApi {
@@ -1645,15 +1639,72 @@ interface StorefrontItem {
1645
1639
  }[];
1646
1640
  };
1647
1641
  }
1642
+ interface StorefrontCollectionItem {
1643
+ itemId: string;
1644
+ resolvedPrice: {
1645
+ originalPrice: {
1646
+ type: string;
1647
+ value: string;
1648
+ };
1649
+ finalPrice: {
1650
+ type: string;
1651
+ value: string;
1652
+ };
1653
+ appliedSales: {
1654
+ saleId: string;
1655
+ discountType: string;
1656
+ discountValue: number;
1657
+ discountPrice?: {
1658
+ type: string;
1659
+ value: string;
1660
+ };
1661
+ }[];
1662
+ };
1663
+ }
1664
+ interface StorefrontCollection {
1665
+ collectionId: string;
1666
+ price: {
1667
+ type: string;
1668
+ value: string;
1669
+ };
1670
+ entitlement: {
1671
+ consumable: boolean;
1672
+ durationDays?: number;
1673
+ };
1674
+ refundEligible: boolean;
1675
+ refundWindowHours: number;
1676
+ resolvedDefaults: {
1677
+ originalPrice: {
1678
+ type: string;
1679
+ value: string;
1680
+ };
1681
+ finalPrice: {
1682
+ type: string;
1683
+ value: string;
1684
+ };
1685
+ appliedSales: {
1686
+ saleId: string;
1687
+ discountType: string;
1688
+ discountValue: number;
1689
+ discountPrice?: {
1690
+ type: string;
1691
+ value: string;
1692
+ };
1693
+ }[];
1694
+ };
1695
+ items: StorefrontCollectionItem[];
1696
+ }
1648
1697
  interface StorefrontResponse {
1649
1698
  configId: string;
1650
1699
  items: StorefrontItem[];
1700
+ collections?: StorefrontCollection[];
1651
1701
  }
1652
1702
  interface ShopOrder {
1653
1703
  orderId: string;
1654
1704
  userId: string;
1655
1705
  gameId: string;
1656
1706
  configId: string;
1707
+ collectionId?: string | null;
1657
1708
  itemId: string;
1658
1709
  itemSnapshot: {
1659
1710
  name: string;
@@ -1712,6 +1763,15 @@ interface ShopOrderHistoryResponse {
1712
1763
  success: boolean;
1713
1764
  orders: ShopOrder[];
1714
1765
  }
1766
+ /**
1767
+ * Resolve pricing for a collection item from the storefront response.
1768
+ * Returns the item's resolvedPrice if it has an override, otherwise the collection's resolvedDefaults.
1769
+ * Returns null if the collection is not found.
1770
+ *
1771
+ * This is a pure function co-located with the storefront types it operates on.
1772
+ * Import it directly rather than calling it through a ShopApi instance.
1773
+ */
1774
+ declare function resolveCollectionItemPrice(storefront: StorefrontResponse, collectionId: string, itemId: string): StorefrontCollection['resolvedDefaults'] | null;
1715
1775
  interface ShopApi {
1716
1776
  /** Get the full storefront catalog for the current game */
1717
1777
  getCatalog(includeInactive?: boolean, includeExpired?: boolean, includeUnreleased?: boolean): Promise<StorefrontResponse>;
@@ -1719,6 +1779,8 @@ interface ShopApi {
1719
1779
  getItemDetail(itemId: string): Promise<StorefrontItem>;
1720
1780
  /** Purchase a shop item */
1721
1781
  purchase(itemId: string, idempotencyKey: string): Promise<ShopPurchaseResponse>;
1782
+ /** Purchase a collection item */
1783
+ purchaseCollectionItem(collectionId: string, itemId: string, idempotencyKey: string): Promise<ShopPurchaseResponse>;
1722
1784
  /** Get a specific order by ID */
1723
1785
  getOrder(orderId: string): Promise<ShopPurchaseResponse>;
1724
1786
  /** Get order history for the current game */
@@ -2275,4 +2337,4 @@ interface AdsApi {
2275
2337
  showInterstitialAd(options?: ShowInterstitialAdOptions): Promise<boolean>;
2276
2338
  }
2277
2339
 
2278
- export { type AiChatCompletionRequest as $, type AnalyticsApi as A, type BatchRecipeRequirementsResult as B, type ScheduleLocalNotification as C, type ScheduleNotificationOptions as D, type PopupsApi as E, type ShowToastOptions as F, type ShowInterstitialAdOptions as G, type Host as H, type ShowRewardedAdOptions as I, type ProfileApi as J, type DeviceApi as K, type DeviceInfo as L, type EnvironmentApi as M, type NavigationApi as N, type EnvironmentInfo as O, type Profile as P, type QuitOptions as Q, type RundotGameAPI as R, type SimulationRunSummary as S, type SystemApi as T, type SafeArea as U, type CdnApi as V, type FetchFromCdnOptions as W, type TimeApi as X, type ServerTimeData as Y, type GetFutureTimeOptions as Z, type AiApi as _, type RecipeRequirementResult as a, type LeaderboardApi as a$, type AiChatCompletionData as a0, type HapticsApi as a1, HapticFeedbackStyle as a2, type FeaturesApi as a3, type Experiment as a4, type LifecycleApi as a5, type SleepCallback as a6, type Subscription as a7, type AwakeCallback as a8, type PauseCallback as a9, type RoomMessageEvent as aA, type ProposedMoveEvent as aB, RundotGameTransport as aC, type RoomsApi as aD, type CreateRoomOptions as aE, type JoinOrCreateRoomOptions as aF, type JoinOrCreateResult as aG, type ListRoomsOptions as aH, type UpdateRoomDataOptions as aI, type RoomMessageRequest as aJ, type StartRoomGameOptions as aK, type ProposeMoveRequest as aL, type ProposeMoveResult as aM, type ValidateMoveVerdict as aN, type ValidateMoveResult as aO, type RoomSubscriptionOptions as aP, type LoggingApi as aQ, type IapApi as aR, type SpendCurrencyOptions as aS, type SpendCurrencyResult as aT, type SubscriptionTier as aU, type RunSubscriptionsResponse as aV, type SubscriptionInterval as aW, type PurchaseSubscriptionResponse as aX, type OpenStoreResult as aY, type LoadEmbeddedAssetsResponse as aZ, type SharedAssetsApi as a_, type ResumeCallback as aa, type QuitCallback as ab, type SimulationApi as ac, type SimulationSlotValidationResult as ad, type SimulationBatchOperation as ae, type SimulationBatchOperationsResult as af, type SimulationAvailableItem as ag, type SimulationPowerPreview as ah, type SimulationSlotMutationResult as ai, type SimulationSlotContainer as aj, type SimulationAssignment as ak, type SimulationState as al, type ExecuteRecipeOptions as am, type ExecuteRecipeResponse as an, type CollectRecipeResult as ao, type ResetStateOptions as ap, type ResetStateResult as aq, type GetActiveRunsOptions as ar, type ExecuteScopedRecipeOptions as as, type ExecuteScopedRecipeResult as at, type GetAvailableRecipesOptions as au, type GetAvailableRecipesResult as av, type Recipe as aw, type GetBatchRecipeRequirements as ax, type TriggerRecipeChainOptions as ay, type RoomDataUpdate as az, type RundotGameSimulationStateResponse as b, type RoomMessageEventType as b$, type ScoreToken as b0, type SubmitScoreParams as b1, type SubmitScoreResult as b2, type GetPagedScoresOptions as b3, type PagedScoresResponse as b4, type PlayerRankOptions as b5, type PlayerRankResult as b6, type GetPodiumScoresOptions as b7, type PodiumScoresResponse as b8, type PreloaderApi as b9, type ImageGenResult as bA, type MultiplayerApi as bB, type InitializationContext as bC, type InitializationOptions as bD, type ServerRoom as bE, type ServerPlayer as bF, type RoomEvents as bG, type ConnectionState as bH, type BaseMessage as bI, type BaseState as bJ, type AiMessage as bK, type Asset as bL, type Category as bM, MockAvatarApi as bN, type SubPath as bO, type IsPlayerSubscribedRequest as bP, type RunSubscription as bQ, type PurchaseSubscriptionRequest as bR, type GetSubscriptionsForTierRequest as bS, type TimeIntervalTriggerInput as bT, type NotificationTriggerInput as bU, type OnRequestCallback as bV, type OnResponseCallback as bW, type OnNotificationCallback as bX, type RpcTransport as bY, type ImageGenModel as bZ, type JoinRoomMatchCriteria as b_, type SocialApi as ba, type ShareMetadata as bb, type ShareLinkResult as bc, type SocialQRCodeOptions as bd, type QRCodeResult as be, type ShareClickData as bf, type EntitlementApi as bg, type Entitlement as bh, type LedgerEntry as bi, type ShopApi as bj, type StorefrontResponse as bk, type StorefrontItem as bl, type ShopPurchaseResponse as bm, type ShopOrderHistoryResponse as bn, type PromptLoginResult as bo, type AccessTier as bp, type AccessGateApi as bq, type ImageGenApi as br, type UgcApi as bs, type Avatar3dApi as bt, type AssetManifest as bu, type Avatar3dConfig as bv, type ShowEditorOptions as bw, type Avatar3dEdits as bx, type AdsApi as by, type ImageGenParams as bz, type SimulationUpdateType as c, type RoomMessagePayload as c0, type ProposedMovePayload as c1, ROOM_GAME_PHASES as c2, type RoomGamePhase as c3, type RundotGameRoomRulesGameState as c4, type RundotGameRoomRules as c5, type RundotGameRoomCustomMetadata as c6, type RundotGameRoomPayload as c7, type RoomEnvelopeResponse as c8, type RoomsEnvelopeResponse as c9, type JoinOrCreateRoomEnvelopeResponse as ca, type RecipeInfo as cb, type SimulationPersonalState as cc, type SimulationRoomActiveRecipe as cd, type SimulationRoomState as ce, type SimulationBatchOperationAssign as cf, type SimulationBatchOperationRemove as cg, type SimulationBatchOperationResult as ch, RpcSharedAssetsApi as ci, type LoadEmbeddedAssetsRequest as cj, type LeaderboardModeConfig as ck, type LeaderboardPeriodType as cl, type LeaderboardPeriodConfig as cm, type LeaderboardAntiCheatConfig as cn, type LeaderboardDisplaySettings as co, type LeaderboardConfig as cp, type LeaderboardEntry as cq, type PodiumScoresContext as cr, type ShopOrder as cs, type HudInsets as ct, createHost as cu, AccessDeniedError as cv, type SimulationEntityUpdate as d, type SimulationActiveRunsUpdate as e, type SimulationSnapshotUpdate as f, type SimulationUpdateData as g, type SimulationSubscribeOptions as h, type RundotGameSimulationEffect as i, type RundotGameSimulationRecipe as j, type RundotGameSimulationConfig as k, type RecipeRequirementQuery as l, type RundotGameExecuteRecipeOptions as m, type RundotGameExecuteScopedRecipeOptions as n, type RundotGameAvailableRecipe as o, type RundotGameCollectRecipeResult as p, type RundotGameExecuteRecipeResult as q, RundotGameRoom as r, type RpcRequest as s, type RpcResponse as t, type RpcNotification as u, RpcClient as v, type StorageApi as w, type NavigationStackInfo as x, type PushAppOptions as y, type NotificationsApi as z };
2340
+ export { type AiChatCompletionRequest as $, type AnalyticsApi as A, type BatchRecipeRequirementsResult as B, type ScheduleLocalNotification as C, type ScheduleNotificationOptions as D, type PopupsApi as E, type ShowToastOptions as F, type ShowInterstitialAdOptions as G, type Host as H, type ShowRewardedAdOptions as I, type ProfileApi as J, type DeviceApi as K, type DeviceInfo as L, type EnvironmentApi as M, type NavigationApi as N, type EnvironmentInfo as O, type Profile as P, type QuitOptions as Q, type RundotGameAPI as R, type SimulationRunSummary as S, type SystemApi as T, type SafeArea as U, type CdnApi as V, type FetchFromCdnOptions as W, type TimeApi as X, type ServerTimeData as Y, type GetFutureTimeOptions as Z, type AiApi as _, type RecipeRequirementResult as a, type LeaderboardApi as a$, type AiChatCompletionData as a0, type HapticsApi as a1, HapticFeedbackStyle as a2, type FeaturesApi as a3, type Experiment as a4, type LifecycleApi as a5, type SleepCallback as a6, type Subscription as a7, type AwakeCallback as a8, type PauseCallback as a9, type RoomMessageEvent as aA, type ProposedMoveEvent as aB, RundotGameTransport as aC, type RoomsApi as aD, type CreateRoomOptions as aE, type JoinOrCreateRoomOptions as aF, type JoinOrCreateResult as aG, type ListRoomsOptions as aH, type UpdateRoomDataOptions as aI, type RoomMessageRequest as aJ, type StartRoomGameOptions as aK, type ProposeMoveRequest as aL, type ProposeMoveResult as aM, type ValidateMoveVerdict as aN, type ValidateMoveResult as aO, type RoomSubscriptionOptions as aP, type LoggingApi as aQ, type IapApi as aR, type SpendCurrencyOptions as aS, type SpendCurrencyResult as aT, type SubscriptionTier as aU, type RunSubscriptionsResponse as aV, type SubscriptionInterval as aW, type PurchaseSubscriptionResponse as aX, type OpenStoreResult as aY, type LoadEmbeddedAssetsResponse as aZ, type SharedAssetsApi as a_, type ResumeCallback as aa, type QuitCallback as ab, type SimulationApi as ac, type SimulationSlotValidationResult as ad, type SimulationBatchOperation as ae, type SimulationBatchOperationsResult as af, type SimulationAvailableItem as ag, type SimulationPowerPreview as ah, type SimulationSlotMutationResult as ai, type SimulationSlotContainer as aj, type SimulationAssignment as ak, type SimulationState as al, type ExecuteRecipeOptions as am, type ExecuteRecipeResponse as an, type CollectRecipeResult as ao, type ResetStateOptions as ap, type ResetStateResult as aq, type GetActiveRunsOptions as ar, type ExecuteScopedRecipeOptions as as, type ExecuteScopedRecipeResult as at, type GetAvailableRecipesOptions as au, type GetAvailableRecipesResult as av, type Recipe as aw, type GetBatchRecipeRequirements as ax, type TriggerRecipeChainOptions as ay, type RoomDataUpdate as az, type RundotGameSimulationStateResponse as b, type RoomMessagePayload as b$, type ScoreToken as b0, type SubmitScoreParams as b1, type SubmitScoreResult as b2, type GetPagedScoresOptions as b3, type PagedScoresResponse as b4, type PlayerRankOptions as b5, type PlayerRankResult as b6, type GetPodiumScoresOptions as b7, type PodiumScoresResponse as b8, type PreloaderApi as b9, type ImageGenResult as bA, type MultiplayerApi as bB, type InitializationContext as bC, type InitializationOptions as bD, type ServerRoom as bE, type ServerPlayer as bF, type RoomEvents as bG, type ConnectionState as bH, type Protocol as bI, type AiMessage as bJ, type Asset as bK, type Category as bL, MockAvatarApi as bM, type SubPath as bN, type IsPlayerSubscribedRequest as bO, type RunSubscription as bP, type PurchaseSubscriptionRequest as bQ, type GetSubscriptionsForTierRequest as bR, type TimeIntervalTriggerInput as bS, type NotificationTriggerInput as bT, type OnRequestCallback as bU, type OnResponseCallback as bV, type OnNotificationCallback as bW, type RpcTransport as bX, type ImageGenModel as bY, type JoinRoomMatchCriteria as bZ, type RoomMessageEventType as b_, type SocialApi as ba, type ShareMetadata as bb, type ShareLinkResult as bc, type SocialQRCodeOptions as bd, type QRCodeResult as be, type ShareClickData as bf, type EntitlementApi as bg, type Entitlement as bh, type LedgerEntry as bi, type ShopApi as bj, type StorefrontResponse as bk, type StorefrontItem as bl, type ShopPurchaseResponse as bm, type ShopOrderHistoryResponse as bn, type PromptLoginResult as bo, type AccessTier as bp, type AccessGateApi as bq, type ImageGenApi as br, type UgcApi as bs, type Avatar3dApi as bt, type AssetManifest as bu, type Avatar3dConfig as bv, type ShowEditorOptions as bw, type Avatar3dEdits as bx, type AdsApi as by, type ImageGenParams as bz, type SimulationUpdateType as c, type ProposedMovePayload as c0, ROOM_GAME_PHASES as c1, type RoomGamePhase as c2, type RundotGameRoomRulesGameState as c3, type RundotGameRoomRules as c4, type RundotGameRoomCustomMetadata as c5, type RundotGameRoomPayload as c6, type RoomEnvelopeResponse as c7, type RoomsEnvelopeResponse as c8, type JoinOrCreateRoomEnvelopeResponse as c9, type RecipeInfo as ca, type SimulationPersonalState as cb, type SimulationRoomActiveRecipe as cc, type SimulationRoomState as cd, type SimulationBatchOperationAssign as ce, type SimulationBatchOperationRemove as cf, type SimulationBatchOperationResult as cg, RpcSharedAssetsApi as ch, type LoadEmbeddedAssetsRequest as ci, type LeaderboardModeConfig as cj, type LeaderboardPeriodType as ck, type LeaderboardPeriodConfig as cl, type LeaderboardAntiCheatConfig as cm, type LeaderboardDisplaySettings as cn, type LeaderboardConfig as co, type LeaderboardEntry as cp, type PodiumScoresContext as cq, type StorefrontCollectionItem as cr, type StorefrontCollection as cs, type ShopOrder as ct, resolveCollectionItemPrice as cu, type HudInsets as cv, createHost as cw, AccessDeniedError as cx, type SimulationEntityUpdate as d, type SimulationActiveRunsUpdate as e, type SimulationSnapshotUpdate as f, type SimulationUpdateData as g, type SimulationSubscribeOptions as h, type RundotGameSimulationEffect as i, type RundotGameSimulationRecipe as j, type RundotGameSimulationConfig as k, type RecipeRequirementQuery as l, type RundotGameExecuteRecipeOptions as m, type RundotGameExecuteScopedRecipeOptions as n, type RundotGameAvailableRecipe as o, type RundotGameCollectRecipeResult as p, type RundotGameExecuteRecipeResult as q, RundotGameRoom as r, type RpcRequest as s, type RpcResponse as t, type RpcNotification as u, RpcClient as v, type StorageApi as w, type NavigationStackInfo as x, type PushAppOptions as y, type NotificationsApi as z };
@@ -3709,6 +3709,14 @@ function initializeEntitlements(rundotGameApiInstance, host) {
3709
3709
  rundotGameApiInstance.entitlements = host.entitlements;
3710
3710
  }
3711
3711
 
3712
+ // src/shop/ShopApi.ts
3713
+ function resolveCollectionItemPrice(storefront, collectionId, itemId) {
3714
+ const collection = storefront.collections?.find((c) => c.collectionId === collectionId);
3715
+ if (!collection) return null;
3716
+ const overrideItem = collection.items.find((i) => i.itemId === itemId);
3717
+ return overrideItem ? overrideItem.resolvedPrice : collection.resolvedDefaults;
3718
+ }
3719
+
3712
3720
  // src/shop/RpcShopApi.ts
3713
3721
  var RpcShopApi = class {
3714
3722
  rpcClient;
@@ -3736,6 +3744,13 @@ var RpcShopApi = class {
3736
3744
  );
3737
3745
  return response;
3738
3746
  }
3747
+ async purchaseCollectionItem(collectionId, itemId, idempotencyKey) {
3748
+ const response = await this.rpcClient.call(
3749
+ "H5_SHOP_PURCHASE" /* H5_SHOP_PURCHASE */,
3750
+ { itemId, idempotencyKey, collectionId }
3751
+ );
3752
+ return response;
3753
+ }
3739
3754
  async getOrder(orderId) {
3740
3755
  const response = await this.rpcClient.call(
3741
3756
  "H5_SHOP_GET_ORDER_STATUS" /* H5_SHOP_GET_ORDER_STATUS */,
@@ -3818,6 +3833,30 @@ var MOCK_ITEMS = [
3818
3833
  }
3819
3834
  }
3820
3835
  ];
3836
+ var MOCK_COLLECTIONS = [
3837
+ {
3838
+ collectionId: "episodes",
3839
+ price: { type: "bucks", value: "50" },
3840
+ entitlement: { consumable: false },
3841
+ refundEligible: true,
3842
+ refundWindowHours: 24,
3843
+ resolvedDefaults: {
3844
+ originalPrice: { type: "bucks", value: "50" },
3845
+ finalPrice: { type: "bucks", value: "50" },
3846
+ appliedSales: []
3847
+ },
3848
+ items: [
3849
+ {
3850
+ itemId: "ep-premium-1",
3851
+ resolvedPrice: {
3852
+ originalPrice: { type: "bucks", value: "100" },
3853
+ finalPrice: { type: "bucks", value: "100" },
3854
+ appliedSales: []
3855
+ }
3856
+ }
3857
+ ]
3858
+ }
3859
+ ];
3821
3860
  function createMockOrder(itemId, item) {
3822
3861
  const now = (/* @__PURE__ */ new Date()).toISOString();
3823
3862
  const mockItem = item || MOCK_ITEMS[0];
@@ -3864,7 +3903,8 @@ var MockShopApi = class {
3864
3903
  }
3865
3904
  return {
3866
3905
  configId: "mock-config-123",
3867
- items
3906
+ items,
3907
+ collections: MOCK_COLLECTIONS
3868
3908
  };
3869
3909
  }
3870
3910
  async getItemDetail(itemId) {
@@ -3883,6 +3923,15 @@ var MockShopApi = class {
3883
3923
  order: createMockOrder(itemId, item)
3884
3924
  };
3885
3925
  }
3926
+ async purchaseCollectionItem(collectionId, itemId, _idempotencyKey) {
3927
+ console.log(`[Mock Shop] purchaseCollectionItem: ${collectionId}/${itemId}`);
3928
+ const order = createMockOrder(itemId);
3929
+ order.collectionId = collectionId;
3930
+ return {
3931
+ success: true,
3932
+ order
3933
+ };
3934
+ }
3886
3935
  async getOrder(orderId) {
3887
3936
  console.log(`[Mock Shop] getOrder: ${orderId}`);
3888
3937
  const order = createMockOrder("speed_boost");
@@ -4180,7 +4229,6 @@ var RundotServerRoom = class _RundotServerRoom {
4180
4229
  roomCode;
4181
4230
  playerId;
4182
4231
  ws;
4183
- _state;
4184
4232
  _locked = false;
4185
4233
  _phase = "active";
4186
4234
  events = {};
@@ -4194,40 +4242,32 @@ var RundotServerRoom = class _RundotServerRoom {
4194
4242
  }
4195
4243
  warn(msg) {
4196
4244
  }
4245
+ toMessage(msgType, data) {
4246
+ return { ...data, type: msgType };
4247
+ }
4197
4248
  /**
4198
4249
  * Attempt a phase transition. Returns true if the transition occurred.
4199
4250
  * Once `dead`, no further transitions are allowed.
4200
4251
  */
4201
- toMessage(msgType, data) {
4202
- return { ...data, type: msgType };
4203
- }
4204
4252
  transitionTo(newPhase) {
4205
4253
  if (this._phase === "dead" || this._phase === newPhase) return false;
4206
4254
  this.log(`phase ${this._phase} \u2192 ${newPhase}`);
4207
4255
  this._phase = newPhase;
4208
4256
  return true;
4209
4257
  }
4210
- constructor(roomCode, playerId, ws, initialState) {
4258
+ constructor(roomCode, playerId, ws) {
4211
4259
  this.roomCode = roomCode;
4212
4260
  this.playerId = playerId;
4213
4261
  this.ws = ws;
4214
- this._state = initialState;
4215
- this.log(`created roomCode=${roomCode} playerId=${playerId} stateKeys=${Object.keys(initialState)}`);
4262
+ this.log(`created roomCode=${roomCode} playerId=${playerId}`);
4216
4263
  ws.on({
4217
- onMessage: (msg) => this.handleMessage(msg),
4218
- onStateChange: (state) => this.handleConnectionState(state),
4219
- onError: (err) => {
4220
- this.warn(`ws error: ${err.message}`);
4221
- this.events.onError?.(err.message);
4222
- }
4264
+ onMessage: (msg) => this.onMessageReceived(msg),
4265
+ onStateChange: (state) => this.onConnectionStateChanged(state),
4266
+ onError: (err) => this.onError(err)
4223
4267
  });
4224
4268
  this.startPing();
4225
4269
  }
4226
4270
  // ── Accessors ──
4227
- /** Current room state (synced from the server) */
4228
- get state() {
4229
- return this._state;
4230
- }
4231
4271
  /** Whether the room is locked */
4232
4272
  get locked() {
4233
4273
  return this._locked;
@@ -4252,7 +4292,7 @@ var RundotServerRoom = class _RundotServerRoom {
4252
4292
  * Send a typed message to the server room.
4253
4293
  */
4254
4294
  send(message) {
4255
- if (this._phase === "dead") return;
4295
+ if (this._phase === "dead" || this._phase === "reconnecting") return;
4256
4296
  const { type, ...data } = message;
4257
4297
  this.ws.send({ type: "message", msgType: type, data });
4258
4298
  }
@@ -4272,90 +4312,127 @@ var RundotServerRoom = class _RundotServerRoom {
4272
4312
  return Date.now() + this._serverTimeOffset;
4273
4313
  }
4274
4314
  // ── Message Handling ──
4275
- handleMessage(msg) {
4315
+ onMessageReceived(msg) {
4276
4316
  if (msg.type !== "pong") {
4277
4317
  this.log(`handleMessage type=${msg.type}`);
4278
4318
  }
4279
4319
  switch (msg.type) {
4280
- case "room:sync":
4281
- this._state = msg.state;
4282
- this.log(`state synced, keys=${Object.keys(msg.state)}`);
4283
- this.events.onStateChange?.(msg.state);
4284
- break;
4285
4320
  case "room:broadcast":
4286
- this.log(`broadcast msgType=${msg.msgType}`);
4287
- this.events.onMessage?.(this.toMessage(msg.msgType, msg.data));
4321
+ this.onBroadcastMessageReceived(msg);
4288
4322
  break;
4289
4323
  case "room:sendTo":
4290
- this.log(`sendTo msgType=${msg.msgType}`);
4291
- this.events.onPrivateMessage?.(this.toMessage(msg.msgType, msg.data));
4324
+ this.onSendToMessageReceived(msg);
4292
4325
  break;
4293
4326
  case "room:lock":
4294
- this._locked = true;
4295
- this.log(`room locked`);
4296
- this.events.onLock?.();
4327
+ this.onLockMessageReceived();
4297
4328
  break;
4298
4329
  case "room:unlock":
4299
- this._locked = false;
4300
- this.log(`room unlocked`);
4301
- this.events.onUnlock?.();
4330
+ this.onUnlockMessageReceived();
4302
4331
  break;
4303
4332
  case "room:playerJoined":
4304
- this.log(`playerJoined id=${msg.playerId}`);
4305
- this.events.onPlayerJoined?.({
4306
- id: msg.playerId,
4307
- username: msg.username,
4308
- avatarUrl: msg.avatarUrl
4309
- });
4333
+ this.onPlayerJoinedMessageReceived(msg);
4310
4334
  break;
4311
4335
  case "room:playerLeft":
4312
- this.log(`playerLeft id=${msg.playerId}`);
4313
- this.events.onPlayerLeft?.(msg.playerId);
4336
+ this.onPlayerLeftMessageReceived(msg);
4314
4337
  break;
4315
4338
  case "room:error":
4316
- this.warn(`error: ${msg.message}`);
4317
- this.events.onError?.(msg.message);
4339
+ this.onErrorMessageReceived(msg);
4318
4340
  break;
4319
4341
  case "room:reconnecting":
4320
- if (this.transitionTo("reconnecting")) {
4321
- this.events.onReconnecting?.();
4322
- }
4342
+ this.onReconnectingMessageReceived();
4323
4343
  break;
4324
4344
  case "room:reconnected":
4325
- this.ws.confirmConnection();
4326
- if (this.transitionTo("active")) {
4327
- this.events.onReconnected?.();
4328
- }
4345
+ this.onReconnectedMessageReceived(msg);
4329
4346
  break;
4330
- case "pong": {
4331
- const now = Date.now();
4332
- const rtt = now - msg.ts;
4333
- this._latency = Math.round(rtt / 2);
4334
- this._serverTimeOffset = msg.serverTs - now + this._latency;
4347
+ case "pong":
4348
+ this.onPongMessageReceived(msg);
4335
4349
  break;
4336
- }
4337
4350
  }
4338
4351
  }
4339
- handleConnectionState(state) {
4352
+ onBroadcastMessageReceived(msg) {
4353
+ this.log(`broadcast msgType=${msg.msgType}`);
4354
+ this.events.onMessage?.(this.toMessage(msg.msgType, msg.data));
4355
+ }
4356
+ onSendToMessageReceived(msg) {
4357
+ this.log(`sendTo msgType=${msg.msgType}`);
4358
+ this.events.onPrivateMessage?.(this.toMessage(msg.msgType, msg.data));
4359
+ }
4360
+ onLockMessageReceived() {
4361
+ this._locked = true;
4362
+ this.log(`room locked`);
4363
+ this.events.onLock?.();
4364
+ }
4365
+ onUnlockMessageReceived() {
4366
+ this._locked = false;
4367
+ this.log(`room unlocked`);
4368
+ this.events.onUnlock?.();
4369
+ }
4370
+ onPlayerJoinedMessageReceived(msg) {
4371
+ this.log(`playerJoined id=${msg.playerId}`);
4372
+ this.events.onPlayerJoined?.({
4373
+ id: msg.playerId,
4374
+ username: msg.username,
4375
+ avatarUrl: msg.avatarUrl
4376
+ });
4377
+ }
4378
+ onPlayerLeftMessageReceived(msg) {
4379
+ this.log(`playerLeft id=${msg.playerId}`);
4380
+ this.events.onPlayerLeft?.(msg.playerId);
4381
+ }
4382
+ onErrorMessageReceived(msg) {
4383
+ this.warn(`error: ${msg.message}`);
4384
+ this.events.onError?.(msg.message);
4385
+ }
4386
+ onReconnectingMessageReceived() {
4387
+ if (this.transitionTo("reconnecting")) {
4388
+ this.events.onReconnecting?.();
4389
+ }
4390
+ }
4391
+ onReconnectedMessageReceived(_msg) {
4392
+ this.ws.confirmConnection();
4393
+ if (this.transitionTo("active")) {
4394
+ this.events.onReconnected?.();
4395
+ }
4396
+ }
4397
+ onPongMessageReceived(msg) {
4398
+ const now = Date.now();
4399
+ const rtt = now - msg.ts;
4400
+ this._latency = Math.round(rtt / 2);
4401
+ this._serverTimeOffset = msg.serverTs - now + this._latency;
4402
+ }
4403
+ onError(err) {
4404
+ this.warn(`ws error: ${err.message}`);
4405
+ this.events.onError?.(err.message);
4406
+ }
4407
+ onConnectionStateChanged(state) {
4340
4408
  this.log(`connectionState=${state}`);
4341
4409
  switch (state) {
4342
4410
  case "connected":
4343
- this.startPing();
4411
+ this.onConnectedStateEntered();
4344
4412
  break;
4345
4413
  case "disconnected":
4346
- this.stopPing();
4347
- if (this.transitionTo("dead")) {
4348
- this.events.onDisconnect?.();
4349
- }
4414
+ this.onDisconnectedStateEntered();
4350
4415
  break;
4351
4416
  case "reconnecting":
4352
- this.stopPing();
4353
- if (this.transitionTo("reconnecting")) {
4354
- this.events.onReconnecting?.();
4355
- }
4417
+ this.onReconnectingStateEntered();
4356
4418
  break;
4357
4419
  }
4358
4420
  }
4421
+ onConnectedStateEntered() {
4422
+ this.startPing();
4423
+ }
4424
+ onDisconnectedStateEntered() {
4425
+ this.stopPing();
4426
+ if (this.transitionTo("dead")) {
4427
+ this.events.onDisconnect?.();
4428
+ }
4429
+ }
4430
+ onReconnectingStateEntered() {
4431
+ this.stopPing();
4432
+ if (this.transitionTo("reconnecting")) {
4433
+ this.events.onReconnecting?.();
4434
+ }
4435
+ }
4359
4436
  startPing() {
4360
4437
  if (this.pingInterval) return;
4361
4438
  this.pingInterval = setInterval(() => {
@@ -4386,6 +4463,7 @@ var ServerWebSocket = class {
4386
4463
  _getJoinTicket;
4387
4464
  _authMessage;
4388
4465
  _isReconnect = false;
4466
+ _authenticated = false;
4389
4467
  constructor(options) {
4390
4468
  this.url = options.url;
4391
4469
  this.maxReconnectAttempts = options.maxReconnectAttempts ?? 10;
@@ -4421,6 +4499,7 @@ var ServerWebSocket = class {
4421
4499
  connect() {
4422
4500
  if (this.disposed) return;
4423
4501
  console.log(`[ServerWebSocket] connect() isReconnect=${this._isReconnect} url=${this.url}`);
4502
+ this._authenticated = false;
4424
4503
  this.setState(this._isReconnect ? "reconnecting" : "connecting");
4425
4504
  try {
4426
4505
  if (this.ws) {
@@ -4446,7 +4525,7 @@ var ServerWebSocket = class {
4446
4525
  * Send a message to the server.
4447
4526
  */
4448
4527
  send(msg) {
4449
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
4528
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this._authenticated) {
4450
4529
  this.ws.send(JSON.stringify(msg));
4451
4530
  }
4452
4531
  }
@@ -4485,6 +4564,7 @@ var ServerWebSocket = class {
4485
4564
  const authMsg = { ...this._authMessage, ticket: freshTicket, reconnect: true };
4486
4565
  console.log(`[ServerWebSocket] sending reconnect auth roomId=${this._authMessage._roomId}`);
4487
4566
  socket.send(JSON.stringify(authMsg));
4567
+ this._authenticated = true;
4488
4568
  } catch {
4489
4569
  console.error(`[ServerWebSocket] failed to get join ticket for reconnection`);
4490
4570
  this.events.onError?.(new Error("Failed to get join ticket for reconnection"));
@@ -4494,6 +4574,7 @@ var ServerWebSocket = class {
4494
4574
  } else if (this._authMessage) {
4495
4575
  if (socket.readyState === WebSocket.OPEN) {
4496
4576
  socket.send(JSON.stringify(this._authMessage));
4577
+ this._authenticated = true;
4497
4578
  }
4498
4579
  }
4499
4580
  if (this.ws !== socket) return;
@@ -4509,6 +4590,7 @@ var ServerWebSocket = class {
4509
4590
  }
4510
4591
  onClose(event) {
4511
4592
  console.log(`[ServerWebSocket] onclose code=${event.code} reason=${event.reason} disposed=${this.disposed}`);
4593
+ this._authenticated = false;
4512
4594
  if (this.disposed) {
4513
4595
  this.setState("disconnected");
4514
4596
  return;
@@ -4548,6 +4630,9 @@ var ServerWebSocket = class {
4548
4630
  }
4549
4631
  };
4550
4632
 
4633
+ // src/mp/client/ServerProtocol.ts
4634
+ var PROTOCOL_VERSION = 1;
4635
+
4551
4636
  // src/mp/client/WsMultiplayerApi.ts
4552
4637
  var WsMultiplayerApi = class {
4553
4638
  serverUrl;
@@ -4571,7 +4656,7 @@ var WsMultiplayerApi = class {
4571
4656
  async _joinWithAction(action, roomType, roomCode) {
4572
4657
  const req = { roomType, action, roomCode };
4573
4658
  const ticket = await this.getJoinTicket(req);
4574
- return this._connect({ type: "auth", ticket, roomType, action, roomCode });
4659
+ return this._connect({ type: "auth", protocolVersion: PROTOCOL_VERSION, ticket, roomType, action, roomCode });
4575
4660
  }
4576
4661
  _connect(authMsg) {
4577
4662
  const wsUrl = this.resolveServerUrl().replace(/^http/, "ws") + "/ws";
@@ -4598,9 +4683,9 @@ var WsMultiplayerApi = class {
4598
4683
  if (resolved) return;
4599
4684
  console.log(`[WsMultiplayerApi] pre-join msg: ${msg.type}`, msg);
4600
4685
  if (msg.type === "room:joined") {
4601
- console.log(`[WsMultiplayerApi] room:joined roomCode=${msg.roomCode} playerId=${msg.playerId} stateKeys=${Object.keys(msg.state ?? {})}`);
4686
+ console.log(`[WsMultiplayerApi] room:joined roomCode=${msg.roomCode} playerId=${msg.playerId}`);
4602
4687
  ws.setAuthMessage({ ...authMsg, _roomId: msg._roomId });
4603
- const room = new RundotServerRoom(msg.roomCode, msg.playerId, ws, msg.state);
4688
+ const room = new RundotServerRoom(msg.roomCode, msg.playerId, ws);
4604
4689
  settle(() => resolve(room));
4605
4690
  } else if (msg.type === "room:error") {
4606
4691
  console.error(`[WsMultiplayerApi] room:error code=${msg.code} message=${msg.message}`);
@@ -4633,14 +4718,22 @@ var WsMultiplayerApi = class {
4633
4718
  function readDevServerUrl() {
4634
4719
  return typeof window !== "undefined" ? window.__RUNDOT_MULTIPLAYER_DEV_SERVER__ : void 0;
4635
4720
  }
4636
- function createDevDelegate(serverUrl) {
4721
+ function createDevDelegate(serverUrl, getProfile) {
4637
4722
  return new WsMultiplayerApi({
4638
4723
  serverUrl,
4639
4724
  getJoinTicket: async (req) => {
4725
+ const profile = getProfile?.();
4726
+ const body = {
4727
+ ...req,
4728
+ gameId: "dev-game",
4729
+ profileId: profile?.id,
4730
+ username: profile?.username,
4731
+ avatarUrl: profile?.avatarUrl
4732
+ };
4640
4733
  const resp = await fetch(`${serverUrl}/tickets`, {
4641
4734
  method: "POST",
4642
4735
  headers: { "Content-Type": "application/json" },
4643
- body: JSON.stringify({ ...req, gameId: "dev-game" })
4736
+ body: JSON.stringify(body)
4644
4737
  });
4645
4738
  if (!resp.ok) {
4646
4739
  const err = await resp.json().catch(() => ({ error: "Ticket request failed" }));
@@ -4653,10 +4746,10 @@ function createDevDelegate(serverUrl) {
4653
4746
  }
4654
4747
  var MockMultiplayerApi = class {
4655
4748
  delegate = null;
4656
- constructor() {
4749
+ constructor(options) {
4657
4750
  const devServerUrl = readDevServerUrl();
4658
4751
  if (devServerUrl) {
4659
- this.delegate = createDevDelegate(devServerUrl);
4752
+ this.delegate = createDevDelegate(devServerUrl, options?.getProfile);
4660
4753
  console.log(`[MockMultiplayerApi] Dev server detected at ${devServerUrl}`);
4661
4754
  }
4662
4755
  }
@@ -4671,7 +4764,7 @@ var MockMultiplayerApi = class {
4671
4764
  });
4672
4765
  const roomCode = Math.random().toString(36).slice(2, 8).toUpperCase();
4673
4766
  const playerId = `mock-player-${Math.random().toString(36).slice(2, 8)}`;
4674
- const room = new RundotServerRoom(roomCode, playerId, ws, {});
4767
+ const room = new RundotServerRoom(roomCode, playerId, ws);
4675
4768
  console.log(`[MockMultiplayerApi] Created mock room ${roomCode} (${roomType})`);
4676
4769
  return room;
4677
4770
  }
@@ -4685,6 +4778,6 @@ var MockMultiplayerApi = class {
4685
4778
  }
4686
4779
  };
4687
4780
 
4688
- export { AccessDeniedError, BaseCdnApi, DEFAULT_SHARED_LIB_CDN_BASE, EMBEDDED_LIBRARIES, EMBEDDED_LIBRARY_BY_KEY, FILE_EXTENSION_PATTERN, HapticFeedbackStyle, HostCdnApi, HostDeviceApi, HostEnvironmentApi, HostProfileApi, HostSystemApi, HostTimeApi, MIN_CDN_PATH_SEGMENTS, MODULE_TO_LIBRARY_SPECIFIERS, MockAccessGateApi, MockAdsApi, MockAiApi, MockAnalyticsApi, MockAvatarApi, MockCdnApi, MockDeviceApi, MockEntitlementApi, MockEnvironmentApi, MockFeaturesApi, MockHapticsApi, MockIapApi, MockLifecycleApi, MockLoggingApi, MockMultiplayerApi, MockNavigationApi, MockNotificationsApi, MockPopupsApi, MockPreloaderApi, MockProfileApi, MockSharedAssetsApi, MockShopApi, MockSocialApi, MockStorageApi, MockSystemApi, MockTimeApi, ROOM_GAME_PHASES, RpcAccessGateApi, RpcAdsApi, RpcAiApi, RpcAnalyticsApi, RpcAvatarApi, RpcEntitlementApi, RpcFeaturesApi, RpcHapticsApi, RpcIapApi, RpcLifecycleApi, RpcLoggingApi, RpcNavigationApi, RpcNotificationsApi, RpcPopupsApi, RpcPreloaderApi, RpcRoomsApi, RpcSharedAssetsApi, RpcShopApi, RpcStorageApi, RundotGameMessageId, RundotGameRoom, SandboxProfileApi, WsMultiplayerApi, applyAccessGates, base64ToArrayBuffer, base64ToUtf8, createAccessGatedApi, createMockStorageApi, generateId, getLibraryDefinition, hasHostedUrlStructure, initializeAccessGate, initializeAds, initializeAi, initializeAnalytics, initializeAvatar3d, initializeCdn, initializeEntitlements, initializeFeaturesApi, initializeHaptics, initializeIap, initializeLifecycleApi, initializeLocalNotifications, initializeLoggingApi, initializePopups, initializePreloader, initializeProfile, initializeRoomsApi, initializeShop, initializeStackNavigation, initializeStorage, initializeSystem, initializeTime, isPacificDaylightTime, parseCdnPathSegments, setupRoomNotifications };
4689
- //# sourceMappingURL=chunk-BYPG3HXZ.js.map
4690
- //# sourceMappingURL=chunk-BYPG3HXZ.js.map
4781
+ export { AccessDeniedError, BaseCdnApi, DEFAULT_SHARED_LIB_CDN_BASE, EMBEDDED_LIBRARIES, EMBEDDED_LIBRARY_BY_KEY, FILE_EXTENSION_PATTERN, HapticFeedbackStyle, HostCdnApi, HostDeviceApi, HostEnvironmentApi, HostProfileApi, HostSystemApi, HostTimeApi, MIN_CDN_PATH_SEGMENTS, MODULE_TO_LIBRARY_SPECIFIERS, MockAccessGateApi, MockAdsApi, MockAiApi, MockAnalyticsApi, MockAvatarApi, MockCdnApi, MockDeviceApi, MockEntitlementApi, MockEnvironmentApi, MockFeaturesApi, MockHapticsApi, MockIapApi, MockLifecycleApi, MockLoggingApi, MockMultiplayerApi, MockNavigationApi, MockNotificationsApi, MockPopupsApi, MockPreloaderApi, MockProfileApi, MockSharedAssetsApi, MockShopApi, MockSocialApi, MockStorageApi, MockSystemApi, MockTimeApi, ROOM_GAME_PHASES, RpcAccessGateApi, RpcAdsApi, RpcAiApi, RpcAnalyticsApi, RpcAvatarApi, RpcEntitlementApi, RpcFeaturesApi, RpcHapticsApi, RpcIapApi, RpcLifecycleApi, RpcLoggingApi, RpcNavigationApi, RpcNotificationsApi, RpcPopupsApi, RpcPreloaderApi, RpcRoomsApi, RpcSharedAssetsApi, RpcShopApi, RpcStorageApi, RundotGameMessageId, RundotGameRoom, SandboxProfileApi, WsMultiplayerApi, applyAccessGates, base64ToArrayBuffer, base64ToUtf8, createAccessGatedApi, createMockStorageApi, generateId, getLibraryDefinition, hasHostedUrlStructure, initializeAccessGate, initializeAds, initializeAi, initializeAnalytics, initializeAvatar3d, initializeCdn, initializeEntitlements, initializeFeaturesApi, initializeHaptics, initializeIap, initializeLifecycleApi, initializeLocalNotifications, initializeLoggingApi, initializePopups, initializePreloader, initializeProfile, initializeRoomsApi, initializeShop, initializeStackNavigation, initializeStorage, initializeSystem, initializeTime, isPacificDaylightTime, parseCdnPathSegments, resolveCollectionItemPrice, setupRoomNotifications };
4782
+ //# sourceMappingURL=chunk-6QSD4ZGK.js.map
4783
+ //# sourceMappingURL=chunk-6QSD4ZGK.js.map