@indiegems/gem-web-sdk 24.0.0 → 24.0.2
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 +324 -127
- package/dist/gem-web-sdk.conformance.js +1 -1
- package/dist/gem-web-sdk.esm.js +1 -1
- package/dist/gem-web-sdk.function.js +1 -1
- package/dist/gem-web-sdk.game.js +2 -2
- package/dist/gem-web-sdk.min.js +2 -2
- package/dist/gem-web-sdk.sdk.js +2 -2
- package/dist/gem-web-sdk.server.js +1 -1
- package/docs/api/api-reference-function.md +1 -1
- package/docs/api/api-reference-game.md +1 -1
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +1 -1
- package/docs/changelog.md +324 -127
- package/docs/getting-started.md +1 -1
- package/docs/manifest.json +6 -6
- package/docs/server-bundles.md +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,73 +8,205 @@ Each release states its impact on a game that upgrades to it:
|
|
|
8
8
|
- **recommended**: nothing should break, but something is worth acting on: a type to rebuild against, a deprecation, or work you may stop doing.
|
|
9
9
|
- **none**: upgrading asks nothing of a game.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
A release opens with a short summary of what it is about, and its entries are grouped under up to six headings:
|
|
12
12
|
|
|
13
13
|
- **Must change**: code or behaviour a game has to change, or it stops compiling or stops working.
|
|
14
14
|
- **Should change**: something is deprecated. It keeps working for now; move off it before it is removed.
|
|
15
15
|
- **May stop**: work a game can drop, because the SDK or the Arcade now does it.
|
|
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
|
+
- **Fixes and improvements**: bugs fixed and behaviour improved without a change to the API. Nothing to change in your game.
|
|
19
|
+
|
|
20
|
+
## 24.0.2
|
|
21
|
+
|
|
22
|
+
Upgrading from 24.0.1. Impact: none.
|
|
23
|
+
|
|
24
|
+
This release changes nothing in the public API and asks nothing of a game. The changes are internal: CI checks that the telemetry event lists stay in sync, telemetry documentation, and test vectors for the economy preview.
|
|
25
|
+
|
|
26
|
+
Nothing in this release asks anything of a game.
|
|
27
|
+
|
|
28
|
+
## 24.0.1
|
|
29
|
+
|
|
30
|
+
Upgrading from 24.0.0. Impact: none.
|
|
31
|
+
|
|
32
|
+
This release has no public API changes, so there is nothing to change in your game and no reason to rebuild. The commits only touch the SDK's release tooling and vendored test data.
|
|
33
|
+
|
|
34
|
+
Nothing in this release asks anything of a game.
|
|
18
35
|
|
|
19
36
|
## 24.0.0
|
|
20
37
|
|
|
21
38
|
Upgrading from 23.3.0. Impact: required.
|
|
22
39
|
|
|
40
|
+
This release is mostly the economy-reset work: games can sell a pass by its slug, preview what a config grants including boosts and passes, read earnings from receipts, and gate on a player's passes and boost from a dedicated server. Pass `endsAt` is deprecated in favour of `daysLeft`. A game should review how it tells guests apart and how it reads the offer `kind` of a coin pack, and anyone running the parent conformance kit must adopt kit version 9.
|
|
41
|
+
|
|
23
42
|
### Must change
|
|
24
43
|
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
- **Offer `kind` has a new value, and non-GEMS packs now report it.** `ArcadeOffer.kind` is now typed `ArcadeOfferKind`. A pack of any platform currency other than GEMS is reported as `coin_pack`. `gem_pack` is deprecated and stays for GEMS packs. Code that matches `kind === "gem_pack"` to find every currency pack will miss coin packs, and an exhaustive `switch` over `kind` will stop compiling.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// before
|
|
48
|
+
if (offer.kind === "gem_pack") showPack(offer);
|
|
49
|
+
|
|
50
|
+
// after
|
|
51
|
+
if (offer.kind === "gem_pack" || offer.kind === "coin_pack") showPack(offer);
|
|
52
|
+
```
|
|
53
|
+
- **Do not use `gem.store.offers` answering `sign_in_required` to detect a guest.** A current Arcade answers a guest's `offers` with `ok`, because a guest can buy. On an older Arcade `signInAvailable` reads `true` for every player. For a sign-up gift or any guest-only prompt, read `gem.player.isGuest`. It is `true` only when the Arcade says the player is a guest who can sign up here. It is `false` for a full account, a surface with no sign-up, and an older Arcade that does not say, so no one with an account is offered a gift. It follows a guest who signs up in place.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
if (gem.player.isGuest) offerSignUpGift();
|
|
57
|
+
```
|
|
58
|
+
- **Parent conformance kit is now version 9.** `PARENT_CONFORMANCE_VERSION` went from 8 to 9. If you maintain an Arcade parent or a surface that runs the kit, add the new `storeScenario` member (a `StoreScenario`) to your `ParentUnderTest` adapter. The kit adds six pass cases:
|
|
59
|
+
- a pass listed by slug;
|
|
60
|
+
- a pass not on sale;
|
|
61
|
+
- a pass sold to a guest and to a holder;
|
|
62
|
+
- a pass left out after two declined sheets;
|
|
63
|
+
- a minor's listing, which must be indistinguishable from not on sale.
|
|
64
|
+
|
|
65
|
+
Games themselves do not run the kit.
|
|
28
66
|
|
|
29
67
|
### Should change
|
|
30
68
|
|
|
31
|
-
- GamePass.endsAt is deprecated
|
|
69
|
+
- **`GamePass.endsAt` is deprecated; use `GamePass.daysLeft`.** An exact end instant could be matched across games to link one player's identities, so the platform will stop sending it and `endsAt` will read `null`. `daysLeft` is whole days, capped at 7. Show it as a countdown instead of computing from a timestamp.
|
|
32
70
|
|
|
33
|
-
|
|
71
|
+
```ts
|
|
72
|
+
// before
|
|
73
|
+
const ends = pass.endsAt; // Date-like instant
|
|
74
|
+
|
|
75
|
+
// after
|
|
76
|
+
if (pass.daysLeft !== undefined) label = `${pass.daysLeft} days left`;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Check the `daysLeft` type in the reference for how an open-ended pass reads.
|
|
34
80
|
|
|
35
|
-
|
|
81
|
+
See [the deprecation policy](docs/deprecation-policy.md#4-what-is-deprecated-today).
|
|
36
82
|
|
|
37
83
|
### Rebuild
|
|
38
84
|
|
|
39
|
-
- Types
|
|
85
|
+
- **Types gained members; recompiling is enough.** `GemServer.match` and `GemMatchServer.match` gained `playerPasses` and `playerBoost`, and the offers result type gained optional `pass` and `earnMultipliers`. Existing code that only calls them compiles unchanged. Test doubles or fakes that implement `match` need the two new methods.
|
|
40
86
|
|
|
41
87
|
### New capability
|
|
42
88
|
|
|
43
|
-
-
|
|
44
|
-
- A game can sell a pass by its slug. StoreApi.passOnSale tells you whether the pass is on sale here, and StoreApi.showPass opens its sheet. The ShowPassOutcome it returns is ok only once a fresh read of the player's passes shows the pass held, and otherwise ok or pending as that type describes.
|
|
45
|
-
- A game can preview what a config change would grant before applying it. You pass a config id, never an amount or a pass. The preview works out the player's multiplier, holdings and local offers, and reports grants, platform grants with a breakdown crediting a held pass or Boost, bonuses with eligibility and unmet requirements, and upsells for passes the player could buy. Rolls are reported as a count from a table, never an outcome. Errors and targets have their own types.
|
|
46
|
-
- Earnings can be read from a receipt. The new earnings API turns a receipt's platform grant change into the same platform grant shape the preview uses, from the base amount granted and the boost source. It is available on the browser, on the function half, and on the server and match server. The function half resolves the currency from the invoking player's currencies, while the server half takes a currency id only.
|
|
47
|
-
- A dedicated game server can now read a player's passes and Boost in a match. Use the match player passes read to gate on a pass, using the same GamePass shape as the browser. The match player Boost read returns a MatchPlayerBoost with its multipliers, for display only. Both reads go through optional runtime hooks, host.passes and host.boost, and an image without a hook, or a listen host, is refused by name. A refusal surfaces as an API error that keeps its original code.
|
|
89
|
+
- **Sell a pass by its slug.** `gem.store.showPass(slug)` opens the Arcade's store sheet for a pass and answers a `ShowPassOutcome`. It answers `ok` only once a fresh read of `gem.me.passes` shows the pass held; see `ShowPassOutcome` for the other outcomes, including `pending`. `gem.store.passOnSale(slug)` tells you whether the store sells that pass here, so you can hide a button when it does not. A guest can buy, and a minor's listing is indistinguishable from not on sale.
|
|
48
90
|
|
|
49
|
-
|
|
91
|
+
```ts
|
|
92
|
+
if (await gem.store.passOnSale("season-pass")) {
|
|
93
|
+
const outcome = await gem.store.showPass("season-pass");
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Offers also carry `pass` (the slug of a pass they sell) and `earnMultipliers` (`ArcadeOfferEarnMultiplier`: a `currencySlug` and a `multiplierBp` in basis points). Use them to show what a pass or boost raises.
|
|
98
|
+
- **Preview what a config grants.** `gem.gameConfig.preview` takes a config id, either `{ recipe }` or `{ progression, add? }`, never an amount or a pass. The SDK works out the result from the loaded config and the player's boost, passes and the offers on sale here. The `ConfigPreview` lists:
|
|
99
|
+
- `grants`: items, game currencies, and rolls as "N rolls from {table}", never an outcome;
|
|
100
|
+
- `platformGrants`: `base`, `total` and a `breakdown` crediting the held pass or boost, with `on_complete` mappings reported as "if it completes";
|
|
101
|
+
- `bonuses`: a `BonusPreview` per bonus with `eligible: true | false | 'unknown'` and its `unmet` requirements;
|
|
102
|
+
- `upsells`: a `PassUpsell` for each pass on sale that the player does not hold, listed only when it would raise a payout or make a bonus eligible.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const preview = await gem.gameConfig.preview({ recipe: "forge_sword" });
|
|
106
|
+
for (const up of preview.upsells) offerPass(up);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Check `PreviewError` for refusals.
|
|
110
|
+
- **Read earnings from a receipt.** `gem.earnings.fromReceipt(change)` turns a receipt's `platform_grant` change into the same platform-grant shape the preview uses (`PlatformGrantPreview`, with a `PlatformGrantSource`), built from `base_granted` and the boost source. It is available in the browser, on the function half (the currency slug comes from the invoking player's currencies), and on the server half (by currency id only).
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const earned = gem.earnings.fromReceipt(receipt.changes[0]);
|
|
114
|
+
```
|
|
115
|
+
- **Gate on a player's passes and boost from a dedicated server.** `gem.match.playerPasses(playerId)` answers the same `GamePass` shape as the browser. It is what a server should gate on. `gem.match.playerBoost(playerId)` answers a `MatchPlayerBoost` (made of `MatchBoostMultiplier`s) and is for display only.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const passes = await gem.match.playerPasses(playerId);
|
|
119
|
+
const boost = await gem.match.playerBoost(playerId);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
A refusal arrives as a `GemApiError` that keeps the API's code, such as `client_permission_denied` or `player_not_in_match`. These reads go through the runtime hooks `host.passes(playerId)` and `host.boost(playerId)`, which answer a `GemServerHostRead` (`{ status, body }`). Hosts implement them optionally. A runtime image without them, or a listen host, refuses the call by name.
|
|
123
|
+
|
|
124
|
+
### Fixes and improvements
|
|
125
|
+
|
|
126
|
+
- `gem.me.passes` now answers "not held" for a known guest without calling the server.
|
|
127
|
+
- A guest's `gem.store.offers` now answers `ok` on a current Arcade, since a guest can buy.
|
|
50
128
|
|
|
51
|
-
|
|
129
|
+
## 23.3.0
|
|
52
130
|
|
|
53
131
|
Upgrading from 23.2.0. Impact: required.
|
|
54
132
|
|
|
55
|
-
|
|
133
|
+
This release adds `signInAvailable`, a signal that tells a game whether a sign-in ask can show the player anything, so the game can decide whether to draw a sign-in button or offer a sign-up gift. The API changes are additive: `gem.account` gains a value and a change listener, and the `onPlayer` callback gains an optional second argument. In the Gem Arcade app every player is signed in, so the signal is `false` there.
|
|
134
|
+
|
|
135
|
+
### Rebuild
|
|
56
136
|
|
|
57
|
-
- FrameCallbacks.
|
|
58
|
-
|
|
137
|
+
- The `onPlayer` callback now receives an optional second argument, `signInAvailable?: boolean`, in both `FrameOptions` and `FrameCallbacks`. Existing handlers that take only `player` still compile, so recompiling is enough. A handler that wants the value can take the second parameter:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// before
|
|
141
|
+
onPlayer(player) { render(player); }
|
|
142
|
+
|
|
143
|
+
// after
|
|
144
|
+
onPlayer(player, signInAvailable) { render(player, signInAvailable); }
|
|
145
|
+
```
|
|
59
146
|
|
|
60
147
|
### New capability
|
|
61
148
|
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
149
|
+
- `gem.account.signInAvailable` says whether asking the player to sign in can show anything. `gem.account.onSignInAvailableChange` lets a game react when that value changes.
|
|
150
|
+
|
|
151
|
+
The value is:
|
|
152
|
+
|
|
153
|
+
- `false` before a player has arrived.
|
|
154
|
+
- `false` wherever the parent has no `signIn`.
|
|
155
|
+
- `true` when the parent is older than the field, which is how `requestSignIn` already behaved there.
|
|
156
|
+
|
|
157
|
+
`requestSignIn` itself is unchanged. Check the signal before drawing a sign-in button:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
function updateSignInButton(available: boolean) {
|
|
161
|
+
signInButton.hidden = !available;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
updateSignInButton(gem.account.signInAvailable);
|
|
165
|
+
gem.account.onSignInAvailableChange((available) => updateSignInButton(available));
|
|
166
|
+
```
|
|
167
|
+
- The player object now carries an optional boolean `signInAvailable` (`GemPlayer.signInAvailable`), which is the value that `gem.account.signInAvailable` reads. `SendPlayerInput` accepts the same field.
|
|
168
|
+
|
|
169
|
+
In the Gem Arcade app every player is a full account, so `signInAvailable` is `false` there and a sign-in ask answers `signed_in` at once. To gate a sign-up gift, test `signInAvailable === true`.
|
|
170
|
+
|
|
171
|
+
On an older Arcade the signal reads `true` for every player. Until the Arcade is updated, combine it with the game's existing guest test:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
if (gem.account.signInAvailable === true && isGuest(player)) {
|
|
175
|
+
offerSignUpGift();
|
|
176
|
+
}
|
|
177
|
+
```
|
|
66
178
|
|
|
67
179
|
## 23.2.0
|
|
68
180
|
|
|
69
|
-
|
|
181
|
+
Upgrading from 23.1.3. Impact: recommended.
|
|
70
182
|
|
|
71
|
-
|
|
183
|
+
`rewardedStatus` now reports the Coin and Gem prices a rewarded placement can be unlocked with, as well as whether the ad can be watched. `grantable` now means the ad or any price can unlock the placement, so check any code that treats it as meaning an ad is available.
|
|
72
184
|
|
|
73
185
|
### New capability
|
|
74
186
|
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
-
|
|
187
|
+
- A rewarded placement can now also be unlocked for platform Coins or Gems. The status you get from `rewardedStatus` has two new fields:
|
|
188
|
+
|
|
189
|
+
- `adGrantable` is true when the ad alone can unlock the placement. Ad caps limit only this value.
|
|
190
|
+
- `options` is the list of `RewardedOption` entries, the prices configured for the placement. It says nothing about the current player.
|
|
191
|
+
|
|
192
|
+
`grantable` is now true when the ad or any price can unlock the placement. A price counts only when the Arcade reports it as grantable. If the Arcade reports prices but no ad information, `adGrantable` reads as false.
|
|
193
|
+
|
|
194
|
+
Use the new fields to label the button, for example:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
if (status.adGrantable) {
|
|
198
|
+
showWatchAdButton();
|
|
199
|
+
}
|
|
200
|
+
if (status.options.length > 0) {
|
|
201
|
+
showPriceButtons(status.options);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Currencies and scope for placement prices are set with `gemctl platform config`.
|
|
206
|
+
|
|
207
|
+
### Fixes and improvements
|
|
208
|
+
|
|
209
|
+
- `grantable` on the rewarded status now stays true when a Coin or Gem price can unlock the placement, even if the ad's caps have been reached. If your game used `grantable` to decide whether to show a "watch ad" button, switch to `adGrantable`.
|
|
78
210
|
|
|
79
211
|
## 23.1.3
|
|
80
212
|
|
|
@@ -96,23 +228,32 @@ Nothing in this release asks anything of a game.
|
|
|
96
228
|
|
|
97
229
|
## 23.1.0
|
|
98
230
|
|
|
99
|
-
*Written from the API facts alone; no summary was generated for this release.*
|
|
100
|
-
|
|
101
231
|
Upgrading from 23.0.6. Impact: required.
|
|
102
232
|
|
|
233
|
+
Games can now read the player's Arcade Text size and scale their UI to match by opting in with `onTextScale`. Games that don't opt in see no change. Anyone running the parent conformance kit must update their parent adapter for kit version 8.
|
|
234
|
+
|
|
103
235
|
### Must change
|
|
104
236
|
|
|
105
|
-
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
237
|
+
- The parent conformance kit moved from version 7 to 8 and adds a `setTextScale` member to the `ParentUnderTest` adapter. If you run the kit against a parent or host implementation, add `setTextScale` to your adapter so the three new text-scale cases can run. Games that don't run the kit have nothing to change.
|
|
238
|
+
|
|
239
|
+
### Rebuild
|
|
240
|
+
|
|
241
|
+
- `GemCapability` gains the `"textScale"` member, and `GemPrefs.recommended` and the `GemHost.sendPrefs` argument gain an optional `textScale?: number`. Recompiling is enough for most games. Only code with an exhaustive `switch` or a `Record<GemCapability, …>` needs a `textScale` case or key added.
|
|
109
242
|
|
|
110
243
|
### New capability
|
|
111
244
|
|
|
112
|
-
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
245
|
+
- A game can now follow the player's Arcade Text size. The value arrives as `gem.prefs.recommended.textScale`, an optional integer from 100 to 200. Passing `onTextScale` to `createGem` opts in and advertises the new `textScale` capability. The callback fires on the first push and on every change, and `gem.textScale` holds the latest value. The SDK applies nothing itself, so the game must apply the value. The host only sends the value to games that advertise the capability.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const gem = await createGem({
|
|
249
|
+
onTextScale: (scale) => {
|
|
250
|
+
// 100 = normal size; apply it to the root font size
|
|
251
|
+
document.documentElement.style.fontSize = `${scale}%`;
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
For the scale to take effect, size your UI in `rem` units.
|
|
116
257
|
|
|
117
258
|
## 23.0.6
|
|
118
259
|
|
|
@@ -152,50 +293,65 @@ Nothing in this release asks anything of a game.
|
|
|
152
293
|
|
|
153
294
|
## 23.0.0
|
|
154
295
|
|
|
155
|
-
*Written from the API facts alone; no summary was generated for this release.*
|
|
156
|
-
|
|
157
296
|
Upgrading from 22.0.0. Impact: required.
|
|
158
297
|
|
|
298
|
+
This release adds a shared Friends window to the Arcade, opened with `gem.friends.inviteToPlay()` and `gem.friends.setInviteParams()`, and makes `gem.me.inventory.onChanged` fire for inventory changes the game did not make through the SDK. Games that only call the SDK need no code changes. Anyone who hosts games in their own parent page (a `GemHost` embedder) or runs the parent conformance kit must update for conformance version 7 and the wider message types.
|
|
299
|
+
|
|
159
300
|
### Must change
|
|
160
301
|
|
|
161
|
-
-
|
|
162
|
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
###
|
|
170
|
-
|
|
171
|
-
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
302
|
+
- The parent conformance kit moves from version 6 to **7**. If you embed games with your own parent and run the kit, update your test setup:
|
|
303
|
+
|
|
304
|
+
- The fixture now names a game and a channel (`ParentFixture.gameId`, `ParentFixture.channel`). Use the values the kit gives you, and make your parent's inventory relay check the token's game and channel against them.
|
|
305
|
+
- Your adapter (`ParentUnderTest`) gains `deliverSocket`. The kit uses it to push socket events into your parent so the new inventory cases can run. Implement it so the socket event reaches the same relay path a real socket would.
|
|
306
|
+
- Five new inventory cases now run against every parent, including one checking that another game's inventory change is never relayed.
|
|
307
|
+
|
|
308
|
+
A parent that fails these cases does not pass conformance version 7.
|
|
309
|
+
|
|
310
|
+
### May stop
|
|
311
|
+
|
|
312
|
+
- You can stop polling or manually re-reading inventory after a reward lands. Previously `gem.me.inventory.onChanged` fired only for writes made through the SDK, so games re-read inventory after `gem.functions.invoke`. When the Arcade relays `gem.inventoryChanged` (it must advertise the `inventoryChanged` capability), the SDK drops its cached inventory reads for `gem.me` and every character it holds, and `onChanged` fires. This covers grants from your function or server, purchases, gifts and changes from another device.
|
|
313
|
+
|
|
314
|
+
### Rebuild
|
|
315
|
+
|
|
316
|
+
- The message unions and message-name lists grew. `FrameMessage` and `FRAME_TO_PARENT` gain `gem.requestPlayInvite` and `gem.inviteParams`. `ParentMessage` and `PARENT_TO_FRAME` gain `gem.inventoryChanged` and `gem.playInviteResult`. Recompiling is enough for most code. If you have an exhaustive `switch` over these unions, or a table keyed by the message names, add cases for the new messages, or the build will fail or the messages will be unhandled.
|
|
317
|
+
- `GemCapability` gains two members: `playInvites` and `inventoryChanged`. Recompiling is enough unless you have an exhaustive `switch` or a `Record<GemCapability, ...>`. In that case, add entries for the two new values.
|
|
318
|
+
- `GemFrame.requestArcade` now takes `GemArcadeAsk | GemFriendsWindowAsk` instead of `GemArcadeAsk`. Callers are unaffected and recompiling is enough. If you implement or wrap `GemFrame` yourself (for example in a test double), widen your `requestArcade` parameter to the new union.
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
// before
|
|
322
|
+
requestArcade(msg: GemArcadeAsk): boolean
|
|
323
|
+
// after
|
|
324
|
+
requestArcade(msg: GemArcadeAsk | GemFriendsWindowAsk): boolean
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### New capability
|
|
328
|
+
|
|
329
|
+
- A game can ask the Arcade to open its shared **Friends window**, and can declare the launch params that window carries when the player opens it from the Arcade menu. Share and friend invites now use the same launch params. Both calls need the `playInvites` capability.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
// On a player press (e.g. an "Invite friends" button):
|
|
333
|
+
const shown = await gem.friends.inviteToPlay({ params: { room: "ABCD" } });
|
|
334
|
+
// shown is false outside the Arcade, without the capability, without a user press,
|
|
335
|
+
// while another sheet is open, or on timeout. It never rejects.
|
|
336
|
+
|
|
337
|
+
// Standing declaration for the window opened from the Arcade menu:
|
|
338
|
+
gem.friends.setInviteParams({ room: "ABCD" });
|
|
339
|
+
gem.friends.setInviteParams(null); // clear it
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
- `inviteToPlay({ params? })` returns `Promise<boolean>`. It is limited to 6 asks a minute.
|
|
343
|
+
- `setInviteParams(params | null)` is limited to 30 a minute. The SDK holds the value and re-sends it on every handshake. It re-checks the value against the parent's `shareRemap`.
|
|
344
|
+
- `params` has the same shape and rules as the params of `gem.share.request`. Where `gem.share.request` throws on invalid params, the two invite calls log a warning and send nothing.
|
|
345
|
+
- For embedders: a parent built on `GemHost` can now serve the Friends window and relay inventory changes.
|
|
346
|
+
|
|
347
|
+
- **Friends window**: pass `onRequestPlayInvite` and `onInviteParams` (in `HostOptions`/`HostCallbacks`) to handle the game's `gem.requestPlayInvite` and `gem.inviteParams` messages. Call `host.answerPlayInvite(...)` to send back `gem.playInviteResult { requestId, shown }`.
|
|
348
|
+
- **Inventory**: `GemHost` relays the game's `game_inventory_changed` socket event as `gem.inventoryChanged`. It crosses only after a token, only for the token's game and channel, and only to a frame that advertised the `inventoryChanged` capability. No fields are copied, and sends are coalesced to at most one every two seconds. If you parse the socket yourself, call `sendInventoryChanged` for the same send; `relayInventoryEvent` is also new on `GemHost`.
|
|
349
|
+
- **Validators**: `validateGemRequestPlayInvite`, `validateGemInviteParams`, `validateGemPlayInviteResult` and `validateGemInventoryChanged` check the new messages (`GemRequestPlayInvite`, `GemInviteParams`, `GemPlayInviteResult`, `GemInventoryChanged`).
|
|
350
|
+
|
|
351
|
+
### Fixes and improvements
|
|
352
|
+
|
|
353
|
+
- `gem.me.inventory.onChanged` now fires when the game's inventory changes on the server (a grant from your function or server, a purchase, a gift, or a change from another device). Before, it fired only for writes made through the SDK, so games had to poll after `gem.functions.invoke` to see a reward land. This needs an Arcade that relays `gem.inventoryChanged`.
|
|
354
|
+
- A closed `GemHost` no longer sends a trailing `gem.inventoryChanged` after it has been closed.
|
|
199
355
|
|
|
200
356
|
## 22.0.0
|
|
201
357
|
|
|
@@ -336,30 +492,26 @@ Upgrading from 17.0.0. Impact: required.
|
|
|
336
492
|
|
|
337
493
|
## 17.0.0
|
|
338
494
|
|
|
339
|
-
*Written from the API facts alone; no summary was generated for this release.*
|
|
340
|
-
|
|
341
495
|
Upgrading from 16.3.1. Impact: required.
|
|
342
496
|
|
|
497
|
+
This release adds `gem.store.showOffer`, which lets a game point at a single SKU and have the Arcade draw the offer in its own sheet, for promotions whose pitch the bridge cannot carry (pass perks, sales, what a holder already has). Games that only use `gem.store.purchase` need no code changes beyond recompiling. If you run your own parent with `GemHost`, the conformance version moves to 3 and you should re-run the conformance kit.
|
|
498
|
+
|
|
343
499
|
### Must change
|
|
344
500
|
|
|
345
|
-
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
- PARENT_TO_FRAME
|
|
350
|
-
- ParentMessage changed its signature.
|
|
501
|
+
- The parent conformance version went from `2` to `3`. If you embed games with `GemHost` (your own parent page) rather than only building a game for the Arcade, re-run the conformance kit: kit 3 adds three store-offer-sheet cases. Its checkout back-off now also holds a sheet back, so a game cannot get around a back-off by switching from a purchase request to a sheet request. A game that only runs inside the Arcade has nothing to change for this.
|
|
502
|
+
|
|
503
|
+
### Rebuild
|
|
504
|
+
|
|
505
|
+
- The message and capability unions each gained a member: `GemCapability` now includes `"storeOfferSheet"`, `FrameMessage` includes `GemRequestStoreOfferSheet` (`"gem.requestStoreOfferSheet"` in `FRAME_TO_PARENT`), and `ParentMessage` includes `GemStoreOfferSheetResult` (`"gem.storeOfferSheetResult"` in `PARENT_TO_FRAME`). Recompiling is enough for most games. Only code that switches exhaustively over one of these unions, or over those constant lists, needs a new case for the added member.
|
|
351
506
|
|
|
352
507
|
### New capability
|
|
353
508
|
|
|
354
|
-
-
|
|
355
|
-
|
|
356
|
-
-
|
|
357
|
-
-
|
|
358
|
-
-
|
|
359
|
-
-
|
|
360
|
-
- HostOptions.onRequestStoreOfferSheet was added.
|
|
361
|
-
- validateGemRequestStoreOfferSheet was added.
|
|
362
|
-
- validateGemStoreOfferSheetResult was added.
|
|
509
|
+
- `store.showOffer` hands the Arcade one SKU and the Arcade draws the offer in its own sheet. Use it when the pitch is something your game cannot draw faithfully: a pass's perks, a sale, or what a holder already has. The sheet's Buy button opens the same checkout as a normal purchase, and none of the pitch details cross to the game, so the game never redraws a promotion.
|
|
510
|
+
|
|
511
|
+
- Use `store.purchase` for an offer your game draws itself, and `store.showOffer` for the rest.
|
|
512
|
+
- `store.showOfferAvailable` is a synchronous check you can call first to see whether the Arcade will draw a sheet, so you can fall back or hide the entry point.
|
|
513
|
+
- `showOffer` shares `purchase`'s bounds and acting lock, and waits with no timeout, as `purchase` does.
|
|
514
|
+
- For parents built on `GemHost`, there is a new request and result pair for the offer sheet. Pass `onRequestStoreOfferSheet` in `HostOptions` (it is also on `HostCallbacks`) to receive a `GemRequestStoreOfferSheet`. When the player is done with the sheet, answer it with `GemHost.answerStoreOfferSheet`, which sends a `GemStoreOfferSheetResult` to the game. `validateGemRequestStoreOfferSheet` and `validateGemStoreOfferSheetResult` validate the two message shapes.
|
|
363
515
|
|
|
364
516
|
## 16.3.1
|
|
365
517
|
|
|
@@ -490,21 +642,42 @@ Upgrading from 15.0.0. Impact: none.
|
|
|
490
642
|
|
|
491
643
|
## 15.0.0
|
|
492
644
|
|
|
493
|
-
*Written from the API facts alone; no summary was generated for this release.*
|
|
494
|
-
|
|
495
645
|
Upgrading from 14.0.3. Impact: required.
|
|
496
646
|
|
|
497
|
-
|
|
647
|
+
This release adds the Arcade's Music and Sound switches for games: pass `onAudioChannels` to `createGem` and the game is told separately when to play music and when to play sound effects. Nothing in the existing API is removed. The release is marked required because public types changed (`GemCapability`, `GemHost.sendPrefs`), so rebuild the game against it.
|
|
648
|
+
|
|
649
|
+
### Rebuild
|
|
498
650
|
|
|
499
|
-
-
|
|
500
|
-
|
|
651
|
+
- Two public types changed in additive ways. Recompiling is enough for almost every game and no code change is needed.
|
|
652
|
+
|
|
653
|
+
- `GemCapability` gained the `"audioChannels"` token. Only a game that switches exhaustively over `GemCapability` (for example with a `never` check in the default branch) will fail to compile. Add a case for `"audioChannels"` there.
|
|
654
|
+
- `GemHost.sendPrefs` now accepts optional `music?: boolean` and `sounds?: boolean` in its `prefs` argument. Existing calls stay valid. This only matters to a game or test harness that implements or calls the host side.
|
|
501
655
|
|
|
502
656
|
### New capability
|
|
503
657
|
|
|
504
|
-
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
658
|
+
- The Arcade's Gem menu has Music and Sound switches. A game can now honour them by passing `onAudioChannels` to `createGem`. The SDK cannot split a game's audio itself, so it advertises the `audioChannels` capability only when the game passes this handler.
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
const gem = createGem({
|
|
662
|
+
onAudioChannels: ({ music, sounds }: GemAudioChannels) => {
|
|
663
|
+
musicBus.setEnabled(music);
|
|
664
|
+
sfxBus.setEnabled(sounds);
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Behaviour:
|
|
670
|
+
|
|
671
|
+
- The handler is called on the first `gem.prefs` and again whenever either value changes.
|
|
672
|
+
- `gem.prefs.music` and `gem.prefs.sounds` are always present and default to `true`, including when the Arcade does not send them.
|
|
673
|
+
- `muted` stays the master mute, which the SDK enforces. It is not folded into `music` or `sounds`, so check both if you need the effective state.
|
|
674
|
+
- The new `GemAudioChannels` type describes the `{ music, sounds }` argument.
|
|
675
|
+
|
|
676
|
+
A game that does not pass `onAudioChannels` keeps working. The Arcade falls back to setting `muted: true` while both switches are off. It drives Music through the game's declared `icon: 'music'` setting (`gem.settingChange`) where there is one.
|
|
677
|
+
|
|
678
|
+
### Fixes and improvements
|
|
679
|
+
|
|
680
|
+
- The Gem menu's quick-row Music switch previously worked only for games that declared an `icon: 'music'` setting, and Sound was only the master mute. Both are now independent switches that every game can honour through `onAudioChannels`.
|
|
508
681
|
|
|
509
682
|
## 14.0.3
|
|
510
683
|
|
|
@@ -856,33 +1029,57 @@ Upgrading from 8.0.0. Impact: required.
|
|
|
856
1029
|
|
|
857
1030
|
## 8.0.0
|
|
858
1031
|
|
|
859
|
-
*Written from the API facts alone; no summary was generated for this release.*
|
|
860
|
-
|
|
861
1032
|
Upgrading from 7.9.1. Impact: required.
|
|
862
1033
|
|
|
863
|
-
|
|
1034
|
+
This release adds `gem.vibration`, which vibrates on iPhones and other browsers without `navigator.vibrate`, and `gem.friends` reads for the friend challenges a player received. Rebuild your game against the new types: `GemCapability`, `FrameMessage` and `BucketConfig` all gained members, which can break exhaustive code.
|
|
1035
|
+
|
|
1036
|
+
### Rebuild
|
|
1037
|
+
|
|
1038
|
+
- `GemCapability` gains `"vibration"`, `FrameMessage` gains `GemVibrate`, and `FRAME_TO_PARENT` gains `"gem.vibrate"`. Recompiling is enough for most games. If you have an exhaustive `switch` over these unions, or a `Record<GemCapability, …>`, add a case or entry for the new member. If you use `FRAME_TO_PARENT` as a list of known message types, expect one more entry.
|
|
1039
|
+
- `BucketConfig` gains an optional `max_balance?: number | null`, a per-bucket holding cap that is omitted when there is none. Recompiling is enough. Code that reads bucket configs should treat a missing or `null` value as no cap.
|
|
1040
|
+
|
|
1041
|
+
### New capability
|
|
1042
|
+
|
|
1043
|
+
- **Read friend challenges a player received.** `gem.friends` gains three calls for the receiving side of friend challenges:
|
|
1044
|
+
|
|
1045
|
+
- `gem.friends.launchedChallenge(opts?)` resolves to the `FriendChallenge` the player launched your game from, or `null` for an ordinary launch.
|
|
1046
|
+
- `gem.friends.inbox(opts?)` resolves to a `FriendChallengesPage` of every challenge the player received, paged with an opaque cursor like `list`. Answered challenges are included and sorted last, so you can recognise a relaunch.
|
|
1047
|
+
- `gem.friends.markAnswered(id)` marks a challenge answered. It is idempotent.
|
|
1048
|
+
|
|
1049
|
+
```ts
|
|
1050
|
+
const gem = await createGem();
|
|
1051
|
+
const challenge = await gem.friends.launchedChallenge();
|
|
1052
|
+
if (challenge && !challenge.answeredAt) {
|
|
1053
|
+
// start the challenge round, then when the player finishes:
|
|
1054
|
+
await gem.friends.markAnswered(challenge.id);
|
|
1055
|
+
}
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
Things to know:
|
|
1059
|
+
|
|
1060
|
+
- `createGem` reads `gemChallenge` from `location.search` when it runs. If your game reads its own launch keys and cleans the URL with `history.replaceState`, do that **after** `createGem`, otherwise `launchedChallenge` silently resolves `null`. Use `launchedChallenge` instead of reading the URL yourself.
|
|
1061
|
+
- `launchedChallenge` never rejects for an ordinary launch. It resolves `null` for no challenge key, a value that is not a UUID, or the platform's uniform not-found answer (`inbox_item_not_found`), as with a shared link or guest launch. Any other 404 still rejects.
|
|
1062
|
+
- `launchedChallenge` is not cached. Check `answeredAt` on each call to tell a replay apart.
|
|
1063
|
+
- `markAnswered` rejects with `not_found` for an unknown challenge or someone else's, and with `invalid` locally for a value that cannot be an id.
|
|
1064
|
+
- `limit` in `inbox` and `list` must be a non-negative integer. A negative, fractional or `NaN` limit is refused with `invalid`.
|
|
1065
|
+
- Challenge `params` come from another player, so check each one before using it.
|
|
1066
|
+
- **Vibrate on every platform that can.** `navigator.vibrate` is missing in every WebKit browser, including the Gem Arcade iOS app, and in Firefox 129+. `gem.vibration` is a drop-in for it, taking the same pattern and returning the same boolean.
|
|
1067
|
+
|
|
1068
|
+
```ts
|
|
1069
|
+
if (gem.vibration.available) {
|
|
1070
|
+
const ok = gem.vibration.vibrate([30, 40, 30]);
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
The SDK picks the route by feature detection. If `navigator.vibrate` exists and the device has a coarse pointer, it vibrates locally in the frame. Otherwise it asks the Arcade, if the Arcade supports vibration. If neither works, `available` is `false` and `vibrate` returns `false`.
|
|
1075
|
+
|
|
1076
|
+
The same rules apply on both routes:
|
|
1077
|
+
|
|
1078
|
+
- The frame must have had user activation.
|
|
1079
|
+
- A pattern has at most 99 entries, each 0 to 10 000 ms, and a 10 s total.
|
|
1080
|
+
- At most 600 calls per minute are allowed.
|
|
864
1081
|
|
|
865
|
-
-
|
|
866
|
-
- FRAME_TO_PARENT changed its signature.
|
|
867
|
-
- FrameMessage changed its signature.
|
|
868
|
-
- GemCapability changed its signature.
|
|
869
|
-
|
|
870
|
-
### New capability
|
|
871
|
-
|
|
872
|
-
- FriendChallenge was added.
|
|
873
|
-
- FriendChallengesPage was added.
|
|
874
|
-
- FriendsApi.inbox was added.
|
|
875
|
-
- FriendsApi.launchedChallenge was added.
|
|
876
|
-
- FriendsApi.markAnswered was added.
|
|
877
|
-
- FriendsApiDependencies.launchedChallengeId was added.
|
|
878
|
-
- ListFriendChallengesOptions was added.
|
|
879
|
-
- Gem.vibration was added.
|
|
880
|
-
- GemFrame.requestVibrate was added.
|
|
881
|
-
- VibrationApi was added.
|
|
882
|
-
- GemVibrate was added.
|
|
883
|
-
- HostCallbacks.onVibrate was added.
|
|
884
|
-
- HostOptions.onVibrate was added.
|
|
885
|
-
- validateGemVibrate was added.
|
|
1082
|
+
A refused call returns `false` and logs one warning per session through the game's raw log option. `GemFrame.requestVibrate` is the low-level message call behind it. Most games should use `gem.vibration`.
|
|
886
1083
|
|
|
887
1084
|
## 7.9.1
|
|
888
1085
|
|