@indiegems/gem-web-sdk 22.0.0 → 23.0.1
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 +53 -0
- package/dist/conformance.d.ts +4 -1
- package/dist/function.d.ts +9 -1
- package/dist/game.d.ts +40 -2
- package/dist/gem-web-sdk.bridge.js +2 -2
- package/dist/gem-web-sdk.conformance.js +3 -3
- package/dist/gem-web-sdk.esm.js +3 -3
- package/dist/gem-web-sdk.function.js +3 -3
- package/dist/gem-web-sdk.game.js +3 -3
- package/dist/gem-web-sdk.min.js +3 -3
- package/dist/gem-web-sdk.sdk.js +3 -3
- package/dist/gem-web-sdk.server.js +2 -2
- package/dist/index.d.ts +54 -6
- package/dist/sdk.d.ts +40 -2
- package/docs/api/api-reference-function.md +129 -9
- package/docs/api/api-reference-game.md +141 -13
- package/docs/api/api-reference-server.md +7 -1
- package/docs/api/reference.json +165 -9
- package/docs/changelog.md +53 -0
- package/docs/friends.md +120 -15
- package/docs/getting-started.md +11 -4
- package/docs/inventory-and-catalogs.md +29 -11
- package/docs/manifest.json +16 -16
- package/docs/rooms-and-matches.md +6 -3
- package/docs/server-bundles.md +2 -2
- package/docs/serverless-functions.md +21 -0
- package/docs/trading-and-gifting.md +12 -9
- package/package.json +1 -1
- package/schema/bridge.v1.json +225 -0
- package/schema/capabilities.public.json +14 -0
package/docs/api/reference.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"source": {
|
|
4
4
|
"repo": "IndieGems/gem-web-sdk",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "23.0.1"
|
|
6
6
|
},
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1155,7 +1155,7 @@
|
|
|
1155
1155
|
"group": "saves",
|
|
1156
1156
|
"signature": "refresh(): void",
|
|
1157
1157
|
"documented": true,
|
|
1158
|
-
"summary": "Throw away what is cached, so the next [InventoryApi.snapshot](api-reference-game.md#inventoryapi-snapshot) really\nasks.\n\nFor when something you did not do changed the inventory — a purchase on\nanother device, a grant from a server. The SDK
|
|
1158
|
+
"summary": "Throw away what is cached, so the next [InventoryApi.snapshot](api-reference-game.md#inventoryapi-snapshot) really\nasks.\n\nFor when something you did not do changed the inventory — a purchase on\nanother device, a grant from a server — and nothing told you. The SDK\nalready drops the cache itself after any change it made, and on an\nArcade's hint that this game's inventory changed on the server.",
|
|
1159
1159
|
"deprecated": null,
|
|
1160
1160
|
"params": [],
|
|
1161
1161
|
"returns": null,
|
|
@@ -1171,7 +1171,7 @@
|
|
|
1171
1171
|
"group": "saves",
|
|
1172
1172
|
"signature": "onChanged(listener: () => void): () => void",
|
|
1173
1173
|
"documented": true,
|
|
1174
|
-
"summary": "Run something whenever this inventory changes, and get back a function that\nstops listening.\n\
|
|
1174
|
+
"summary": "Run something whenever this inventory changes, and get back a function that\nstops listening.\n\nFires for every change THIS game made through this SDK, and — on an Arcade\nthat relays it — for any change to this game's inventory on the server: a\ngrant from your function or server, a purchase, a gift accepted, a support\ngrant, another device. It is a cue to re-read, not a receipt: it carries\nnothing, so call [InventoryApi.snapshot](api-reference-game.md#inventoryapi-snapshot) for what changed. After your\nown function's grant, show the function's answer and let this redraw.\n\nAn older Arcade sends no hint, and there it fires for your own writes\nonly. Keep a fallback that does not depend on it: refresh on\n`visibilitychange` to `visible` and after any action that could involve\nanother player. Do not poll.",
|
|
1175
1175
|
"deprecated": null,
|
|
1176
1176
|
"params": [
|
|
1177
1177
|
{
|
|
@@ -10095,6 +10095,38 @@
|
|
|
10095
10095
|
"defaultValue": null,
|
|
10096
10096
|
"guides": []
|
|
10097
10097
|
},
|
|
10098
|
+
{
|
|
10099
|
+
"id": "PlayInviteRequest",
|
|
10100
|
+
"name": "PlayInviteRequest",
|
|
10101
|
+
"anchor": "playinviterequest",
|
|
10102
|
+
"group": "friends",
|
|
10103
|
+
"signature": "interface PlayInviteRequest",
|
|
10104
|
+
"documented": true,
|
|
10105
|
+
"summary": "What `gem.friends.inviteToPlay` takes.",
|
|
10106
|
+
"deprecated": null,
|
|
10107
|
+
"params": [],
|
|
10108
|
+
"returns": null,
|
|
10109
|
+
"throws": null,
|
|
10110
|
+
"example": null,
|
|
10111
|
+
"defaultValue": null,
|
|
10112
|
+
"guides": []
|
|
10113
|
+
},
|
|
10114
|
+
{
|
|
10115
|
+
"id": "PlayInviteRequest.params",
|
|
10116
|
+
"name": "params",
|
|
10117
|
+
"anchor": "playinviterequest-params",
|
|
10118
|
+
"group": "friends",
|
|
10119
|
+
"signature": "readonly params?: Readonly<Record<string, string>>",
|
|
10120
|
+
"documented": true,
|
|
10121
|
+
"summary": "Your own launch parameters for what the Arcade's link and invites open —\n`{ room: joinCode }` for a room joined by code — exactly as they should\nreach your game, on `gem.share.request`'s rules: at most 16, keys a letter\nthen up to 63 letters, digits, `_` or `-`, string values of at most 512\ncharacters. For this one opening, they replace what\n`gem.friends.setInviteParams` declared. Omit it to use that declaration.",
|
|
10122
|
+
"deprecated": null,
|
|
10123
|
+
"params": [],
|
|
10124
|
+
"returns": null,
|
|
10125
|
+
"throws": null,
|
|
10126
|
+
"example": null,
|
|
10127
|
+
"defaultValue": null,
|
|
10128
|
+
"guides": []
|
|
10129
|
+
},
|
|
10098
10130
|
{
|
|
10099
10131
|
"id": "FriendsApi",
|
|
10100
10132
|
"name": "FriendsApi",
|
|
@@ -10157,6 +10189,52 @@
|
|
|
10157
10189
|
"defaultValue": null,
|
|
10158
10190
|
"guides": []
|
|
10159
10191
|
},
|
|
10192
|
+
{
|
|
10193
|
+
"id": "FriendsApi.inviteToPlay",
|
|
10194
|
+
"name": "inviteToPlay",
|
|
10195
|
+
"anchor": "friendsapi-invitetoplay",
|
|
10196
|
+
"group": "friends",
|
|
10197
|
+
"signature": "inviteToPlay(request?: PlayInviteRequest): Promise<boolean>",
|
|
10198
|
+
"documented": true,
|
|
10199
|
+
"summary": "Ask the Arcade to open ITS invite sheet, so the player can invite friends\nto come and play this game with them.\n\nThe Arcade lists the player's friends in its own chrome and sends an\ninvite only when the player presses Invite there. A friend who accepts is\nopened into this game by the Arcade, and their quick-join seats them in\nthe player's room when there is a seat: your game makes no call for it,\nand reads them as a room member who shares the player's `groupId`. Your\ngame names nobody and writes no text.\n\nAn Arcade that does not offer the sheet answers `false` at once, and asks\nnothing. Hide the button on `gem.frame.parentSupports('playInvites')`\nrather than letting a player press one that cannot work.\n\n⚠️ **Call it ONLY from a player's press**, never on a timer or as a screen\nopens. The Arcade answers an ask with no press behind it `false`, and\nshows nothing.\n\nRESOLVES `true` when the Arcade opened its sheet, and `false` when it did\nnot: outside the Arcade, without `playInvites`, with another sheet\nalready open, without a press, or when no answer arrived. **That is all it\nsays** — never whom the player invited, how many, or whether any invite\nwas sent — so never gate a reward on it. Never rejects.\n\nCalls made while an ask is open share its answer.\n\n**The window's share link and its invites are one system.** Pass\n`params` — your own launch parameters, `{ room: joinCode }` for a room\njoined by code — and a friend who accepts and anyone who opens the link\narrive with them, on `gem.share.request`'s rules. Omitted, the window uses\nwhat [FriendsApi.setInviteParams](api-reference-game.md#friendsapi-setinviteparams) declared. A param those rules\nrefuse is said on the console and nothing is asked: the call resolves\n`false`.",
|
|
10200
|
+
"deprecated": null,
|
|
10201
|
+
"params": [
|
|
10202
|
+
{
|
|
10203
|
+
"name": "request",
|
|
10204
|
+
"type": "PlayInviteRequest",
|
|
10205
|
+
"optional": true,
|
|
10206
|
+
"summary": null
|
|
10207
|
+
}
|
|
10208
|
+
],
|
|
10209
|
+
"returns": null,
|
|
10210
|
+
"throws": null,
|
|
10211
|
+
"example": "```ts\ninviteButton.onclick = async () => {\n const shown = await gem.friends.inviteToPlay({ params: { room: joinCode } });\n if (!shown) showJoinCode(); // the Arcade's sheet did not open: your own fallback\n};\n```",
|
|
10212
|
+
"defaultValue": null,
|
|
10213
|
+
"guides": []
|
|
10214
|
+
},
|
|
10215
|
+
{
|
|
10216
|
+
"id": "FriendsApi.setInviteParams",
|
|
10217
|
+
"name": "setInviteParams",
|
|
10218
|
+
"anchor": "friendsapi-setinviteparams",
|
|
10219
|
+
"group": "friends",
|
|
10220
|
+
"signature": "setInviteParams(params: Readonly<Record<string, string>> | null): boolean",
|
|
10221
|
+
"documented": true,
|
|
10222
|
+
"summary": "Say what the Arcade's Friends window opens when the PLAYER opens it from\nthe Arcade's own menu over your game: your launch parameters, carried by\nits share link and its friend invites alike, on `gem.share.request`'s\nrules. Pass `null` to clear.\n\nCall it when the player enters a room joined by code —\n`setInviteParams({ room: joinCode })` — and `setInviteParams(null)` when\nthey leave it, so the window never invites anyone into a room that is\ngone. The latest call stands until the next; the SDK holds it and says it\nagain after a reconnect. An explicit `inviteToPlay({ params })` wins for\nthat one opening. Nothing comes back, and nothing changes on screen.",
|
|
10223
|
+
"deprecated": null,
|
|
10224
|
+
"params": [
|
|
10225
|
+
{
|
|
10226
|
+
"name": "params",
|
|
10227
|
+
"type": "Readonly<Record<string, string>> | null",
|
|
10228
|
+
"optional": false,
|
|
10229
|
+
"summary": null
|
|
10230
|
+
}
|
|
10231
|
+
],
|
|
10232
|
+
"returns": "whether the declaration reached the Arcade now. `false` outside\n the Arcade, without `playInvites`, before the bridge is up (it is sent on\n connect) and for params the share rules refuse, which are said on the\n console; the last declaration then stands.",
|
|
10233
|
+
"throws": null,
|
|
10234
|
+
"example": "```ts\n// On entering a room joined by code:\ngem.friends.setInviteParams({ room: joinCode });\n// ...and on leaving it:\ngem.friends.setInviteParams(null);\n```",
|
|
10235
|
+
"defaultValue": null,
|
|
10236
|
+
"guides": []
|
|
10237
|
+
},
|
|
10160
10238
|
{
|
|
10161
10239
|
"id": "FriendsApi.inbox",
|
|
10162
10240
|
"name": "inbox",
|
|
@@ -10553,7 +10631,7 @@
|
|
|
10553
10631
|
"group": "functions",
|
|
10554
10632
|
"signature": "invoke(input: unknown, options?: FunctionInvokeOptions): Promise<FunctionResult>",
|
|
10555
10633
|
"documented": true,
|
|
10556
|
-
"summary": "Call the function published on your channel, and get its answer.\n\nThe input is sent as your function's `input`, byte for byte. Pass a\n`Uint8Array`, an `ArrayBuffer` or another typed array to send bytes as they\nare. Pass anything else and it is sent as its JSON text — a string too, so\n`'ping'` arrives as `\"ping\"` with its quotes.\n\nThe call is made once. It is never repeated for you, because your function\nmay have granted or written something: see [GemFunctionError.retryable](api-reference-game.md#gemfunctionerror-retryable)\nfor whether repeating it could help.\n\n```js\nconst result = await gem.functions.invoke({ v: 2, runId, finishMs });\nconst { accepted, why } = result.json();\n```\n\nWhen the call pays a reward the player sees, pass the reward's `cueId`, the\nsame one the page gives `gem.cueReward` for it, and the Gem Arcade flies it\nonce:\n\n```js\nconst cueId = crypto.randomUUID(); // one per award, also passed to the cue\nawait gem.functions.invoke({ claim: 'daily' }, { cueId });\n```",
|
|
10634
|
+
"summary": "Call the function published on your channel, and get its answer.\n\nThe input is sent as your function's `input`, byte for byte. Pass a\n`Uint8Array`, an `ArrayBuffer` or another typed array to send bytes as they\nare. Pass anything else and it is sent as its JSON text — a string too, so\n`'ping'` arrives as `\"ping\"` with its quotes.\n\nThe call is made once. It is never repeated for you, because your function\nmay have granted or written something: see [GemFunctionError.retryable](api-reference-game.md#gemfunctionerror-retryable)\nfor whether repeating it could help.\n\nThe answer is the receipt: when this resolves, what your function says it\ngranted has been written. A game shows it at once and re-reads the\ninventory in the background (`gem.me.inventory.refresh()`, then\n`snapshot()`, or on `gem.me.inventory.onChanged`); never hold the player on\na loop that re-reads until the grant shows.\n\n```js\nconst result = await gem.functions.invoke({ v: 2, runId, finishMs });\nconst { accepted, why } = result.json();\n```\n\nWhen the call pays a reward the player sees, pass the reward's `cueId`, the\nsame one the page gives `gem.cueReward` for it, and the Gem Arcade flies it\nonce:\n\n```js\nconst cueId = crypto.randomUUID(); // one per award, also passed to the cue\nawait gem.functions.invoke({ claim: 'daily' }, { cueId });\n```",
|
|
10557
10635
|
"deprecated": null,
|
|
10558
10636
|
"params": [
|
|
10559
10637
|
{
|
|
@@ -13554,7 +13632,7 @@
|
|
|
13554
13632
|
"group": "start",
|
|
13555
13633
|
"signature": "readonly friends: FriendsApi",
|
|
13556
13634
|
"documented": true,
|
|
13557
|
-
"summary": "The signed-in player's accepted friends — display identity only,\nread-only, paginated — and the
|
|
13635
|
+
"summary": "The signed-in player's accepted friends — display identity only,\nread-only, paginated — and the asks that reach past them:\n[FriendsApi.challenge](api-reference-game.md#friendsapi-challenge), which asks the Arcade to send a friend a\nchallenge after the player confirms it in the Arcade's own sheet, and\n[FriendsApi.inviteToPlay](api-reference-game.md#friendsapi-invitetoplay), which asks the Arcade to open its own\ninvite-to-play sheet. The receiving side reads the challenges this player\nholds in this game:\n[FriendsApi.launchedChallenge](api-reference-game.md#friendsapi-launchedchallenge) for the one this frame was launched\nwith, [FriendsApi.inbox](api-reference-game.md#friendsapi-inbox) for all of them, and\n[FriendsApi.markAnswered](api-reference-game.md#friendsapi-markanswered) once one has been played.\n\nEvery accepted friend appears whether or not they have played this game;\nan invite-a-friend flow needs exactly the ones who have not. The graph\nitself — requests, accepts, blocks, presence — stays on the Arcade: there\nis nothing here to mutate but a challenge's own answered mark, and nothing\nthat says who is online.\n\nA player surface only. In a context with no player — a game server — the\nplatform refuses the read; a server has no friend list.",
|
|
13558
13636
|
"deprecated": null,
|
|
13559
13637
|
"params": [],
|
|
13560
13638
|
"returns": null,
|
|
@@ -22499,7 +22577,7 @@
|
|
|
22499
22577
|
"group": "rooms",
|
|
22500
22578
|
"signature": "readonly playWith: PlayWithOutcome | null",
|
|
22501
22579
|
"documented": true,
|
|
22502
|
-
"summary": "What the last [RoomManager.quickJoin](api-reference-game.md#roommanager-quickjoin) did when the Arcade opened\nyour game to play with someone — a friend whose Join the player pressed,\
|
|
22580
|
+
"summary": "What the last [RoomManager.quickJoin](api-reference-game.md#roommanager-quickjoin) did when the Arcade opened\nyour game to play with someone — a friend whose Join the player pressed,\na friend's invite, or their party's leader. Read-only.\n\n- `'joined'` — the player was seated in that person's room, as a member\n sharing their `groupId`.\n- `'unavailable'` — that room could not be joined (no seat, a match that\n admits nobody late, another mode), so the quick-join went on as it\n always does. No reason is given.\n- `null` — no quick-join has asked: the Arcade did not open the game to\n play with anyone, or nothing has quick-joined yet.\n\nYour game makes no call for this, names nobody and is told nobody's name:\nthe Arcade adds a launch key of its own, an opaque ticket, and every\n`quickJoin` passes it on. Nothing else\nchanges — the room is the one `quickJoin` answers with, `joined` fires as\nfor any quick-join, and no `seated` follows, because your own call made\nthe seat. Use it for a line of text (\"You joined your friend's game\") or\nto skip a mode picker, never to decide anything of value.",
|
|
22503
22581
|
"deprecated": null,
|
|
22504
22582
|
"params": [],
|
|
22505
22583
|
"returns": null,
|
|
@@ -27110,7 +27188,7 @@
|
|
|
27110
27188
|
"group": "functions",
|
|
27111
27189
|
"signature": "invoke(input: unknown, options?: FunctionInvokeOptions): Promise<FunctionResult>",
|
|
27112
27190
|
"documented": true,
|
|
27113
|
-
"summary": "Call the function published on your channel, and get its answer.\n\nThe input is sent as your function's `input`, byte for byte. Pass a\n`Uint8Array`, an `ArrayBuffer` or another typed array to send bytes as they\nare. Pass anything else and it is sent as its JSON text — a string too, so\n`'ping'` arrives as `\"ping\"` with its quotes.\n\nThe call is made once. It is never repeated for you, because your function\nmay have granted or written something: see [GemFunctionError.retryable](api-reference-server.md#gemfunctionerror-retryable)\nfor whether repeating it could help.\n\n```js\nconst result = await gem.functions.invoke({ v: 2, runId, finishMs });\nconst { accepted, why } = result.json();\n```\n\nWhen the call pays a reward the player sees, pass the reward's `cueId`, the\nsame one the page gives `gem.cueReward` for it, and the Gem Arcade flies it\nonce:\n\n```js\nconst cueId = crypto.randomUUID(); // one per award, also passed to the cue\nawait gem.functions.invoke({ claim: 'daily' }, { cueId });\n```",
|
|
27191
|
+
"summary": "Call the function published on your channel, and get its answer.\n\nThe input is sent as your function's `input`, byte for byte. Pass a\n`Uint8Array`, an `ArrayBuffer` or another typed array to send bytes as they\nare. Pass anything else and it is sent as its JSON text — a string too, so\n`'ping'` arrives as `\"ping\"` with its quotes.\n\nThe call is made once. It is never repeated for you, because your function\nmay have granted or written something: see [GemFunctionError.retryable](api-reference-server.md#gemfunctionerror-retryable)\nfor whether repeating it could help.\n\nThe answer is the receipt: when this resolves, what your function says it\ngranted has been written. A game shows it at once and re-reads the\ninventory in the background (`gem.me.inventory.refresh()`, then\n`snapshot()`, or on `gem.me.inventory.onChanged`); never hold the player on\na loop that re-reads until the grant shows.\n\n```js\nconst result = await gem.functions.invoke({ v: 2, runId, finishMs });\nconst { accepted, why } = result.json();\n```\n\nWhen the call pays a reward the player sees, pass the reward's `cueId`, the\nsame one the page gives `gem.cueReward` for it, and the Gem Arcade flies it\nonce:\n\n```js\nconst cueId = crypto.randomUUID(); // one per award, also passed to the cue\nawait gem.functions.invoke({ claim: 'daily' }, { cueId });\n```",
|
|
27114
27192
|
"deprecated": null,
|
|
27115
27193
|
"params": [
|
|
27116
27194
|
{
|
|
@@ -32511,7 +32589,7 @@
|
|
|
32511
32589
|
"group": "inventory",
|
|
32512
32590
|
"signature": "refresh(): void",
|
|
32513
32591
|
"documented": true,
|
|
32514
|
-
"summary": "Throw away what is cached, so the next [InventoryApi.snapshot](api-reference-function.md#inventoryapi-snapshot) really\nasks.\n\nFor when something you did not do changed the inventory — a purchase on\nanother device, a grant from a server. The SDK
|
|
32592
|
+
"summary": "Throw away what is cached, so the next [InventoryApi.snapshot](api-reference-function.md#inventoryapi-snapshot) really\nasks.\n\nFor when something you did not do changed the inventory — a purchase on\nanother device, a grant from a server — and nothing told you. The SDK\nalready drops the cache itself after any change it made, and on an\nArcade's hint that this game's inventory changed on the server.",
|
|
32515
32593
|
"deprecated": null,
|
|
32516
32594
|
"params": [],
|
|
32517
32595
|
"returns": null,
|
|
@@ -32527,7 +32605,7 @@
|
|
|
32527
32605
|
"group": "inventory",
|
|
32528
32606
|
"signature": "onChanged(listener: () => void): () => void",
|
|
32529
32607
|
"documented": true,
|
|
32530
|
-
"summary": "Run something whenever this inventory changes, and get back a function that\nstops listening.\n\
|
|
32608
|
+
"summary": "Run something whenever this inventory changes, and get back a function that\nstops listening.\n\nFires for every change THIS game made through this SDK, and — on an Arcade\nthat relays it — for any change to this game's inventory on the server: a\ngrant from your function or server, a purchase, a gift accepted, a support\ngrant, another device. It is a cue to re-read, not a receipt: it carries\nnothing, so call [InventoryApi.snapshot](api-reference-function.md#inventoryapi-snapshot) for what changed. After your\nown function's grant, show the function's answer and let this redraw.\n\nAn older Arcade sends no hint, and there it fires for your own writes\nonly. Keep a fallback that does not depend on it: refresh on\n`visibilitychange` to `visible` and after any action that could involve\nanother player. Do not poll.",
|
|
32531
32609
|
"deprecated": null,
|
|
32532
32610
|
"params": [
|
|
32533
32611
|
{
|
|
@@ -36279,6 +36357,38 @@
|
|
|
36279
36357
|
"defaultValue": null,
|
|
36280
36358
|
"guides": []
|
|
36281
36359
|
},
|
|
36360
|
+
{
|
|
36361
|
+
"id": "PlayInviteRequest",
|
|
36362
|
+
"name": "PlayInviteRequest",
|
|
36363
|
+
"anchor": "playinviterequest",
|
|
36364
|
+
"group": "friends",
|
|
36365
|
+
"signature": "interface PlayInviteRequest",
|
|
36366
|
+
"documented": true,
|
|
36367
|
+
"summary": "What `gem.friends.inviteToPlay` takes.",
|
|
36368
|
+
"deprecated": null,
|
|
36369
|
+
"params": [],
|
|
36370
|
+
"returns": null,
|
|
36371
|
+
"throws": null,
|
|
36372
|
+
"example": null,
|
|
36373
|
+
"defaultValue": null,
|
|
36374
|
+
"guides": []
|
|
36375
|
+
},
|
|
36376
|
+
{
|
|
36377
|
+
"id": "PlayInviteRequest.params",
|
|
36378
|
+
"name": "params",
|
|
36379
|
+
"anchor": "playinviterequest-params",
|
|
36380
|
+
"group": "friends",
|
|
36381
|
+
"signature": "readonly params?: Readonly<Record<string, string>>",
|
|
36382
|
+
"documented": true,
|
|
36383
|
+
"summary": "Your own launch parameters for what the Arcade's link and invites open —\n`{ room: joinCode }` for a room joined by code — exactly as they should\nreach your game, on `gem.share.request`'s rules: at most 16, keys a letter\nthen up to 63 letters, digits, `_` or `-`, string values of at most 512\ncharacters. For this one opening, they replace what\n`gem.friends.setInviteParams` declared. Omit it to use that declaration.",
|
|
36384
|
+
"deprecated": null,
|
|
36385
|
+
"params": [],
|
|
36386
|
+
"returns": null,
|
|
36387
|
+
"throws": null,
|
|
36388
|
+
"example": null,
|
|
36389
|
+
"defaultValue": null,
|
|
36390
|
+
"guides": []
|
|
36391
|
+
},
|
|
36282
36392
|
{
|
|
36283
36393
|
"id": "FriendsApi",
|
|
36284
36394
|
"name": "FriendsApi",
|
|
@@ -36341,6 +36451,52 @@
|
|
|
36341
36451
|
"defaultValue": null,
|
|
36342
36452
|
"guides": []
|
|
36343
36453
|
},
|
|
36454
|
+
{
|
|
36455
|
+
"id": "FriendsApi.inviteToPlay",
|
|
36456
|
+
"name": "inviteToPlay",
|
|
36457
|
+
"anchor": "friendsapi-invitetoplay",
|
|
36458
|
+
"group": "friends",
|
|
36459
|
+
"signature": "inviteToPlay(request?: PlayInviteRequest): Promise<boolean>",
|
|
36460
|
+
"documented": true,
|
|
36461
|
+
"summary": "Ask the Arcade to open ITS invite sheet, so the player can invite friends\nto come and play this game with them.\n\nThe Arcade lists the player's friends in its own chrome and sends an\ninvite only when the player presses Invite there. A friend who accepts is\nopened into this game by the Arcade, and their quick-join seats them in\nthe player's room when there is a seat: your game makes no call for it,\nand reads them as a room member who shares the player's `groupId`. Your\ngame names nobody and writes no text.\n\nAn Arcade that does not offer the sheet answers `false` at once, and asks\nnothing. Hide the button on `gem.frame.parentSupports('playInvites')`\nrather than letting a player press one that cannot work.\n\n⚠️ **Call it ONLY from a player's press**, never on a timer or as a screen\nopens. The Arcade answers an ask with no press behind it `false`, and\nshows nothing.\n\nRESOLVES `true` when the Arcade opened its sheet, and `false` when it did\nnot: outside the Arcade, without `playInvites`, with another sheet\nalready open, without a press, or when no answer arrived. **That is all it\nsays** — never whom the player invited, how many, or whether any invite\nwas sent — so never gate a reward on it. Never rejects.\n\nCalls made while an ask is open share its answer.\n\n**The window's share link and its invites are one system.** Pass\n`params` — your own launch parameters, `{ room: joinCode }` for a room\njoined by code — and a friend who accepts and anyone who opens the link\narrive with them, on `gem.share.request`'s rules. Omitted, the window uses\nwhat [FriendsApi.setInviteParams](api-reference-function.md#friendsapi-setinviteparams) declared. A param those rules\nrefuse is said on the console and nothing is asked: the call resolves\n`false`.",
|
|
36462
|
+
"deprecated": null,
|
|
36463
|
+
"params": [
|
|
36464
|
+
{
|
|
36465
|
+
"name": "request",
|
|
36466
|
+
"type": "PlayInviteRequest",
|
|
36467
|
+
"optional": true,
|
|
36468
|
+
"summary": null
|
|
36469
|
+
}
|
|
36470
|
+
],
|
|
36471
|
+
"returns": null,
|
|
36472
|
+
"throws": null,
|
|
36473
|
+
"example": "```ts\ninviteButton.onclick = async () => {\n const shown = await gem.friends.inviteToPlay({ params: { room: joinCode } });\n if (!shown) showJoinCode(); // the Arcade's sheet did not open: your own fallback\n};\n```",
|
|
36474
|
+
"defaultValue": null,
|
|
36475
|
+
"guides": []
|
|
36476
|
+
},
|
|
36477
|
+
{
|
|
36478
|
+
"id": "FriendsApi.setInviteParams",
|
|
36479
|
+
"name": "setInviteParams",
|
|
36480
|
+
"anchor": "friendsapi-setinviteparams",
|
|
36481
|
+
"group": "friends",
|
|
36482
|
+
"signature": "setInviteParams(params: Readonly<Record<string, string>> | null): boolean",
|
|
36483
|
+
"documented": true,
|
|
36484
|
+
"summary": "Say what the Arcade's Friends window opens when the PLAYER opens it from\nthe Arcade's own menu over your game: your launch parameters, carried by\nits share link and its friend invites alike, on `gem.share.request`'s\nrules. Pass `null` to clear.\n\nCall it when the player enters a room joined by code —\n`setInviteParams({ room: joinCode })` — and `setInviteParams(null)` when\nthey leave it, so the window never invites anyone into a room that is\ngone. The latest call stands until the next; the SDK holds it and says it\nagain after a reconnect. An explicit `inviteToPlay({ params })` wins for\nthat one opening. Nothing comes back, and nothing changes on screen.",
|
|
36485
|
+
"deprecated": null,
|
|
36486
|
+
"params": [
|
|
36487
|
+
{
|
|
36488
|
+
"name": "params",
|
|
36489
|
+
"type": "Readonly<Record<string, string>> | null",
|
|
36490
|
+
"optional": false,
|
|
36491
|
+
"summary": null
|
|
36492
|
+
}
|
|
36493
|
+
],
|
|
36494
|
+
"returns": "whether the declaration reached the Arcade now. `false` outside\n the Arcade, without `playInvites`, before the bridge is up (it is sent on\n connect) and for params the share rules refuse, which are said on the\n console; the last declaration then stands.",
|
|
36495
|
+
"throws": null,
|
|
36496
|
+
"example": "```ts\n// On entering a room joined by code:\ngem.friends.setInviteParams({ room: joinCode });\n// ...and on leaving it:\ngem.friends.setInviteParams(null);\n```",
|
|
36497
|
+
"defaultValue": null,
|
|
36498
|
+
"guides": []
|
|
36499
|
+
},
|
|
36344
36500
|
{
|
|
36345
36501
|
"id": "FriendsApi.inbox",
|
|
36346
36502
|
"name": "inbox",
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,59 @@ 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
|
+
## 23.0.1
|
|
20
|
+
|
|
21
|
+
Upgrading from 23.0.0. Impact: none.
|
|
22
|
+
|
|
23
|
+
Nothing in this release asks anything of a game.
|
|
24
|
+
|
|
25
|
+
## 23.0.0
|
|
26
|
+
|
|
27
|
+
*Written from the API facts alone; no summary was generated for this release.*
|
|
28
|
+
|
|
29
|
+
Upgrading from 22.0.0. Impact: required.
|
|
30
|
+
|
|
31
|
+
### Must change
|
|
32
|
+
|
|
33
|
+
- PARENT_CONFORMANCE_VERSION changed its signature.
|
|
34
|
+
- GemFrame.requestArcade changed its signature.
|
|
35
|
+
- FRAME_TO_PARENT changed its signature.
|
|
36
|
+
- FrameMessage changed its signature.
|
|
37
|
+
- GemCapability changed its signature.
|
|
38
|
+
- PARENT_TO_FRAME changed its signature.
|
|
39
|
+
- ParentMessage changed its signature.
|
|
40
|
+
|
|
41
|
+
### New capability
|
|
42
|
+
|
|
43
|
+
- ParentFixture.channel was added.
|
|
44
|
+
- ParentFixture.gameId was added.
|
|
45
|
+
- ParentUnderTest.deliverSocket was added.
|
|
46
|
+
- CharactersApi.refreshHeld was added.
|
|
47
|
+
- FriendsApi.inviteToPlay was added.
|
|
48
|
+
- FriendsApi.setInviteParams was added.
|
|
49
|
+
- FriendsApiDependencies.inviteToPlay was added.
|
|
50
|
+
- FriendsApiDependencies.setInviteParams was added.
|
|
51
|
+
- PlayInviteRequest was added.
|
|
52
|
+
- FrameCallbacks.onInventoryChanged was added.
|
|
53
|
+
- FrameCallbacks.onPlayInviteResult was added.
|
|
54
|
+
- FrameOptions.onInventoryChanged was added.
|
|
55
|
+
- FrameOptions.onPlayInviteResult was added.
|
|
56
|
+
- GemHost.answerPlayInvite was added.
|
|
57
|
+
- GemHost.relayInventoryEvent was added.
|
|
58
|
+
- GemHost.sendInventoryChanged was added.
|
|
59
|
+
- GemInventoryChanged was added.
|
|
60
|
+
- GemInviteParams was added.
|
|
61
|
+
- GemPlayInviteResult was added.
|
|
62
|
+
- GemRequestPlayInvite was added.
|
|
63
|
+
- HostCallbacks.onInviteParams was added.
|
|
64
|
+
- HostCallbacks.onRequestPlayInvite was added.
|
|
65
|
+
- HostOptions.onInviteParams was added.
|
|
66
|
+
- HostOptions.onRequestPlayInvite was added.
|
|
67
|
+
- validateGemInventoryChanged was added.
|
|
68
|
+
- validateGemInviteParams was added.
|
|
69
|
+
- validateGemPlayInviteResult was added.
|
|
70
|
+
- validateGemRequestPlayInvite was added.
|
|
71
|
+
|
|
19
72
|
## 22.0.0
|
|
20
73
|
|
|
21
74
|
Upgrading from 21.1.1. Impact: required.
|
package/docs/friends.md
CHANGED
|
@@ -11,8 +11,9 @@ for (const friend of page.friends) {
|
|
|
11
11
|
}
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
That read,
|
|
15
|
-
confirms with the player and sends itself
|
|
14
|
+
That read, two asks — [sending a friend a challenge](#friend-challenges), which the Arcade
|
|
15
|
+
confirms with the player and sends itself, and [inviting friends to play](#inviting-a-friend-to-play),
|
|
16
|
+
which opens the Arcade's own invite sheet — and [reading the challenges the player
|
|
16
17
|
received](#reading-the-challenges-a-player-received) in your game are the whole surface. The
|
|
17
18
|
boundary around them is the thing to understand before you design against it.
|
|
18
19
|
|
|
@@ -36,8 +37,10 @@ Everything else about the friend graph stays on the Arcade, by design:
|
|
|
36
37
|
answered (`gem.friends.markAnswered`), which records that they played it and tells nobody.
|
|
37
38
|
- **No states.** Pending requests, blocks and relationship history do not appear. An accepted
|
|
38
39
|
friend appears; everyone else does not exist on this surface.
|
|
39
|
-
- **No presence, no invites.** Nothing here says who is online
|
|
40
|
-
is something happening
|
|
40
|
+
- **No presence, and no invites delivered to your game.** Nothing here says who is online, and an
|
|
41
|
+
invite is something happening _in_ the graph, so it is rendered where the graph lives. Your game
|
|
42
|
+
may [ask the Arcade to open its own invite sheet](#inviting-a-friend-to-play), and that is the
|
|
43
|
+
whole of it: the Arcade picks no one for you, and tells you nothing back. See the
|
|
41
44
|
deliberate-absence notes in [`rooms-and-matches.md`](rooms-and-matches.md). A friend challenge
|
|
42
45
|
is not an invite to play together: it is a card in the friend's Arcade inbox, written by the
|
|
43
46
|
platform from your template, that launches your game when they tap it — whenever they next open
|
|
@@ -46,7 +49,7 @@ Everything else about the friend graph stays on the Arcade, by design:
|
|
|
46
49
|
Two properties worth designing around rather than discovering:
|
|
47
50
|
|
|
48
51
|
- **Every accepted friend appears, whether or not they have played your game.** That is the
|
|
49
|
-
point, not an oversight — an invite-a-friend flow needs exactly the friends who have
|
|
52
|
+
point, not an oversight — an invite-a-friend flow needs exactly the friends who have _not_
|
|
50
53
|
played it. If you only want friends with progress in your game, join the list against your own
|
|
51
54
|
data (a friend's `playerId` is the same id leaderboard rows and transfers carry).
|
|
52
55
|
- **It is a player surface only.** On a dedicated server there is no `gem.friends`, and the route
|
|
@@ -132,12 +135,12 @@ the card already says who sent it, and a name in a param is one nobody checked.
|
|
|
132
135
|
`challenge()` resolves for every answer the player or the platform gives, and none of them
|
|
133
136
|
carries a reason:
|
|
134
137
|
|
|
135
|
-
| outcome
|
|
136
|
-
|
|
137
|
-
| `accepted`
|
|
138
|
-
| `cancelled`
|
|
139
|
-
| `refused`
|
|
140
|
-
| `unavailable` | the Arcade could not offer this, or no answer arrived
|
|
138
|
+
| outcome | what happened | what to show |
|
|
139
|
+
| ------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
|
|
140
|
+
| `accepted` | the player pressed Send and the platform took it | "Challenge sent", or nothing — the Arcade already said so |
|
|
141
|
+
| `cancelled` | the player closed the sheet, or another prompt already held the screen | **nothing** |
|
|
142
|
+
| `refused` | the platform will not send this one — a guest or signed out, not friends with that player, or no access to this channel | **nothing** |
|
|
143
|
+
| `unavailable` | the Arcade could not offer this, or no answer arrived | **nothing**, and never retry it on your own |
|
|
141
144
|
|
|
142
145
|
⚠️ **`accepted` is not a delivery receipt.** The platform may decide not to deliver a challenge
|
|
143
146
|
— the friend muted your game, has had enough challenges today, or cannot receive them — and
|
|
@@ -171,6 +174,7 @@ existing `instanceof GemApiError` handling keeps working:
|
|
|
171
174
|
digits, `_` and `-`, a plain object of strings and integers, at most 8 keys — so most of these
|
|
172
175
|
reach you before anything is shown to the player. `detail` is the platform's sentence, for your
|
|
173
176
|
console, never for a player.
|
|
177
|
+
|
|
174
178
|
- **`rate_limited`** — the player has sent too many. `retryAfterSeconds` is the earliest a fresh
|
|
175
179
|
press could succeed, and it can be up to a day.
|
|
176
180
|
|
|
@@ -199,9 +203,10 @@ const challenge = await gem.friends.launchedChallenge();
|
|
|
199
203
|
if (challenge?.template === 'beat_code_jar') {
|
|
200
204
|
// Check every param before it does anything, as you would a launch key: the template
|
|
201
205
|
// constrained it when it was sent, but your config may have changed since.
|
|
202
|
-
const jar =
|
|
203
|
-
|
|
204
|
-
|
|
206
|
+
const jar =
|
|
207
|
+
typeof challenge.params.jar === 'string' && /^[A-Z0-9]{7}$/.test(challenge.params.jar)
|
|
208
|
+
? challenge.params.jar
|
|
209
|
+
: null;
|
|
205
210
|
if (jar) {
|
|
206
211
|
startJar(jar, {
|
|
207
212
|
from: challenge.from, // who sent it, from the platform's records
|
|
@@ -273,6 +278,106 @@ while (page.nextCursor) {
|
|
|
273
278
|
|
|
274
279
|
For the challenge this launch carried, use `launchedChallenge()` rather than searching the inbox.
|
|
275
280
|
|
|
281
|
+
## Inviting a friend to play
|
|
282
|
+
|
|
283
|
+
Your game may ask the Arcade to open **its own invite sheet**, where the player picks friends to
|
|
284
|
+
invite to come and play your game with them, now:
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
inviteButton.hidden = !gem.frame.parentSupports('playInvites');
|
|
288
|
+
inviteButton.onclick = async () => {
|
|
289
|
+
const shown = await gem.friends.inviteToPlay();
|
|
290
|
+
if (!shown) showJoinCode(); // the sheet did not open: offer your own way in
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The Arcade lists the player's friends in its own chrome, pauses your game while the sheet is
|
|
295
|
+
open, and sends an invite each time the player presses Invite there. A friend who accepts is
|
|
296
|
+
opened into your game by the Arcade, and their own quick-join seats them in the player's room
|
|
297
|
+
when it has a seat — your game makes no call for it, is not told who is coming, and finds them in
|
|
298
|
+
the room as a member who shares the player's `groupId`. [Landing in a friend's
|
|
299
|
+
room](rooms-and-matches.md#5e-landing-in-a-friends-room-play-with) is how they arrive.
|
|
300
|
+
|
|
301
|
+
An invite is live, unlike a challenge: it reaches a friend who is on the Arcade now, and nothing
|
|
302
|
+
waits in an inbox. For "beat my score whenever you next play", send a
|
|
303
|
+
[friend challenge](#friend-challenges) instead.
|
|
304
|
+
|
|
305
|
+
### Invites and share links are one window
|
|
306
|
+
|
|
307
|
+
The sheet is the Arcade's **Friends window**. The player also opens it from the Arcade's own menu
|
|
308
|
+
over your game, without your game asking. It holds the friends to invite **and** a link to share
|
|
309
|
+
with anyone else, and both open your game the same way: with **your launch parameters**, on
|
|
310
|
+
exactly [`gem.share.request`'s rules](getting-started.md#linking-to-your-game). So a game whose
|
|
311
|
+
rooms are joined by a code says the code once, and a friend who accepts an invite and a stranger
|
|
312
|
+
who opens the shared link both arrive with it.
|
|
313
|
+
|
|
314
|
+
There are two ways to say it, for the window's two ways in:
|
|
315
|
+
|
|
316
|
+
- **`gem.friends.setInviteParams(params)`** — a standing declaration, for the window the PLAYER
|
|
317
|
+
opens from the Arcade's menu. Call it when the player enters a room joined by code, and
|
|
318
|
+
`setInviteParams(null)` when they leave it, so the window never invites anyone into a room that
|
|
319
|
+
is gone. The latest call stands; the SDK holds it and says it again after a reconnect.
|
|
320
|
+
- **`gem.friends.inviteToPlay({ params })`** — from your own Invite button, for that one opening.
|
|
321
|
+
It wins over the declaration. Omit `params` to use the declaration.
|
|
322
|
+
|
|
323
|
+
A room joined by code, end to end:
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
// The host makes the room and says what the window should open.
|
|
327
|
+
const room = await gem.rooms.create({/* your template */});
|
|
328
|
+
gem.friends.setInviteParams({ room: room.joinCode });
|
|
329
|
+
|
|
330
|
+
// Your own Invite button, if you draw one, needs nothing more:
|
|
331
|
+
inviteButton.onclick = () => gem.friends.inviteToPlay();
|
|
332
|
+
|
|
333
|
+
// Leaving the room clears it.
|
|
334
|
+
gem.friends.setInviteParams(null);
|
|
335
|
+
|
|
336
|
+
// Arrival is unchanged: whoever opens the link or accepts the invite launches your game
|
|
337
|
+
// with ?room=<code>, and you join as you already do for a shared link.
|
|
338
|
+
const code = new URLSearchParams(location.search).get('room');
|
|
339
|
+
if (code) await gem.rooms.joinByCode(code);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The params name what a link opens and never a person, so they tell the Arcade nothing about the
|
|
343
|
+
player's friends, and nothing about who was invited comes back. A param the share rules refuse —
|
|
344
|
+
a reserved key, more than 16, a bad key or value — is said on the console and nothing is sent:
|
|
345
|
+
`inviteToPlay` resolves `false`, and `setInviteParams` returns `false` and leaves the last
|
|
346
|
+
declaration standing. A game with a quick-join hub needs no params at all: the friend's
|
|
347
|
+
quick-join already seats them in the player's room (above).
|
|
348
|
+
|
|
349
|
+
### One answer, and nothing else
|
|
350
|
+
|
|
351
|
+
`inviteToPlay()` resolves one boolean. It never rejects.
|
|
352
|
+
|
|
353
|
+
| resolves | what happened |
|
|
354
|
+
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
355
|
+
| `true` | the Arcade opened its invite sheet |
|
|
356
|
+
| `false` | it did not: outside the Arcade or without `playInvites` (answered at once, with nothing asked), no press behind the ask, another sheet or prompt already open, a player the Arcade offers no invites to, or no answer arrived |
|
|
357
|
+
|
|
358
|
+
**What your game never learns:** whom the player invited, how many, whether any invite was sent,
|
|
359
|
+
delivered or accepted, or why the sheet did not open. Who a player invites is a fact about the
|
|
360
|
+
people they know, and whether an invite landed is a fact about somebody else's settings; neither
|
|
361
|
+
is your game's. `true` means a sheet opened, not that anybody was invited.
|
|
362
|
+
|
|
363
|
+
**Your game names nobody and writes nothing.** No player, no text: the friends on the sheet are
|
|
364
|
+
the Arcade's own list, and the words on the sheet and on the invite are the Arcade's. Your launch
|
|
365
|
+
params say what the link opens, as a share's do, and nothing else.
|
|
366
|
+
|
|
367
|
+
### Rules that are not optional
|
|
368
|
+
|
|
369
|
+
- **Call it only from a player's press.** The Arcade answers an ask with no press behind it
|
|
370
|
+
`false`, and shows nothing. Never on a timer, as a screen opens, or after a long `await`.
|
|
371
|
+
- **Never gate progress or a reward on it.** You cannot tell whether anyone was invited, by
|
|
372
|
+
design, so a reward for it is a reward for pressing a button.
|
|
373
|
+
- **Never retry it on your own.** `false` is an answer: offer your own way in instead, such as a
|
|
374
|
+
join code or a [share link](getting-started.md#linking-to-your-game).
|
|
375
|
+
- **One sheet at a time.** Calls made while an ask is open share its answer, and an ask that
|
|
376
|
+
arrives while the Arcade already shows a sheet is answered `false`.
|
|
377
|
+
- **Hide the button where it cannot work.** `gem.frame.parentSupports('playInvites')` is false
|
|
378
|
+
outside the Arcade — a local dev host included — and `inviteToPlay()` answers `false` there at
|
|
379
|
+
once, asking nothing. Try the real flow on your game's dev channel in the Arcade.
|
|
380
|
+
|
|
276
381
|
## Inspecting a player: game profiles
|
|
277
382
|
|
|
278
383
|
The "inspect this player" screen — tap a roster row, a leaderboard entry or a friend, see who
|
|
@@ -296,7 +401,7 @@ profile. Entries arrive ordered by key, each naming its slot's declared `visibil
|
|
|
296
401
|
|
|
297
402
|
Three properties to design around:
|
|
298
403
|
|
|
299
|
-
- **A game shapes its profile in the game config, not here.** Profile content
|
|
404
|
+
- **A game shapes its profile in the game config, not here.** Profile content _is_ the game's
|
|
300
405
|
showcase slots — save schemas declared with a `visibility` of `friends` or `public` — and the
|
|
301
406
|
write path is `gem.showcase.set` (or your dedicated server, for values a modified client must
|
|
302
407
|
not forge). There is no profile write on this surface, and identity is the player's own,
|
package/docs/getting-started.md
CHANGED
|
@@ -50,7 +50,7 @@ either way.
|
|
|
50
50
|
```jsonc
|
|
51
51
|
// package.json
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@indiegems/gem-web-sdk": "
|
|
53
|
+
"@indiegems/gem-web-sdk": "23.0.1"
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -308,6 +308,12 @@ whether they shared, cancelled, or closed the panel. Where the arcade handles it
|
|
|
308
308
|
`gem.share.available` is true; outside the arcade, before the bridge is up, and anywhere the
|
|
309
309
|
arcade does not share your game, it is false.
|
|
310
310
|
|
|
311
|
+
**The Arcade's Friends window shares the same link.** The player can open it from the Arcade's
|
|
312
|
+
menu over your game to invite friends and share a link, and both carry the launch parameters you
|
|
313
|
+
give `gem.friends.setInviteParams` or `gem.friends.inviteToPlay`, on the rules below. A game whose
|
|
314
|
+
rooms are joined by code declares the code there; see
|
|
315
|
+
[Invites and share links are one window](friends.md#invites-and-share-links-are-one-window).
|
|
316
|
+
|
|
311
317
|
`request` checks every parameter against the rules below **before** anything is sent, and
|
|
312
318
|
throws on the first one the arcade would refuse, naming it: more than 16, a key that breaks the
|
|
313
319
|
key rule or is one the arcade will not carry, or a value that is not a string, is too long, or
|
|
@@ -386,9 +392,10 @@ the arcade applies:
|
|
|
386
392
|
the sign-in return marker.
|
|
387
393
|
- Attribution and campaign keys: `_s`, `utm_*` (every key with that prefix, matched
|
|
388
394
|
case-sensitively, so UTM_X and utmost are yours), `fbclid`, `ttclid`, `gclid`, `gbraid`,
|
|
389
|
-
`wbraid`, `msclkid
|
|
390
|
-
|
|
391
|
-
your frame, restored from their
|
|
395
|
+
`wbraid`, `msclkid`, and `gem_link`, a search ad's link code on your store page. The arcade
|
|
396
|
+
strips the visitor's own campaign and click ids, but a link a game shares, or one anyone
|
|
397
|
+
writes by hand, can still put `utm_*` and the click ids into your frame, restored from their
|
|
398
|
+
escaped form.
|
|
392
399
|
|
|
393
400
|
A link your game shares can still carry most of them: see
|
|
394
401
|
[Sharing a reserved key](#sharing-a-reserved-key) below.
|