@indiegems/gem-web-sdk 22.0.0 → 23.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.
@@ -48,8 +48,8 @@
48
48
  },
49
49
  {
50
50
  "file": "changelog.md",
51
- "sha256": "8d83b78666b70888b6ecd2ffc6877956787daca3698eff5844e86f724699ce52",
52
- "updated": "2026-11-04T23:21:33Z"
51
+ "sha256": "20215a4410087dd6076afd787bcfd4ee29bc5bf50e56562563e32a060cd74cc4",
52
+ "updated": "2026-11-05T00:34:54Z"
53
53
  },
54
54
  {
55
55
  "file": "daily-challenges.md",
@@ -68,8 +68,8 @@
68
68
  },
69
69
  {
70
70
  "file": "friends.md",
71
- "sha256": "d8e1f07a10ebe5743a1d56d615731cc6e5965e1a380490d2a1362dc6c1263d2d",
72
- "updated": "2026-10-12T16:40:39Z"
71
+ "sha256": "fd6db8da391647677568c0d2257dc5c92cbfe311759191b5e28fa3f66747aee6",
72
+ "updated": "2026-11-04T23:46:11Z"
73
73
  },
74
74
  {
75
75
  "file": "game-economy-tutorial.md",
@@ -78,8 +78,8 @@
78
78
  },
79
79
  {
80
80
  "file": "getting-started.md",
81
- "sha256": "9cf851ec0894fd27350d492ce412ff025c86e17e2c9ecf900da7276868732540",
82
- "updated": "2026-11-04T23:21:33Z"
81
+ "sha256": "c2f0c4fe33cc3e34120773a606179a4254963b957ab39cdda746c2f01db493d4",
82
+ "updated": "2026-11-05T00:34:54Z"
83
83
  },
84
84
  {
85
85
  "file": "graphics-route.md",
@@ -88,8 +88,8 @@
88
88
  },
89
89
  {
90
90
  "file": "inventory-and-catalogs.md",
91
- "sha256": "eea98a531d814113474ab381b1e404a0c0c30c8c770ebe5c68da853eab85564d",
92
- "updated": "2026-11-04T23:07:19Z"
91
+ "sha256": "f5aa4e405989da098317ca704b4dd8b996a1369c78ef0515ed7442886aee85fc",
92
+ "updated": "2026-11-04T23:38:54Z"
93
93
  },
94
94
  {
95
95
  "file": "leaderboards.md",
@@ -133,8 +133,8 @@
133
133
  },
134
134
  {
135
135
  "file": "rooms-and-matches.md",
136
- "sha256": "4c1c32c5523eca1a8e3a2e06b42dab3ec4c70e8bd77496614aca909732485da4",
137
- "updated": "2026-10-21T19:51:32Z"
136
+ "sha256": "dcbd9dda741656e5e346c2fea3240331ac19d055aede0ff27d706a63efb35500",
137
+ "updated": "2026-11-04T23:26:42Z"
138
138
  },
139
139
  {
140
140
  "file": "saving-player-progress.md",
@@ -143,8 +143,8 @@
143
143
  },
144
144
  {
145
145
  "file": "server-bundles.md",
146
- "sha256": "0f74e07ded6c9fb339c3bafa26e4af5153ce0bf20476fdb70444dcad2070766e",
147
- "updated": "2026-11-04T23:21:33Z"
146
+ "sha256": "fe9e5ced6e2e6bc4d583697c5f206f2f0431af8926eab7d8076f7f46e42864c9",
147
+ "updated": "2026-11-05T00:34:54Z"
148
148
  },
149
149
  {
150
150
  "file": "serverless-functions-js.md",
@@ -153,13 +153,13 @@
153
153
  },
154
154
  {
155
155
  "file": "serverless-functions.md",
156
- "sha256": "4c38d374fd80424be8ce774916e82253e68d8878d8b20558b815feec80ff4ee7",
157
- "updated": "2026-11-04T21:50:20Z"
156
+ "sha256": "fa1d161689e30d2300ab2ec7de3f3a5c9147966f559f7272b9e9b91852ecbeb4",
157
+ "updated": "2026-11-04T23:38:54Z"
158
158
  },
