@indiegems/gem-web-sdk 19.0.2 → 20.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/conformance.d.ts +1 -1
- package/dist/game.d.ts +51 -3
- 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 +57 -6
- package/dist/sdk.d.ts +51 -3
- package/docs/api/api-reference-function.md +1 -1
- package/docs/api/api-reference-game.md +19 -17
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +7 -7
- package/docs/arcade-and-store.md +48 -43
- package/docs/changelog.md +26 -0
- package/docs/dedicated-server-flow.md +30 -2
- package/docs/getting-started.md +3 -3
- package/docs/inventory-and-catalogs.md +23 -22
- package/docs/manifest.json +18 -18
- package/docs/match-session.md +9 -0
- package/docs/p2p-and-parties.md +79 -6
- package/docs/server-bundles.md +2 -2
- package/docs/what-a-game-must-do.md +8 -5
- package/package.json +1 -1
- package/schema/bridge.v1.json +122 -60
- package/schema/capabilities.public.json +6 -0
|
@@ -591,7 +591,6 @@ everything under `platform_prices` may carry a price, and a currency under neith
|
|
|
591
591
|
all. The grant rows also carry the platform's ceilings, and a game does not declare its own; a
|
|
592
592
|
`max_per_window` on a mapping is ignored, with a warning.
|
|
593
593
|
|
|
594
|
-
|
|
595
594
|
A browser-only game cannot award a platform currency without a server **or a serverless function**.
|
|
596
595
|
That is the price of the platform never trusting a client. A game with no dedicated server writes
|
|
597
596
|
the source progression from a function the player calls, on a progression that opts in with
|
|
@@ -657,10 +656,10 @@ subject. [brand-art.md](brand-art.md) has both, how to draw them, and the rest o
|
|
|
657
656
|
|
|
658
657
|
Every game can choose between two purchase owners for the same published platform price:
|
|
659
658
|
|
|
660
|
-
| Call
|
|
661
|
-
|
|
|
662
|
-
| `gem.purchases.request(...)`
|
|
663
|
-
| `gem.me.mutations.purchase(...)` | your game
|
|
659
|
+
| Call | Who owns confirmation | Transport | Result |
|
|
660
|
+
| -------------------------------- | --------------------- | ------------------------------------------------------------- | ---------------------------------------- |
|
|
661
|
+
| `gem.purchases.request(...)` | the platform | postMessage bridge; platform sheet always opens | `ok`, `declined`, or `unavailable` |
|
|
662
|
+
| `gem.me.mutations.purchase(...)` | your game | direct authenticated SDK request; no bridge or platform sheet | an order-style result or a `GemApiError` |
|
|
664
663
|
|
|
665
664
|
There is no approval step. Both paths are available to every game.
|
|
666
665
|
|
|
@@ -818,21 +817,21 @@ local lookup. The purchase catalog is keyed by ids. Recipes also require `catalo
|
|
|
818
817
|
One vocabulary covers both paths: the sheet's `res.reason` and the direct path's `err.code` are
|
|
819
818
|
the same strings.
|
|
820
819
|
|
|
821
|
-
| Code
|
|
822
|
-
|
|
|
823
|
-
| `unknown_currency`
|
|
824
|
-
| `currency_not_in_game_payable`
|
|
825
|
-
| `not_purchasable`
|
|
826
|
-
| `no_price_in_currency`
|
|
827
|
-
| `busy`
|
|
828
|
-
| `cancelled`
|
|
829
|
-
| `insufficient_funds`
|
|
830
|
-
| `price_mismatch`, `config_version_stale`
|
|
831
|
-
| `platform_currency_unavailable`
|
|
832
|
-
| `not_available`
|
|
833
|
-
| `recipe_locked`
|
|
834
|
-
| `recipe_on_cooldown`, `recipe_max_uses_exceeded`, `item_max_stack_exceeded`, `recipe_costs_unmet`, `quantity_invalid` | the recipe or item refused this purchase before any charge
|
|
835
|
-
| `error`
|
|
820
|
+
| Code | What happened | What to do |
|
|
821
|
+
| --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
822
|
+
| `unknown_currency` | the slug is not an active platform currency | write `ARCADE_TICKETS` or `GEMS`; the direct path's message lists what is active |
|
|
823
|
+
| `currency_not_in_game_payable` | the currency exists but cannot pay for game items | price in a currency `gemctl platform config` lists under `platform_prices` |
|
|
824
|
+
| `not_purchasable` | the target is not in the purchase catalog on this channel, or cannot be bought | send the id (not the slug); promote the config version that prices it |
|
|
825
|
+
| `no_price_in_currency` | the target has no price in that currency | ask in the currency it is priced in, or add the price |
|
|
826
|
+
| `busy` | a purchase is already in flight, or a sheet is open | ask again only from a fresh player action |
|
|
827
|
+
| `cancelled` | the player dismissed the sheet | nothing — return to the shop |
|
|
828
|
+
| `insufficient_funds` | the player cannot afford it | say so in your shop's words; never draw a top-up screen |
|
|
829
|
+
| `price_mismatch`, `config_version_stale` | the price or config changed since your catalog read; nothing was charged | re-read the catalog and re-render |
|
|
830
|
+
| `platform_currency_unavailable` | the currency is not available to this purchase right now | show the store as unavailable |
|
|
831
|
+
| `not_available` | the recipe is outside its availability window, or inactive; nothing was charged | hide or disable the row until its window opens |
|
|
832
|
+
| `recipe_locked` | the recipe's prerequisites are not met; nothing was charged | show the lock from `recipeAvailability().lockReasons` |
|
|
833
|
+
| `recipe_on_cooldown`, `recipe_max_uses_exceeded`, `item_max_stack_exceeded`, `recipe_costs_unmet`, `quantity_invalid` | the recipe or item refused this purchase before any charge | reflect it on the row; `recipeAvailability` has the detail |
|
|
834
|
+
| `error` | anything else, or no reliable answer | one neutral "store unavailable"; after `unavailable`, re-read inventory |
|
|
836
835
|
|
|
837
836
|
Reasons are for your shop's state and your logs. Branch money on `outcome` and on a re-read of
|
|
838
837
|
inventory, never on a reason, and do not retry because of one.
|
|
@@ -964,7 +963,9 @@ const gems = allowances.find((a) => a.currencySlug === 'GEMS');
|
|
|
964
963
|
// 0 or less once `resetsAt` has passed: the read is stale, so say nothing about when.
|
|
965
964
|
const hours = gems?.resetsAt ? Math.ceil((gems.resetsAt - Date.now()) / 3_600_000) : 0;
|
|
966
965
|
if (gems && gems.remaining > 0) {
|
|
967
|
-
hud.setText(
|
|
966
|
+
hud.setText(
|
|
967
|
+
`You can still earn ${gems.remaining} Gems` + (hours > 0 ? `, more in ${hours}h` : ''),
|
|
968
|
+
);
|
|
968
969
|
} else if (gems?.resetsAt) {
|
|
969
970
|
// Spent for now: say when more arrives, never "0 left".
|
|
970
971
|
hud.setText(hours > 0 ? `More Gems in ${hours}h` : 'More Gems soon');
|
|
@@ -1001,7 +1002,7 @@ end of the reported period when both of the platform's ceilings are spent.
|
|
|
1001
1002
|
|
|
1002
1003
|
**Display only. Never gate gameplay or a grant on it.**
|
|
1003
1004
|
|
|
1004
|
-
- ✅ ``
|
|
1005
|
+
- ✅ ``hud.setText(`${gems.remaining} Gems left to earn today`)``
|
|
1005
1006
|
- ❌ `if (gems.remaining > 0) awardDailyBonus()`
|
|
1006
1007
|
|
|
1007
1008
|
Grant anyway: the platform trims every grant to what is left, and the receipt is the authority on
|
package/docs/manifest.json
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"file": "arcade-and-store.md",
|
|
21
|
-
"sha256": "
|
|
22
|
-
"updated": "2026-
|
|
21
|
+
"sha256": "79bd7829d5a6577d3619d7fd831b74065df883e11eadd1b8153754d97a88474a",
|
|
22
|
+
"updated": "2026-11-02T18:42:37Z"
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"file": "arcade-avatars.md",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"file": "changelog.md",
|
|
51
|
-
"sha256": "
|
|
52
|
-
"updated": "2026-11-
|
|
51
|
+
"sha256": "8a292ee18fe7f910be0af049d96df66ccebf5c810fe5495c11d5352f7ee121f4",
|
|
52
|
+
"updated": "2026-11-03T00:39:38Z"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"file": "daily-challenges.md",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
"file": "dedicated-server-flow.md",
|
|
61
|
-
"sha256": "
|
|
62
|
-
"updated": "2026-10-
|
|
61
|
+
"sha256": "4a51622df43be68b5b9a88ae89907972090e64fd9b793ba74e5daa576489281c",
|
|
62
|
+
"updated": "2026-10-27T02:41:17Z"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"file": "deprecation-policy.md",
|
|
@@ -78,13 +78,13 @@
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"file": "getting-started.md",
|
|
81
|
-
"sha256": "
|
|
82
|
-
"updated": "2026-11-
|
|
81
|
+
"sha256": "0c4cee83ffc77711f43ccdaafb394283a5a412b78395db1434ebbc2ab6943667",
|
|
82
|
+
"updated": "2026-11-03T00:39:38Z"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"file": "inventory-and-catalogs.md",
|
|
86
|
-
"sha256": "
|
|
87
|
-
"updated": "2026-11-
|
|
86
|
+
"sha256": "dab6e42f4ad5cc2a63c2adb11e85053f963cd3166d1c447394cd6f532bb6c006",
|
|
87
|
+
"updated": "2026-11-02T18:42:37Z"
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
"file": "leaderboards.md",
|
|
@@ -93,13 +93,13 @@
|
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"file": "match-session.md",
|
|
96
|
-
"sha256": "
|
|
97
|
-
"updated": "2026-
|
|
96
|
+
"sha256": "121eb2bd92da9daf3922097699e0ab450da6a087eec8de552fe573094cf7e2c4",
|
|
97
|
+
"updated": "2026-11-02T16:12:53Z"
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"file": "p2p-and-parties.md",
|
|
101
|
-
"sha256": "
|
|
102
|
-
"updated": "2026-
|
|
101
|
+
"sha256": "1de834a88746a4c7b6de555b3e6e3500fce0b7e75fc3cb87d46795efc7e27940",
|
|
102
|
+
"updated": "2026-11-02T23:33:48Z"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
105
|
"file": "platform-moments.md",
|
|
@@ -138,8 +138,8 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"file": "server-bundles.md",
|
|
141
|
-
"sha256": "
|
|
142
|
-
"updated": "2026-11-
|
|
141
|
+
"sha256": "f54aeb88c268e5b657be9d0ea323d44cc61bfbb76fc84670f8a607d4514b005d",
|
|
142
|
+
"updated": "2026-11-03T00:39:38Z"
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
"file": "serverless-functions-js.md",
|
|
@@ -163,8 +163,8 @@
|
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
"file": "what-a-game-must-do.md",
|
|
166
|
-
"sha256": "
|
|
167
|
-
"updated": "2026-11-
|
|
166
|
+
"sha256": "059f3c8a31c0f6098d2c8e4836b79e866d0c5486443dc89039e160648946bd68",
|
|
167
|
+
"updated": "2026-11-02T23:27:22Z"
|
|
168
168
|
}
|
|
169
169
|
]
|
|
170
170
|
}
|
package/docs/match-session.md
CHANGED
|
@@ -155,6 +155,15 @@ own. When a host migration is already in flight the attempt waits it out instead
|
|
|
155
155
|
the way back, and it re-attaches this connection without a dial (§5.1 of
|
|
156
156
|
[p2p-and-parties.md](p2p-and-parties.md)).
|
|
157
157
|
|
|
158
|
+
**A relay that moves is not a drop.** When the platform takes a TURN relay out of service, or one
|
|
159
|
+
dies under a match, the SDK moves the peer-to-peer connection to another relay with an ICE restart
|
|
160
|
+
on the connection it already has (§4.1 and §5 of [p2p-and-parties.md](p2p-and-parties.md)). The
|
|
161
|
+
data channels stay open, so there is no `matchSuspended`, no `matchReconnected` and nothing to
|
|
162
|
+
resync — at most a pause of a few seconds while the route changes. Only a move that misses the
|
|
163
|
+
platform's deadline, or a restart that never comes back, leaves that path: the first is a drop with
|
|
164
|
+
`cause: 'link_closed'` and the re-dial above; the second is treated as the host being gone, as §5
|
|
165
|
+
there describes.
|
|
166
|
+
|
|
158
167
|
The loop never runs while the page is hidden. It parks, and the next visible edge — the phone
|
|
159
168
|
unlocked, the tab foregrounded — is itself a read, so a match that ended while the player was away
|
|
160
169
|
resolves to `matchEnded` and one that is still running resumes reconnecting. Nothing in your game
|
package/docs/p2p-and-parties.md
CHANGED
|
@@ -327,6 +327,14 @@ peer connection's configuration when it is constructed. Leasing afterwards
|
|
|
327
327
|
produces a connection that works whenever both peers are directly reachable —
|
|
328
328
|
which passes every test on a LAN and fails for a large minority of real players.
|
|
329
329
|
|
|
330
|
+
**A lease has a lifetime, and a match can outlast it.** The SDK keeps the
|
|
331
|
+
lease's expiry and renews it whenever it is about to build a connection on a
|
|
332
|
+
lease within two minutes of running out — a host answering a player's offer,
|
|
333
|
+
or a player rebuilding after a migration — so a host
|
|
334
|
+
deep into a long match never answers with credentials that have already
|
|
335
|
+
lapsed. A renewal also reaches every connection already up, ready for its next
|
|
336
|
+
change of route. Renewals are paced at one per event, never one per candidate.
|
|
337
|
+
|
|
330
338
|
**Which cluster to lease from is normally nobody's decision to make here.** The
|
|
331
339
|
parent measures the regions and hands the close ones across the bridge, and each
|
|
332
340
|
peer leases from its own best one — a relay near that peer, whichever cluster
|
|
@@ -343,6 +351,23 @@ alike — which is a decision to move the room. When nothing resolves one the
|
|
|
343
351
|
connection proceeds without a relay and says so loudly, because the failure it
|
|
344
352
|
causes is the one that looks like success.
|
|
345
353
|
|
|
354
|
+
**A lease refused for its cluster moves to the next one the page sent.** The page
|
|
355
|
+
sends up to three clusters, best first. When the platform refuses a lease because
|
|
356
|
+
of the cluster it named — `cluster_hidden`, an operator took it out of service and
|
|
357
|
+
no other cluster in its region can relay for it, or `invalid_cluster_target`, it
|
|
358
|
+
is not one the platform runs — the SDK leases from the next, before any connection
|
|
359
|
+
is built. Only when every one is refused does the connection proceed without a
|
|
360
|
+
relay, with the same warning. Any other refusal is not walked, because another
|
|
361
|
+
cluster would answer it the same. A cluster you named yourself is the only one
|
|
362
|
+
tried. A renewal walks the same way, starting from the cluster that granted the
|
|
363
|
+
lease it replaces, and a re-dial after a dropped connection leases afresh over the
|
|
364
|
+
whole list, so a cluster hidden mid-match is passed over either way.
|
|
365
|
+
|
|
366
|
+
An earlier SDK version asks only the best cluster. Where the platform can answer
|
|
367
|
+
from another cluster in the same region it does, and that SDK gets its relay
|
|
368
|
+
anyway; where it cannot, that SDK connects without one. `dedicated-server-flow.md`
|
|
369
|
+
§5 says what hiding a cluster does to rooms and matches.
|
|
370
|
+
|
|
346
371
|
**`null` names no cluster, exactly as omitting the option does.** A game picking
|
|
347
372
|
its target out of a list writes `targets[0]?.id ?? null`, so that is the spelling
|
|
348
373
|
ordinary code arrives with — and it defers to the parent's answer the same way an
|
|
@@ -351,6 +376,41 @@ request for a cluster with no id: that costs the caller the no-relay warning and
|
|
|
351
376
|
reports "the relay was refused" for a player whose actual problem is that nobody
|
|
352
377
|
ever resolved a region for them.
|
|
353
378
|
|
|
379
|
+
### 4.1 A relay going away does not end the match
|
|
380
|
+
|
|
381
|
+
Relays are taken out of service for deploys, credential rotation and scaling.
|
|
382
|
+
The platform does it in three ordered steps:
|
|
383
|
+
|
|
384
|
+
1. **Delist.** No new lease names the relay, so no new connection is built on it.
|
|
385
|
+
2. **Move.** Every player in a match that may be using it is told, and the
|
|
386
|
+
connections on it move elsewhere while it still forwards traffic.
|
|
387
|
+
3. **Stop.** The relay stops once nobody is on it, or when the platform's
|
|
388
|
+
deadline for the move passes.
|
|
389
|
+
|
|
390
|
+
**Your game sees none of it.** On the notice every player renews its lease and
|
|
391
|
+
drops the draining relay from the list it holds. A player whose connection to
|
|
392
|
+
the host runs through that relay moves with an **ICE restart on the connection
|
|
393
|
+
it already has**: new candidates on the remaining relays, the same DTLS
|
|
394
|
+
session, the same three data channels. The host answers on its existing
|
|
395
|
+
connection. Nothing closes, `gem.match` raises no `matchSuspended`, and nobody
|
|
396
|
+
reports the host unreachable. A player whose route does not touch the draining
|
|
397
|
+
relay renews and does nothing else, and the host renews and answers but never
|
|
398
|
+
offers a restart of its own.
|
|
399
|
+
|
|
400
|
+
**If the move has not completed by the platform's deadline**, the player drops
|
|
401
|
+
that connection and the ordinary reconnect takes over (§5.1, and §4 of
|
|
402
|
+
[match-session.md](match-session.md)): a fresh session at the same host, over
|
|
403
|
+
the renewed lease, reported as this player's own link closing — `link_closed` —
|
|
404
|
+
because the host is fine. The same happens at once if the host has already let
|
|
405
|
+
go of that player's connection when the restart reaches it: the host can only
|
|
406
|
+
answer from a new connection, which the player's existing one cannot use.
|
|
407
|
+
|
|
408
|
+
**The announced move needs the Arcade page to pass the notice on.** The page is
|
|
409
|
+
what relays it into the game frame, and a page whose side of the bridge
|
|
410
|
+
predates the SDK release that brought this drops it. Until the page is at that
|
|
411
|
+
release or later, a relay stopping under a match is recovered the way an
|
|
412
|
+
unannounced loss is (§5): a few seconds of quiet, then the same restart.
|
|
413
|
+
|
|
354
414
|
---
|
|
355
415
|
|
|
356
416
|
## 5. Host migration is where a split match can happen
|
|
@@ -388,18 +448,31 @@ trying to fix, and the old host is already gone. A host that settled on its own
|
|
|
388
448
|
send could not tell that apart from a healthy match. The active phase is bounded
|
|
389
449
|
at 30 seconds and a stall is reported as `migration_failed`.
|
|
390
450
|
|
|
391
|
-
A non-host
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
451
|
+
A non-host whose connection to the host goes quiet **tries to bring it back
|
|
452
|
+
before it reports anything**. ICE `disconnected` is what a lost relay looks like
|
|
453
|
+
as much as a lost host, so the player first renews its lease and restarts ICE on
|
|
454
|
+
the existing connection — the move §4.1 describes, without the notice — and
|
|
455
|
+
gives it about eight seconds to come back. Only if it does not, or the
|
|
456
|
+
connection fails or closes outright meanwhile, does the player report the host
|
|
457
|
+
unreachable, **once per epoch**. A relay that dies without warning therefore no
|
|
458
|
+
longer ends a two-player match or migrates a larger one away from a healthy
|
|
459
|
+
host; the cost is that a host who really has gone is reported a few seconds
|
|
460
|
+
later.
|
|
461
|
+
|
|
462
|
+
The report is a vote, not a verdict. In a match of three or more, gem-api
|
|
463
|
+
migrates on a quorum of at least two independent reporters, so one player
|
|
464
|
+
cannot unseat a host who is fine for everyone else. In a two-player match there
|
|
465
|
+
is nobody to elect, and the lone peer's report ends the match with `host_left`
|
|
466
|
+
— which is why the report waits for the restart to fail.
|
|
395
467
|
|
|
396
468
|
### 5.1 What `gem.match` does with a migration
|
|
397
469
|
|
|
398
470
|
A `MatchConnection` to a hosting peer survives a migration as a **pause**: its
|
|
399
471
|
channels re-resolve on `hostChanged`, so the game sees a gap rather than a new
|
|
400
472
|
connection to build. What the room reports meanwhile is the ordinary session
|
|
401
|
-
vocabulary — `matchSuspended` with cause `
|
|
402
|
-
link
|
|
473
|
+
vocabulary — `matchSuspended` with cause `link_closed` when this side sees the
|
|
474
|
+
host's link fail or `host_unreachable` when the platform reports the host gone,
|
|
475
|
+
`matchReconnected` with `attempt: 0` when the migration seated a
|
|
403
476
|
new host and the link came back without a re-dial, and `matchEnded` with
|
|
404
477
|
`host_left` or `migration_failed` when it did not. The screens for each are
|
|
405
478
|
[match-session.md](match-session.md) §3 and §4; the listen host's own
|
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/
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v20.1.0/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v20.1.0/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -53,10 +53,10 @@ No `baseUrl`. The Arcade names the backend on every token and the SDK routes the
|
|
|
53
53
|
tokens only if its origin is allowed, and the pages that matter are always allowed without a word
|
|
54
54
|
from you:
|
|
55
55
|
|
|
56
|
-
- **the Gem Arcade website
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
- **the Gem Arcade website and app** — the SDK carries their origins as `ARCADE_EMBEDDERS`, on every
|
|
57
|
+
platform the app runs on. The list also holds `https://developer.gemarcade.com`, which is
|
|
58
|
+
reserved and not live: no page is served there. You play your own dev and test builds with
|
|
59
|
+
`gemctl serve` or the Gem Editor, or on your game's dev or test channel in the Arcade;
|
|
60
60
|
- **a dev host on your own machine** — `gemctl serve` and the Gem Editor frame a locally-served game
|
|
61
61
|
from a loopback origin whose port changes every run, and the SDK admits that shape by rule, only
|
|
62
62
|
while your game itself is served from loopback. Once published, nothing about the rule applies.
|
|
@@ -1710,7 +1710,10 @@ stops your media elements, and restarts neither. The snippet above has stopped l
|
|
|
1710
1710
|
the SDK does this part for you. After the page was hidden, or a context left `running` without your
|
|
1711
1711
|
asking, it listens again for the player's next lift in your frame, resumes the contexts that were
|
|
1712
1712
|
running and replays the elements that were playing when the sound was cut off, then stops
|
|
1713
|
-
listening.
|
|
1713
|
+
listening. A context that reads `running` after the page comes back can still be silent, as when
|
|
1714
|
+
your own `resume()` ran outside a tap, so on that first lift the SDK also suspends and resumes each
|
|
1715
|
+
context that had run, once per return: your clock pauses for a moment, and the sound comes back.
|
|
1716
|
+
You write nothing for it. It stays out of your way:
|
|
1714
1717
|
|
|
1715
1718
|
- **A context you `suspend()` yourself is left alone** until you call `resume()`. If you pause your
|
|
1716
1719
|
audio when the page is hidden and resume it when it comes back, keep doing that: the SDK steps in
|