@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
package/dist/sdk.d.ts
CHANGED
|
@@ -2254,6 +2254,15 @@ interface GemSignalEvent {
|
|
|
2254
2254
|
readonly matchId: string;
|
|
2255
2255
|
readonly data: unknown;
|
|
2256
2256
|
}
|
|
2257
|
+
interface GemRelayDraining {
|
|
2258
|
+
readonly gem: typeof PROTOCOL;
|
|
2259
|
+
readonly type: "gem.relayDraining";
|
|
2260
|
+
readonly matchId: string;
|
|
2261
|
+
readonly clusterTargetId: string;
|
|
2262
|
+
readonly relayIps: string[];
|
|
2263
|
+
readonly drainingSince: string;
|
|
2264
|
+
readonly deadline: string;
|
|
2265
|
+
}
|
|
2257
2266
|
interface GemDedicatedEvent {
|
|
2258
2267
|
readonly gem: typeof PROTOCOL;
|
|
2259
2268
|
readonly type: "gem.dedicatedEvent";
|
|
@@ -2570,6 +2579,7 @@ interface TelemetrySample {
|
|
|
2570
2579
|
readonly type: string;
|
|
2571
2580
|
readonly value?: number;
|
|
2572
2581
|
readonly transport?: TransportLabel;
|
|
2582
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
2573
2583
|
}
|
|
2574
2584
|
interface P2pApiOptions {
|
|
2575
2585
|
readonly client: GemClient;
|
|
@@ -2686,9 +2696,12 @@ interface IceCandidateLike {
|
|
|
2686
2696
|
readonly sdpMid?: string | null;
|
|
2687
2697
|
readonly sdpMLineIndex?: number | null;
|
|
2688
2698
|
}
|
|
2699
|
+
type EventListenerLike = {
|
|
2700
|
+
bivarianceHack(event: unknown): void;
|
|
2701
|
+
}["bivarianceHack"];
|
|
2689
2702
|
interface EventTargetLike {
|
|
2690
|
-
addEventListener(type: string, listener:
|
|
2691
|
-
removeEventListener(type: string, listener:
|
|
2703
|
+
addEventListener(type: string, listener: EventListenerLike): void;
|
|
2704
|
+
removeEventListener(type: string, listener: EventListenerLike): void;
|
|
2692
2705
|
}
|
|
2693
2706
|
interface DataChannelLike extends EventTargetLike {
|
|
2694
2707
|
readonly readyState: string;
|
|
@@ -2703,6 +2716,16 @@ interface DataChannelInitLike {
|
|
|
2703
2716
|
readonly negotiated?: boolean;
|
|
2704
2717
|
readonly id?: number;
|
|
2705
2718
|
}
|
|
2719
|
+
interface IceConfigurationLike {
|
|
2720
|
+
iceServers?: {
|
|
2721
|
+
urls: string | string[];
|
|
2722
|
+
username?: string;
|
|
2723
|
+
credential?: string;
|
|
2724
|
+
}[];
|
|
2725
|
+
}
|
|
2726
|
+
interface StatsReportLike {
|
|
2727
|
+
forEach(callback: (value: unknown, key: string) => void): void;
|
|
2728
|
+
}
|
|
2706
2729
|
interface PeerConnectionLike extends EventTargetLike {
|
|
2707
2730
|
readonly localDescription: SessionDescriptionLike | null;
|
|
2708
2731
|
readonly remoteDescription: SessionDescriptionLike | null;
|
|
@@ -2715,6 +2738,10 @@ interface PeerConnectionLike extends EventTargetLike {
|
|
|
2715
2738
|
setRemoteDescription(description: SessionDescriptionLike): Promise<void>;
|
|
2716
2739
|
addIceCandidate(candidate: IceCandidateLike): Promise<void>;
|
|
2717
2740
|
close(): void;
|
|
2741
|
+
setConfiguration?(configuration: IceConfigurationLike): void;
|
|
2742
|
+
getConfiguration?(): IceConfigurationLike;
|
|
2743
|
+
restartIce?(): void;
|
|
2744
|
+
getStats?(): Promise<StatsReportLike>;
|
|
2718
2745
|
}
|
|
2719
2746
|
interface PeerConnectionConfig {
|
|
2720
2747
|
readonly iceServers: readonly {
|
|
@@ -2749,6 +2776,7 @@ interface P2pSessionOptions {
|
|
|
2749
2776
|
readonly baseUrl: string;
|
|
2750
2777
|
readonly signaling: SignalingClient;
|
|
2751
2778
|
readonly clusterTargetId?: ClusterTargetLike;
|
|
2779
|
+
readonly clusterTargetIds?: readonly string[];
|
|
2752
2780
|
readonly peerConnectionFactory?: PeerConnectionFactory;
|
|
2753
2781
|
readonly certificate?: unknown;
|
|
2754
2782
|
readonly connectTimeoutMs?: number;
|
|
@@ -2765,7 +2793,11 @@ declare class P2pSession {
|
|
|
2765
2793
|
private readonly keys;
|
|
2766
2794
|
private localPlayerId;
|
|
2767
2795
|
private migration;
|
|
2768
|
-
private
|
|
2796
|
+
private lease;
|
|
2797
|
+
private clusterTargetIds;
|
|
2798
|
+
private leaseRenewal;
|
|
2799
|
+
private leaseAskedAt;
|
|
2800
|
+
private leaseRenewalWarned;
|
|
2769
2801
|
private credential;
|
|
2770
2802
|
private nextPeerId;
|
|
2771
2803
|
private ended;
|
|
@@ -2779,6 +2811,7 @@ declare class P2pSession {
|
|
|
2779
2811
|
private readonly answeredSessions;
|
|
2780
2812
|
private credentialGeneration;
|
|
2781
2813
|
private credentialDeadline;
|
|
2814
|
+
private readonly drainTimers;
|
|
2782
2815
|
constructor(options: P2pSessionOptions);
|
|
2783
2816
|
get matchId(): string;
|
|
2784
2817
|
get playerId(): string;
|
|
@@ -2804,6 +2837,7 @@ declare class P2pSession {
|
|
|
2804
2837
|
private fetchCredential;
|
|
2805
2838
|
private waitForCredential;
|
|
2806
2839
|
private onOffer;
|
|
2840
|
+
private answer;
|
|
2807
2841
|
private onAnswer;
|
|
2808
2842
|
private hostVerified;
|
|
2809
2843
|
private maybeAuthenticate;
|
|
@@ -2814,6 +2848,17 @@ declare class P2pSession {
|
|
|
2814
2848
|
private watchMigration;
|
|
2815
2849
|
private onMatchEnded;
|
|
2816
2850
|
private onSignalingError;
|
|
2851
|
+
private onRelayDraining;
|
|
2852
|
+
private onDrainDeadline;
|
|
2853
|
+
private withoutDrainingRelays;
|
|
2854
|
+
private restartIceToHost;
|
|
2855
|
+
private offerIceRestart;
|
|
2856
|
+
private progressRestart;
|
|
2857
|
+
private reportRestart;
|
|
2858
|
+
private takeLease;
|
|
2859
|
+
private refreshLease;
|
|
2860
|
+
private reconfigureLinks;
|
|
2861
|
+
private iceServers;
|
|
2817
2862
|
private openLink;
|
|
2818
2863
|
private flushCandidates;
|
|
2819
2864
|
private onReliableOpen;
|
|
@@ -2822,6 +2867,8 @@ declare class P2pSession {
|
|
|
2822
2867
|
private refuseLink;
|
|
2823
2868
|
private finishAdmission;
|
|
2824
2869
|
private onIceState;
|
|
2870
|
+
private recoverHostLink;
|
|
2871
|
+
private voteHostUnreachable;
|
|
2825
2872
|
private onConnectionState;
|
|
2826
2873
|
private expose;
|
|
2827
2874
|
private dropLink;
|
|
@@ -4726,6 +4773,7 @@ interface FrameCallbacks {
|
|
|
4726
4773
|
onTicketEvent?(msg: GemTicketEvent): void;
|
|
4727
4774
|
onSocketState?(msg: GemSocketState): void;
|
|
4728
4775
|
onSignalEvent?(msg: GemSignalEvent): void;
|
|
4776
|
+
onRelayDraining?(msg: GemRelayDraining): void;
|
|
4729
4777
|
onDedicatedEvent?(msg: GemDedicatedEvent): void;
|
|
4730
4778
|
onClusterTarget?(msg: GemClusterTarget): void;
|
|
4731
4779
|
onPartyResult?(msg: GemPartyResult): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The function surface — API reference
|
|
2
2
|
|
|
3
|
-
Generated from `@indiegems/gem-web-sdk/function`
|
|
3
|
+
Generated from `@indiegems/gem-web-sdk/function` v20.1.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
|
|
4
4
|
|
|
5
5
|
`@indiegems/gem-web-sdk/function` exports exactly the symbols on this page and nothing else.
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The browser surface — API reference
|
|
2
2
|
|
|
3
|
-
Generated from `@indiegems/gem-web-sdk/sdk`
|
|
3
|
+
Generated from `@indiegems/gem-web-sdk/sdk` v20.1.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
|
|
4
4
|
|
|
5
5
|
`@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/game` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
|
|
6
6
|
|
|
@@ -4779,16 +4779,17 @@ purchase(skuId: string): Promise<ArcadeOutcome>
|
|
|
4779
4779
|
Ask the arcade to open checkout for one SKU from [StoreApi.offers](#storeapi-offers):
|
|
4780
4780
|
the payment provider's page on the web, the app store's purchase sheet in
|
|
4781
4781
|
the Arcade's apps, straight away, with no confirmation screen of the
|
|
4782
|
-
arcade's own first.
|
|
4783
|
-
|
|
4782
|
+
arcade's own first. It needs no press: call it from your Buy button's
|
|
4783
|
+
handler, or after an `await` in it, and the checkout opens either way.
|
|
4784
4784
|
|
|
4785
4785
|
- `ok` — the player paid. Re-read balances and inventory through `gem.me`;
|
|
4786
4786
|
nothing here carries an amount. With `reason: 'pending'`, the provider
|
|
4787
4787
|
has not settled yet: [StoreApi.onSettled](#storeapi-onsettled) hears when it does.
|
|
4788
4788
|
- `declined` — nothing was bought: `cancelled` (the player closed the
|
|
4789
|
-
checkout, or the offer cannot be bought again yet) or `busy` (
|
|
4790
|
-
|
|
4791
|
-
|
|
4789
|
+
checkout, or the offer cannot be bought again yet) or `busy` (another
|
|
4790
|
+
checkout or Arcade sheet was already open). Return to your screen
|
|
4791
|
+
silently, and do not ask again unprompted: the player's next press opens
|
|
4792
|
+
the checkout again, however many times they closed it.
|
|
4792
4793
|
- `unavailable` with `store_off`, `sign_in_required`, `not_found`,
|
|
4793
4794
|
`unsupported`, `invalid_argument` or a payment provider's failure code —
|
|
4794
4795
|
nothing was bought.
|
|
@@ -4819,7 +4820,7 @@ Ask the arcade to show one SKU from [StoreApi.offers](#storeapi-offers) in its o
|
|
|
4819
4820
|
sheet, over your paused and hidden game: its art, what the player gets, a
|
|
4820
4821
|
sale if one is on, and, for a pass the player already holds, how long it
|
|
4821
4822
|
has left. The sheet's Buy opens the same checkout as
|
|
4822
|
-
[StoreApi.purchase](#storeapi-purchase).
|
|
4823
|
+
[StoreApi.purchase](#storeapi-purchase). Like `purchase`, it needs no press.
|
|
4823
4824
|
|
|
4824
4825
|
Use it for an offer you would have to hardcode a pitch for: VIP and other
|
|
4825
4826
|
passes, and bundles. Your button only needs to say what it opens. Draw a
|
|
@@ -4828,8 +4829,8 @@ Gem pack yourself and use `purchase` instead. Check
|
|
|
4828
4829
|
|
|
4829
4830
|
It answers exactly as `purchase` does, with one addition: `declined` /
|
|
4830
4831
|
`cancelled` also covers the player leaving the sheet without pressing Buy,
|
|
4831
|
-
and you are not told which of the two they closed.
|
|
4832
|
-
|
|
4832
|
+
and you are not told which of the two they closed. Do not ask again
|
|
4833
|
+
unprompted; the player's next press opens it again. `unavailable` / `unsupported`
|
|
4833
4834
|
means this Arcade does not draw the sheet, and nothing was sent.
|
|
4834
4835
|
`unavailable` / `error` means **the outcome is unknown**: re-read balances
|
|
4835
4836
|
and inventory through `gem.me`, and keep listening with
|
|
@@ -9503,8 +9504,9 @@ ARCADE_EMBEDDERS: readonly string[]
|
|
|
9503
9504
|
```
|
|
9504
9505
|
|
|
9505
9506
|
The origins the Gem Arcade embeds a game from, which every game accepts
|
|
9506
|
-
without naming them: the website
|
|
9507
|
-
|
|
9507
|
+
without naming them: the website and the Gem Arcade app. The list also
|
|
9508
|
+
holds `https://developer.gemarcade.com`, which is reserved and not live —
|
|
9509
|
+
no page is served there, so nothing frames a game from it.
|
|
9508
9510
|
|
|
9509
9511
|
You do not pass these. They are always allowed, and
|
|
9510
9512
|
[GemOptions.allowedEmbedders](#gemoptions-allowedembedders) adds to them rather than replacing them.
|
|
@@ -9533,8 +9535,8 @@ readonly allowedEmbedders?: readonly string[]
|
|
|
9533
9535
|
Extra origins allowed to embed your game, **added to** the ones the Gem
|
|
9534
9536
|
Arcade uses. Most games leave this out.
|
|
9535
9537
|
|
|
9536
|
-
The website
|
|
9537
|
-
|
|
9538
|
+
The website and the Gem Arcade app are always allowed — see
|
|
9539
|
+
[ARCADE_EMBEDDERS](#arcade_embedders) — so do not list them. Set this only when a site
|
|
9538
9540
|
of your own embeds your game.
|
|
9539
9541
|
|
|
9540
9542
|
```js
|
|
@@ -11581,7 +11583,7 @@ sheet. To spend Gems or Coins on your game's own items, use
|
|
|
11581
11583
|
[Gem.purchases](#gem-purchases).
|
|
11582
11584
|
|
|
11583
11585
|
List what the store sells here with `offers()`, show each `priceDisplay`
|
|
11584
|
-
exactly as given, and call `purchase(skuId)`
|
|
11586
|
+
exactly as given, and call `purchase(skuId)` when the player chooses to buy. For a
|
|
11585
11587
|
pass, a bundle or anything else whose pitch you would have to hardcode, call
|
|
11586
11588
|
`showOffer(skuId)` instead, and the arcade draws the offer. After `ok`,
|
|
11587
11589
|
re-read balances through [Gem.me](#gem-me), and `gem.me.passes` for a pass.
|
|
@@ -12592,9 +12594,9 @@ await gem.awaitBridge(); // before loading your main bundle
|
|
|
12592
12594
|
const me = await gem.awaitIdentity(); // who is playing, once sign-in completes
|
|
12593
12595
|
```
|
|
12594
12596
|
|
|
12595
|
-
No options are needed. The Gem Arcade's website
|
|
12596
|
-
|
|
12597
|
-
|
|
12597
|
+
No options are needed. The Gem Arcade's website and app are always allowed to
|
|
12598
|
+
embed your game ([ARCADE_EMBEDDERS](#arcade_embedders)), and the arcade tells your game
|
|
12599
|
+
which build it is.
|
|
12598
12600
|
|
|
12599
12601
|
A dev host on your own machine (`gemctl serve`, the Gem Editor) needs no
|
|
12600
12602
|
entry either: it frames a locally-served game from a loopback origin whose
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The server surface — API reference
|
|
2
2
|
|
|
3
|
-
Generated from `@indiegems/gem-web-sdk/sdk`
|
|
3
|
+
Generated from `@indiegems/gem-web-sdk/sdk` v20.1.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
|
|
4
4
|
|
|
5
5
|
`@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/server` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
|
|
6
6
|
|
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": "20.1.0"
|
|
6
6
|
},
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -5735,7 +5735,7 @@
|
|
|
5735
5735
|
"group": "store",
|
|
5736
5736
|
"signature": "purchase(skuId: string): Promise<ArcadeOutcome>",
|
|
5737
5737
|
"documented": true,
|
|
5738
|
-
"summary": "Ask the arcade to open checkout for one SKU from [StoreApi.offers](api-reference-game.md#storeapi-offers):\nthe payment provider's page on the web, the app store's purchase sheet in\nthe Arcade's apps, straight away, with no confirmation screen of the\narcade's own first.
|
|
5738
|
+
"summary": "Ask the arcade to open checkout for one SKU from [StoreApi.offers](api-reference-game.md#storeapi-offers):\nthe payment provider's page on the web, the app store's purchase sheet in\nthe Arcade's apps, straight away, with no confirmation screen of the\narcade's own first. It needs no press: call it from your Buy button's\nhandler, or after an `await` in it, and the checkout opens either way.\n\n- `ok` — the player paid. Re-read balances and inventory through `gem.me`;\n nothing here carries an amount. With `reason: 'pending'`, the provider\n has not settled yet: [StoreApi.onSettled](api-reference-game.md#storeapi-onsettled) hears when it does.\n- `declined` — nothing was bought: `cancelled` (the player closed the\n checkout, or the offer cannot be bought again yet) or `busy` (another\n checkout or Arcade sheet was already open). Return to your screen\n silently, and do not ask again unprompted: the player's next press opens\n the checkout again, however many times they closed it.\n- `unavailable` with `store_off`, `sign_in_required`, `not_found`,\n `unsupported`, `invalid_argument` or a payment provider's failure code —\n nothing was bought.\n- `unavailable` / `error` — **the outcome is unknown, and the player may\n have paid.** It is what you get when the bridge closes while checkout\n is open, or the arcade has no reliable answer. Never tell the player\n nothing was bought: re-read balances and inventory through `gem.me`,\n and keep listening with [StoreApi.onSettled](api-reference-game.md#storeapi-onsettled).\n\nNO TIMEOUT: it waits for as long as the arcade is connected, because a\npayment takes as long as it takes.",
|
|
5739
5739
|
"deprecated": null,
|
|
5740
5740
|
"params": [
|
|
5741
5741
|
{
|
|
@@ -5758,7 +5758,7 @@
|
|
|
5758
5758
|
"group": "store",
|
|
5759
5759
|
"signature": "showOffer(skuId: string): Promise<ArcadeOutcome>",
|
|
5760
5760
|
"documented": true,
|
|
5761
|
-
"summary": "Ask the arcade to show one SKU from [StoreApi.offers](api-reference-game.md#storeapi-offers) in its own\nsheet, over your paused and hidden game: its art, what the player gets, a\nsale if one is on, and, for a pass the player already holds, how long it\nhas left. The sheet's Buy opens the same checkout as\n[StoreApi.purchase](api-reference-game.md#storeapi-purchase).
|
|
5761
|
+
"summary": "Ask the arcade to show one SKU from [StoreApi.offers](api-reference-game.md#storeapi-offers) in its own\nsheet, over your paused and hidden game: its art, what the player gets, a\nsale if one is on, and, for a pass the player already holds, how long it\nhas left. The sheet's Buy opens the same checkout as\n[StoreApi.purchase](api-reference-game.md#storeapi-purchase). Like `purchase`, it needs no press.\n\nUse it for an offer you would have to hardcode a pitch for: VIP and other\npasses, and bundles. Your button only needs to say what it opens. Draw a\nGem pack yourself and use `purchase` instead. Check\n[StoreApi.showOfferAvailable](api-reference-game.md#storeapi-showofferavailable) first, and that the SKU is in `offers`.\n\nIt answers exactly as `purchase` does, with one addition: `declined` /\n`cancelled` also covers the player leaving the sheet without pressing Buy,\nand you are not told which of the two they closed. Do not ask again\nunprompted; the player's next press opens it again. `unavailable` / `unsupported`\nmeans this Arcade does not draw the sheet, and nothing was sent.\n`unavailable` / `error` means **the outcome is unknown**: re-read balances\nand inventory through `gem.me`, and keep listening with\n[StoreApi.onSettled](api-reference-game.md#storeapi-onsettled).\n\nNO TIMEOUT: it waits for as long as the arcade is connected, because a\nplayer may read a sheet and pay for as long as they like.",
|
|
5762
5762
|
"deprecated": null,
|
|
5763
5763
|
"params": [
|
|
5764
5764
|
{
|
|
@@ -11139,7 +11139,7 @@
|
|
|
11139
11139
|
"group": "start",
|
|
11140
11140
|
"signature": "ARCADE_EMBEDDERS: readonly string[]",
|
|
11141
11141
|
"documented": true,
|
|
11142
|
-
"summary": "The origins the Gem Arcade embeds a game from, which every game accepts\nwithout naming them: the website
|
|
11142
|
+
"summary": "The origins the Gem Arcade embeds a game from, which every game accepts\nwithout naming them: the website and the Gem Arcade app. The list also\nholds `https://developer.gemarcade.com`, which is reserved and not live —\nno page is served there, so nothing frames a game from it.\n\nYou do not pass these. They are always allowed, and\n[GemOptions.allowedEmbedders](api-reference-game.md#gemoptions-allowedembedders) adds to them rather than replacing them.",
|
|
11143
11143
|
"deprecated": null,
|
|
11144
11144
|
"params": [],
|
|
11145
11145
|
"returns": null,
|
|
@@ -11171,7 +11171,7 @@
|
|
|
11171
11171
|
"group": "start",
|
|
11172
11172
|
"signature": "readonly allowedEmbedders?: readonly string[]",
|
|
11173
11173
|
"documented": true,
|
|
11174
|
-
"summary": "Extra origins allowed to embed your game, **added to** the ones the Gem\nArcade uses. Most games leave this out.\n\nThe website
|
|
11174
|
+
"summary": "Extra origins allowed to embed your game, **added to** the ones the Gem\nArcade uses. Most games leave this out.\n\nThe website and the Gem Arcade app are always allowed — see\n[ARCADE_EMBEDDERS](api-reference-game.md#arcade_embedders) — so do not list them. Set this only when a site\nof your own embeds your game.\n\n```js\ncreateGem({ allowedEmbedders: ['https://my-own-site.example'] })\n```\n\nA page whose origin is not allowed gets no connection, and — deliberately —\nis told nothing about why. That means a typo here looks exactly like a\nhandshake that has not arrived: pass [GemOptions.onRefused](api-reference-game.md#gemoptions-onrefused) while you\nare integrating, because it is the only place the difference shows.\n\n⚠️ **Bake these in. Never build them from anything the page can\ninfluence** — not `location`, not a query parameter, not\n`document.referrer`. A value the embedder supplies is a check that can never\nfail. Do not add a `localhost:<port>` entry for your dev host either; the\nSDK admits one on your own machine by rule.",
|
|
11175
11175
|
"deprecated": null,
|
|
11176
11176
|
"params": [],
|
|
11177
11177
|
"returns": null,
|
|
@@ -13228,7 +13228,7 @@
|
|
|
13228
13228
|
"group": "start",
|
|
13229
13229
|
"signature": "readonly store: StoreApi",
|
|
13230
13230
|
"documented": true,
|
|
13231
|
-
"summary": "**Real money.** The Arcade Store's own Gem packs, Boosts, passes and\nbundles, sold for the player's money through the arcade's own purchase\nsheet. To spend Gems or Coins on your game's own items, use\n[Gem.purchases](api-reference-game.md#gem-purchases).\n\nList what the store sells here with `offers()`, show each `priceDisplay`\nexactly as given, and call `purchase(skuId)`
|
|
13231
|
+
"summary": "**Real money.** The Arcade Store's own Gem packs, Boosts, passes and\nbundles, sold for the player's money through the arcade's own purchase\nsheet. To spend Gems or Coins on your game's own items, use\n[Gem.purchases](api-reference-game.md#gem-purchases).\n\nList what the store sells here with `offers()`, show each `priceDisplay`\nexactly as given, and call `purchase(skuId)` when the player chooses to buy. For a\npass, a bundle or anything else whose pitch you would have to hardcode, call\n`showOffer(skuId)` instead, and the arcade draws the offer. After `ok`,\nre-read balances through [Gem.me](api-reference-game.md#gem-me), and `gem.me.passes` for a pass.\nNothing rejects.",
|
|
13232
13232
|
"deprecated": null,
|
|
13233
13233
|
"params": [],
|
|
13234
13234
|
"returns": null,
|
|
@@ -13974,7 +13974,7 @@
|
|
|
13974
13974
|
"group": "start",
|
|
13975
13975
|
"signature": "function createGem(options?: GemOptions): Gem",
|
|
13976
13976
|
"documented": true,
|
|
13977
|
-
"summary": "Connect your game to the Gem Arcade. This is the one call every game makes.\n\nIt assembles everything and connects to the arcade that framed your game. The\nsame code runs on your machine and in production without a flag — but it needs\na frame either way. Opened at the top level it throws\n[GemUnframedError](api-reference-game.md#gemunframederror), which says how to run it: in the Arcade, the Editor,\nor the Gem dev host on your machine (`npm run dev`, signed in to `gemctl`).\n\nOne dependency, imported like anything else. The install is in\n[getting-started](../getting-started.md#3-add-the-sdk).\n\n```js\nimport { createGem } from '@indiegems/gem-web-sdk/sdk';\n\nconst gem = createGem();\n\nawait gem.awaitBridge(); // before loading your main bundle\nconst me = await gem.awaitIdentity(); // who is playing, once sign-in completes\n```\n\nNo options are needed. The Gem Arcade's website
|
|
13977
|
+
"summary": "Connect your game to the Gem Arcade. This is the one call every game makes.\n\nIt assembles everything and connects to the arcade that framed your game. The\nsame code runs on your machine and in production without a flag — but it needs\na frame either way. Opened at the top level it throws\n[GemUnframedError](api-reference-game.md#gemunframederror), which says how to run it: in the Arcade, the Editor,\nor the Gem dev host on your machine (`npm run dev`, signed in to `gemctl`).\n\nOne dependency, imported like anything else. The install is in\n[getting-started](../getting-started.md#3-add-the-sdk).\n\n```js\nimport { createGem } from '@indiegems/gem-web-sdk/sdk';\n\nconst gem = createGem();\n\nawait gem.awaitBridge(); // before loading your main bundle\nconst me = await gem.awaitIdentity(); // who is playing, once sign-in completes\n```\n\nNo options are needed. The Gem Arcade's website and app are always allowed to\nembed your game ([ARCADE_EMBEDDERS](api-reference-game.md#arcade_embedders)), and the arcade tells your game\nwhich build it is.\n\nA dev host on your own machine (`gemctl serve`, the Gem Editor) needs no\nentry either: it frames a locally-served game from a loopback origin whose\nport changes every run, and the SDK admits it by rule — only while the game\nitself is served from loopback, so nothing changes once you publish.\n\nPass `allowedEmbedders` only when a site of your own frames your game. It\nadds to the arcade's origins; see [GemOptions.allowedEmbedders](api-reference-game.md#gemoptions-allowedembedders).",
|
|
13978
13978
|
"deprecated": null,
|
|
13979
13979
|
"params": [
|
|
13980
13980
|
{
|
package/docs/arcade-and-store.md
CHANGED
|
@@ -33,7 +33,7 @@ itself. You get an outcome.
|
|
|
33
33
|
- [1. Two surfaces, and which one is real money](#1-two-surfaces-and-which-one-is-real-money)
|
|
34
34
|
- [2. Nothing rejects](#2-nothing-rejects)
|
|
35
35
|
- [3. What is available here](#3-what-is-available-here)
|
|
36
|
-
- [4.
|
|
36
|
+
- [4. Leaving takes a press; buying does not](#4-leaving-takes-a-press-buying-does-not)
|
|
37
37
|
- [5. App links](#5-app-links)
|
|
38
38
|
- [6. Opening another game](#6-opening-another-game)
|
|
39
39
|
- [7. Similar games, as cards](#7-similar-games-as-cards)
|
|
@@ -88,20 +88,25 @@ the bridge is up. A feature can be off for reasons you
|
|
|
88
88
|
cannot see from inside the frame: a TV, an in-app browser that cannot take payment, an app build
|
|
89
89
|
where it is withheld. Do not guess; read it.
|
|
90
90
|
|
|
91
|
-
## 4.
|
|
91
|
+
## 4. Leaving takes a press; buying does not
|
|
92
92
|
|
|
93
93
|
Four calls put something in front of the player: `gem.arcade.openApp`, `gem.arcade.openGame`,
|
|
94
|
-
`gem.store.purchase` and `gem.store.showOffer`.
|
|
95
|
-
|
|
96
|
-
`
|
|
97
|
-
|
|
94
|
+
`gem.store.purchase` and `gem.store.showOffer`.
|
|
95
|
+
|
|
96
|
+
- **Leaving the game takes a press.** Call `openApp` and `openGame` from the player's own press
|
|
97
|
+
handler, in the same turn as the press. The Arcade checks that a person pressed, and refuses an
|
|
98
|
+
ask nobody pressed for with `declined` / `busy`, showing nothing. Never on a timer, never on
|
|
99
|
+
load, never in a retry loop.
|
|
100
|
+
- **Buying does not.** `gem.store.purchase` and `gem.store.showOffer` open their checkout or sheet
|
|
101
|
+
after an `await`, from a timer, or where the browser cannot report a press: the checkout is where
|
|
102
|
+
the player confirms, and your game cannot press it. Nothing backs off either, so the player's
|
|
103
|
+
next press after closing a checkout opens it again. Still ask only because the player chose to
|
|
104
|
+
buy.
|
|
98
105
|
- **Controllers count.** A gamepad press the Arcade sees counts as a press. Do not add a keyboard or
|
|
99
106
|
pointer workaround for pad players.
|
|
100
107
|
- **One at a time.** Only one of these is open at once, across both surfaces and the Arcade's other
|
|
101
108
|
sheets (a Gem purchase, a share, a friend challenge). A second ask while one is open is
|
|
102
109
|
`declined` / `busy`. The SDK answers it for you without sending.
|
|
103
|
-
- **Never on a timer, never on load, never in a retry loop.** None of them would have a press, so
|
|
104
|
-
all of them would be refused.
|
|
105
110
|
|
|
106
111
|
The reads (`appLinks`, `similarGames`, `offers`) need no press. A second call while one is open
|
|
107
112
|
shares the first one's answer.
|
|
@@ -250,9 +255,10 @@ first: the payment provider's page on the web, the app store's own purchase shee
|
|
|
250
255
|
apps. That checkout is where the player confirms and pays. Your game is resumed before the answer
|
|
251
256
|
arrives, so your own celebration is visible.
|
|
252
257
|
|
|
253
|
-
- **Call it
|
|
254
|
-
|
|
255
|
-
much
|
|
258
|
+
- **Call it when the player chooses to buy** ([§4](#4-leaving-takes-a-press-buying-does-not)).
|
|
259
|
+
No press is needed — an `await` before it is fine — but the player's press on your Buy button is
|
|
260
|
+
what they expect to open checkout, so make that button say plainly what it buys and for how much
|
|
261
|
+
(`priceDisplay`).
|
|
256
262
|
- **It has no timeout.** A payment takes as long as it takes, so the call waits for as long as the
|
|
257
263
|
Arcade is connected.
|
|
258
264
|
- `ok`: the player paid. **Read balances and inventory through `gem.me`**; the answer carries no
|
|
@@ -266,10 +272,11 @@ arrives, so your own celebration is visible.
|
|
|
266
272
|
```
|
|
267
273
|
|
|
268
274
|
- `declined` / `cancelled`: the player closed the checkout without paying. Return to your screen
|
|
269
|
-
quietly and **do not ask again unprompted
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
275
|
+
quietly and **do not ask again unprompted**. Nothing backs off: the player's next press opens the
|
|
276
|
+
checkout again, however many times they closed it. An offer the player cannot buy again yet also
|
|
277
|
+
answers `declined` / `cancelled`: the Arcade tells the player when it can be bought, and opens
|
|
278
|
+
nothing.
|
|
279
|
+
- `declined` / `busy`: another checkout or Arcade sheet was already open. Nothing was shown.
|
|
273
280
|
- `unavailable` with `store_off`, `sign_in_required` or `not_found` (a SKU that is not in
|
|
274
281
|
`offers()` right now), or with a payment provider's own failure code: nothing was bought. The
|
|
275
282
|
first three show nothing.
|
|
@@ -304,13 +311,13 @@ pass the player already holds, how long it has left. The sheet's Buy opens the s
|
|
|
304
311
|
|
|
305
312
|
Pick by who can say what the offer is:
|
|
306
313
|
|
|
307
|
-
| | **You draw it: `offers()` + `purchase()`**
|
|
308
|
-
| --------------- |
|
|
309
|
-
| Use it for | Gem packs: a name, an amount and a price say it all
|
|
310
|
-
| Your button | says what it buys and shows `priceDisplay`
|
|
311
|
-
| The press opens | the checkout, straight away
|
|
312
|
-
| Perks and sales | not available to you
|
|
313
|
-
| A holder sees | the same Buy button
|
|
314
|
+
| | **You draw it: `offers()` + `purchase()`** | **The Arcade draws it: `showOffer()`** |
|
|
315
|
+
| --------------- | --------------------------------------------------- | --------------------------------------------------------------- |
|
|
316
|
+
| Use it for | Gem packs: a name, an amount and a price say it all | VIP and other passes, and bundles |
|
|
317
|
+
| Your button | says what it buys and shows `priceDisplay` | says what it opens, such as "Get VIP" |
|
|
318
|
+
| The press opens | the checkout, straight away | the Arcade's sheet, then the checkout if the player presses Buy |
|
|
319
|
+
| Perks and sales | not available to you | always current, in the player's language |
|
|
320
|
+
| A holder sees | the same Buy button | what they already have |
|
|
314
321
|
|
|
315
322
|
- **Never write an offer's perks into your game.** They change without a new build of your game, and
|
|
316
323
|
yours would go stale. Your button only needs the offer's name.
|
|
@@ -328,10 +335,8 @@ Pick by who can say what the offer is:
|
|
|
328
335
|
|
|
329
336
|
Every rule of `purchase` holds ([§9](#9-buying-from-the-arcade-store)), and the answers are the
|
|
330
337
|
same, with one addition. `declined` / `cancelled` also covers the player leaving the sheet without
|
|
331
|
-
pressing Buy, and you are not told which of the two they closed.
|
|
332
|
-
|
|
333
|
-
while. The press shows nothing during a back-off, so give the player something else to do on that
|
|
334
|
-
screen.
|
|
338
|
+
pressing Buy, and you are not told which of the two they closed. Nothing backs off: the player's
|
|
339
|
+
next press on either button opens it again.
|
|
335
340
|
|
|
336
341
|
## 11. What the player holds, and a VIP door
|
|
337
342
|
|
|
@@ -445,14 +450,14 @@ player, and the route refuses it with `403`. The body is `{ pass, held, ends_at
|
|
|
445
450
|
While something listens to `on('changed')`, the SDK reads again at each of these, and calls you only
|
|
446
451
|
if the answer differs:
|
|
447
452
|
|
|
448
|
-
| Moment
|
|
449
|
-
|
|
|
450
|
-
| `store.purchase` or `store.showOffer` answers `ok`
|
|
451
|
-
| Either answers `unavailable` / `error`
|
|
452
|
-
| `gem.store.onSettled` fires
|
|
453
|
-
| Your game resumes
|
|
454
|
-
| The player signs in
|
|
455
|
-
| A held pass reaches `endsAt`
|
|
453
|
+
| Moment | Why you cannot rely on something else |
|
|
454
|
+
| -------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
455
|
+
| `store.purchase` or `store.showOffer` answers `ok` | `onSettled` does not fire after a plain `ok` |
|
|
456
|
+
| Either answers `unavailable` / `error` | The outcome is unknown, and the player may have paid |
|
|
457
|
+
| `gem.store.onSettled` fires | `ok` / `pending` still reads as not held |
|
|
458
|
+
| Your game resumes | A purchase in the Arcade's own store, from the Gem menu, sends no event at all |
|
|
459
|
+
| The player signs in | A guest who signs in may hold VIP |
|
|
460
|
+
| A held pass reaches `endsAt` | VIP does not renew, so the door has to lock by itself |
|
|
456
461
|
|
|
457
462
|
A read that fails changes nothing, and is tried again later. A read that was already on its way when
|
|
458
463
|
a purchase ended is never reused, so a stale answer cannot shut the door on the buyer. If you read
|
|
@@ -473,15 +478,15 @@ Until then, decide in a function, or leave a VIP door out of a dedicated-server
|
|
|
473
478
|
|
|
474
479
|
## 12. Outcomes and reasons
|
|
475
480
|
|
|
476
|
-
| Call
|
|
477
|
-
|
|
|
478
|
-
| `appLinks`
|
|
479
|
-
| `openApp`
|
|
480
|
-
| `openGame`
|
|
481
|
-
| `similarGames`
|
|
482
|
-
| `store.offers`
|
|
483
|
-
| `store.purchase`
|
|
484
|
-
| `store.showOffer` | paid, or `pending`
|
|
481
|
+
| Call | `ok` | `declined` | `unavailable` |
|
|
482
|
+
| ----------------- | ------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
483
|
+
| `appLinks` | listings to draw | | `unsupported`, `error` |
|
|
484
|
+
| `openApp` | opened, or `handed_off` | `busy` | `already_in_app`, `unsupported`, `error` |
|
|
485
|
+
| `openGame` | the next game is starting | `busy`, `same_game` | `not_found`, `unsupported`, `error` |
|
|
486
|
+
| `similarGames` | cards, possibly none | | `unsupported`, `error` |
|
|
487
|
+
| `store.offers` | offers, possibly none | | `sign_in_required`, `store_off`, `unsupported`, `error` |
|
|
488
|
+
| `store.purchase` | paid, or `pending` | `cancelled`, `busy` | `sign_in_required`, `store_off`, `not_found`, the provider's reason, `unsupported`, `error` (outcome unknown) |
|
|
489
|
+
| `store.showOffer` | paid, or `pending` | `cancelled`, `busy` | `sign_in_required`, `store_off`, `not_found`, the provider's reason, `unsupported`, `error` (outcome unknown) |
|
|
485
490
|
|
|
486
491
|
Every reason is a short code of lower-case letters and underscores. The Arcade turns a payment
|
|
487
492
|
provider's own code into that shape before it reaches you.
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,32 @@ 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
|
+
## 20.1.0
|
|
20
|
+
|
|
21
|
+
Upgrading from 20.0.0. Impact: required.
|
|
22
|
+
|
|
23
|
+
### Rebuild
|
|
24
|
+
|
|
25
|
+
- The list of capability names a game can advertise gained one value, p2pRelayDrain. Recompile. If the game switches exhaustively over capability names, add a case for it. Otherwise nothing else changes.
|
|
26
|
+
- The list of messages the Arcade page sends to the game frame gained one entry, gem.relayDraining, and the parent message type gained the matching member. Recompile. If the game handles every parent message in an exhaustive switch, add a case for the new one. Otherwise nothing else changes.
|
|
27
|
+
|
|
28
|
+
### New capability
|
|
29
|
+
|
|
30
|
+
- A game can now be told when a relay carrying its peer-to-peer match is draining and by when. Pass onRelayDraining in the frame options, or implement it on the frame callbacks, to receive the notice. The SDK already moves the connection off the draining relay by itself, so use this only to inform the player or log it. The notice is sent only by an Arcade page that supports it.
|
|
31
|
+
- Telemetry samples can now carry labels, so a game that reads or forwards telemetry samples can see per-sample label values such as outcome and reason.
|
|
32
|
+
|
|
33
|
+
## 20.0.0
|
|
34
|
+
|
|
35
|
+
Upgrading from 19.0.2. Impact: required.
|
|
36
|
+
|
|
37
|
+
### Must change
|
|
38
|
+
|
|
39
|
+
- The parent conformance version moved from 3 to 4. If your game, test setup or tooling checks against the conformance version or runs the parent conformance checks, update it to expect version 4. Checks written for version 3 will now fail where they expect a purchase to be refused without a player press, or expect purchases to be refused after a player dismissed a checkout or sheet.
|
|
40
|
+
|
|
41
|
+
### May stop
|
|
42
|
+
|
|
43
|
+
- Purchases no longer need a player press. A purchase asked from a timer or after an await opens its checkout, and the checkout is the confirmation. Games can drop code that holds a purchase request back until a tap or click. Games can also drop code that waits out a pause after a player dismisses a checkout or sheet, because the next request opens it again. Two things are unchanged: leaving the game still needs a press, and a purchase asked while another surface is open is still declined as busy.
|
|
44
|
+
|
|
19
45
|
## 19.0.2
|
|
20
46
|
|
|
21
47
|
Upgrading from 19.0.1. Impact: none.
|
|
@@ -715,11 +715,39 @@ host does, and the SDK does it for you:
|
|
|
715
715
|
|
|
716
716
|
| What gem-api answers | Where | What the SDK does |
|
|
717
717
|
| --- | --- | --- |
|
|
718
|
-
| `invalid_cluster_target` | `create()
|
|
718
|
+
| `invalid_cluster_target` | `create()` | Creates in the next cluster the page sent, at once, until every one has been refused; then waits up to five seconds for the page to send a different list, and tries once more with a cluster it has not yet tried |
|
|
719
|
+
| `invalid_cluster_target` | `quickJoin()` | Waits up to five seconds for the page to send a different list, and tries once more with it |
|
|
719
720
|
| `invalid_cluster_target` | `startMatch()` — the platform does not run the room's cluster | Starts once in this player's best other cluster, naming it, which moves the room |
|
|
720
721
|
| `capacity_unavailable` twice in a row | `startMatch()` | Moves to the next acceptable cluster, once, and keeps waiting there |
|
|
721
722
|
|
|
722
|
-
**Each
|
|
723
|
+
**Each wait and each move happens once.** `create()` trying each cluster the page sent counts as one
|
|
724
|
+
step, and it tries three at most, because the page sends three at most. A refusal after that is the
|
|
725
|
+
answer, and it reaches you as the error.
|
|
726
|
+
|
|
727
|
+
### When the platform hides a cluster
|
|
728
|
+
|
|
729
|
+
An operator can **hide** a cluster to take it out of service without stopping it: it leaves the list
|
|
730
|
+
the page measures, it takes no new rooms, no new matches and no new TURN relay leases, and the
|
|
731
|
+
matches already running on it carry on. A page that measured before the hide still lists it until
|
|
732
|
+
it measures again, so the SDK is what moves that player on:
|
|
733
|
+
|
|
734
|
+
- **`create()`** is refused with `invalid_cluster_target` for the hidden cluster and creates in the
|
|
735
|
+
next one the page sent, with no wait.
|
|
736
|
+
- **`quickJoin()` and matchmaking** need nothing: the platform drops the hidden cluster from the list
|
|
737
|
+
they send and serves the rest.
|
|
738
|
+
- **`startMatch()`** in a room on the hidden cluster is refused the same way and starts once in the
|
|
739
|
+
host's best other cluster, which moves the room — as for any cluster the platform stopped running.
|
|
740
|
+
- **A P2P relay lease** is answered from another cluster in the same region when there is one, and
|
|
741
|
+
otherwise refused; the SDK then leases from the next cluster the page sent. See
|
|
742
|
+
`p2p-and-parties.md` §4.
|
|
743
|
+
|
|
744
|
+
An earlier SDK version reaches the same place more slowly or not at all. Its `create()` names only the
|
|
745
|
+
best cluster, waits up to five seconds for a list the page will not send, and then gives you the
|
|
746
|
+
refusal, so a game that creates rooms shows an error until the page measures again. Its P2P lease asks
|
|
747
|
+
the hidden cluster once and, with no other cluster in that region to answer for it, connects without
|
|
748
|
+
a relay. Its `startMatch()` and matchmaking behave as above, and so does its `quickJoin()` from
|
|
749
|
+
5.11.0 on, when the SDK began sending the page's whole list; before that a quick-join names one
|
|
750
|
+
cluster, and one naming only the hidden cluster is refused.
|
|
723
751
|
|
|
724
752
|
**An automatic start has nobody to answer, so the host's SDK answers it.** When a room's template
|
|
725
753
|
starts the match as it fills to `min_players` and the platform refuses that start, no call is waiting
|
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": "20.1.0"
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -137,8 +137,8 @@ const me = await gem.awaitIdentity();
|
|
|
137
137
|
|
|
138
138
|
Two things about that call are worth getting right the first time.
|
|
139
139
|
|
|
140
|
-
**You do not list who may embed your game.** The Gem Arcade's website
|
|
141
|
-
|
|
140
|
+
**You do not list who may embed your game.** The Gem Arcade's website and app are always
|
|
141
|
+
allowed, and so is a dev host on your own machine (`gemctl serve`, the Gem Editor) while your
|
|
142
142
|
game is served from loopback. `allowedEmbedders` exists only for a page beyond those — see
|
|
143
143
|
[the embedder allowlist](what-a-game-must-do.md#1-the-embedder-allowlist) — and it adds to the
|
|
144
144
|
arcade's origins rather than replacing them.
|