159
159
  {
160
160
  "file": "trading-and-gifting.md",
161
- "sha256": "e5a77bcae4243fbf69a34fa6440114677401255b40ee9825c15b87c6570e5030",
162
- "updated": "2026-10-21T19:36:16Z"
161
+ "sha256": "24ff7ebfc10b5186527cc51b49d8f2f556b43e6ff37b06db20ce3c7efd87855a",
162
+ "updated": "2026-11-04T23:38:54Z"
163
163
  },
164
164
  {
165
165
  "file": "vibration.md",
@@ -56,7 +56,9 @@ Two engine signals are deliberately absent, and each absence is a rule rather th
56
56
  display identity only, via `gem.friends` ([`friends.md`](friends.md)) — and nothing else about
57
57
  the graph crosses the frame. No presence, no pending requests, no invite events, no mutations.
58
58
  An invite is something happening *in* the graph, so it is delivered and rendered where the
59
- graph lives, on the Arcade. Derived questions also keep being answered where the data lives:
59
+ graph lives, on the Arcade. A game may ask the Arcade to open its own invite sheet
60
+ (`gem.friends.inviteToPlay`), and a friend who accepts arrives by quick-join (§5e) — but the
61
+ invite itself never reaches a game. Derived questions also keep being answered where the data lives:
60
62
  the friend view in [`leaderboards.md`](leaderboards.md) re-ranks a board server-side, and a
61
63
  party crosses into a room as an opaque grouping.
62
64
  - **`ping_results_ready`** — the engine's measurement is a UDP echo, which a browser cannot perform.
@@ -1004,8 +1006,9 @@ but never start short", name the same slugs on `queueForMatch()` instead.
1004
1006
  ## 5e. Landing in a friend's room: play-with
1005
1007
 
1006
1008
  The Arcade can open your game **to play with someone**: when the player presses Join on a friend
1007
- who is playing it, or answers the prompt the Arcade shows a party member when their leader starts
1008
- it. Your game makes no call for this. The Arcade adds a
1009
+ who is playing it, accepts an invite a friend sent from inside it
1010
+ ([`friends.md`](friends.md#inviting-a-friend-to-play)), or answers the prompt the Arcade shows a
1011
+ party member when their leader starts it. Your game makes no call for this. The Arcade adds a
1009
1012
  launch key of its own, `gemPlayWith`: an opaque ticket the platform issued for this player, which
1010
1013
  names nobody your game can recognise. The SDK reads it once as `createGem` runs, and every
1011
1014
  `quickJoin()` of the session passes it on: the platform tries the room of the person the ticket
@@ -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/v22.0.0/gem-web-sdk.tgz
84
- curl -fsSL https://downloads.gemarcade.com/sdk/v22.0.0/gem-web-sdk.tgz.sri
83
+ curl -fsSL -O https://downloads.gemarcade.com/sdk/v23.0.0/gem-web-sdk.tgz
84
+ curl -fsSL https://downloads.gemarcade.com/sdk/v23.0.0/gem-web-sdk.tgz.sri
85
85
  openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
86
86
  ```
87
87
 
@@ -150,6 +150,9 @@ fires them only when you opt in:
150
150
  - **A function a player called pays that player.** Its `call-api` writes the opted-in progression
151
151
  for the player id `get-context` gave it, and the platform grants and unlocks as it does for your
152
152
  server's write: currency on every channel, achievements on `release` only.
153
+ - **The function's answer should name what it paid.** The game shows the reward from that answer,
154
+ at once, rather than reading the inventory until it appears
155
+ ([section 6](#6-calling-it-from-your-game)).
153
156
  - **It pays nobody else.** It cannot write another player at all
154
157
  (`403 function_foreign_entity_write`, the previous subsection), so it cannot pay one, directly or
155
158
  through anything your config writes on the order's behalf. A character never earns a platform
@@ -683,6 +686,24 @@ A value JSON cannot carry, such as a `BigInt`, is refused before anything is sen
683
686
  - `result.text()` reads them as UTF-8, and throws if they are not UTF-8 rather than changing them;
684
687
  - `result.bytes` is the bytes themselves, for a function that answers in binary.
685
688
 
689
+ **The answer is the receipt.** When `invoke` resolves, what your function says it granted has
690
+ already been written: the write happens inside the function, before it answers, and a refusal is
691
+ its `Err`, so a resolved call is never a "maybe". Show what the answer says and let the player go
692
+ on at once. Re-read in the background, and redraw when the read lands:
693
+
694
+ ```js
695
+ const { granted } = (await gem.functions.invoke(claim)).json();
696
+ showReward(granted); // the function's word: show it now
697
+ gem.me.inventory.refresh(); // drop the cache...
698
+ gem.me.inventory.snapshot().then(drawBag); // ...and redraw from a fresh read, in the background
699
+ ```
700
+
701
+ **Never hold the player on a loop that re-reads until the grant shows.** That wait adds the
702
+ platform's read latency to every reward and buys nothing the answer did not already say. On an
703
+ Arcade that relays it, `gem.me.inventory.onChanged` also fires once the platform has the change
704
+ ([inventory-and-catalogs.md](inventory-and-catalogs.md#6-when-to-re-read)), so a listener there can
705
+ do the redraw for you.
706
+
686
707
  **It sends the call once.** The SDK never repeats a function call for you, because your function
687
708
  may have granted or written something, and a retry you cannot see is how one grant becomes two.
688
709
  Whether repeating it could help is on the error. A repeated call may grant again, and what stops
@@ -38,15 +38,18 @@ await gem.transfers.reject(transferId);
38
38
  await gem.transfers.cancel(transferId);
39
39
  ```
40
40
 
41
- ## The recipient is not told
42
-
43
- **A transfer produces no event in the recipient's game.** As everywhere else, there is no push
44
- into a sandboxed frame — the arriving gift is, by definition, a change some _other_ player made,
45
- which is precisely the kind of change your game cannot be notified of. See
46
- [`inventory-and-catalogs.md`](inventory-and-catalogs.md#6-when-to-re-read) for the full rule; the
47
- short version is that the recipient sees the gift the next time their game re-reads its inventory,
48
- so re-read after any action that could have involved another player, and design so a few seconds
49
- of not-yet-seeing-it is harmless.
41
+ ## How the recipient hears about it
42
+
43
+ **On an Arcade that relays it, the recipient's game is told its inventory changed.** The gift
44
+ lands in the recipient's inventory for your game, and the Arcade sends that game the same
45
+ field-free hint as for any other change to its own inventory: the SDK drops its cached reads and
46
+ `gem.me.inventory.onChanged` fires, so a listener that re-reads shows the gift. The hint names no
47
+ sender, item or amount; the re-read is what tells you. See
48
+ [`inventory-and-catalogs.md`](inventory-and-catalogs.md#6-when-to-re-read) for the full rule.
49
+
50
+ **On an older Arcade nothing is pushed.** The recipient sees the gift the next time their game
51
+ re-reads its inventory, so re-read after any action that could have involved another player, and
52
+ design so a few seconds of not-yet-seeing-it is harmless either way.
50
53
 
51
54
  ## `transfer_policy` is your game's rule, not the platform's
52
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indiegems/gem-web-sdk",
3
- "version": "22.0.0",
3
+ "version": "23.0.0",
4
4
  "description": "postMessage bridge contract between gem-web and browser-playable games",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1222,6 +1222,44 @@
1222
1222
  "rateLimit": { "perMinute": 30 }
1223
1223
  },
1224
1224
 
1225
+ {
1226
+ "type": "gem.inventoryChanged",
1227
+ "direction": "parentToFrame",
1228
+ "transport": "port",
1229
+ "$comment": [
1230
+ "Something in this player's inventory for THIS game and channel MAY have",
1231
+ "changed on the server, so the frame should re-read it with its own",
1232
+ "token if it shows it. Gated on `inventoryChanged`: a parent sends it",
1233
+ "only when `frameSupports('inventoryChanged')`.",
1234
+ "",
1235
+ "NO FIELDS, AND IT SHOULD STAY THAT WAY. The frame re-reads its own",
1236
+ "scope from gem-api, the authority, so the bridge never becomes a second",
1237
+ "source of truth for a balance or an item. This message never carries",
1238
+ "an item, a quantity, a balance, an order, an entity, a source or any",
1239
+ "game's id: it says only that a change happened, around now.",
1240
+ "",
1241
+ "WHEN A PARENT MAY SEND IT: on gem-api's `game_inventory_changed` socket",
1242
+ "event, only when its `game_id` is the game this session's token was",
1243
+ "granted for and its `channel` is that token's channel, and only after",
1244
+ "that token was sent. NEVER for another game's scope, another channel's,",
1245
+ "or the platform wallet (`platform_grant_fired` and the other wallet",
1246
+ "events): that send would tell this game, to the second, when another",
1247
+ "game or the platform paid the player -- a timing oracle for payouts",
1248
+ "this game is otherwise never shown. Nothing from the socket event's",
1249
+ "payload is copied. It should coalesce a burst -- one send now, then at",
1250
+ "most one trailing send every two seconds -- so a function that grants",
1251
+ "in a loop stays inside the limit.",
1252
+ "",
1253
+ "A HINT, NOT A RECEIPT. A game that granted through its own function",
1254
+ "already has the function's answer, which is the receipt; this is the",
1255
+ "cue to redraw from a fresh read. An embedder that never sends it is not",
1256
+ "defective. The SDK drops its cached reads of the game's inventory and",
1257
+ "makes no request because of it."
1258
+ ],
1259
+ "fields": {},
1260
+ "rateLimit": { "perMinute": 30 }
1261
+ },
1262
+
1225
1263
  {
1226
1264
  "type": "gem.viewport",
1227
1265
  "direction": "parentToFrame",
@@ -5282,6 +5320,193 @@
5282
5320
  },
5283
5321
  "rateLimit": { "perMinute": 30 }
5284
5322
  },
5323
+ {
5324
+ "type": "gem.requestPlayInvite",
5325
+ "direction": "frameToParent",
5326
+ "transport": "port",
5327
+ "$comment": [
5328
+ "Ask the parent to open ITS OWN invite-to-play sheet for this game, so",
5329
+ "the player can invite friends to come and play it with them - the",
5330
+ "\"Invite friends\" button of a lobby or a hub. Gated by the",
5331
+ "`playInvites` capability. Answered by `gem.playInviteResult`.",
5332
+ "",
5333
+ "A PLATFORM DECISION: DESIGN.md section 10's eighth amendment,",
5334
+ "decided by the platform owner on 2026-11-04, amending its 'no",
5335
+ "invites in the frame'. The sheet is the Arcade's own Friends window,",
5336
+ "the one the player also opens from the Arcade's menu.",
5337
+ "",
5338
+ "WHY THIS CROSSES THE BRIDGE AT ALL. A game knows the moment an invite is",
5339
+ "worth offering - a lobby with an empty seat, a hub the player just",
5340
+ "reached - but an invite puts a notice in front of another person, which",
5341
+ "is an account-session act the game-scoped token is refused by",
5342
+ "construction, and the friends worth inviting are chosen by their",
5343
+ "presence, which never reaches a frame. So the game ASKS, and the parent,",
5344
+ "which holds the account session, renders its own sheet of the player's",
5345
+ "friends and sends an invite only on the player's own press in it.",
5346
+ "",
5347
+ "A CORRELATOR AND, OPTIONALLY, THE GAME'S LAUNCH PARAMS, deliberately",
5348
+ "nothing more. The params say what the window's link and invites open,",
5349
+ "as a share's do, and name nobody. No player id: a game",
5350
+ "that could name the friend would be choosing whom the platform notifies,",
5351
+ "and could learn from the answer whether that friend is reachable. No",
5352
+ "game id and no channel: the parent takes both from what it framed. No",
5353
+ "text: what the sheet and the invite say is the platform's own.",
5354
+ "",
5355
+ "WHAT THE PARENT OWES, none of which this schema can enforce:",
5356
+ "",
5357
+ " ADVERTISE IT AS A DEPLOYMENT-WIDE FACT, never by account state, and",
5358
+ " only once the platform has decided it. A guest, or a player with no",
5359
+ " friends, is the parent's to explain in its own chrome or to answer",
5360
+ " `shown: false`; a token that blinked with sign-in would move that",
5361
+ " disclosure into the handshake.",
5362
+ "",
5363
+ " CHECK ACTIVATION ON RECEIPT. Read `navigator.userActivation.isActive`",
5364
+ " synchronously when the message arrives, before any `await`, and answer",
5365
+ " an ask that arrives without it `shown: false` with nothing rendered.",
5366
+ " The frame chooses the moment it asks; the player's press is what makes",
5367
+ " a sheet over their game wanted rather than a nag.",
5368
+ "",
5369
+ " ONE SHEET AT A TIME, AND CAPPED. An ask arriving while this sheet - or",
5370
+ " any other sheet that holds the stage: a purchase, a sign-in prompt, a",
5371
+ " friend challenge - is open is answered `shown: false` with nothing",
5372
+ " rendered, and the open invite sheet holds the stage against those asks",
5373
+ " too. A short cooldown after the player closes one is the parent's",
5374
+ " call.",
5375
+ "",
5376
+ " RENDER ONLY FROM PLATFORM RECORDS. The friends, their presence and the",
5377
+ " game's name come from the parent's own reads, never from the frame.",
5378
+ " Each Invite press is one account-session send.",
5379
+ "",
5380
+ " PAUSE THE GAME while the sheet is open (`gem.pause`) and lift it when",
5381
+ " the sheet closes, returning focus to the frame.",
5382
+ "",
5383
+ " ANSWER ONCE, AS THE SHEET OPENS. `shown: true` once it has painted,",
5384
+ " not when it closes: how long the player browsed, and what they did in",
5385
+ " it, are theirs.",
5386
+ "",
5387
+ "NOT AN AUTHORIZATION CONTROL. The player's Invite press in the parent's",
5388
+ "chrome is the control, and the platform's account-session send is the",
5389
+ "enforcement: it re-checks the friendship, blocks and every budget,",
5390
+ "whatever reached it."
5391
+ ],
5392
+ "fields": {
5393
+ "requestId": {
5394
+ "kind": "string",
5395
+ "maxLength": 64,
5396
+ "$comment": "Opaque and frame-chosen. The parent treats it as an echo-only correlator, exactly as it does for gem.requestFriendChallenge - it is not unique across tabs or reloads and must never key anything. It may also contain newlines and control characters, so it is not safe to write into a log line unescaped."
5397
+ },
5398
+ "params": {
5399
+ "kind": "array",
5400
+ "maxItems": 16,
5401
+ "optional": true,
5402
+ "$comment": [
5403
+ "The game's own launch parameters for what the invite and the",
5404
+ "window's share link open - `{ room: <join code> }` for a game whose",
5405
+ "rooms are joined by code. The same shape, grammar and reserved-key",
5406
+ "rules as `gem.requestShare.params`: the window's share link and its",
5407
+ "friend invites carry the same params, so a friend who accepts and a",
5408
+ "stranger who opens the link arrive the same way. ABSENT: the parent",
5409
+ "uses the params the game last declared with `gem.inviteParams`, or",
5410
+ "none. PRESENT, even empty: these, for this one opening.",
5411
+ "",
5412
+ "The parent applies the share rules to them and answers `shown:",
5413
+ "false`, rendering nothing, for anything it would refuse. They name",
5414
+ "what the link opens, never a person."
5415
+ ],
5416
+ "items": {
5417
+ "kind": "object",
5418
+ "fields": {
5419
+ "key": { "kind": "string", "maxLength": 64 },
5420
+ "value": { "kind": "string", "maxLength": 512 }
5421
+ }
5422
+ }
5423
+ }
5424
+ },
5425
+ "rateLimit": {
5426
+ "perMinute": 6,
5427
+ "$comment": "Bounds a BUGGY game. NOT a security control: a hostile game posts on the port it holds rather than calling the SDK. What stops a game nagging is the parent's activation check, its one-sheet-at-a-time rule and the player's own Invite press."
5428
+ }
5429
+ },
5430
+ {
5431
+ "type": "gem.playInviteResult",
5432
+ "direction": "parentToFrame",
5433
+ "transport": "port",
5434
+ "$comment": [
5435
+ "The answer to one gem.requestPlayInvite. Gated by the `playInvites`",
5436
+ "capability.",
5437
+ "",
5438
+ "ONE BOOLEAN, AND NOTHING ELSE. `shown: true` - the parent opened its",
5439
+ "invite sheet. `shown: false` - nothing was rendered: no activation,",
5440
+ "another sheet held the stage, a guest, a parent with no sheet to offer.",
5441
+ "",
5442
+ "WHAT THE FRAME NEVER LEARNS, and a parent must not attach: whom the",
5443
+ "player invited, how many, whether any invite was sent, delivered or",
5444
+ "accepted, or why nothing was shown. Who the player invites is a fact",
5445
+ "about the people they know, and whether an invite landed is a fact about",
5446
+ "a stranger's settings; neither is the game's. So `shown: true` grants",
5447
+ "nothing and is no sign anything was sent - a game never gates a reward",
5448
+ "on it. A",
5449
+ "friend who accepts arrives the ordinary way: the Arcade opens the game",
5450
+ "for them, and its own quick-join seats them beside the player.",
5451
+ "",
5452
+ "PERMANENTLY A BOOLEAN. A field reporting the outcome would undo the",
5453
+ "amendment this pair rides on (DESIGN.md section 10, pending the",
5454
+ "platform's decision)."
5455
+ ],
5456
+ "fields": {
5457
+ "requestId": {
5458
+ "kind": "string",
5459
+ "maxLength": 64,
5460
+ "$comment": "Echoes the gem.requestPlayInvite that asked. Echo-only."
5461
+ },
5462
+ "shown": {
5463
+ "kind": "boolean",
5464
+ "$comment": "Whether the parent opened its invite sheet. It grants nothing, and says nothing about whether anyone was invited."
5465
+ }
5466
+ },
5467
+ "rateLimit": { "perMinute": 12 }
5468
+ },
5469
+ {
5470
+ "type": "gem.inviteParams",
5471
+ "direction": "frameToParent",
5472
+ "transport": "port",
5473
+ "$comment": [
5474
+ "A standing declaration: the launch parameters the Arcade's Friends",
5475
+ "window should carry when the PLAYER opens it from the Arcade's own",
5476
+ "menu, over this game - its share link and its friend invites alike.",
5477
+ "Gated by the `playInvites` capability. Nothing answers it.",
5478
+ "",
5479
+ "A game sends it when it enters a room joined by code (`{ room:",
5480
+ "<join code> }`) and clears it - `params` absent - when it leaves. The",
5481
+ "latest one stands until the next, and an explicit",
5482
+ "`gem.requestPlayInvite` carrying `params` wins for that one opening.",
5483
+ "The SDK re-sends the latest on every handshake, so a fresh parent",
5484
+ "holds what the game last said.",
5485
+ "",
5486
+ "THE SAME PARAMS AS A SHARE, on the same rules. They name what a link",
5487
+ "opens and never a person: no player, no game id and no channel",
5488
+ "travel. The parent applies the share rules to them when it uses",
5489
+ "them, and leaves a link it would refuse unbuilt rather than",
5490
+ "carrying it. Sending one changes nothing on screen and tells the",
5491
+ "game nothing."
5492
+ ],
5493
+ "fields": {
5494
+ "params": {
5495
+ "kind": "array",
5496
+ "maxItems": 16,
5497
+ "optional": true,
5498
+ "$comment": "The declared params, in order; the shape, grammar and reserved-key rules of `gem.requestShare.params`. ABSENT clears the declaration.",
5499
+ "items": {
5500
+ "kind": "object",
5501
+ "fields": {
5502
+ "key": { "kind": "string", "maxLength": 64 },
5503
+ "value": { "kind": "string", "maxLength": 512 }
5504
+ }
5505
+ }
5506
+ }
5507
+ },
5508
+ "rateLimit": { "perMinute": 30 }
5509
+ },
5285
5510
  {
5286
5511
  "type": "gem.requestAppLinks",
5287
5512
  "direction": "frameToParent",
@@ -46,6 +46,14 @@
46
46
  "gem.friendChallengeResult"
47
47
  ]
48
48
  },
49
+ "playInvites": {
50
+ "implemented": true,
51
+ "gates": [
52
+ "gem.requestPlayInvite",
53
+ "gem.playInviteResult",
54
+ "gem.inviteParams"
55
+ ]
56
+ },
49
57
  "devtools": {
50
58
  "implemented": true,
51
59
  "gates": [
@@ -213,6 +221,12 @@
213
221
  "gem.earnAllowanceChanged"
214
222
  ]
215
223
  },
224
+ "inventoryChanged": {
225
+ "implemented": true,
226
+ "gates": [
227
+ "gem.inventoryChanged"
228
+ ]
229
+ },
216
230
  "deviceSettings": {
217
231
  "implemented": true,
218
232
  "gates": [