@indiegems/gem-web-sdk 23.2.0 → 24.0.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/dist/server.d.ts CHANGED
@@ -139,9 +139,15 @@ interface GemServerHost extends ServerBundleHost {
139
139
  broadcast(data: string | Uint8Array, options?: BroadcastOptions): void;
140
140
  kick(playerId: string, reason?: string): void;
141
141
  endMatch?(): Promise<"ok" | "already_ended" | "refused" | "failed" | (string & {})>;
142
+ passes?(playerId: string): Promise<GemServerHostRead>;
143
+ boost?(playerId: string): Promise<GemServerHostRead>;
142
144
  log(message: string): void;
143
145
  warn(message: string): void;
144
146
  }
147
+ interface GemServerHostRead {
148
+ readonly status: number;
149
+ readonly body: unknown;
150
+ }
145
151
  interface HostTokenSourceOptions {
146
152
  readonly clock: Clock;
147
153
  readonly timeoutMs?: number;
@@ -1774,6 +1780,40 @@ interface ServerListChallengesOptions {
1774
1780
  interface ServerChallenges {
1775
1781
  list(options: ServerListChallengesOptions): Promise<Challenge[]>;
1776
1782
  }
1783
+ interface MatchBoostMultiplier {
1784
+ readonly currencySlug: string;
1785
+ readonly multiplier: number;
1786
+ }
1787
+ interface MatchPlayerBoost {
1788
+ readonly active: boolean;
1789
+ readonly multipliers: readonly MatchBoostMultiplier[];
1790
+ readonly expiresInDays?: number;
1791
+ }
1792
+ type PlatformGrantSource = {
1793
+ readonly source: "pass";
1794
+ readonly pass: string;
1795
+ readonly amount: number;
1796
+ } | {
1797
+ readonly source: "boost";
1798
+ readonly amount: number;
1799
+ };
1800
+ interface PlatformGrantPreview {
1801
+ readonly currencySlug?: string;
1802
+ readonly currencyId?: string;
1803
+ readonly base: number;
1804
+ readonly total: number;
1805
+ readonly breakdown: readonly PlatformGrantSource[];
1806
+ readonly ifComplete?: true;
1807
+ }
1808
+ interface EarningsApi {
1809
+ fromReceipt(change: unknown): Promise<PlatformGrantPreview | null>;
1810
+ }
1811
+ interface GamePass {
1812
+ readonly pass: string;
1813
+ readonly held: boolean;
1814
+ readonly daysLeft: number;
1815
+ readonly endsAt: string | null;
1816
+ }
1777
1817
  interface GemServerOptions {
1778
1818
  readonly baseUrl?: string;
1779
1819
  readonly clock?: Clock;
@@ -1812,7 +1852,10 @@ interface GemServer {
1812
1852
  readonly seats?: number;
1813
1853
  }, signal?: AbortSignal): Promise<MatchBackfillState>;
1814
1854
  reportPlayers(reports: readonly MatchPlayerReport[], signal?: AbortSignal): Promise<MatchPlayerReportResult[]>;
1855
+ playerPasses(playerId: string, signal?: AbortSignal): Promise<readonly GamePass[]>;
1856
+ playerBoost(playerId: string, signal?: AbortSignal): Promise<MatchPlayerBoost>;
1815
1857
  };
1858
+ readonly earnings: EarningsApi;
1816
1859
  readonly analytics: ServerAnalytics;
1817
1860
  readonly functions: FunctionsApi;
1818
1861
  readonly challenges: ServerChallenges;
@@ -1913,4 +1956,4 @@ interface AchievementReceipt {
1913
1956
  readonly reason?: string;
1914
1957
  }
1915
1958
  declare function achievementReceipts(order: unknown): AchievementReceipt[];
1916
- export { type AchievementReceipt, type AnalyticsFlushResult, type AnalyticsRejection, type AnalyticsValue, AuthError, type AuthFailure, type AuthState, type Base64String, type BroadcastOptions, type BytesResponse, CONDITIONAL_HEADERS, type Challenge, type ChallengeWindow, type ClientOptions, type Clock, type ConditionalRequestHeaders, type FunctionFailure, type FunctionInvokeOptions, type FunctionResult, FunctionsApi, type GameApi, GemApiError, GemClient, type GemErrorReason, GemFunctionError, type GemMatchServer, type GemServer, type GemServerBundle, type GemServerFactory, type GemServerHost, type GemServerHostProfile, type GemServerIdentity, type GemServerLifecycle, type GemServerOptions, type GemServerProfile, type HostTokenSource, type HostTokenSourceOptions, type MatchBackfillState, type MatchPlayerReport, type MatchPlayerReportResult, PlayerText, type RequestOptions, type RoomArgValue, type RuntimeShutdownPayload, type SendOptions, type ServerAnalytics, type ServerApi, type ServerBundleHost, type ServerChallenges, type ServerListChallengesOptions, type ServerShutdownInfo, type ServerTokenGrant, type TokenGrant, type Transport, TransportRefusal, type TransportRequest, type TransportResponse, type VersionedGetOptions, type VersionedResponse, achievementReceipts, asPlayerText, createGemServer, createHostTokenSource, createMatchServer, defineServer, systemClock, text, toBase64 };
1959
+ export { type AchievementReceipt, type AnalyticsFlushResult, type AnalyticsRejection, type AnalyticsValue, AuthError, type AuthFailure, type AuthState, type Base64String, type BroadcastOptions, type BytesResponse, CONDITIONAL_HEADERS, type Challenge, type ChallengeWindow, type ClientOptions, type Clock, type ConditionalRequestHeaders, type EarningsApi, type FunctionFailure, type FunctionInvokeOptions, type FunctionResult, FunctionsApi, type GameApi, type GamePass, GemApiError, GemClient, type GemErrorReason, GemFunctionError, type GemMatchServer, type GemServer, type GemServerBundle, type GemServerFactory, type GemServerHost, type GemServerHostProfile, type GemServerHostRead, type GemServerIdentity, type GemServerLifecycle, type GemServerOptions, type GemServerProfile, type HostTokenSource, type HostTokenSourceOptions, type MatchBackfillState, type MatchBoostMultiplier, type MatchPlayerBoost, type MatchPlayerReport, type MatchPlayerReportResult, type PlatformGrantPreview, type PlatformGrantSource, PlayerText, type RequestOptions, type RoomArgValue, type RuntimeShutdownPayload, type SendOptions, type ServerAnalytics, type ServerApi, type ServerBundleHost, type ServerChallenges, type ServerListChallengesOptions, type ServerShutdownInfo, type ServerTokenGrant, type TokenGrant, type Transport, TransportRefusal, type TransportRequest, type TransportResponse, type VersionedGetOptions, type VersionedResponse, achievementReceipts, asPlayerText, createGemServer, createHostTokenSource, createMatchServer, defineServer, systemClock, text, toBase64 };
package/docs/analytics.md CHANGED
@@ -295,6 +295,46 @@ It counts against the event's three dimensions and its cardinality cap like any
295
295
  on the events that answer the question rather than on all of them. Name the dimension after your
296
296
  experiment, never `variant`: the platform fills that one in itself, for store-page experiments.
297
297
 
298
+ **Declare the test too, or the platform's results never show it.** The platform reads your own
299
+ A/B test, every arm side by side, only when your game config names it under `analytics_tests`:
300
+ which funnel it is read through, and which dimension of that funnel's first event holds the arm.
301
+ This config uploads as it stands; its `tutorial_finished` entry is the one above, so declare it
302
+ once:
303
+
304
+ ```yaml
305
+ analytics_events:
306
+ - id: tutorial_started # step 0: carries the arm dimension
307
+ dimensions:
308
+ tutorial_length: { values: [short, long] }
309
+ - id: tutorial_finished
310
+ dimensions:
311
+ tutorial_length: { values: [short, long] }
312
+
313
+ analytics_funnels:
314
+ - id: tutorial_test
315
+ steps: [tutorial_started, tutorial_finished]
316
+
317
+ analytics_tests:
318
+ - id: tutorial_length_ab
319
+ name: "Tutorial: short vs long" # optional, at most 60 characters
320
+ funnel: tutorial_test # declared in analytics_funnels
321
+ arm: tutorial_length # a values dimension of the funnel's step-0 event
322
+ arms: [short, long] # 2 to 8 of its declared values; the first is the control
323
+ step_labels: [Started, Finished] # optional, one per step
324
+ ```
325
+
326
+ - **The funnel's first step (step 0) must be your own event, and it must carry the arm
327
+ dimension.** Here `tutorial_started` declares `tutorial_length: { values: [short, long] }` and
328
+ your game passes the selection on it. Never start the funnel at `gem_session_start`: that is
329
+ the platform's event, and it can't carry your arm.
330
+ - **The arm is a `values` dimension.** Its `arms` are values that dimension declares, so give
331
+ `experiment` a list of strings, not a count, for a test you want read this way.
332
+ - **The first arm listed is the control.** The others are compared against it.
333
+
334
+ It ships with the rest of your config, uploaded and promoted to the channel. The declaration
335
+ records nothing new about any player: the arm is already in your events, and the declaration only
336
+ names the funnel and the dimension.
337
+
298
338
  **The selection can be reproduced anywhere** — a dedicated server, an analytics query — because
299
339
  the hash is fixed and will not change: FNV-1a 32-bit over the UTF-8 bytes of
300
340
  `"<key>:<playerId>"`, then MurmurHash3's 32-bit finalizer (`fmix32`), as an unsigned integer, mod
@@ -1,6 +1,6 @@
1
1
  # The function surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/function` v23.2.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
3
+ Generated from `@indiegems/gem-web-sdk/function` v24.0.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
4
4
 
5
5
  `@indiegems/gem-web-sdk/function` exports exactly the symbols on this page and nothing else.
6
6
 
@@ -360,7 +360,8 @@ dimension for it on the events you want to break down, with the arms as its
360
360
  values, named after the experiment, and pass the selection there yourself:
361
361
  `gem.analytics.track('tutorial_finished', { tutorial_length: tutorial })`.
362
362
  Never the platform's `variant` field, which belongs to store-page
363
- experiments.
363
+ experiments. For the platform's results to show the test, also declare it
364
+ under `analytics_tests` in your game config (see docs/analytics.md).
364
365
 
365
366
  WAITS FOR THE PLAYER. The id arrives with the first play token, so a call
366
367
  during boot waits for it, and rejects as `Gem.awaitIdentity` does
@@ -1667,6 +1668,125 @@ only when your studio corrects a running period; clients may hold the old
1667
1668
  content for up to an hour afterwards, and the hash is how a verifier tells
1668
1669
  them apart.
1669
1670
 
1671
+ <a id="platformgrantsource"></a>
1672
+
1673
+ ## PlatformGrantSource
1674
+
1675
+ ```ts
1676
+ type PlatformGrantSource = { source: "pass"; pass: string; amount: number } | { source: "boost"; amount: number }
1677
+ ```
1678
+
1679
+ Where the part of a payout above its base comes from.
1680
+
1681
+ <a id="platformgrantpreview"></a>
1682
+
1683
+ ## PlatformGrantPreview
1684
+
1685
+ ```ts
1686
+ interface PlatformGrantPreview
1687
+ ```
1688
+
1689
+ One platform payout — Coins, today — as the preview expects it or a receipt
1690
+ reports it, in one shape, so "you will earn" and "you earned" render with one
1691
+ component.
1692
+
1693
+ <a id="platformgrantpreview-currencyslug"></a>
1694
+
1695
+ ## PlatformGrantPreview.currencySlug
1696
+
1697
+ ```ts
1698
+ readonly currencySlug?: string
1699
+ ```
1700
+
1701
+ The currency's slug, e.g. `ARCADE_TICKETS`. Absent when unknown.
1702
+
1703
+ <a id="platformgrantpreview-currencyid"></a>
1704
+
1705
+ ## PlatformGrantPreview.currencyId
1706
+
1707
+ ```ts
1708
+ readonly currencyId?: string
1709
+ ```
1710
+
1711
+ The currency's id, when it is known.
1712
+
1713
+ <a id="platformgrantpreview-base"></a>
1714
+
1715
+ ## PlatformGrantPreview.base
1716
+
1717
+ ```ts
1718
+ readonly base: number
1719
+ ```
1720
+
1721
+ What a player with no pass and no Boost gets.
1722
+
1723
+ <a id="platformgrantpreview-total"></a>
1724
+
1725
+ ## PlatformGrantPreview.total
1726
+
1727
+ ```ts
1728
+ readonly total: number
1729
+ ```
1730
+
1731
+ What this player gets: `base` plus `breakdown`.
1732
+
1733
+ <a id="platformgrantpreview-breakdown"></a>
1734
+
1735
+ ## PlatformGrantPreview.breakdown
1736
+
1737
+ ```ts
1738
+ readonly breakdown: readonly PlatformGrantSource[]
1739
+ ```
1740
+
1741
+ The part above `base`, and what it is from. Empty when `total` is `base`.
1742
+
1743
+ <a id="platformgrantpreview-ifcomplete"></a>
1744
+
1745
+ ## PlatformGrantPreview.ifComplete
1746
+
1747
+ ```ts
1748
+ readonly ifComplete?: true
1749
+ ```
1750
+
1751
+ `true` for an `on_complete` payout in a preview: it pays only if this
1752
+ claim completes the progression. Absent otherwise, and on a receipt.
1753
+
1754
+ <a id="earningsapi"></a>
1755
+
1756
+ ## EarningsApi
1757
+
1758
+ ```ts
1759
+ interface EarningsApi
1760
+ ```
1761
+
1762
+ `gem.earnings`: what a payout was, in the shape `gem.gameConfig.preview`
1763
+ says it would be.
1764
+
1765
+ <a id="earningsapi-fromreceipt"></a>
1766
+
1767
+ ## EarningsApi.fromReceipt
1768
+
1769
+ ```ts
1770
+ fromReceipt(change: unknown): Promise<PlatformGrantPreview | null>
1771
+ ```
1772
+
1773
+ Turn one `platform_grant` change from an order's receipt into a
1774
+ [PlatformGrantPreview](#platformgrantpreview): `base` is what a player with no pass and no
1775
+ Boost would have got under the same limits, `total` what this player got,
1776
+ and `breakdown` the pass's or the Boost's part, even when a limit trimmed
1777
+ the payout. Draw "you earned" with the component that drew "you will
1778
+ earn". Resolves `null` for any other kind of change.
1779
+
1780
+ **This and `preview` are the only way to show a platform payout.** Never
1781
+ multiply by `gem.player.boost`, and never copy a config amount into your
1782
+ game.
1783
+
1784
+ | Parameter | Type |
1785
+ | --- | --- |
1786
+ | `change` | `unknown` |
1787
+
1788
+ **Returns.** Always resolves; never rejects.
1789
+
1670
1790
  <a id="currencybalance"></a>
1671
1791
 
1672
1792
  ## CurrencyBalance
@@ -3183,49 +3303,6 @@ was already claimed from it — the progress resets, the rewards stay.
3183
3303
  | `progressionId` | `string` |
3184
3304
  | `signal` | `AbortSignal` |
3185
3305
 
3186
- <a id="gamepass"></a>
3187
-
3188
- ## GamePass
3189
-
3190
- ```ts
3191
- interface GamePass
3192
- ```
3193
-
3194
- One pass games may see, and whether this player holds it. Listed whether they
3195
- hold it or not, and this is everything a game learns about it.
3196
-
3197
- <a id="gamepass-pass"></a>
3198
-
3199
- ## GamePass.pass
3200
-
3201
- ```ts
3202
- readonly pass: string
3203
- ```
3204
-
3205
- The pass's offering slug, such as `vip`. Stable and not localized: key on this.
3206
-
3207
- <a id="gamepass-held"></a>
3208
-
3209
- ## GamePass.held
3210
-
3211
- ```ts
3212
- readonly held: boolean
3213
- ```
3214
-
3215
- Whether the player holds it right now.
3216
-
3217
- <a id="gamepass-endsat"></a>
3218
-
3219
- ## GamePass.endsAt
3220
-
3221
- ```ts
3222
- readonly endsAt: string | null
3223
- ```
3224
-
3225
- When the player stops holding it, RFC 3339 in UTC, grace and any queued
3226
- period included. `null` when it is not held, and when it is held with no
3227
- end, so read [GamePass.held](#gamepass-held) first: `null` alone never means ended.
3228
-
3229
3306
  <a id="passesevent"></a>
3230
3307
 
3231
3308
  ## PassesEvent
@@ -7028,6 +7105,67 @@ const theirs = await gem.leaderboards.character('time-trial', characterId);
7028
7105
  caller may not act for this character at all — that one is decided from
7029
7106
  the caller's own claims, not from which character was asked for.
7030
7107
 
7108
+ <a id="gamepass"></a>
7109
+
7110
+ ## GamePass
7111
+
7112
+ ```ts
7113
+ interface GamePass
7114
+ ```
7115
+
7116
+ One pass games may see, and whether this player holds it. Listed whether they
7117
+ hold it or not, and this is everything a game learns about it.
7118
+
7119
+ <a id="gamepass-pass"></a>
7120
+
7121
+ ## GamePass.pass
7122
+
7123
+ ```ts
7124
+ readonly pass: string
7125
+ ```
7126
+
7127
+ The pass's offering slug, such as `vip`. Stable and not localized: key on this.
7128
+
7129
+ <a id="gamepass-held"></a>
7130
+
7131
+ ## GamePass.held
7132
+
7133
+ ```ts
7134
+ readonly held: boolean
7135
+ ```
7136
+
7137
+ Whether the player holds it right now.
7138
+
7139
+ <a id="gamepass-daysleft"></a>
7140
+
7141
+ ## GamePass.daysLeft
7142
+
7143
+ ```ts
7144
+ readonly daysLeft: number
7145
+ ```
7146
+
7147
+ Whole days the player keeps it, rounded up so a pass with any time left
7148
+ reads at least `1`, and **capped at 7**: `7` means "7 or more", and is
7149
+ also what a pass held with no end reads. `0` when it is not held. Show
7150
+ `7` as "7+ days left", never as an exact end.
7151
+
7152
+ <a id="gamepass-endsat"></a>
7153
+
7154
+ ## GamePass.endsAt
7155
+
7156
+ ```ts
7157
+ readonly endsAt: string | null
7158
+ ```
7159
+
7160
+ **Deprecated.** Use [GamePass.daysLeft](#gamepass-daysleft). An exact instant is a value two
7161
+ games could match to link one player's identities, so the platform will
7162
+ stop sending it; it reads `null` then. See
7163
+ [the deprecation policy](../deprecation-policy.md).
7164
+
7165
+ When the player stops holding it, RFC 3339 in UTC, grace and any queued
7166
+ period included. `null` when it is not held, and when it is held with no
7167
+ end, so read [GamePass.held](#gamepass-held) first: `null` alone never means ended.
7168
+
7031
7169
  <a id="platformcurrencyaccess"></a>
7032
7170
 
7033
7171
  ## PlatformCurrencyAccess
@@ -8714,6 +8852,19 @@ The invoking player's platform-currency balances and earn allowance. The
8714
8852
  platform refuses both for a game server. `earnAllowance.on()` never fires
8715
8853
  here: the hint comes from the arcade, and a function has none.
8716
8854
 
8855
+ <a id="gemfunction-earnings"></a>
8856
+
8857
+ ## GemFunction.earnings
8858
+
8859
+ ```ts
8860
+ readonly earnings: EarningsApi
8861
+ ```
8862
+
8863
+ What a platform payout was: `earnings.fromReceipt(change)` turns a
8864
+ `platform_grant` change from a receipt into the shape the browser's payout
8865
+ preview gives — `base`, `total`, and the pass's or the Boost's part — to
8866
+ return to the game that draws it.
8867
+
8717
8868
  <a id="gemfunction-profiles"></a>
8718
8869
 
8719
8870
  ## GemFunction.profiles