@indiegems/gem-web-sdk 7.5.0 → 7.6.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.
- package/CHANGELOG.md +886 -0
- package/README.md +1 -0
- package/dist/function.d.ts +978 -0
- package/dist/game.d.ts +4 -1
- package/dist/gem-web-sdk.bridge.js +1 -1
- package/dist/gem-web-sdk.esm.js +1 -1
- package/dist/gem-web-sdk.function.js +3 -0
- package/dist/gem-web-sdk.game.js +2 -2
- package/dist/gem-web-sdk.min.js +2 -2
- package/dist/gem-web-sdk.sdk.js +2 -2
- package/dist/gem-web-sdk.server.js +1 -1
- package/dist/sdk.d.ts +4 -1
- package/docs/analytics.md +2 -3
- package/docs/api/api-reference-game.md +60 -33
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +29 -12
- package/docs/changelog.md +886 -0
- package/docs/getting-started.md +8 -6
- package/docs/manifest.json +18 -8
- package/docs/server-bundles.md +2 -2
- package/docs/serverless-functions-js.md +139 -0
- package/docs/what-a-game-must-do.md +39 -26
- package/package.json +7 -2
- package/schema/bridge.v1.json +1 -1
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": "7.
|
|
53
|
+
"@indiegems/gem-web-sdk": "7.6.0"
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -157,11 +157,13 @@ your reports arrive with no picture. Two-dimensional canvas games need nothing h
|
|
|
157
157
|
covers overriding the capture and attaching your own debug state, which is the part actually
|
|
158
158
|
worth spending effort on.
|
|
159
159
|
|
|
160
|
-
**There is no offline mode.** A game must be framed by
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
**There is no offline mode.** A game must be framed by a page that speaks the bridge: opened
|
|
161
|
+
at the top level, `createGem` throws `GemUnframedError`, which says how to run it. To run framed
|
|
162
|
+
without publishing, use the Gem dev host, signed in to `gemctl`: `npm run dev` in a project the
|
|
163
|
+
Gem tools scaffolded (`gemctl serve start . --dev`, your vite server with hot reload), or
|
|
164
|
+
`gemctl serve start .` for a build. The Gem Editor's Play frames it too, and the SDK
|
|
165
|
+
repository's `examples/dev-harness/` is a local parent page that frames your dev server. Never
|
|
166
|
+
open the dev server's own address.
|
|
165
167
|
|
|
166
168
|
## 5. Tell the arcade when you are ready
|
|
167
169
|
|
package/docs/manifest.json
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"file": "analytics.md",
|
|
16
|
-
"sha256": "
|
|
17
|
-
"updated": "2026-10-
|
|
16
|
+
"sha256": "57fb07c3d649c2758a35660707d31b05164b363e9a425156978aed394d086339",
|
|
17
|
+
"updated": "2026-10-02T14:04:37Z"
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"file": "arcade-economy.md",
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
"sha256": "cf54939a0fcee1f5a6e11b9de52b5ed498ec8c373683e92aa5bc6654b12eb0ab",
|
|
32
32
|
"updated": "2026-10-01T03:49:20Z"
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
"file": "changelog.md",
|
|
36
|
+
"sha256": "02083c9ed1d62baed5a297ca71b602608afa90320afb73a21ff03187da11346f",
|
|
37
|
+
"updated": "2026-10-02T04:41:46Z"
|
|
38
|
+
},
|
|
34
39
|
{
|
|
35
40
|
"file": "cross-promotion.md",
|
|
36
41
|
"sha256": "3a8121a9c973e4a1bfed076a3226d139f8b4e38a173a06ea9f08000c72bca4b4",
|
|
@@ -63,8 +68,8 @@
|
|
|
63
68
|
},
|
|
64
69
|
{
|
|
65
70
|
"file": "getting-started.md",
|
|
66
|
-
"sha256": "
|
|
67
|
-
"updated": "2026-10-
|
|
71
|
+
"sha256": "fd859a01988ecf57c857dcfba9958a8a32a65ff2a8ecfaf55f33f9045d368180",
|
|
72
|
+
"updated": "2026-10-02T17:28:15Z"
|
|
68
73
|
},
|
|
69
74
|
{
|
|
70
75
|
"file": "inventory-and-catalogs.md",
|
|
@@ -113,8 +118,13 @@
|
|
|
113
118
|
},
|
|
114
119
|
{
|
|
115
120
|
"file": "server-bundles.md",
|
|
116
|
-
"sha256": "
|
|
117
|
-
"updated": "2026-10-
|
|
121
|
+
"sha256": "8190da0484b7e1ae6f636068b06a43729f3ef132488f5a0cfd2fc5c2988bc55b",
|
|
122
|
+
"updated": "2026-10-02T17:28:15Z"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"file": "serverless-functions-js.md",
|
|
126
|
+
"sha256": "86b0f9f0b742a662ebb78b1538cbbc399309725faf22f46f613b63b5e0864082",
|
|
127
|
+
"updated": "2026-10-02T17:17:29Z"
|
|
118
128
|
},
|
|
119
129
|
{
|
|
120
130
|
"file": "serverless-functions.md",
|
|
@@ -128,8 +138,8 @@
|
|
|
128
138
|
},
|
|
129
139
|
{
|
|
130
140
|
"file": "what-a-game-must-do.md",
|
|
131
|
-
"sha256": "
|
|
132
|
-
"updated": "2026-10-
|
|
141
|
+
"sha256": "a0a2952a2b16b43b0af831ff58ad8fabe47b414f57fccd2cb2d866861a65465e",
|
|
142
|
+
"updated": "2026-10-02T14:04:37Z"
|
|
133
143
|
}
|
|
134
144
|
]
|
|
135
145
|
}
|
package/docs/server-bundles.md
CHANGED
|
@@ -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/v7.
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/v7.
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v7.6.0/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v7.6.0/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Serverless functions in JavaScript
|
|
2
|
+
|
|
3
|
+
<!-- gem-web: unpublished — the platform does not run JavaScript functions yet -->
|
|
4
|
+
|
|
5
|
+
A serverless function can be written in JavaScript or TypeScript instead of Rust: the language your
|
|
6
|
+
game is already written in, so the verification code in your browser game moves into a function
|
|
7
|
+
with no change beyond bundling. You upload one bundled ES module, and the platform runs it on a
|
|
8
|
+
JavaScript engine of its own.
|
|
9
|
+
|
|
10
|
+
Everything [serverless-functions.md](serverless-functions.md) says about what a function is for,
|
|
11
|
+
what it may change, its limits, channels, and calling it from your game holds here unchanged. This
|
|
12
|
+
page covers only what is different: how you write one, how the SDK behaves inside it, and how
|
|
13
|
+
publishing checks it.
|
|
14
|
+
|
|
15
|
+
## 1. The contract
|
|
16
|
+
|
|
17
|
+
The same as a component's: bytes in, bytes out, one fresh instance per call, nothing kept between
|
|
18
|
+
calls. Your module exports **exactly one** of two functions, and which one decides how the bytes
|
|
19
|
+
are read and written:
|
|
20
|
+
|
|
21
|
+
| Export | Input | Return |
|
|
22
|
+
| ------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `handle(input)` | `Uint8Array`: the request body, byte for byte | `Uint8Array`, `ArrayBuffer` or `string` (sent as UTF-8); anything else is an error |
|
|
24
|
+
| `handleJson(input)` | the body parsed as JSON; an empty body is `undefined` | any JSON-serializable value, sent as its JSON text; `undefined` is sent as `null`; a `BigInt` is an error |
|
|
25
|
+
|
|
26
|
+
Either may be `async`; the platform waits for the promise. Exporting neither, or both, is refused
|
|
27
|
+
when you upload. A caller cannot tell which kind of function answered: `gem.functions.invoke`, the
|
|
28
|
+
limits and the refusals in serverless-functions.md are identical for both.
|
|
29
|
+
|
|
30
|
+
**A thrown exception is `Err`.** The call fails with `function_error`, and `err.functionMessage` is
|
|
31
|
+
the exception's `name: message` (a thrown value that is not an `Error` is its `String()`). The
|
|
32
|
+
message and the stack go to the platform's log for the invocation, never to the caller. A
|
|
33
|
+
`handleJson` whose input is not JSON, or whose return value JSON cannot carry, fails the same way.
|
|
34
|
+
|
|
35
|
+
**`console.*` reaches the invocation's log** at the matching level (`debug`, `info`, `warn`,
|
|
36
|
+
`error`); `console.log` and every other method log at `info`. As with a component's `log`, nothing
|
|
37
|
+
reads it back to you: the invocation id is what you quote about one call.
|
|
38
|
+
|
|
39
|
+
## 2. Writing one
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
gemctl function init --lang js functions/score-check
|
|
43
|
+
cd functions/score-check && npm install && npm run build
|
|
44
|
+
gemctl function upload dist/function.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**`init --lang js`** writes a `package.json` whose `build` script type-checks and then bundles to
|
|
48
|
+
one ES module, a TypeScript starter handler and a `tsconfig.json`. gemctl runs neither step:
|
|
49
|
+
`npm run build` is yours, the way `cargo build` is for a component.
|
|
50
|
+
|
|
51
|
+
A handler looks like this:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { createGem } from '@indiegems/gem-web-sdk/function';
|
|
55
|
+
|
|
56
|
+
export async function handleJson(claim: { level: number }) {
|
|
57
|
+
const gem = createGem();
|
|
58
|
+
console.info(`level ${claim.level} claimed by ${gem.identity?.playerId ?? 'a game server'}`);
|
|
59
|
+
// Decide from what the player cannot forge, and make any write through `gem`
|
|
60
|
+
// here, before answering: the answer itself is only advice to the browser.
|
|
61
|
+
return { accepted: false };
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The package is the one your game already depends on, installed as
|
|
66
|
+
[getting-started.md](getting-started.md) shows. What makes a check worth running is
|
|
67
|
+
serverless-functions.md section 1, and it applies unchanged: a save or a setting the player's own
|
|
68
|
+
browser can write is the player's claim, not evidence.
|
|
69
|
+
|
|
70
|
+
**Imports.** The module may import the SDK and nothing else, under any of the three names a game
|
|
71
|
+
already uses: `@indiegems/gem-web-sdk/function`, `@indiegems/gem-web-sdk/sdk`, or
|
|
72
|
+
`@indiegems/gem-web-sdk/game`. Inside a function all three are the same module, the function
|
|
73
|
+
entry, so shared code keeps its import. A name that module does not export, such as
|
|
74
|
+
`defineServer`, fails every call with `function_error`. Everything else your code needs is bundled
|
|
75
|
+
in, which is what the starter's build does. An `import()` at runtime is refused at upload.
|
|
76
|
+
Top-level `await` is allowed and runs on every call.
|
|
77
|
+
|
|
78
|
+
**What the engine gives you.** `Date`, `performance`, `Math.random`, `crypto.getRandomValues`,
|
|
79
|
+
`crypto.randomUUID`, `TextEncoder`, `URL` and `fetch` all work. `fetch` reaches only the platform
|
|
80
|
+
API, through the SDK, and the save links the platform hands you; any other URL is refused. A timer
|
|
81
|
+
waits at most 10 ms: a function has no reason to sleep, and a long one would run out the wall
|
|
82
|
+
clock.
|
|
83
|
+
|
|
84
|
+
**The SDK version your function runs is the one the platform bundles**, not the one in your
|
|
85
|
+
`package.json`; the starter pins the matching version. A method added in a newer SDK reaches a
|
|
86
|
+
function when the platform's engine picks that version up.
|
|
87
|
+
|
|
88
|
+
## 3. The SDK inside a function
|
|
89
|
+
|
|
90
|
+
`createGem()` from `@indiegems/gem-web-sdk/function` is synchronous, `awaitBridge()` and
|
|
91
|
+
`awaitIdentity()` resolve at once, and `gem.me` is the player who called. So a game's boot sequence
|
|
92
|
+
runs unchanged, and the same `gem.me.saves.get`, `gem.leaderboards.get` and `gem.profiles.of` calls
|
|
93
|
+
work, with the same types and the same errors. The function's game and channel come from the call;
|
|
94
|
+
nothing you write names them. `gem.context` is who called, for which game and channel, and
|
|
95
|
+
`gem.identity` is the same three fields a browser game reads from its own `gem.identity`.
|
|
96
|
+
|
|
97
|
+
What a browser game has that is backed by the page or the arcade (ads, purchases, rooms, device
|
|
98
|
+
settings, sharing) is not on `gem` inside a function. Four more differences, each following from
|
|
99
|
+
how a function reaches the platform:
|
|
100
|
+
|
|
101
|
+
- **`gem.me` is the invoker.** When a game server invoked the function there is no player, and
|
|
102
|
+
reading `gem.me` or `gem.transfers` throws saying so; `gem.identity` is `null`. Which writes a
|
|
103
|
+
player-invoked function may make is unchanged (serverless-functions.md section 2).
|
|
104
|
+
- **No conditional requests, and no retries.** The platform call a function makes carries no
|
|
105
|
+
headers, so an `ifMatch` or `ifNoneMatch` cannot travel. A call carrying one is refused with
|
|
106
|
+
`TransportRefusal` naming the option, rather than sent as an overwrite that checks nothing.
|
|
107
|
+
Nothing is retried either: a retry is another platform call and a wait is your function's wall
|
|
108
|
+
clock, so your code decides.
|
|
109
|
+
- **`gem.functions` is absent.** A function may not invoke a function.
|
|
110
|
+
- **`gem.gameConfig` is absent.** The config document is not reachable from a function, so
|
|
111
|
+
`gem.me.currencies.get()` takes a currency id rather than a slug.
|
|
112
|
+
|
|
113
|
+
`gem.friends.challenge()` answers `unavailable` without asking anything, because there is nobody in
|
|
114
|
+
front of a screen to confirm it, and `gem.platform.earnAllowance.on()` never fires.
|
|
115
|
+
|
|
116
|
+
⚠️ **Flush analytics before you return.** A page flushes on a timer and as it closes; a function
|
|
117
|
+
ends the moment your handler settles, so `await gem.analytics.flush()` before you return, or the
|
|
118
|
+
events may be lost.
|
|
119
|
+
|
|
120
|
+
## 4. Check before you upload
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
gemctl function check dist/function.js
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`check` runs the platform's publish-time validation for JavaScript on your machine, and an upload
|
|
127
|
+
answers `400 source_invalid` with the same `details.reason`:
|
|
128
|
+
|
|
129
|
+
| Reason | What it usually is | What to change |
|
|
130
|
+
| ------------------ | ------------------------------------------------------------------------- | ------------------------------------------------- |
|
|
131
|
+
| `not_a_module` | A parse error, with its line and column, or a file that is not UTF-8 | Fix the syntax; bundle to ES2022 |
|
|
132
|
+
| `forbidden_import` | An `import` or `export … from` of anything but the SDK, or an `import()` | Bundle the dependency in; drop the dynamic import |
|
|
133
|
+
| `missing_handle` | Neither `handle` nor `handleJson` is exported (`export *` does not count) | Export one of them from the module itself |
|
|
134
|
+
| `ambiguous_handle` | Both are exported | Keep one |
|
|
135
|
+
| `too_large` | Over the size limit, the same one a component has | Bundle less; check what your build pulled in |
|
|
136
|
+
|
|
137
|
+
An upload also answers `source_invalid`, rather than `component_invalid`, for `not_uploaded`,
|
|
138
|
+
`size_mismatch` and `sha256_mismatch`: upload again. Promoting, channels and invoking are the same
|
|
139
|
+
as for a component (serverless-functions.md sections 4 to 7).
|
|
@@ -187,9 +187,9 @@ try {
|
|
|
187
187
|
|
|
188
188
|
`awaitBridge()` resolves when the bridge leaves `waiting` — connected, refused or closed — or at a
|
|
189
189
|
bounded timeout (default 10s), with the state it settled on. **Proceed on every outcome.**
|
|
190
|
-
`connected` is
|
|
191
|
-
anyway. The wait costs nothing when the handshake is quick: it settles the moment the frame
|
|
192
|
-
connects
|
|
190
|
+
`connected` is a Gem host listening; anything else is a boot with no session, which your game must
|
|
191
|
+
survive anyway. The wait costs nothing when the handshake is quick: it settles the moment the frame
|
|
192
|
+
connects, and at once when the page that framed it is refused.
|
|
193
193
|
|
|
194
194
|
The measured stakes, from the first game to hit this on an iPhone: listener up at ~300ms, then a
|
|
195
195
|
main-thread block — the parent's offer, queued behind the block, was delivered twenty seconds later
|
|
@@ -515,8 +515,8 @@ them**, so the whole migration for a stylesheet is one declaration per edge:
|
|
|
515
515
|
```
|
|
516
516
|
|
|
517
517
|
The `env()` fallback is not decoration. Under a parent that has not sent the message — an older
|
|
518
|
-
arcade, a third-party embedder
|
|
519
|
-
|
|
518
|
+
arcade or dev host, a third-party embedder — the properties are never set and the browser's own
|
|
519
|
+
answer stands, which is what your game did before.
|
|
520
520
|
Under a parent that has, the variable wins. One line, correct in both worlds. The four names are
|
|
521
521
|
`--gem-safe-top`, `--gem-safe-right`, `--gem-safe-bottom` and `--gem-safe-left`.
|
|
522
522
|
|
|
@@ -538,17 +538,21 @@ the `var()` above, or read `gem.viewport`. The release checklist looks for the b
|
|
|
538
538
|
|
|
539
539
|
### The arcade's own controls: reserved areas
|
|
540
540
|
|
|
541
|
-
On a phone your game is full-bleed, and the arcade keeps one
|
|
542
|
-
|
|
543
|
-
the player's wallets (Gems, Tickets, their achievement score) while they are
|
|
544
|
-
[shown](#the-arcades-wallet-show-it-never-draw-it). The cluster stays in the top-left and runs along
|
|
545
|
-
the top row at most about three quarters of the way across on the narrowest phone — but do not
|
|
546
|
-
hard-code a size: read it, as below. Those rectangles of your frame are **reserved**: keep essential
|
|
547
|
-
UI and every touch target out of them.
|
|
541
|
+
On a phone your game is full-bleed, and the arcade keeps one row of its own across the top of it,
|
|
542
|
+
with the middle left open for a centred camera:
|
|
548
543
|
|
|
549
|
-
**
|
|
550
|
-
|
|
551
|
-
|
|
544
|
+
- **Top-left: the Back tab** — the Gem Arcade wordmark, always there. **Do not put a back arrow of
|
|
545
|
+
your own top-left**: it would sit under the arcade's tab, which leaves the game, while yours would
|
|
546
|
+
mean something else.
|
|
547
|
+
- **Top-right: the player's wallets** (Gems, Tickets, their achievement score) while your screen
|
|
548
|
+
[shows them](#the-arcades-wallet-show-it-never-draw-it). Put your own settings, pause or menu
|
|
549
|
+
button top-right **below** them, at `top: var(--gem-reserved-tr-height, …)` — and when gameplay
|
|
550
|
+
hides the wallets with `gem.setWalletVisible(false)`, that rectangle shrinks to nothing and your
|
|
551
|
+
button moves up into the corner on its own.
|
|
552
|
+
|
|
553
|
+
Those rectangles of your frame are **reserved**: keep essential UI and every touch target out of
|
|
554
|
+
them. **Never hard-code their size or height.** They differ by phone, and in native fullscreen the
|
|
555
|
+
whole row may rise into the camera band — read them, as below.
|
|
552
556
|
|
|
553
557
|
An inset cannot say that — as a top inset, a tab a few dozen pixels wide would take your whole top
|
|
554
558
|
strip. So `gem.viewport` also carries the rectangles, in the same CSS pixels of your frame, measured
|
|
@@ -567,13 +571,21 @@ from its top-left corner:
|
|
|
567
571
|
reserved rectangles reach in from its two edges. Push the corner's UI clear of it:
|
|
568
572
|
|
|
569
573
|
```css
|
|
570
|
-
/*
|
|
574
|
+
/* settings in the top-right corner, below the wallets — and up in the corner when they are hidden */
|
|
575
|
+
#settings {
|
|
576
|
+
top: max(var(--gem-safe-top, env(safe-area-inset-top, 0px)), var(--gem-reserved-tr-height, 0px));
|
|
577
|
+
right: var(--gem-safe-right, 0px);
|
|
578
|
+
}
|
|
579
|
+
/* a score in the top-left corner, below the tab */
|
|
571
580
|
#score {
|
|
572
581
|
top: max(var(--gem-safe-top, env(safe-area-inset-top, 0px)), var(--gem-reserved-tl-height, 0px));
|
|
573
582
|
left: var(--gem-safe-left, 0px);
|
|
574
583
|
}
|
|
575
|
-
/* or a row along the top,
|
|
576
|
-
#top-bar {
|
|
584
|
+
/* or a row along the top, between the two */
|
|
585
|
+
#top-bar {
|
|
586
|
+
padding-left: var(--gem-reserved-tl-width, 0px);
|
|
587
|
+
padding-right: var(--gem-reserved-tr-width, 0px);
|
|
588
|
+
}
|
|
577
589
|
```
|
|
578
590
|
|
|
579
591
|
All eight are written on every push, `0px` for a clear corner, so a rotation never leaves a stale
|
|
@@ -581,9 +593,9 @@ value; they are never set under an embedder that sends no viewport, which is wha
|
|
|
581
593
|
fallback is for. A rectangle belongs to the corner its centre lies nearest to. For a HUD laid out
|
|
582
594
|
from script, read `gem.viewport.reserved` — `[]` when nothing is covered.
|
|
583
595
|
|
|
584
|
-
**Lay out against it whenever it changes.** Shown wallets
|
|
585
|
-
|
|
586
|
-
are hidden never reserves anything — it floats over your game and moves nothing.
|
|
596
|
+
**Lay out against it whenever it changes.** Shown wallets reserve the top-right corner; hiding them
|
|
597
|
+
shrinks it to nothing, and only the tab's top-left rectangle is left. A wallet the arcade pops up for
|
|
598
|
+
a moment while they are hidden never reserves anything — it floats over your game and moves nothing.
|
|
587
599
|
|
|
588
600
|
**Older SDKs get it folded into the top inset.** A game on an SDK from before reserved areas cannot
|
|
589
601
|
read the rectangles, so the arcade sends it none and instead raises `insets.top` — and so
|
|
@@ -764,7 +776,8 @@ and the release checklist checks that it does.
|
|
|
764
776
|
|
|
765
777
|
The player's platform balances — Gems, Tickets (`ARCADE_TICKETS`) and their achievement score —
|
|
766
778
|
belong to the Arcade. **Your game never draws one**, and never shows a pending state for one: the
|
|
767
|
-
Arcade takes every figure from the platform and draws the wallets itself,
|
|
779
|
+
Arcade takes every figure from the platform and draws the wallets itself, in the top-right corner
|
|
780
|
+
of your frame on a phone.
|
|
768
781
|
|
|
769
782
|
**They are shown unless your game hides them.** A game that does nothing has every wallet on show
|
|
770
783
|
the whole time. The one rule: **hide them during gameplay, show them on your menus.**
|
|
@@ -780,10 +793,10 @@ pauseMenu.onOpen = () => gem.setWalletVisible(true);
|
|
|
780
793
|
- **It is screen state, not a toggle.** Each call is the whole answer and replaces the last. Call it
|
|
781
794
|
as often as you like: before the Arcade connects the state is held and sent when it does, and
|
|
782
795
|
again after any reconnect; a repeat of the current state is not re-sent.
|
|
783
|
-
- **Shown wallets take space; a pop does not.** Shown, they
|
|
784
|
-
[
|
|
785
|
-
|
|
786
|
-
and never moves its layout.
|
|
796
|
+
- **Shown wallets take space; a pop does not.** Shown, they reserve the top-right
|
|
797
|
+
[corner](#the-arcades-own-controls-reserved-areas), so lay your menu out against
|
|
798
|
+
`--gem-reserved-tr-*` — your settings button sits below them; hidden, the corner is yours again and
|
|
799
|
+
the same `var()` moves the button up. A pop floats over your game and never moves its layout.
|
|
787
800
|
- **Narrowing is optional.** On a screen that sells in one currency — a ticket store — you may show
|
|
788
801
|
only that one: `gem.setWalletVisible(true, { only: ['ARCADE_TICKETS'] })`, with `'score'` for the
|
|
789
802
|
achievement score. Use it only there; leaving it out shows every wallet, which is always safe.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indiegems/gem-web-sdk",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.6.0",
|
|
4
4
|
"description": "postMessage bridge contract between gem-web and browser-playable games",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
"types": "./dist/server-api.d.ts",
|
|
32
32
|
"import": "./dist/gem-web-sdk.server-api.js"
|
|
33
33
|
},
|
|
34
|
+
"./function": {
|
|
35
|
+
"types": "./dist/function.d.ts",
|
|
36
|
+
"import": "./dist/gem-web-sdk.function.js"
|
|
37
|
+
},
|
|
34
38
|
"./schema": "./schema/bridge.v1.json",
|
|
35
39
|
"./brand/*": "./brand/*"
|
|
36
40
|
},
|
|
@@ -45,6 +49,7 @@
|
|
|
45
49
|
"docs/*.md",
|
|
46
50
|
"docs/manifest.json",
|
|
47
51
|
"docs/api/*.md",
|
|
48
|
-
"docs/api/reference.json"
|
|
52
|
+
"docs/api/reference.json",
|
|
53
|
+
"CHANGELOG.md"
|
|
49
54
|
]
|
|
50
55
|
}
|
package/schema/bridge.v1.json
CHANGED
|
@@ -894,7 +894,7 @@
|
|
|
894
894
|
"AMENDED, DELIBERATELY: this message once carried insets only. It now also",
|
|
895
895
|
"carries `reserved`, rectangles of the frame covered by the parent's own",
|
|
896
896
|
"controls floating over the game (a Back tab in a corner, the wallet chips",
|
|
897
|
-
"shown
|
|
897
|
+
"shown in another). An inset can only describe a full-width or full-height band,",
|
|
898
898
|
"and a corner tab expressed as a top inset would take the whole top strip",
|
|
899
899
|
"from a full-bleed game to clear a control a few dozen pixels wide. A",
|
|
900
900
|
"rectangle says exactly which part, and still says nothing about what the",
|