@indiegems/gem-web-sdk 20.1.0 → 21.1.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -0
  3. package/brand/ticket-hero-128.png +0 -0
  4. package/brand/ticket-hero-256.png +0 -0
  5. package/brand/ticket-hero-384.png +0 -0
  6. package/brand/ticket-hero-512.png +0 -0
  7. package/brand/ticket-hero-768.png +0 -0
  8. package/brand/ticket-icon-20-1.5x.png +0 -0
  9. package/brand/ticket-icon-20-1x.png +0 -0
  10. package/brand/ticket-icon-20-2x.png +0 -0
  11. package/brand/ticket-icon-20-3x.png +0 -0
  12. package/brand/ticket-icon-32-1.5x.png +0 -0
  13. package/brand/ticket-icon-32-1x.png +0 -0
  14. package/brand/ticket-icon-32-2x.png +0 -0
  15. package/brand/ticket-icon-32-3x.png +0 -0
  16. package/brand/ticket-icon-ingame-20-1.5x.png +0 -0
  17. package/brand/ticket-icon-ingame-20-1x.png +0 -0
  18. package/brand/ticket-icon-ingame-20-2x.png +0 -0
  19. package/brand/ticket-icon-ingame-20-3x.png +0 -0
  20. package/brand/ticket-icon-ingame-32-1.5x.png +0 -0
  21. package/brand/ticket-icon-ingame-32-1x.png +0 -0
  22. package/brand/ticket-icon-ingame-32-2x.png +0 -0
  23. package/brand/ticket-icon-ingame-32-3x.png +0 -0
  24. package/brand/ticket-pile-1-128.png +0 -0
  25. package/brand/ticket-pile-1-256.png +0 -0
  26. package/brand/ticket-pile-1-384.png +0 -0
  27. package/brand/ticket-pile-1-512.png +0 -0
  28. package/brand/ticket-pile-1-768.png +0 -0
  29. package/brand/ticket-pile-2-128.png +0 -0
  30. package/brand/ticket-pile-2-256.png +0 -0
  31. package/brand/ticket-pile-2-384.png +0 -0
  32. package/brand/ticket-pile-2-512.png +0 -0
  33. package/brand/ticket-pile-2-768.png +0 -0
  34. package/dist/conformance.d.ts +1 -1
  35. package/dist/dev-tools.d.ts +1 -0
  36. package/dist/function.d.ts +11 -3
  37. package/dist/game.d.ts +79 -12
  38. package/dist/gem-web-sdk.bridge.js +2 -2
  39. package/dist/gem-web-sdk.conformance.js +3 -3
  40. package/dist/gem-web-sdk.dev-tools.js +2 -0
  41. package/dist/gem-web-sdk.esm.js +3 -3
  42. package/dist/gem-web-sdk.function.js +3 -3
  43. package/dist/gem-web-sdk.game.js +3 -3
  44. package/dist/gem-web-sdk.min.js +3 -3
  45. package/dist/gem-web-sdk.sdk.js +3 -3
  46. package/dist/gem-web-sdk.server.js +2 -2
  47. package/dist/index.d.ts +103 -48
  48. package/dist/sdk.d.ts +79 -12
  49. package/docs/analytics.md +12 -0
  50. package/docs/api/api-reference-function.md +14 -2
  51. package/docs/api/api-reference-game.md +357 -21
  52. package/docs/api/api-reference-server.md +1 -1
  53. package/docs/api/reference.json +413 -16
  54. package/docs/arcade-economy.md +2 -2
  55. package/docs/brand-art.md +65 -16
  56. package/docs/changelog.md +40 -0
  57. package/docs/game-economy-tutorial.md +6 -3
  58. package/docs/getting-started.md +122 -30
  59. package/docs/graphics-route.md +188 -0
  60. package/docs/inventory-and-catalogs.md +69 -27
  61. package/docs/manifest.json +23 -18
  62. package/docs/server-bundles.md +3 -3
  63. package/docs/what-a-game-must-do.md +33 -2
  64. package/package.json +5 -1
  65. package/schema/bridge.v1.json +263 -12
  66. package/schema/capabilities.public.json +26 -0
@@ -654,18 +654,25 @@ subject. [brand-art.md](brand-art.md) has both, how to draw them, and the rest o
654
654
 
655
655
  ### Being paid in one
656
656
 
