@indiegems/gem-web-sdk 16.3.1 → 18.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.
@@ -1,6 +1,6 @@
1
1
  # The function surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/function` v16.3.1, 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` v18.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
 
@@ -1,6 +1,6 @@
1
1
  # The browser surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v16.3.1, 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/sdk` v18.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/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/game` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
6
6
 
@@ -4605,6 +4605,19 @@ readonly available: boolean
4605
4605
  Whether the Arcade Store answers here, synchronously. Draw your own "Get
4606
4606
  Gems" button only when it is true.
4607
4607
 
4608
+ <a id="storeapi-showofferavailable"></a>
4609
+
4610
+ ## StoreApi.showOfferAvailable
4611
+
4612
+ ```ts
4613
+ readonly showOfferAvailable: boolean
4614
+ ```
4615
+
4616
+ Whether [StoreApi.showOffer](#storeapi-showoffer) answers here, synchronously. An older
4617
+ Arcade can sell through `purchase` and still not draw this sheet, so read
4618
+ this, not [StoreApi.available](#storeapi-available), before you draw a button that calls
4619
+ `showOffer`. `false` until the bridge is up.
4620
+
4608
4621
  <a id="storeapi-offers"></a>
4609
4622
 
4610
4623
  ## StoreApi.offers
@@ -4659,6 +4672,43 @@ payment takes as long as it takes.
4659
4672
 
4660
4673
  **Returns.** Always resolves; never rejects.
4661
4674
 
4675
+ <a id="storeapi-showoffer"></a>
4676
+
4677
+ ## StoreApi.showOffer
4678
+
4679
+ ```ts
4680
+ showOffer(skuId: string): Promise<ArcadeOutcome>
4681
+ ```
4682
+
4683
+ Ask the arcade to show one SKU from [StoreApi.offers](#storeapi-offers) in its own
4684
+ sheet, over your paused and hidden game: its art, what the player gets, a
4685
+ sale if one is on, and, for a pass the player already holds, how long it
4686
+ has left. The sheet's Buy opens the same checkout as
4687
+ [StoreApi.purchase](#storeapi-purchase). **Call it from the player's press handler.**
4688
+
4689
+ Use it for an offer you would have to hardcode a pitch for: VIP and other
4690
+ passes, and bundles. Your button only needs to say what it opens. Draw a
4691
+ Gem pack yourself and use `purchase` instead. Check
4692
+ [StoreApi.showOfferAvailable](#storeapi-showofferavailable) first, and that the SKU is in `offers`.
4693
+
4694
+ It answers exactly as `purchase` does, with one addition: `declined` /
4695
+ `cancelled` also covers the player leaving the sheet without pressing Buy,
4696
+ and you are not told which of the two they closed. That starts the same
4697
+ back-off, so do not ask again unprompted. `unavailable` / `unsupported`
4698
+ means this Arcade does not draw the sheet, and nothing was sent.
4699
+ `unavailable` / `error` means **the outcome is unknown**: re-read balances
4700
+ and inventory through `gem.me`, and keep listening with
4701
+ [StoreApi.onSettled](#storeapi-onsettled).
4702
+
4703
+ NO TIMEOUT: it waits for as long as the arcade is connected, because a
4704
+ player may read a sheet and pay for as long as they like.
4705
+
4706
+ | Parameter | Type |
4707
+ | --- | --- |
4708
+ | `skuId` | `string` |
4709
+
4710
+ **Returns.** Always resolves; never rejects.
4711
+
4662
4712
  <a id="storeapi-onsettled"></a>
4663
4713
 
4664
4714
  ## StoreApi.onSettled
@@ -6919,6 +6969,37 @@ A corner of your frame, for `gem.setGemCorner`: where you would like the
6919
6969
  arcade's Gem button to sit. `'top-left'` is where it sits unless you ask.
6920
6970
  Physical corners: `'top-left'` is the top-left in every language.
6921
6971
 
6972
+ <a id="menustyle"></a>
6973
+
6974
+ ## MenuStyle
6975
+
6976
+ ```ts
6977
+ interface MenuStyle
6978
+ ```
6979
+
6980
+ Two colours for the arcade's own screens over your game, for
6981
+ `gem.setMenuStyle`: each exactly `#rrggbb`. The arcade works out the rest.
6982
+
6983
+ <a id="menustyle-ground"></a>
6984
+
6985
+ ## MenuStyle.ground
6986
+
6987
+ ```ts
6988
+ readonly ground: string
6989
+ ```
6990
+
6991
+ The colour the arcade's surfaces are built on, such as `'#1b2a4a'`.
6992
+
6993
+ <a id="menustyle-accent"></a>
6994
+
6995
+ ## MenuStyle.accent
6996
+
6997
+ ```ts
6998
+ readonly accent: string
6999
+ ```
7000
+
7001
+ The one highlight, at least 3:1 against `ground`, such as `'#ffb300'`.
7002
+
6922
7003
  <a id="framestate"></a>
6923
7004
 
6924
7005
  ## FrameState
@@ -7296,6 +7377,29 @@ its control.
7296
7377
  up, for an arcade that cannot move its control, for a value that is not
7297
7378
  one of the four corners, and past this frame's own limit.
7298
7379
 
7380
+ <a id="gemframe-setmenustyle"></a>
7381
+
7382
+ ## GemFrame.setMenuStyle
7383
+
7384
+ ```ts
7385
+ setMenuStyle(style: MenuStyle | null): boolean
7386
+ ```
7387
+
7388
+ Give the arcade's own screens over your game two colours to wear, or
7389
+ `null` to hand them back to the player's style: the whole answer each
7390
+ time, the latest one winning.
7391
+
7392
+ Prefer `gem.setMenuStyle`, which checks the colours and holds them across
7393
+ a reconnect. This one sends only while connected, and only to an arcade
7394
+ that can wear them.
7395
+
7396
+ | Parameter | Type |
7397
+ | --- | --- |
7398
+ | `style` | `MenuStyle \| null` |
7399
+
7400
+ **Returns.** whether the message left the frame: `false` before the bridge is
7401
+ up, for an arcade that cannot wear them, and past this frame's own limit.
7402
+
7299
7403
  <a id="gemframe-requestshare"></a>
7300
7404
 
7301
7405
  ## GemFrame.requestShare
@@ -11216,8 +11320,10 @@ sheet. To spend Gems or Coins on your game's own items, use
11216
11320
  [Gem.purchases](#gem-purchases).
11217
11321
 
11218
11322
  List what the store sells here with `offers()`, show each `priceDisplay`
11219
- exactly as given, and call `purchase(skuId)` from the player's press. After
11220
- `ok`, re-read balances through [Gem.me](#gem-me). Nothing rejects.
11323
+ exactly as given, and call `purchase(skuId)` from the player's press. For a
11324
+ pass, a bundle or anything else whose pitch you would have to hardcode, call
11325
+ `showOffer(skuId)` instead, and the arcade draws the offer. After `ok`,
11326
+ re-read balances through [Gem.me](#gem-me). Nothing rejects.
11221
11327
 
11222
11328
  **Guides.** [arcade-and-store](../arcade-and-store.md)
11223
11329
 
@@ -11967,6 +12073,45 @@ with a warning on the console, and changes nothing.
11967
12073
  up (the corner is held and sent when it connects), under an embedder that
11968
12074
  cannot move its control, and for a value that is not a corner.
11969
12075
 
12076
+ <a id="gem-setmenustyle"></a>
12077
+
12078
+ ## Gem.setMenuStyle
12079
+
12080
+ ```ts
12081
+ setMenuStyle(style: MenuStyle | null): boolean
12082
+ ```
12083
+
12084
+ Give the arcade's own screens over your game your colours, so they match
12085
+ it: on the Gem Arcade, the burger button, the Gem menu and its pages, and
12086
+ every Arcade pop-up over the running game.
12087
+
12088
+ ```js
12089
+ gem.setMenuStyle({ ground: '#1b2a4a', accent: '#ffb300' });
12090
+ gem.setMenuStyle(null); // back to the player's own style
12091
+ ```
12092
+
12093
+ **Colours only.** `ground` is what the arcade's surfaces are built on and
12094
+ `accent` its one highlight; the arcade works out the text, rims, button
12095
+ shapes and fonts, so its screens stay readable on any ground.
12096
+
12097
+ **The Arcade refuses colours it cannot use.** Each must be exactly
12098
+ `#rrggbb`, and `accent` must reach 3:1 contrast against `ground`. Anything
12099
+ else is ignored, with a warning on the console giving the measured ratio:
12100
+ the last colours stand, nothing is sent, and this returns `false`.
12101
+
12102
+ **Your colours win over the player's chosen style while your game runs**,
12103
+ and the player's style comes back when it ends. It is screen state, not a
12104
+ toggle: each call is the whole answer, so you may change it between levels.
12105
+ Call it at boot, so your colours arrive with the first screen.
12106
+
12107
+ | Parameter | Type | Description |
12108
+ | --- | --- | --- |
12109
+ | `style` | `MenuStyle \| null` | The two colours, or `null` to hand back to the player's style. |
12110
+
12111
+ **Returns.** `true` when the arcade was told now; `false` before the bridge is
12112
+ up (the colours are held and sent when it connects), under an embedder
12113
+ that cannot wear them, and for colours it would refuse.
12114
+
11970
12115
  <a id="gem-reportframe"></a>
11971
12116
 
11972
12117
  ## Gem.reportFrame
@@ -1,6 +1,6 @@
1
1
  # The server surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v16.3.1, 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/sdk` v18.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/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/server` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
6
6
 
@@ -2,7 +2,7 @@
2
2
  "schema_version": 2,
3
3
  "source": {
4
4
  "repo": "IndieGems/gem-web-sdk",
5
- "version": "16.3.1"
5
+ "version": "18.0.0"
6
6
  },
7
7
  "entries": [
8
8
  {
@@ -5537,6 +5537,22 @@
5537
5537
  "defaultValue": null,
5538
5538
  "guides": []
5539
5539
  },
5540
+ {
5541
+ "id": "StoreApi.showOfferAvailable",
5542
+ "name": "showOfferAvailable",
5543
+ "anchor": "storeapi-showofferavailable",
5544
+ "group": "store",
5545
+ "signature": "readonly showOfferAvailable: boolean",
5546
+ "documented": true,
5547
+ "summary": "Whether [StoreApi.showOffer](api-reference-game.md#storeapi-showoffer) answers here, synchronously. An older\nArcade can sell through `purchase` and still not draw this sheet, so read\nthis, not [StoreApi.available](api-reference-game.md#storeapi-available), before you draw a button that calls\n`showOffer`. `false` until the bridge is up.",
5548
+ "deprecated": null,
5549
+ "params": [],
5550
+ "returns": null,
5551
+ "throws": null,
5552
+ "example": null,
5553
+ "defaultValue": null,
5554
+ "guides": []
5555
+ },
5540
5556
  {
5541
5557
  "id": "StoreApi.offers",
5542
5558
  "name": "offers",
@@ -5576,6 +5592,29 @@
5576
5592
  "defaultValue": null,
5577
5593
  "guides": []
5578
5594
  },
5595
+ {
5596
+ "id": "StoreApi.showOffer",
5597
+ "name": "showOffer",
5598
+ "anchor": "storeapi-showoffer",
5599
+ "group": "store",
5600
+ "signature": "showOffer(skuId: string): Promise<ArcadeOutcome>",
5601
+ "documented": true,
5602
+ "summary": "Ask the arcade to show one SKU from [StoreApi.offers](api-reference-game.md#storeapi-offers) in its own\nsheet, over your paused and hidden game: its art, what the player gets, a\nsale if one is on, and, for a pass the player already holds, how long it\nhas left. The sheet's Buy opens the same checkout as\n[StoreApi.purchase](api-reference-game.md#storeapi-purchase). **Call it from the player's press handler.**\n\nUse it for an offer you would have to hardcode a pitch for: VIP and other\npasses, and bundles. Your button only needs to say what it opens. Draw a\nGem pack yourself and use `purchase` instead. Check\n[StoreApi.showOfferAvailable](api-reference-game.md#storeapi-showofferavailable) first, and that the SKU is in `offers`.\n\nIt answers exactly as `purchase` does, with one addition: `declined` /\n`cancelled` also covers the player leaving the sheet without pressing Buy,\nand you are not told which of the two they closed. That starts the same\nback-off, so do not ask again unprompted. `unavailable` / `unsupported`\nmeans this Arcade does not draw the sheet, and nothing was sent.\n`unavailable` / `error` means **the outcome is unknown**: re-read balances\nand inventory through `gem.me`, and keep listening with\n[StoreApi.onSettled](api-reference-game.md#storeapi-onsettled).\n\nNO TIMEOUT: it waits for as long as the arcade is connected, because a\nplayer may read a sheet and pay for as long as they like.",
5603
+ "deprecated": null,
5604
+ "params": [
5605
+ {
5606
+ "name": "skuId",
5607
+ "type": "string",
5608
+ "optional": false,
5609
+ "summary": null
5610
+ }
5611
+ ],
5612
+ "returns": "Always resolves; never rejects.",
5613
+ "throws": null,
5614
+ "example": null,
5615
+ "defaultValue": null,
5616
+ "guides": []
5617
+ },
5579
5618
  {
5580
5619
  "id": "StoreApi.onSettled",
5581
5620
  "name": "onSettled",
@@ -8265,6 +8304,54 @@
8265
8304
  "defaultValue": null,
8266
8305
  "guides": []
8267
8306
  },
8307
+ {
8308
+ "id": "MenuStyle",
8309
+ "name": "MenuStyle",
8310
+ "anchor": "menustyle",
8311
+ "group": "frame",
8312
+ "signature": "interface MenuStyle",
8313
+ "documented": true,
8314
+ "summary": "Two colours for the arcade's own screens over your game, for\n`gem.setMenuStyle`: each exactly `#rrggbb`. The arcade works out the rest.",
8315
+ "deprecated": null,
8316
+ "params": [],
8317
+ "returns": null,
8318
+ "throws": null,
8319
+ "example": null,
8320
+ "defaultValue": null,
8321
+ "guides": []
8322
+ },
8323
+ {
8324
+ "id": "MenuStyle.ground",
8325
+ "name": "ground",
8326
+ "anchor": "menustyle-ground",
8327
+ "group": "frame",
8328
+ "signature": "readonly ground: string",
8329
+ "documented": true,
8330
+ "summary": "The colour the arcade's surfaces are built on, such as `'#1b2a4a'`.",
8331
+ "deprecated": null,
8332
+ "params": [],
8333
+ "returns": null,
8334
+ "throws": null,
8335
+ "example": null,
8336
+ "defaultValue": null,
8337
+ "guides": []
8338
+ },
8339
+ {
8340
+ "id": "MenuStyle.accent",
8341
+ "name": "accent",
8342
+ "anchor": "menustyle-accent",
8343
+ "group": "frame",
8344
+ "signature": "readonly accent: string",
8345
+ "documented": true,
8346
+ "summary": "The one highlight, at least 3:1 against `ground`, such as `'#ffb300'`.",
8347
+ "deprecated": null,
8348
+ "params": [],
8349
+ "returns": null,
8350
+ "throws": null,
8351
+ "example": null,
8352
+ "defaultValue": null,
8353
+ "guides": []
8354
+ },
8268
8355
  {
8269
8356
  "id": "FrameState",
8270
8357
  "name": "FrameState",
@@ -8645,6 +8732,29 @@
8645
8732
  "defaultValue": null,
8646
8733
  "guides": []
8647
8734
  },
8735
+ {
8736
+ "id": "GemFrame.setMenuStyle",
8737
+ "name": "setMenuStyle",
8738
+ "anchor": "gemframe-setmenustyle",
8739
+ "group": "frame",
8740
+ "signature": "setMenuStyle(style: MenuStyle | null): boolean",
8741
+ "documented": true,
8742
+ "summary": "Give the arcade's own screens over your game two colours to wear, or\n`null` to hand them back to the player's style: the whole answer each\ntime, the latest one winning.\n\nPrefer `gem.setMenuStyle`, which checks the colours and holds them across\na reconnect. This one sends only while connected, and only to an arcade\nthat can wear them.",
8743
+ "deprecated": null,
8744
+ "params": [
8745
+ {
8746
+ "name": "style",
8747
+ "type": "MenuStyle | null",
8748
+ "optional": false,
8749
+ "summary": null
8750
+ }
8751
+ ],
8752
+ "returns": "whether the message left the frame: `false` before the bridge is\n up, for an arcade that cannot wear them, and past this frame's own limit.",
8753
+ "throws": null,
8754
+ "example": null,
8755
+ "defaultValue": null,
8756
+ "guides": []
8757
+ },
8648
8758
  {
8649
8759
  "id": "GemFrame.requestShare",
8650
8760
  "name": "requestShare",
@@ -12790,7 +12900,7 @@
12790
12900
  "group": "start",
12791
12901
  "signature": "readonly store: StoreApi",
12792
12902
  "documented": true,
12793
- "summary": "**Real money.** The Arcade Store's own Gem packs, Boosts, passes and\nbundles, sold for the player's money through the arcade's own purchase\nsheet. To spend Gems or Coins on your game's own items, use\n[Gem.purchases](api-reference-game.md#gem-purchases).\n\nList what the store sells here with `offers()`, show each `priceDisplay`\nexactly as given, and call `purchase(skuId)` from the player's press. After\n`ok`, re-read balances through [Gem.me](api-reference-game.md#gem-me). Nothing rejects.",
12903
+ "summary": "**Real money.** The Arcade Store's own Gem packs, Boosts, passes and\nbundles, sold for the player's money through the arcade's own purchase\nsheet. To spend Gems or Coins on your game's own items, use\n[Gem.purchases](api-reference-game.md#gem-purchases).\n\nList what the store sells here with `offers()`, show each `priceDisplay`\nexactly as given, and call `purchase(skuId)` from the player's press. For a\npass, a bundle or anything else whose pitch you would have to hardcode, call\n`showOffer(skuId)` instead, and the arcade draws the offer. After `ok`,\nre-read balances through [Gem.me](api-reference-game.md#gem-me). Nothing rejects.",
12794
12904
  "deprecated": null,
12795
12905
  "params": [],
12796
12906
  "returns": null,
@@ -13325,6 +13435,29 @@
13325
13435
  "defaultValue": null,
13326
13436
  "guides": []
13327
13437
  },
13438
+ {
13439
+ "id": "Gem.setMenuStyle",
13440
+ "name": "setMenuStyle",
13441
+ "anchor": "gem-setmenustyle",
13442
+ "group": "start",
13443
+ "signature": "setMenuStyle(style: MenuStyle | null): boolean",
13444
+ "documented": true,
13445
+ "summary": "Give the arcade's own screens over your game your colours, so they match\nit: on the Gem Arcade, the burger button, the Gem menu and its pages, and\nevery Arcade pop-up over the running game.\n\n```js\ngem.setMenuStyle({ ground: '#1b2a4a', accent: '#ffb300' });\ngem.setMenuStyle(null); // back to the player's own style\n```\n\n**Colours only.** `ground` is what the arcade's surfaces are built on and\n`accent` its one highlight; the arcade works out the text, rims, button\nshapes and fonts, so its screens stay readable on any ground.\n\n**The Arcade refuses colours it cannot use.** Each must be exactly\n`#rrggbb`, and `accent` must reach 3:1 contrast against `ground`. Anything\nelse is ignored, with a warning on the console giving the measured ratio:\nthe last colours stand, nothing is sent, and this returns `false`.\n\n**Your colours win over the player's chosen style while your game runs**,\nand the player's style comes back when it ends. It is screen state, not a\ntoggle: each call is the whole answer, so you may change it between levels.\nCall it at boot, so your colours arrive with the first screen.",
13446
+ "deprecated": null,
13447
+ "params": [
13448
+ {
13449
+ "name": "style",
13450
+ "type": "MenuStyle | null",
13451
+ "optional": false,
13452
+ "summary": "The two colours, or `null` to hand back to the player's style."
13453
+ }
13454
+ ],
13455
+ "returns": "`true` when the arcade was told now; `false` before the bridge is\n up (the colours are held and sent when it connects), under an embedder\n that cannot wear them, and for colours it would refuse.",
13456
+ "throws": null,
13457
+ "example": null,
13458
+ "defaultValue": null,
13459
+ "guides": []
13460
+ },
13328
13461
  {
13329
13462
  "id": "Gem.reportFrame",
13330
13463
  "name": "reportFrame",
@@ -18,6 +18,12 @@ async function onBuyPressed(offer) {
18
18
  // Read through gem.me; nothing here carries an amount. `error` is "outcome unknown" (§9).
19
19
  if (outcome === 'ok' || reason === 'error') await refreshBalances();
20
20
  }
21
+
22
+ // From the press on your own "Get VIP" button: the Arcade draws the offer.
23
+ async function onVipPressed() {
24
+ const { outcome, reason } = await gem.store.showOffer(VIP_SKU);
25
+ if (outcome === 'ok' || reason === 'error') await refreshBalances();
26
+ }
21
27
  ```
22
28
 
23
29
  **Your game asks; the Arcade decides.** Nothing your game sends carries a URL, a price or a card
@@ -33,7 +39,8 @@ itself. You get an outcome.
33
39
  - [7. Similar games, as cards](#7-similar-games-as-cards)
34
40
  - [8. The Arcade Store's offers](#8-the-arcade-stores-offers)
35
41
  - [9. Buying from the Arcade Store](#9-buying-from-the-arcade-store)
36
- - [10. Outcomes and reasons](#10-outcomes-and-reasons)
42
+ - [10. Letting the Arcade show one offer](#10-letting-the-arcade-show-one-offer)
43
+ - [11. Outcomes and reasons](#11-outcomes-and-reasons)
37
44
 
38
45
  ## 1. Two surfaces, and which one is real money
39
46
 
@@ -41,7 +48,7 @@ itself. You get an outcome.
41
48
  | ------------ | ------------------------------------------------ | --------------------------------------------------- |
42
49
  | What it does | app-store listings, another game, similar games | the Arcade Store's own SKUs |
43
50
  | Money | none | **real money**, through the Arcade Store's checkout |
44
- | Who draws it | the Arcade (a new tab, a QR code, the next game) | the checkout, over your paused and hidden game |
51
+ | Who draws it | the Arcade (a new tab, a QR code, the next game) | your card or the Arcade's sheet, then the checkout |
45
52
 
46
53
  `gem.store` is not `gem.purchases`. **`gem.purchases` spends Gems or Coins on your game's own
47
54
  items** ([`inventory-and-catalogs.md`](inventory-and-catalogs.md)). **`gem.store` sells the Arcade
@@ -60,7 +67,7 @@ three and never grows:
60
67
 
61
68
  `reason` is a short code that says why. The set is open: a new Arcade build may send a reason your
62
69
  game has never seen, so branch on `outcome` and treat `reason` as a hint. The codes in use today are
63
- in [§10](#10-outcomes-and-reasons).
70
+ in [§11](#11-outcomes-and-reasons).
64
71
 
65
72
  ## 3. What is available here
66
73
 
@@ -71,17 +78,19 @@ An Arcade that does not offer a feature answers every call for it at once with `
71
78
  if (gem.arcade.available.openGame) showMoreGamesSection();
72
79
  if (gem.arcade.available.appLinks) showGetTheAppButton();
73
80
  if (gem.store.available) showGetGemsButton();
81
+ if (gem.store.showOfferAvailable) showGetVipButton();
74
82
  ```
75
83
 
76
- `gem.arcade.available` is `{ appLinks, openGame }`, and `gem.store.available` is one boolean. All of
77
- them are synchronous and `false` until the bridge is up. A feature can be off for reasons you
84
+ `gem.arcade.available` is `{ appLinks, openGame }`, and `gem.store.available` and
85
+ `gem.store.showOfferAvailable` are one boolean each. All of them are synchronous and `false` until
86
+ the bridge is up. A feature can be off for reasons you
78
87
  cannot see from inside the frame: a TV, an in-app browser that cannot take payment, an app build
79
88
  where it is withheld. Do not guess; read it.
80
89
 
81
90
  ## 4. Call the acting ones from a press
82
91
 
83
- Three calls put something in front of the player: `gem.arcade.openApp`, `gem.arcade.openGame` and
84
- `gem.store.purchase`. **Call each one from the player's own press handler**, in the same turn as the
92
+ Four calls put something in front of the player: `gem.arcade.openApp`, `gem.arcade.openGame`,
93
+ `gem.store.purchase` and `gem.store.showOffer`. **Call each one from the player's own press handler**, in the same turn as the
85
94
  press. The Arcade checks that a person pressed, and refuses an ask nobody pressed for with
86
95
  `declined` / `busy`, showing nothing.
87
96
 
@@ -273,7 +282,57 @@ arrives, so your own celebration is visible.
273
282
  if (outcome === 'unavailable' && reason === 'error') await refreshBalances(); // not "nothing bought"
274
283
  ```
275
284
 
276
- ## 10. Outcomes and reasons
285
+ ## 10. Letting the Arcade show one offer
286
+
287
+ ```js
288
+ // Decide on the screen you draw the button on.
289
+ const { offers } = await gem.store.offers();
290
+ const vip = offers.find((offer) => offer.skuId === VIP_SKU);
291
+ if (gem.store.showOfferAvailable && vip) showGetVipButton();
292
+ else hideGetVipButton();
293
+
294
+ // From the press on that button.
295
+ const { outcome, reason } = await gem.store.showOffer(VIP_SKU);
296
+ if (outcome === 'ok' || reason === 'error') await refreshBalances();
297
+ ```
298
+
299
+ `showOffer(skuId)` asks the Arcade to draw one SKU from `offers()` in its own sheet, over your paused
300
+ and hidden game. The Arcade draws the art, what the player gets, a sale if one is running, and, for a
301
+ pass the player already holds, how long it has left. The sheet's Buy opens the same checkout
302
+ `purchase` opens.
303
+
304
+ Pick by who can say what the offer is:
305
+
306
+ | | **You draw it: `offers()` + `purchase()`** | **The Arcade draws it: `showOffer()`** |
307
+ | --------------- | --------------------------------------------------------- | ------------------------------------------------------------ |
308
+ | Use it for | Gem packs: a name, an amount and a price say it all | VIP and other passes, and bundles |
309
+ | Your button | says what it buys and shows `priceDisplay` | says what it opens, such as "Get VIP" |
310
+ | The press opens | the checkout, straight away | the Arcade's sheet, then the checkout if the player presses Buy |
311
+ | Perks and sales | not available to you | always current, in the player's language |
312
+ | A holder sees | the same Buy button | what they already have |
313
+
314
+ - **Never write an offer's perks into your game.** They change without a new build of your game, and
315
+ yours would go stale. Your button only needs the offer's name.
316
+ - **Do not show a price on the button.** The sheet shows the right one for this player, which a
317
+ sale or a pass can change, and `priceDisplay` does not say either of those. If you want a price on
318
+ screen, draw the offer yourself and use `purchase`.
319
+ - **You cannot tell whether the player holds a pass.** The Arcade keeps that to itself, and the
320
+ sheet tells the player. So the button stays up for a holder, and that is fine: the sheet is what
321
+ they see, and they choose from there.
322
+ - **Hide the button when the SKU is not in `offers()`.** A SKU that is not on sale here answers
323
+ `unavailable` / `not_found`, and the player would have pressed a button that does nothing.
324
+ - **Read `showOfferAvailable`, not `available`.** An older Arcade can sell through `purchase` and
325
+ still not draw this sheet. It answers `unavailable` / `unsupported` and sends nothing. Hide the
326
+ button there.
327
+
328
+ Every rule of `purchase` holds ([§9](#9-buying-from-the-arcade-store)), and the answers are the
329
+ same, with one addition. `declined` / `cancelled` also covers the player leaving the sheet without
330
+ pressing Buy, and you are not told which of the two they closed. That starts the same back-off as a
331
+ closed checkout, shared with `purchase`: the next ask of either kind is `declined` / `busy` for a
332
+ while. The press shows nothing during a back-off, so give the player something else to do on that
333
+ screen.
334
+
335
+ ## 11. Outcomes and reasons
277
336
 
278
337
  | Call | `ok` | `declined` | `unavailable` |
279
338
  | ---------------- | ------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
@@ -283,6 +342,7 @@ arrives, so your own celebration is visible.
283
342
  | `similarGames` | cards, possibly none | | `unsupported`, `error` |
284
343
  | `store.offers` | offers, possibly none | | `sign_in_required`, `store_off`, `unsupported`, `error` |
285
344
  | `store.purchase` | paid, or `pending` | `cancelled`, `busy` | `sign_in_required`, `store_off`, `not_found`, the provider's reason, `unsupported`, `error` (outcome unknown) |
345
+ | `store.showOffer` | paid, or `pending` | `cancelled`, `busy` | `sign_in_required`, `store_off`, `not_found`, the provider's reason, `unsupported`, `error` (outcome unknown) |
286
346
 
287
347
  Every reason is a short code of lower-case letters and underscores. The Arcade turns a payment
288
348
  provider's own code into that shape before it reaches you.
@@ -296,5 +356,5 @@ Four reasons come from the SDK itself rather than the Arcade:
296
356
  - `error`: no reliable answer arrived. The ask could not be sent, a read or an open waited out its
297
357
  timeout (30 seconds for a read, two minutes for `openApp` and `openGame`), the bridge closed
298
358
  before the answer came, or the Arcade said it had no reliable answer. For a read, try again
299
- later. For `store.purchase` it means **the outcome is unknown**, never "nothing was bought"
300
- ([§9](#9-buying-from-the-arcade-store)).
359
+ later. For `store.purchase` and `store.showOffer` it means **the outcome is unknown**, never
360
+ "nothing was bought" ([§9](#9-buying-from-the-arcade-store)).
@@ -204,9 +204,9 @@ player got.
204
204
  Nothing sells Coins or achievement points, alone or in a bundle. The arcade's own store sells
205
205
  Gems, and the Gems a player bought there spend in your game exactly as the ones they earned do: your
206
206
  game cannot tell the two apart and prices nothing differently for either. Your game never sells
207
- either itself. It may put the arcade store's own Gem packs and Boosts in front of a player from a
208
- button of its own, through `gem.store`, and the arcade prices them, opens its own checkout and
209
- takes the money ([arcade-and-store.md](arcade-and-store.md)). The real-money product that changes
207
+ either itself. It may put the arcade store's own Gem packs, Boosts and passes in front of a player
208
+ from a button of its own, through `gem.store`, and the arcade prices them, opens its own checkout
209
+ and takes the money ([arcade-and-store.md](arcade-and-store.md)). The real-money product that changes
210
210
  what your game pays is a **Boost**, which a player always buys in the arcade's own checkout:
211
211
 
212
212
  - **It multiplies what Gems and Coins grants pay, and the ceilings with them.** A player on a 2×
@@ -246,8 +246,9 @@ animates every payout into the right total. Gems and Coins follow the same rules
246
246
  - **No top-up or payment screen of your own.** Never draw a "buy Coins" offer, a checkout, a
247
247
  wallet top-up, or anything shaped like the arcade's purchase sheet: a game screen that looks like
248
248
  platform payment chrome is an imitation of it, and players cannot tell the two apart. Your shop's
249
- own Buy button is yours, and so is a "Get Gems" button that calls `gem.store.purchase` from the
250
- player's press: the checkout it opens is the arcade store's own
249
+ own Buy button is yours, and so is a "Get Gems" button that calls `gem.store.purchase`, or a "Get
250
+ VIP" button that calls `gem.store.showOffer`, from the player's press: the checkout each one
251
+ leads to is the arcade store's own
251
252
  ([arcade-and-store.md](arcade-and-store.md)). When a purchase is refused for
252
253
  `insufficient_funds`, say so in your shop's own words ("Not enough Coins") and stop there: a
253
254
  refusal never opens the store by itself.
package/docs/changelog.md CHANGED
@@ -16,6 +16,46 @@ Its entries are grouped under up to five headings:
16
16
  - **Rebuild**: a type changed. Recompile against the new version; code that compiles today keeps compiling unless the entry says otherwise.
17
17
  - **New capability**: something a game can now do. Nothing changes unless you use it.
18
18
 
19
+ ## 18.0.0
20
+
21
+ Upgrading from 17.0.0. Impact: required.
22
+
23
+ ### Rebuild
24
+
25
+ - The set of capability names a game can query has a new member, menuStyle. Recompiling is enough, but if your code handles every capability name exhaustively, add a case for the new one.
26
+ - The frame message type has a new member for menu style, and the list of messages a game frame sends to the Arcade now includes gem.menuStyle. Recompiling is enough, but code that handles every message exhaustively needs a case for the new message.
27
+
28
+ ### New capability
29
+
30
+ - A game can now hand the Arcade its own colours for the Arcade menu by calling Gem.setMenuStyle with a MenuStyle value. Check for the menuStyle capability first, since an older Arcade will not act on it. The same call is also available on the frame object.
31
+
32
+ ## 17.0.0
33
+
34
+ *Written from the API facts alone; no summary was generated for this release.*
35
+
36
+ Upgrading from 16.3.1. Impact: required.
37
+
38
+ ### Must change
39
+
40
+ - PARENT_CONFORMANCE_VERSION changed its signature.
41
+ - FRAME_TO_PARENT changed its signature.
42
+ - FrameMessage changed its signature.
43
+ - GemCapability changed its signature.
44
+ - PARENT_TO_FRAME changed its signature.
45
+ - ParentMessage changed its signature.
46
+
47
+ ### New capability
48
+
49
+ - StoreApi.showOffer was added.
50
+ - StoreApi.showOfferAvailable was added.
51
+ - GemHost.answerStoreOfferSheet was added.
52
+ - GemRequestStoreOfferSheet was added.
53
+ - GemStoreOfferSheetResult was added.
54
+ - HostCallbacks.onRequestStoreOfferSheet was added.
55
+ - HostOptions.onRequestStoreOfferSheet was added.
56
+ - validateGemRequestStoreOfferSheet was added.
57
+ - validateGemStoreOfferSheetResult was added.
58
+
19
59
  ## 16.3.1
20
60
 
21
61
  Upgrading from 16.3.0. Impact: none.
@@ -50,7 +50,7 @@ either way.
50
50
  ```jsonc
51
51
  // package.json
52
52
  "dependencies": {
53
- "@indiegems/gem-web-sdk": "16.3.1"
53
+ "@indiegems/gem-web-sdk": "18.0.0"
54
54
  }
55
55
  ```
56
56
 
@@ -827,8 +827,9 @@ inventory, never on a reason, and do not retry because of one.
827
827
  ⚠️ **Never draw a top-up or payment screen of your own** — no checkout, no wallet top-up, nothing
828
828
  that looks like the arcade's purchase sheet or wallet. A game screen shaped like platform payment
829
829
  chrome is an imitation of it. Your Buy button is yours, and taking a player's money is the
830
- arcade's alone: a "Get Gems" button of yours calls `gem.store.purchase` from the player's press,
831
- and the arcade store's own checkout does the rest ([arcade-and-store.md](arcade-and-store.md)). An
830
+ arcade's alone: a "Get Gems" button of yours calls `gem.store.purchase` from the player's press, a
831
+ "Get VIP" button calls `gem.store.showOffer`, and the arcade store's own checkout does the rest
832
+ ([arcade-and-store.md](arcade-and-store.md)). An
832
833
  `insufficient_funds` refusal never opens it by itself.
833
834
 
834
835
  ### What Coins and Gems may buy: the spend scope