657
- Every game can choose between two purchase owners for the same published platform price:
658
-
659
- | Call | Who owns confirmation | Transport | Result |
660
- | -------------------------------- | --------------------- | ------------------------------------------------------------- | ---------------------------------------- |
661
- | `gem.purchases.request(...)` | the platform | postMessage bridge; platform sheet always opens | `ok`, `declined`, or `unavailable` |
662
- | `gem.me.mutations.purchase(...)` | your game | direct authenticated SDK request; no bridge or platform sheet | an order-style result or a `GemApiError` |
663
-
664
- There is no approval step. Both paths are available to every game.
657
+ **Spend platform currency with `gem.purchases.request`.** Every game sells for Gems, Coins or any
658
+ other platform currency through the arcade's buy sheet: the platform confirms the purchase with the
659
+ player, offers a guest an account, and processes the order. Build your shop's Buy button on it.
660
+
661
+ | Call | Who owns confirmation | Transport | Use it for |
662
+ | -------------------------------- | --------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
663
+ | `gem.purchases.request(...)` | the platform | postMessage bridge; platform sheet always opens | **every platform-currency purchase** — answers `ok`, `declined` or `unavailable` |
664
+ | `gem.me.mutations.purchase(...)` | your game | direct authenticated SDK request; no bridge or platform sheet | recipes paid in your own game currency or items (`payment: { kind: 'configured' }`) |
665
+
666
+ `gem.me.mutations.purchase` with a `platform_currency` payment is **not for spending on
667
+ gemarcade.com**. A web guest has no account the platform lets spend, so gem-api refuses them
668
+ `guest_upgrade_required`; the SDK then hands the attempt to the arcade as a safety net (see
669
+ [Game-owned direct purchase](#game-owned-direct-purchase)), but that is a fallback for games that
670
+ have not moved yet, not a flow to design for. A game that spends directly today should move its Buy
671
+ button to `gem.purchases.request`.
665
672
 
666
673
  #### Platform-owned purchase sheet
667
674
 
668
- Use the bridge path when you want the arcade to confirm and process the purchase:
675
+ This is how a game spends platform currency. The arcade confirms and processes the purchase:
669
676
 
670
677
  ```js
671
678
  const config = await gem.gameConfig.load();
@@ -705,8 +712,8 @@ button's handler, never from a timer or on load: without one the answer is `decl
705
712
  `busy`, and nothing is shown. After a player cancels, the arcade answers further asks from your
706
713
  game `busy` for a short, growing interval — do not re-ask on `cancelled`.
707
714
 
708
- Hide this specific affordance when `gem.frame.parentSupports('purchases')` is false. That check does
709
- not apply to the direct SDK path.
715
+ Hide your Buy button when `gem.frame.parentSupports('purchases')` is false: that parent cannot sell
716
+ for platform currency.
710
717
 
711
718
  The sheet shows the item's or recipe's picture from its `icon_url`. You never write that field: it
712
719
  is the `icon:` file you upload with your config (`icon: icons/extra-life.png` in the YAML, uploaded
@@ -731,7 +738,12 @@ backend it answers `undefined` by design: there, your placeholder is what you se
731
738
 
732
739
  #### Game-owned direct purchase
733
740
 
734
- Use the direct path when your game provides its own purchase UI and does not want the platform sheet:
741
+ The direct path submits an order from your game with no platform sheet. Its job is recipes paid in
742
+ your own game currency or items (`payment: { kind: 'configured' }`, below). It also accepts a
743
+ `platform_currency` payment, but **do not spend platform currency this way on gemarcade.com**: a
744
+ web guest is refused (below), and every game spends Gems and Coins through
745
+ [the sheet](#platform-owned-purchase-sheet). The form is documented for games that have not moved
746
+ yet:
735
747
 
736
748
  ```js
737
749
  const result = await gem.me.mutations.purchase({
@@ -809,6 +821,35 @@ try {
809
821
  Do not retry an unsafe write after a timeout; re-read inventory or use the returned order/purchase
810
822
  ids for support.
811
823
 
824
+ **A guest is refused `guest_upgrade_required`, and you draw nothing.** The platform lets only a
825
+ player with an account spend Gems or Coins — which is why platform currency is spent through
826
+ [the sheet](#platform-owned-purchase-sheet), where the arcade handles a guest itself. This is the
827
+ safety net for a game that still spends directly. When a guest's direct purchase is refused, the SDK
828
+ hands the attempt to the arcade, which offers the player an account over your game, and your call
829
+ rejects at once with a `GemApiError` whose `code` is `guest_upgrade_required`. Treat it exactly
830
+ like a decline: return to the shop silently, with no sign-up, "log in to buy" or error screen of
831
+ your own — the arcade's is already showing. The `default` arm of the switch above must not draw
832
+ anything for it either, so give it a quiet case of its own:
833
+
834
+ ```js
835
+ case 'guest_upgrade_required':
836
+ return; // the arcade is offering the player an account; draw nothing
837
+ ```
838
+
839
+ If the player makes an account and buys the item from the arcade's sheet, the purchase completes
840
+ after your call has answered — sometimes after a reload. Hear it with `gem.purchases.onSettled`:
841
+
842
+ ```js
843
+ gem.purchases.onSettled(({ kind, targetId, quantity }) => {
844
+ showGrantFeedback(targetId, quantity); // then re-read; the SDK already refreshed gem.me
845
+ });
846
+ ```
847
+
848
+ The SDK confirms the purchase with the platform before it calls you, and refreshes `gem.me`, so
849
+ `gem.me.inventory.onChanged` fires too. It hears nothing on an arcade that does not support it, and
850
+ never for a purchase an `ok` already reported. This needs SDK 21.1.0 or later; an older build shows
851
+ the guest your `default` arm instead of the arcade's offer.
852
+
812
853
  **`targetId` is the UUID, never the slug.** `config.item(...)` accepts either because it is your
813
854
  local lookup. The purchase catalog is keyed by ids. Recipes also require `catalogId`.
814
855
 
@@ -817,21 +858,22 @@ local lookup. The purchase catalog is keyed by ids. Recipes also require `catalo
817
858
  One vocabulary covers both paths: the sheet's `res.reason` and the direct path's `err.code` are
818
859
  the same strings.
819
860
 
820
- | Code | What happened | What to do |
821
- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
822
- | `unknown_currency` | the slug is not an active platform currency | write `ARCADE_TICKETS` or `GEMS`; the direct path's message lists what is active |
823
- | `currency_not_in_game_payable` | the currency exists but cannot pay for game items | price in a currency `gemctl platform config` lists under `platform_prices` |
824
- | `not_purchasable` | the target is not in the purchase catalog on this channel, or cannot be bought | send the id (not the slug); promote the config version that prices it |
825
- | `no_price_in_currency` | the target has no price in that currency | ask in the currency it is priced in, or add the price |
826
- | `busy` | a purchase is already in flight, or a sheet is open | ask again only from a fresh player action |
827
- | `cancelled` | the player dismissed the sheet | nothing — return to the shop |
828
- | `insufficient_funds` | the player cannot afford it | say so in your shop's words; never draw a top-up screen |
829
- | `price_mismatch`, `config_version_stale` | the price or config changed since your catalog read; nothing was charged | re-read the catalog and re-render |
830
- | `platform_currency_unavailable` | the currency is not available to this purchase right now | show the store as unavailable |
831
- | `not_available` | the recipe is outside its availability window, or inactive; nothing was charged | hide or disable the row until its window opens |
832
- | `recipe_locked` | the recipe's prerequisites are not met; nothing was charged | show the lock from `recipeAvailability().lockReasons` |
833
- | `recipe_on_cooldown`, `recipe_max_uses_exceeded`, `item_max_stack_exceeded`, `recipe_costs_unmet`, `quantity_invalid` | the recipe or item refused this purchase before any charge | reflect it on the row; `recipeAvailability` has the detail |
834
- | `error` | anything else, or no reliable answer | one neutral "store unavailable"; after `unavailable`, re-read inventory |
861
+ | Code | What happened | What to do |
862
+ | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
863
+ | `unknown_currency` | the slug is not an active platform currency | write `ARCADE_TICKETS` or `GEMS`; the direct path's message lists what is active |
864
+ | `currency_not_in_game_payable` | the currency exists but cannot pay for game items | price in a currency `gemctl platform config` lists under `platform_prices` |
865
+ | `not_purchasable` | the target is not in the purchase catalog on this channel, or cannot be bought | send the id (not the slug); promote the config version that prices it |
866
+ | `no_price_in_currency` | the target has no price in that currency | ask in the currency it is priced in, or add the price |
867
+ | `busy` | a purchase is in flight, a sheet is open, or the arcade is offering a guest an account | ask again only from a fresh player action |
868
+ | `cancelled` | the player dismissed the sheet | nothing — return to the shop |
869
+ | `insufficient_funds` | the player cannot afford it | say so in your shop's words; never draw a top-up screen |
870
+ | `guest_upgrade_required` | the player is a guest; only an account may spend. Nothing was charged | nothing — the arcade offers them an account; draw no sign-up screen of your own |
871
+ | `price_mismatch`, `config_version_stale` | the price or config changed since your catalog read; nothing was charged | re-read the catalog and re-render |
872
+ | `platform_currency_unavailable` | the currency is not available to this purchase right now | show the store as unavailable |
873
+ | `not_available` | the recipe is outside its availability window, or inactive; nothing was charged | hide or disable the row until its window opens |
874
+ | `recipe_locked` | the recipe's prerequisites are not met; nothing was charged | show the lock from `recipeAvailability().lockReasons` |
875
+ | `recipe_on_cooldown`, `recipe_max_uses_exceeded`, `item_max_stack_exceeded`, `recipe_costs_unmet`, `quantity_invalid` | the recipe or item refused this purchase before any charge | reflect it on the row; `recipeAvailability` has the detail |
876
+ | `error` | anything else, or no reliable answer | one neutral "store unavailable"; after `unavailable`, re-read inventory |
835
877
 
836
878
  Reasons are for your shop's state and your logs. Branch money on `outcome` and on a re-read of
837
879
  inventory, never on a reason, and do not retry because of one.
@@ -13,8 +13,8 @@
13
13
  },
14
14
  {
15
15
  "file": "analytics.md",
16
- "sha256": "42f3d0d3360e1a6066c21015bd4925475b4336a53611bf04acb773a7c6084e62",
17
- "updated": "2026-10-30T16:54:41Z"
16
+ "sha256": "5814655859e3d6b223f94b71f1b064783b0f242d409c3ec3078089cb4b7c8ec5",
17
+ "updated": "2026-11-04T17:17:14Z"
18
18
  },
19
19
  {
20
20
  "file": "arcade-and-store.md",
@@ -28,8 +28,8 @@
28
28
  },
29
29
  {
30
30
  "file": "arcade-economy.md",
31
- "sha256": "b72474b4694842a1fda030ab9df79987d2906f7a8c922e8630622d747a4b2c3c",
32
- "updated": "2026-10-30T13:36:37Z"
31
+ "sha256": "c0f25495a536d5facdbb63cbcd89acf558e2fe79d44f3a99d35284de1937fc42",
32
+ "updated": "2026-11-04T17:09:56Z"
33
33
  },
34
34
  {
35
35
  "file": "async-opponents.md",
@@ -38,8 +38,8 @@
38
38
  },
39
39
  {
40
40
  "file": "brand-art.md",
41
- "sha256": "b7f8dda1afdb8d56cadee752e6b09fc62238372b68ebc8a746518e528f52e167",
42
- "updated": "2026-10-29T21:40:17Z"
41
+ "sha256": "7d4d5c7226710742efdb958e10387d5e5a3de0622a686734c885720e16a5b128",
42
+ "updated": "2026-11-02T21:18:05Z"
43
43
  },
44
44
  {
45
45
  "file": "branding.md",
@@ -48,8 +48,8 @@
48
48
  },
49
49
  {
50
50
  "file": "changelog.md",
51
- "sha256": "8a292ee18fe7f910be0af049d96df66ccebf5c810fe5495c11d5352f7ee121f4",
52
- "updated": "2026-11-03T00:39:38Z"
51
+ "sha256": "31164cb6810097240ce18733c76b45b0b3e5ba9cda6738bfce7e27503cd90ad1",
52
+ "updated": "2026-11-04T19:04:00Z"
53
53
  },
54
54
  {
55
55
  "file": "daily-challenges.md",
@@ -73,18 +73,23 @@
73
73
  },
74
74
  {
75
75
  "file": "game-economy-tutorial.md",
76
- "sha256": "c037a74f206f094392f09e0f7b292271145a4d72b631b93f878705171e52475a",
77
- "updated": "2026-10-25T19:46:22Z"
76
+ "sha256": "0ab25a9d69cf5e40351624355da5ef84e1431bf8a8a420e2c4d7350e20a5b1c5",
77
+ "updated": "2026-11-04T16:44:33Z"
78
78
  },
79
79
  {
80
80
  "file": "getting-started.md",
81
- "sha256": "0c4cee83ffc77711f43ccdaafb394283a5a412b78395db1434ebbc2ab6943667",
82
- "updated": "2026-11-03T00:39:38Z"
81
+ "sha256": "4cb0f2fec9438cd03a2a785ad6247fe60b32d95c9a9fdcbf0e2cc4390c9f767a",
82
+ "updated": "2026-11-04T19:04:00Z"
83
+ },
84
+ {
85
+ "file": "graphics-route.md",
86
+ "sha256": "c71c826502b415a89dd069f79d22b82f7a9fe49c9da7f5e7a1052763c826f41f",
87
+ "updated": "2026-11-04T17:45:35Z"
83
88
  },
84
89
  {
85
90
  "file": "inventory-and-catalogs.md",
86
- "sha256": "dab6e42f4ad5cc2a63c2adb11e85053f963cd3166d1c447394cd6f532bb6c006",
87
- "updated": "2026-11-02T18:42:37Z"
91
+ "sha256": "2094c555eb5a1a3d817a597ac07b2b193077f92176eb9596c2ba3ed461363bb6",
92
+ "updated": "2026-11-04T16:43:14Z"
88
93
  },
89
94
  {
90
95
  "file": "leaderboards.md",
@@ -138,8 +143,8 @@
138
143
  },
139
144
  {
140
145
  "file": "server-bundles.md",
141
- "sha256": "f54aeb88c268e5b657be9d0ea323d44cc61bfbb76fc84670f8a607d4514b005d",
142
- "updated": "2026-11-03T00:39:38Z"
146
+ "sha256": "a0df89891432d799fe7ad59534c68265937fe5096739afa5fbe230de97837af3",
147
+ "updated": "2026-11-04T19:04:00Z"
143
148
  },
144
149
  {
145
150
  "file": "serverless-functions-js.md",
@@ -163,8 +168,8 @@
163
168
  },
164
169
  {
165
170
  "file": "what-a-game-must-do.md",
166
- "sha256": "059f3c8a31c0f6098d2c8e4836b79e866d0c5486443dc89039e160648946bd68",
167
- "updated": "2026-11-02T23:27:22Z"
171
+ "sha256": "523886cf799570ab3631bbc9db11e03554d2ad403a1083d94038a48acd4fb611",
172
+ "updated": "2026-11-04T18:23:48Z"
168
173
  }
169
174
  ]
170
175
  }
@@ -80,8 +80,8 @@ To check the download yourself: the package npm serves is, byte for byte, the fi
80
80
  also publishes on its download host, with the hash beside it:
81
81
 
82
82
  ```bash
83
- curl -fsSL -O https://downloads.gemarcade.com/sdk/v20.1.0/gem-web-sdk.tgz
84
- curl -fsSL https://downloads.gemarcade.com/sdk/v20.1.0/gem-web-sdk.tgz.sri
83
+ curl -fsSL -O https://downloads.gemarcade.com/sdk/v21.1.0/gem-web-sdk.tgz
84
+ curl -fsSL https://downloads.gemarcade.com/sdk/v21.1.0/gem-web-sdk.tgz.sri
85
85
  openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
86
86
  ```
87
87
 
@@ -550,7 +550,7 @@ document**, so the call shapes provably match the contract:
550
550
  | Loot | `getDropRates` |
551
551
  | Leaderboards | `getLeaderboard` (the whole board — seed a match from current ranks), `getEntityLeaderboard` (one entity's own runs, best first, with its personal best — the read for deciding a reward from what the player actually did). Not `getFriendLeaderboard`: that view is per viewer, so a credential with no player identity is refused |
552
552
  | Transfers | `listTransfers`, `initiateInventoryTransfer`, `getTransferDetails`, `acceptTransfer`, `cancelInventoryTransfer`, `rejectTransfer` |
553
- | Platform-funded purchases | `getPurchaseCatalog` — the catalog read only. A **player client** may use `gem.me.mutations.purchase` for a direct platform-currency order because its token fixes the player, game and channel. A dedicated server token names no player and cannot start one; have the player's client initiate it. |
553
+ | Platform-funded purchases | `getPurchaseCatalog` — the catalog read only. A player spends platform currency only through the arcade's purchase sheet, which their **client** opens with `gem.purchases.request` ([inventory-and-catalogs.md](inventory-and-catalogs.md#16-selling-for-a-platform-currency)). A dedicated server token names no player and cannot start one; have the player's client initiate it. |
554
554
  | Game rooms | `getGameRoomV2`, `updateGameRoomV2`, `getRoomArgs`, `putRoomArgs`, `listGameRooms`, `closeGameRoom`, `kickPlayer`, `transferHostV2` — the reads pre-bound to your own room as `gem.room.get()` / `gem.room.args()`. [Below](#what-your-server-reads-at-startup) |
555
555
  | Game config | `getChannelAssignment` — which config version your server's own channel is running, and a short-lived signed URL for that version's JSON. Pass `gem.identity.channel`: the read is fenced to your own game and channel |
556
556
  | Room templates | `listResolvedTemplates`, `getResolvedTemplate` |
@@ -1454,6 +1454,36 @@ function frame(dt) {
1454
1454
  kind draws that one and ignores the field.
1455
1455
  - **No camera, no call.** A game with no camera to turn never calls `look`.
1456
1456
 
1457
+ ## WebGL or WebGPU: ask before you make your renderer
1458
+
1459
+ Every game that renders with WebGL asks the SDK which renderer to make, just before it makes its
1460
+ first one, and follows the answer for every canvas on the page:
1461
+
1462
+ ```js
1463
+ const { route, listed } = await gem.graphics.preferredRoute();
1464
+ ```
1465
+
1466
+ `route` is `webgl` almost everywhere. It is `webgpu` only on a device the platform lists as broken
1467
+ on WebGL (today the Pixel 10's PowerVR D-Series GPU, on every route but Vulkan, where WebGL crashes
1468
+ the game in Chrome and the whole Arcade app), and only where WebGPU is available. `listed` is true
1469
+ on such a device. So:
1470
+
1471
+ - `route` is `webgpu`: make your WebGPU renderer, and check it really started on WebGPU (with
1472
+ three.js, `renderer.backend.isWebGPUBackend` after `await renderer.init()`). If it did not, draw
1473
+ your **safe WebGL recipe**.
1474
+ - `route` is `webgl` and `listed` is true: draw your safe WebGL recipe. For every listed device
1475
+ today that is no shadow maps.
1476
+ - Otherwise: your normal WebGL renderer.
1477
+
1478
+ That holds for every WebGL canvas the page makes, menus included: one crash takes every context on
1479
+ the page with it. Supporting both means `WebGLRenderer` by default plus `WebGPURenderer` only on
1480
+ listed devices, never `WebGPURenderer` everywhere, and custom GLSL needs a TSL port for WebGPU.
1481
+ The call is bounded: it resolves within 1 second by default, normally at once after
1482
+ `awaitBridge()`, and never rejects. Do not detect devices yourself; the list is the platform's and
1483
+ reaches your live game without a new build. [`graphics-route.md`](graphics-route.md) has the
1484
+ three.js pattern, what both renderers cost, and how to test each answer with the `gemGraphics`
1485
+ launch key.
1486
+
1457
1487
  ## Graphics quality: the Arcade picks the start, your game adapts
1458
1488
 
1459
1489
  Players open a lot of games they have never played. Each one should open at a quality that suits
@@ -2258,8 +2288,9 @@ with your game's card, and shares it through its own sheet, copy or Share panel.
2258
2288
  only where `gem.share.available` is true.
2259
2289
 
2260
2290
  Two things before you reach for it: **call it from the click**, synchronously, never from a timer
2261
- or after an `await`; and **never gate a reward on it**, because your game is told nothing back,
2262
- not even whether the player shared.
2291
+ or after an `await`; and **never gate a reward on it**, because nothing about the player comes
2292
+ back, not even whether they shared. Its promise rejects only when the Arcade refused the link,
2293
+ naming why, so catch it and log it.
2263
2294
  [Linking to your game](getting-started.md#linking-to-your-game) is the section.
2264
2295
 
2265
2296
  ## If your game vibrates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indiegems/gem-web-sdk",
3
- "version": "20.1.0",
3
+ "version": "21.1.0",
4
4
  "description": "postMessage bridge contract between gem-web and browser-playable games",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -39,6 +39,10 @@
39
39
  "types": "./dist/quality.d.ts",
40
40
  "import": "./dist/gem-web-sdk.quality.js"
41
41
  },
42
+ "./dev-tools": {
43
+ "types": "./dist/dev-tools.d.ts",
44
+ "import": "./dist/gem-web-sdk.dev-tools.js"
45
+ },
42
46
  "./conformance": {
43
47
  "types": "./dist/conformance.d.ts",
44
48
  "import": "./dist/gem-web-sdk.conformance.js"