@indiegems/gem-web-sdk 7.9.1 → 8.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 +45 -0
- package/README.md +1 -0
- package/dist/function.d.ts +26 -1
- package/dist/game.d.ts +44 -1
- package/dist/gem-web-sdk.bridge.js +1 -1
- package/dist/gem-web-sdk.esm.js +2 -2
- package/dist/gem-web-sdk.function.js +2 -2
- 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 +2 -2
- package/dist/index.d.ts +21 -3
- package/dist/sdk.d.ts +44 -1
- package/docs/achievements.md +72 -5
- package/docs/api/api-reference-game.md +719 -24
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +671 -15
- package/docs/changelog.md +45 -0
- package/docs/cross-promotion.md +38 -2
- package/docs/friends.md +86 -11
- package/docs/getting-started.md +12 -7
- package/docs/manifest.json +25 -20
- package/docs/saving-player-progress.md +41 -0
- package/docs/server-bundles.md +2 -2
- package/docs/serverless-functions-js.md +3 -2
- package/docs/serverless-functions.md +53 -7
- package/docs/vibration.md +141 -0
- package/docs/what-a-game-must-do.md +136 -3
- package/package.json +1 -1
- package/schema/bridge.v1.json +102 -1
- package/schema/capabilities.public.json +15 -0
|
@@ -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` v8.1.0. Every symbol below links to its page on the Gem Arcade developer site.
|
|
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
|
|
|
@@ -247,6 +247,47 @@ check that a funnel you declared names events you are actually sending.
|
|
|
247
247
|
|
|
248
248
|
Absent when your game declares none.
|
|
249
249
|
|
|
250
|
+
## GameConfig.rewarded_placements
|
|
251
|
+
|
|
252
|
+
[GameConfig.rewarded_placements](https://gemarcade.com/developer/api/game#gameconfig-rewarded_placements)
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
rewarded_placements?: SnapshotRewardedPlacement[]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Your rewarded placements — what pressing a rewarded button grants. An
|
|
259
|
+
array, one row per placement, each naming itself in `placement`, with its
|
|
260
|
+
`grants`, the caps the platform enforces on it (`max_per_day`,
|
|
261
|
+
`cooldown_seconds`, `max_held`) and what happens where no ad serves
|
|
262
|
+
(`without_ads`).
|
|
263
|
+
|
|
264
|
+
Read it to LABEL your button — "+1 Revive" — never to decide a reward. A
|
|
265
|
+
grant is an `item_grant`, with a `quantity` of the item its
|
|
266
|
+
`item_definition_id` names, or a `currency_grant`, with an `amount` of the
|
|
267
|
+
currency its `currency_id` names; the word on the button is that
|
|
268
|
+
definition's `display_name`, from [GameConfig.item](https://gemarcade.com/developer/api/game#gameconfig-item) or
|
|
269
|
+
[GameConfig.currency](https://gemarcade.com/developer/api/game#gameconfig-currency):
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
const row = config?.rewarded_placements?.find((p) => p.placement === 'classic_revive');
|
|
273
|
+
const grant = row?.grants[0];
|
|
274
|
+
const label =
|
|
275
|
+
grant?.type === 'item_grant'
|
|
276
|
+
? `+${grant.quantity} ${config.item(grant.item_definition_id)?.display_name ?? ''}`
|
|
277
|
+
: grant?.type === 'currency_grant'
|
|
278
|
+
? `+${grant.amount} ${config.currency(grant.currency_id)?.display_name ?? ''}`
|
|
279
|
+
: undefined;
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Whether an ask grants anything is the platform's decision, made against
|
|
283
|
+
this config server-side; the grant lands on your game's inventory, and
|
|
284
|
+
re-reading `gem.me.inventory` is the only way to learn that it did. Whether
|
|
285
|
+
the button could pay right now is [AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus).
|
|
286
|
+
|
|
287
|
+
Absent when your game declares none.
|
|
288
|
+
|
|
289
|
+
**Guides.** [cross-promotion](https://gemarcade.com/developer/docs/cross-promotion)
|
|
290
|
+
|
|
250
291
|
## GameConfig.version
|
|
251
292
|
|
|
252
293
|
[GameConfig.version](https://gemarcade.com/developer/api/game#gameconfig-version)
|
|
@@ -2934,6 +2975,11 @@ No reason is given for a `no_ad`, and one will never be added: a reason would
|
|
|
2934
2975
|
tell your game something about the player — an age, a consent state, a
|
|
2935
2976
|
subscription — that game code is deliberately not told.
|
|
2936
2977
|
|
|
2978
|
+
The one thing you can learn is about **your own economy**, and not from this
|
|
2979
|
+
answer: [AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus) reports your own declared rewarded caps
|
|
2980
|
+
as the platform holds them (a platform-wide per-placement maximum, where one
|
|
2981
|
+
is set, can lower a cap). It never reports anything about the player.
|
|
2982
|
+
|
|
2937
2983
|
## AdOutcome
|
|
2938
2984
|
|
|
2939
2985
|
[AdOutcome](https://gemarcade.com/developer/api/game#adoutcome)
|
|
@@ -3009,6 +3055,160 @@ startNextLevel();
|
|
|
3009
3055
|
|
|
3010
3056
|
**Guides.** [cross-promotion](https://gemarcade.com/developer/docs/cross-promotion)
|
|
3011
3057
|
|
|
3058
|
+
## AdsApi.rewardedStatus
|
|
3059
|
+
|
|
3060
|
+
[AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus)
|
|
3061
|
+
|
|
3062
|
+
```ts
|
|
3063
|
+
rewardedStatus(placement: string): Promise<RewardedStatus>
|
|
3064
|
+
```
|
|
3065
|
+
|
|
3066
|
+
Whether a rewarded placement could pay right now, so you can decide whether
|
|
3067
|
+
to draw its button. **It never says whether an ad will play.**
|
|
3068
|
+
|
|
3069
|
+
The answer comes from your own `rewarded_placements` config — its daily
|
|
3070
|
+
cap, cooldown and `max_held` — applied to this player's use of it, as the
|
|
3071
|
+
platform holds them. Nothing in it describes the player. `grantable: true`
|
|
3072
|
+
means the platform would grant if an ad is watched or, where no ad serves,
|
|
3073
|
+
under the placement's `without_ads: grant`; a player can still press the
|
|
3074
|
+
button and get `no_ad`, for reasons your game is not told.
|
|
3075
|
+
|
|
3076
|
+
NOTHING REJECTS, like everything else on `gem.ads`. Without a play token,
|
|
3077
|
+
when the read fails or takes longer than ten seconds, or for a `placement`
|
|
3078
|
+
outside the grammar [AdRequest.placement](https://gemarcade.com/developer/api/game#adrequest-placement) documents, it resolves not
|
|
3079
|
+
declared and not grantable, with no `resetsAt`, and the reason goes to your
|
|
3080
|
+
`onError` handler.
|
|
3081
|
+
|
|
3082
|
+
It is a read of the platform, so call it when you draw the button — on a
|
|
3083
|
+
results or death screen — not every frame. Calls for one placement that
|
|
3084
|
+
overlap share one request. Draw the rest of the screen first rather than
|
|
3085
|
+
holding it on this answer.
|
|
3086
|
+
|
|
3087
|
+
⚠️ It grants nothing and reserves nothing. The reward is granted by the
|
|
3088
|
+
platform when the player asks with `requestAd`; re-read `gem.me.inventory`
|
|
3089
|
+
after that ask settles, whatever it settled as.
|
|
3090
|
+
|
|
3091
|
+
| Parameter | Type | Description |
|
|
3092
|
+
| --- | --- | --- |
|
|
3093
|
+
| `placement` | `string` | A placement your `rewarded_placements` config declares. |
|
|
3094
|
+
|
|
3095
|
+
**Returns.** The placement's state. Always resolves; never rejects.
|
|
3096
|
+
|
|
3097
|
+
```ts
|
|
3098
|
+
const status = await gem.ads.rewardedStatus('classic_revive');
|
|
3099
|
+
reviveButton.disabled = !status.grantable;
|
|
3100
|
+
if (status.nextEligibleAt !== undefined) {
|
|
3101
|
+
reviveButton.title = `Back at ${new Date(status.nextEligibleAt).toLocaleTimeString()}`;
|
|
3102
|
+
}
|
|
3103
|
+
```
|
|
3104
|
+
|
|
3105
|
+
**Guides.** [cross-promotion](https://gemarcade.com/developer/docs/cross-promotion)
|
|
3106
|
+
|
|
3107
|
+
## RewardedStatus
|
|
3108
|
+
|
|
3109
|
+
[RewardedStatus](https://gemarcade.com/developer/api/game#rewardedstatus)
|
|
3110
|
+
|
|
3111
|
+
```ts
|
|
3112
|
+
interface RewardedStatus
|
|
3113
|
+
```
|
|
3114
|
+
|
|
3115
|
+
What [AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus) answers: the state of one rewarded
|
|
3116
|
+
placement for this player, from your own declared caps.
|
|
3117
|
+
|
|
3118
|
+
Every figure comes from **your** `rewarded_placements` config — `max_per_day`,
|
|
3119
|
+
`cooldown_seconds`, `max_held` — applied to this player's use of it, as the
|
|
3120
|
+
platform holds them. The platform can hold a placement's daily cap below what
|
|
3121
|
+
you declared, never above it. Nothing here describes the player.
|
|
3122
|
+
|
|
3123
|
+
⚠️ **It never says whether an ad will play.** Whether one does depends on
|
|
3124
|
+
things your game is deliberately not told. A player can see your button,
|
|
3125
|
+
press it, and get `no_ad`: read that `no_ad` like any other, and never guess
|
|
3126
|
+
a reason.
|
|
3127
|
+
|
|
3128
|
+
## RewardedStatus.declared
|
|
3129
|
+
|
|
3130
|
+
[RewardedStatus.declared](https://gemarcade.com/developer/api/game#rewardedstatus-declared)
|
|
3131
|
+
|
|
3132
|
+
```ts
|
|
3133
|
+
readonly declared: boolean
|
|
3134
|
+
```
|
|
3135
|
+
|
|
3136
|
+
Whether this channel's published config declares the placement.
|
|
3137
|
+
|
|
3138
|
+
`false` for a placement your config does not name, a config that is not
|
|
3139
|
+
promoted to the channel being played, and every answer the platform did not
|
|
3140
|
+
give (see [RewardedStatus.resetsAt](https://gemarcade.com/developer/api/game#rewardedstatus-resetsat)).
|
|
3141
|
+
|
|
3142
|
+
## RewardedStatus.grantable
|
|
3143
|
+
|
|
3144
|
+
[RewardedStatus.grantable](https://gemarcade.com/developer/api/game#rewardedstatus-grantable)
|
|
3145
|
+
|
|
3146
|
+
```ts
|
|
3147
|
+
readonly grantable: boolean
|
|
3148
|
+
```
|
|
3149
|
+
|
|
3150
|
+
Whether pressing your button could pay right now: the platform would grant
|
|
3151
|
+
the reward if an ad is watched or, where no ad serves, under the
|
|
3152
|
+
placement's `without_ads: grant`.
|
|
3153
|
+
|
|
3154
|
+
**Not a promise that an ad will play, and not a promise of a reward.** It
|
|
3155
|
+
is `false` whenever a cap is spent, a cooldown is running, the player holds
|
|
3156
|
+
`max_held`, rewarded is off on this channel, or the arcade running your
|
|
3157
|
+
game does not take ad requests at all.
|
|
3158
|
+
|
|
3159
|
+
## RewardedStatus.remainingToday
|
|
3160
|
+
|
|
3161
|
+
[RewardedStatus.remainingToday](https://gemarcade.com/developer/api/game#rewardedstatus-remainingtoday)
|
|
3162
|
+
|
|
3163
|
+
```ts
|
|
3164
|
+
readonly remainingToday?: number
|
|
3165
|
+
```
|
|
3166
|
+
|
|
3167
|
+
Grants left today under the placement's daily cap, a non-negative integer.
|
|
3168
|
+
|
|
3169
|
+
ABSENT — no key, never a sentinel — when the placement has no daily cap,
|
|
3170
|
+
and on any answer where `declared` is `false`.
|
|
3171
|
+
|
|
3172
|
+
## RewardedStatus.nextEligibleAt
|
|
3173
|
+
|
|
3174
|
+
[RewardedStatus.nextEligibleAt](https://gemarcade.com/developer/api/game#rewardedstatus-nexteligibleat)
|
|
3175
|
+
|
|
3176
|
+
```ts
|
|
3177
|
+
readonly nextEligibleAt?: number
|
|
3178
|
+
```
|
|
3179
|
+
|
|
3180
|
+
When the placement's cooldown ends, in epoch milliseconds — render it with
|
|
3181
|
+
`new Date(nextEligibleAt)`.
|
|
3182
|
+
|
|
3183
|
+
ABSENT when no cooldown is running.
|
|
3184
|
+
|
|
3185
|
+
## RewardedStatus.heldLimitReached
|
|
3186
|
+
|
|
3187
|
+
[RewardedStatus.heldLimitReached](https://gemarcade.com/developer/api/game#rewardedstatus-heldlimitreached)
|
|
3188
|
+
|
|
3189
|
+
```ts
|
|
3190
|
+
readonly heldLimitReached: boolean
|
|
3191
|
+
```
|
|
3192
|
+
|
|
3193
|
+
Whether the player already holds the placement's `max_held` of the item it
|
|
3194
|
+
grants. Spend one first.
|
|
3195
|
+
|
|
3196
|
+
## RewardedStatus.resetsAt
|
|
3197
|
+
|
|
3198
|
+
[RewardedStatus.resetsAt](https://gemarcade.com/developer/api/game#rewardedstatus-resetsat)
|
|
3199
|
+
|
|
3200
|
+
```ts
|
|
3201
|
+
readonly resetsAt?: number
|
|
3202
|
+
```
|
|
3203
|
+
|
|
3204
|
+
When the daily count resets — the next 00:00 UTC — in epoch milliseconds.
|
|
3205
|
+
Take it from here rather than computing midnight in game code.
|
|
3206
|
+
|
|
3207
|
+
ABSENT when the platform gave no answer: no play token yet, a read that
|
|
3208
|
+
failed or took longer than ten seconds, or a platform that does not serve
|
|
3209
|
+
this read. Then the rest of the status reads not declared and not
|
|
3210
|
+
grantable, and the reason went to your `onError` handler.
|
|
3211
|
+
|
|
3012
3212
|
## AnalyticsValue
|
|
3013
3213
|
|
|
3014
3214
|
[AnalyticsValue](https://gemarcade.com/developer/api/game#analyticsvalue)
|
|
@@ -4863,6 +5063,29 @@ sends only while connected, and only to an arcade that draws a wallet.
|
|
|
4863
5063
|
**Returns.** whether it entered the send path — `false` before the bridge is
|
|
4864
5064
|
up, and `false` for an arcade with no wallet to move.
|
|
4865
5065
|
|
|
5066
|
+
## GemFrame.showPortraitBorders
|
|
5067
|
+
|
|
5068
|
+
[GemFrame.showPortraitBorders](https://gemarcade.com/developer/api/game#gemframe-showportraitborders)
|
|
5069
|
+
|
|
5070
|
+
```ts
|
|
5071
|
+
showPortraitBorders(name: string | null): boolean
|
|
5072
|
+
```
|
|
5073
|
+
|
|
5074
|
+
Tell the arcade which of your store page's portrait borders to paint beside
|
|
5075
|
+
your game, or `null` for none.
|
|
5076
|
+
|
|
5077
|
+
Prefer `gem.showPortraitBorders`, which holds your choice until the bridge
|
|
5078
|
+
is up and paces a burst of changes for you. This one sends every call it
|
|
5079
|
+
accepts, only while connected, and only to an arcade that paints borders.
|
|
5080
|
+
|
|
5081
|
+
| Parameter | Type |
|
|
5082
|
+
| --- | --- |
|
|
5083
|
+
| `name` | `string \| null` |
|
|
5084
|
+
|
|
5085
|
+
**Returns.** whether the message left the frame — `false` for a string that is
|
|
5086
|
+
not a border name (with a console warning), before the bridge is up, for
|
|
5087
|
+
an arcade that paints no borders, and past this frame's own limit.
|
|
5088
|
+
|
|
4866
5089
|
## GemFrame.setWalletVisible
|
|
4867
5090
|
|
|
4868
5091
|
[GemFrame.setWalletVisible](https://gemarcade.com/developer/api/game#gemframe-setwalletvisible)
|
|
@@ -4935,6 +5158,30 @@ arcade that handles share.
|
|
|
4935
5158
|
up, for an arcade that does not handle share, and past this frame's own
|
|
4936
5159
|
limit of six a minute.
|
|
4937
5160
|
|
|
5161
|
+
## GemFrame.requestVibrate
|
|
5162
|
+
|
|
5163
|
+
[GemFrame.requestVibrate](https://gemarcade.com/developer/api/game#gemframe-requestvibrate)
|
|
5164
|
+
|
|
5165
|
+
```ts
|
|
5166
|
+
requestVibrate(pattern: readonly number[]): boolean
|
|
5167
|
+
```
|
|
5168
|
+
|
|
5169
|
+
Ask the arcade to play a vibration pattern where this frame has no
|
|
5170
|
+
`navigator.vibrate` of its own. Nothing comes back.
|
|
5171
|
+
|
|
5172
|
+
Prefer `gem.vibration.vibrate`, which calls `navigator.vibrate` itself
|
|
5173
|
+
where it exists, normalizes the pattern and caps its total length. This one
|
|
5174
|
+
checks only the message's bounds, sends only while connected, and only to
|
|
5175
|
+
an arcade that handles vibration.
|
|
5176
|
+
|
|
5177
|
+
| Parameter | Type |
|
|
5178
|
+
| --- | --- |
|
|
5179
|
+
| `pattern` | `readonly number[]` |
|
|
5180
|
+
|
|
5181
|
+
**Returns.** whether the message left the frame — `false` before the bridge is
|
|
5182
|
+
up, for an arcade that does not handle vibration, and past this frame's
|
|
5183
|
+
own limit of 600 a minute.
|
|
5184
|
+
|
|
4938
5185
|
## GemFrame.reportFrameStats
|
|
4939
5186
|
|
|
4940
5187
|
[GemFrame.reportFrameStats](https://gemarcade.com/developer/api/game#gemframe-reportframestats)
|
|
@@ -5067,8 +5314,9 @@ draw your own placeholder rather than a broken image. Pass it through
|
|
|
5067
5314
|
readonly badges: readonly PlayerBadge[]
|
|
5068
5315
|
```
|
|
5069
5316
|
|
|
5070
|
-
The player's equipped badges,
|
|
5071
|
-
|
|
5317
|
+
The player's equipped badges, at most two, in the order the player
|
|
5318
|
+
arranged them. Always a list — empty when they wear none, or when their
|
|
5319
|
+
identity is withheld — so draw what is there and key on `itemId`.
|
|
5072
5320
|
|
|
5073
5321
|
## FriendsPage
|
|
5074
5322
|
|
|
@@ -5121,7 +5369,9 @@ readonly limit?: number
|
|
|
5121
5369
|
```
|
|
5122
5370
|
|
|
5123
5371
|
Friends per page — default 50, at most 200. A player holds at most 1000
|
|
5124
|
-
friends, so five max-size pages is the whole list.
|
|
5372
|
+
friends, so five max-size pages is the whole list. A larger value is
|
|
5373
|
+
clamped to 200; a value that is not a non-negative integer is refused with
|
|
5374
|
+
`invalid`.
|
|
5125
5375
|
|
|
5126
5376
|
## ListFriendsOptions.cursor
|
|
5127
5377
|
|
|
@@ -5144,6 +5394,181 @@ readonly signal?: AbortSignal
|
|
|
5144
5394
|
|
|
5145
5395
|
Abort the read.
|
|
5146
5396
|
|
|
5397
|
+
## FriendChallenge
|
|
5398
|
+
|
|
5399
|
+
[FriendChallenge](https://gemarcade.com/developer/api/game#friendchallenge)
|
|
5400
|
+
|
|
5401
|
+
```ts
|
|
5402
|
+
interface FriendChallenge
|
|
5403
|
+
```
|
|
5404
|
+
|
|
5405
|
+
One friend challenge this player received, in THIS game on THIS channel.
|
|
5406
|
+
|
|
5407
|
+
What the platform holds, not what the card said: there is no rendered line,
|
|
5408
|
+
because your game renders its own copy from [FriendChallenge.template](https://gemarcade.com/developer/api/game#friendchallenge-template)
|
|
5409
|
+
and [FriendChallenge.params](https://gemarcade.com/developer/api/game#friendchallenge-params). Who sent it is
|
|
5410
|
+
[FriendChallenge.from](https://gemarcade.com/developer/api/game#friendchallenge-from), resolved by the platform from its own records.
|
|
5411
|
+
|
|
5412
|
+
## FriendChallenge.id
|
|
5413
|
+
|
|
5414
|
+
[FriendChallenge.id](https://gemarcade.com/developer/api/game#friendchallenge-id)
|
|
5415
|
+
|
|
5416
|
+
```ts
|
|
5417
|
+
readonly id: string
|
|
5418
|
+
```
|
|
5419
|
+
|
|
5420
|
+
The challenge's id — the value the Arcade puts in the `gemChallenge`
|
|
5421
|
+
launch key, and what [FriendsApi.markAnswered](https://gemarcade.com/developer/api/game#friendsapi-markanswered) takes. Opaque: compare
|
|
5422
|
+
it, store it, never parse it.
|
|
5423
|
+
|
|
5424
|
+
## FriendChallenge.from
|
|
5425
|
+
|
|
5426
|
+
[FriendChallenge.from](https://gemarcade.com/developer/api/game#friendchallenge-from)
|
|
5427
|
+
|
|
5428
|
+
```ts
|
|
5429
|
+
readonly from: Friend
|
|
5430
|
+
```
|
|
5431
|
+
|
|
5432
|
+
Who sent it, with the same display identity a [Friend](https://gemarcade.com/developer/api/game#friend) row carries.
|
|
5433
|
+
|
|
5434
|
+
The ONLY name to show. Never read a name out of
|
|
5435
|
+
[FriendChallenge.params](https://gemarcade.com/developer/api/game#friendchallenge-params): a name in a param is one nobody checked.
|
|
5436
|
+
|
|
5437
|
+
## FriendChallenge.template
|
|
5438
|
+
|
|
5439
|
+
[FriendChallenge.template](https://gemarcade.com/developer/api/game#friendchallenge-template)
|
|
5440
|
+
|
|
5441
|
+
```ts
|
|
5442
|
+
readonly template: string
|
|
5443
|
+
```
|
|
5444
|
+
|
|
5445
|
+
The `friend_challenge_templates[].key` it was sent with — which of your
|
|
5446
|
+
templates, so your game knows what was asked.
|
|
5447
|
+
|
|
5448
|
+
## FriendChallenge.params
|
|
5449
|
+
|
|
5450
|
+
[FriendChallenge.params](https://gemarcade.com/developer/api/game#friendchallenge-params)
|
|
5451
|
+
|
|
5452
|
+
```ts
|
|
5453
|
+
readonly params: Readonly<Record<string, string | number>>
|
|
5454
|
+
```
|
|
5455
|
+
|
|
5456
|
+
The params exactly as sent: integers as numbers, date, enum and code
|
|
5457
|
+
params as strings. The template constrained them when the challenge was
|
|
5458
|
+
sent, but the config may have changed since, so check each one against
|
|
5459
|
+
what your game expects before it does anything — as you would a launch
|
|
5460
|
+
key.
|
|
5461
|
+
|
|
5462
|
+
## FriendChallenge.createdAt
|
|
5463
|
+
|
|
5464
|
+
[FriendChallenge.createdAt](https://gemarcade.com/developer/api/game#friendchallenge-createdat)
|
|
5465
|
+
|
|
5466
|
+
```ts
|
|
5467
|
+
readonly createdAt: number
|
|
5468
|
+
```
|
|
5469
|
+
|
|
5470
|
+
When it was sent, in Unix seconds.
|
|
5471
|
+
|
|
5472
|
+
## FriendChallenge.openedAt
|
|
5473
|
+
|
|
5474
|
+
[FriendChallenge.openedAt](https://gemarcade.com/developer/api/game#friendchallenge-openedat)
|
|
5475
|
+
|
|
5476
|
+
```ts
|
|
5477
|
+
readonly openedAt?: number
|
|
5478
|
+
```
|
|
5479
|
+
|
|
5480
|
+
When the player pressed Play on it in the Arcade, in Unix seconds. Absent
|
|
5481
|
+
until they have.
|
|
5482
|
+
|
|
5483
|
+
## FriendChallenge.answeredAt
|
|
5484
|
+
|
|
5485
|
+
[FriendChallenge.answeredAt](https://gemarcade.com/developer/api/game#friendchallenge-answeredat)
|
|
5486
|
+
|
|
5487
|
+
```ts
|
|
5488
|
+
readonly answeredAt?: number
|
|
5489
|
+
```
|
|
5490
|
+
|
|
5491
|
+
When a game marked it answered with [FriendsApi.markAnswered](https://gemarcade.com/developer/api/game#friendsapi-markanswered), in
|
|
5492
|
+
Unix seconds. Absent until one has — so present on a relaunch of a
|
|
5493
|
+
challenge already played, which is how a replay is told from a first
|
|
5494
|
+
attempt.
|
|
5495
|
+
|
|
5496
|
+
## FriendChallengesPage
|
|
5497
|
+
|
|
5498
|
+
[FriendChallengesPage](https://gemarcade.com/developer/api/game#friendchallengespage)
|
|
5499
|
+
|
|
5500
|
+
```ts
|
|
5501
|
+
interface FriendChallengesPage
|
|
5502
|
+
```
|
|
5503
|
+
|
|
5504
|
+
One page of the friend challenges this player holds in this game.
|
|
5505
|
+
|
|
5506
|
+
## FriendChallengesPage.challenges
|
|
5507
|
+
|
|
5508
|
+
[FriendChallengesPage.challenges](https://gemarcade.com/developer/api/game#friendchallengespage-challenges)
|
|
5509
|
+
|
|
5510
|
+
```ts
|
|
5511
|
+
readonly challenges: readonly FriendChallenge[]
|
|
5512
|
+
```
|
|
5513
|
+
|
|
5514
|
+
The page's challenges, newest first, answered ones last.
|
|
5515
|
+
|
|
5516
|
+
## FriendChallengesPage.nextCursor
|
|
5517
|
+
|
|
5518
|
+
[FriendChallengesPage.nextCursor](https://gemarcade.com/developer/api/game#friendchallengespage-nextcursor)
|
|
5519
|
+
|
|
5520
|
+
```ts
|
|
5521
|
+
readonly nextCursor?: string
|
|
5522
|
+
```
|
|
5523
|
+
|
|
5524
|
+
Present exactly when another page exists — pass it back as
|
|
5525
|
+
[ListFriendChallengesOptions.cursor](https://gemarcade.com/developer/api/game#listfriendchallengesoptions-cursor) to continue. Opaque: mint
|
|
5526
|
+
nothing, store nothing, compare nothing.
|
|
5527
|
+
|
|
5528
|
+
## ListFriendChallengesOptions
|
|
5529
|
+
|
|
5530
|
+
[ListFriendChallengesOptions](https://gemarcade.com/developer/api/game#listfriendchallengesoptions)
|
|
5531
|
+
|
|
5532
|
+
```ts
|
|
5533
|
+
interface ListFriendChallengesOptions
|
|
5534
|
+
```
|
|
5535
|
+
|
|
5536
|
+
What [FriendsApi.inbox](https://gemarcade.com/developer/api/game#friendsapi-inbox) takes.
|
|
5537
|
+
|
|
5538
|
+
## ListFriendChallengesOptions.limit
|
|
5539
|
+
|
|
5540
|
+
[ListFriendChallengesOptions.limit](https://gemarcade.com/developer/api/game#listfriendchallengesoptions-limit)
|
|
5541
|
+
|
|
5542
|
+
```ts
|
|
5543
|
+
readonly limit?: number
|
|
5544
|
+
```
|
|
5545
|
+
|
|
5546
|
+
Challenges per page — default 50, at most 200. The platform keeps at most
|
|
5547
|
+
200 per player, so one max-size page is the whole inbox. A larger value is
|
|
5548
|
+
clamped to 200; a value that is not a non-negative integer is refused with
|
|
5549
|
+
`invalid`.
|
|
5550
|
+
|
|
5551
|
+
## ListFriendChallengesOptions.cursor
|
|
5552
|
+
|
|
5553
|
+
[ListFriendChallengesOptions.cursor](https://gemarcade.com/developer/api/game#listfriendchallengesoptions-cursor)
|
|
5554
|
+
|
|
5555
|
+
```ts
|
|
5556
|
+
readonly cursor?: string
|
|
5557
|
+
```
|
|
5558
|
+
|
|
5559
|
+
The previous page's [FriendChallengesPage.nextCursor](https://gemarcade.com/developer/api/game#friendchallengespage-nextcursor), verbatim. A
|
|
5560
|
+
cursor the platform did not mint is refused with `invalid`.
|
|
5561
|
+
|
|
5562
|
+
## ListFriendChallengesOptions.signal
|
|
5563
|
+
|
|
5564
|
+
[ListFriendChallengesOptions.signal](https://gemarcade.com/developer/api/game#listfriendchallengesoptions-signal)
|
|
5565
|
+
|
|
5566
|
+
```ts
|
|
5567
|
+
readonly signal?: AbortSignal
|
|
5568
|
+
```
|
|
5569
|
+
|
|
5570
|
+
Abort the read.
|
|
5571
|
+
|
|
5147
5572
|
## FriendChallengeRequest
|
|
5148
5573
|
|
|
5149
5574
|
[FriendChallengeRequest](https://gemarcade.com/developer/api/game#friendchallengerequest)
|
|
@@ -5423,15 +5848,18 @@ channel: the version the template is missing from.
|
|
|
5423
5848
|
class FriendsApi
|
|
5424
5849
|
```
|
|
5425
5850
|
|
|
5426
|
-
Reading the signed-in player's accepted friends,
|
|
5427
|
-
|
|
5851
|
+
Reading the signed-in player's accepted friends, asking the Arcade to send
|
|
5852
|
+
one of them a friend challenge, and reading the friend challenges this
|
|
5853
|
+
player received in this game.
|
|
5428
5854
|
|
|
5429
5855
|
Reached as `gem.friends`. Display identity only, and every accepted friend
|
|
5430
5856
|
appears whether or not they have played this game — an invite-a-friend flow
|
|
5431
5857
|
needs exactly the ones who have not. The graph itself — requests, accepts,
|
|
5432
5858
|
blocks — is managed on the Arcade, not here. So is a friend challenge's
|
|
5433
5859
|
send: [FriendsApi.challenge](https://gemarcade.com/developer/api/game#friendsapi-challenge) asks, and the Arcade confirms with the
|
|
5434
|
-
player and sends.
|
|
5860
|
+
player and sends. The receiving side is [FriendsApi.launchedChallenge](https://gemarcade.com/developer/api/game#friendsapi-launchedchallenge)
|
|
5861
|
+
(the challenge this frame was launched with), [FriendsApi.inbox](https://gemarcade.com/developer/api/game#friendsapi-inbox) and
|
|
5862
|
+
[FriendsApi.markAnswered](https://gemarcade.com/developer/api/game#friendsapi-markanswered).
|
|
5435
5863
|
|
|
5436
5864
|
```js
|
|
5437
5865
|
let page = await gem.friends.list();
|
|
@@ -5460,7 +5888,8 @@ have played this one.
|
|
|
5460
5888
|
| --- | --- |
|
|
5461
5889
|
| `options` | `ListFriendsOptions` |
|
|
5462
5890
|
|
|
5463
|
-
**Throws.** `invalid` when the cursor is not one a previous page minted
|
|
5891
|
+
**Throws.** `invalid` when the cursor is not one a previous page minted or
|
|
5892
|
+
`limit` is not a non-negative integer,
|
|
5464
5893
|
`forbidden` in a context with no player — this is a player surface, and
|
|
5465
5894
|
a game server has no friend list.
|
|
5466
5895
|
|
|
@@ -5520,6 +5949,123 @@ challengeButton.onclick = async () => {
|
|
|
5520
5949
|
};
|
|
5521
5950
|
```
|
|
5522
5951
|
|
|
5952
|
+
## FriendsApi.inbox
|
|
5953
|
+
|
|
5954
|
+
[FriendsApi.inbox](https://gemarcade.com/developer/api/game#friendsapi-inbox)
|
|
5955
|
+
|
|
5956
|
+
```ts
|
|
5957
|
+
inbox(options?: ListFriendChallengesOptions): Promise<FriendChallengesPage>
|
|
5958
|
+
```
|
|
5959
|
+
|
|
5960
|
+
Read one page of the friend challenges this player holds in THIS game on
|
|
5961
|
+
THIS channel — in every state. Answered challenges are INCLUDED, and sort
|
|
5962
|
+
after the rest; within each part, newest first.
|
|
5963
|
+
|
|
5964
|
+
Pass the result's `nextCursor` back as `cursor` until it stops arriving.
|
|
5965
|
+
A guest holds no challenges, so a guest's page is empty rather than a
|
|
5966
|
+
refusal — build the screen for the empty state, not around an error.
|
|
5967
|
+
|
|
5968
|
+
For the one challenge this frame was launched with, use
|
|
5969
|
+
[FriendsApi.launchedChallenge](https://gemarcade.com/developer/api/game#friendsapi-launchedchallenge) rather than searching this list.
|
|
5970
|
+
|
|
5971
|
+
| Parameter | Type |
|
|
5972
|
+
| --- | --- |
|
|
5973
|
+
| `options` | `ListFriendChallengesOptions` |
|
|
5974
|
+
|
|
5975
|
+
**Throws.** `invalid` when the cursor is not one a previous page minted or
|
|
5976
|
+
`limit` is not a non-negative integer,
|
|
5977
|
+
`forbidden` in a context with no player of this game — a game server,
|
|
5978
|
+
or a token that is not a game-scoped player token.
|
|
5979
|
+
|
|
5980
|
+
```ts
|
|
5981
|
+
const page = await gem.friends.inbox();
|
|
5982
|
+
for (const c of page.challenges) {
|
|
5983
|
+
if (c.template !== 'beat_code_jar') continue;
|
|
5984
|
+
renderChallengeRow(c.from, c.params, c.answeredAt !== undefined);
|
|
5985
|
+
}
|
|
5986
|
+
```
|
|
5987
|
+
|
|
5988
|
+
## FriendsApi.launchedChallenge
|
|
5989
|
+
|
|
5990
|
+
[FriendsApi.launchedChallenge](https://gemarcade.com/developer/api/game#friendsapi-launchedchallenge)
|
|
5991
|
+
|
|
5992
|
+
```ts
|
|
5993
|
+
launchedChallenge(options?: { signal?: AbortSignal }): Promise<FriendChallenge | null>
|
|
5994
|
+
```
|
|
5995
|
+
|
|
5996
|
+
The friend challenge this frame was launched with, or `null`.
|
|
5997
|
+
|
|
5998
|
+
When a player opens a friend challenge in the Arcade, the Arcade launches
|
|
5999
|
+
your game with the challenge's params as launch keys plus its own
|
|
6000
|
+
`gemChallenge` key. This reads that key — never parse it yourself — and
|
|
6001
|
+
looks the challenge up, so you get the sender from the platform's records
|
|
6002
|
+
and the params as they were sent.
|
|
6003
|
+
|
|
6004
|
+
The key is read ONCE, when `createGem` runs, so cleaning your URL with
|
|
6005
|
+
`history.replaceState` afterwards changes nothing here.
|
|
6006
|
+
|
|
6007
|
+
RESOLVES `null`, never rejects, for every launch that is not this
|
|
6008
|
+
player's challenge in this game: no `gemChallenge` key, a value that is
|
|
6009
|
+
not a challenge id, a challenge that is not this player's (a link
|
|
6010
|
+
someone shared), one from another game or channel, and every guest
|
|
6011
|
+
launch. Treat `null` as an ordinary launch.
|
|
6012
|
+
|
|
6013
|
+
A RELAUNCH REPLAYS. Pressing Play again in the Arcade relaunches the
|
|
6014
|
+
same challenge, so the same one comes back; it carries
|
|
6015
|
+
[FriendChallenge.answeredAt](https://gemarcade.com/developer/api/game#friendchallenge-answeredat) once a game has called
|
|
6016
|
+
[FriendsApi.markAnswered](https://gemarcade.com/developer/api/game#friendsapi-markanswered), which is how a replay is told from a
|
|
6017
|
+
first attempt. Asks the platform on every call, so `answeredAt` is
|
|
6018
|
+
current.
|
|
6019
|
+
|
|
6020
|
+
| Parameter | Type |
|
|
6021
|
+
| --- | --- |
|
|
6022
|
+
| `options` | `{ signal?: AbortSignal }` |
|
|
6023
|
+
|
|
6024
|
+
**Throws.** What any platform read throws: `forbidden` in a context with no
|
|
6025
|
+
player of this game, `unavailable` and `rate_limited` when the platform
|
|
6026
|
+
cannot answer now — and `not_found` for a 404 that is NOT the platform's
|
|
6027
|
+
uniform challenge answer (no `inbox_item_not_found` code), which is a
|
|
6028
|
+
platform that does not serve this read rather than an ordinary launch.
|
|
6029
|
+
|
|
6030
|
+
```ts
|
|
6031
|
+
const challenge = await gem.friends.launchedChallenge();
|
|
6032
|
+
if (challenge?.template === 'beat_code_jar') {
|
|
6033
|
+
// Check every param before it does anything: the config may have changed since.
|
|
6034
|
+
const jar = challenge.params.jar;
|
|
6035
|
+
if (typeof jar === 'string' && /^[A-Z0-9]{7}$/.test(jar)) {
|
|
6036
|
+
startJar(jar, { from: challenge.from, replay: challenge.answeredAt !== undefined });
|
|
6037
|
+
}
|
|
6038
|
+
}
|
|
6039
|
+
```
|
|
6040
|
+
|
|
6041
|
+
## FriendsApi.markAnswered
|
|
6042
|
+
|
|
6043
|
+
[FriendsApi.markAnswered](https://gemarcade.com/developer/api/game#friendsapi-markanswered)
|
|
6044
|
+
|
|
6045
|
+
```ts
|
|
6046
|
+
markAnswered(id: string): Promise<void>
|
|
6047
|
+
```
|
|
6048
|
+
|
|
6049
|
+
Mark one of this player's friend challenges answered — the player has
|
|
6050
|
+
played it. Call it when your game has finished the attempt the challenge
|
|
6051
|
+
asked for, win or lose.
|
|
6052
|
+
|
|
6053
|
+
Idempotent: a second call changes nothing, and a challenge keeps the
|
|
6054
|
+
first time it was answered. The sender is told nothing — there is no read
|
|
6055
|
+
receipt — so this is your game's own record, which
|
|
6056
|
+
[FriendChallenge.answeredAt](https://gemarcade.com/developer/api/game#friendchallenge-answeredat) reads back.
|
|
6057
|
+
|
|
6058
|
+
| Parameter | Type | Description |
|
|
6059
|
+
| --- | --- | --- |
|
|
6060
|
+
| `id` | `string` | A [FriendChallenge.id](https://gemarcade.com/developer/api/game#friendchallenge-id), from
|
|
6061
|
+
[FriendsApi.launchedChallenge](https://gemarcade.com/developer/api/game#friendsapi-launchedchallenge) or [FriendsApi.inbox](https://gemarcade.com/developer/api/game#friendsapi-inbox). |
|
|
6062
|
+
|
|
6063
|
+
**Throws.** `not_found` when no challenge of this player's in this game and
|
|
6064
|
+
channel has that id — one answer for unknown and someone else's;
|
|
6065
|
+
`invalid` when `id` cannot be a challenge id at all, before anything is
|
|
6066
|
+
sent; `forbidden` in a context with no player of this game;
|
|
6067
|
+
`rate_limited` past the platform's per-player budget.
|
|
6068
|
+
|
|
5523
6069
|
## FunctionInvokeOptions
|
|
5524
6070
|
|
|
5525
6071
|
[FunctionInvokeOptions](https://gemarcade.com/developer/api/game#functioninvokeoptions)
|
|
@@ -6561,8 +7107,9 @@ draw your own placeholder rather than a broken image. Pass it through
|
|
|
6561
7107
|
readonly badges: readonly PlayerBadge[]
|
|
6562
7108
|
```
|
|
6563
7109
|
|
|
6564
|
-
The player's equipped badges,
|
|
6565
|
-
|
|
7110
|
+
The player's equipped badges, at most two, in the order the player
|
|
7111
|
+
arranged them. Always a list — empty when they wear none, or when their
|
|
7112
|
+
identity is withheld — so draw what is there and key on `itemId`.
|
|
6566
7113
|
|
|
6567
7114
|
## GemPrefs
|
|
6568
7115
|
|
|
@@ -7342,7 +7889,10 @@ resumes it when the promise settles, so you do not pause anything yourself.
|
|
|
7342
7889
|
⚠️ `shown` **grants nothing.** It is not a receipt and not an entitlement,
|
|
7343
7890
|
and a reward must never be handed out on the strength of it.
|
|
7344
7891
|
|
|
7345
|
-
You are told no reason for a no, and there is nothing to infer from one
|
|
7892
|
+
You are told no reason for a no, and there is nothing to infer from one
|
|
7893
|
+
about the player. What you can read is your own economy:
|
|
7894
|
+
`rewardedStatus(placement)` says whether a rewarded placement could pay
|
|
7895
|
+
right now under your own declared caps — never whether an ad will play.
|
|
7346
7896
|
|
|
7347
7897
|
**Guides.** [cross-promotion](https://gemarcade.com/developer/docs/cross-promotion)
|
|
7348
7898
|
|
|
@@ -7405,6 +7955,40 @@ shareButton.onclick = () => {
|
|
|
7405
7955
|
|
|
7406
7956
|
**Guides.** [getting-started](https://gemarcade.com/developer/docs/getting-started)
|
|
7407
7957
|
|
|
7958
|
+
## Gem.vibration
|
|
7959
|
+
|
|
7960
|
+
[Gem.vibration](https://gemarcade.com/developer/api/game#gem-vibration)
|
|
7961
|
+
|
|
7962
|
+
```ts
|
|
7963
|
+
readonly vibration: VibrationApi
|
|
7964
|
+
```
|
|
7965
|
+
|
|
7966
|
+
Vibrate the player's device — call this instead of `navigator.vibrate`,
|
|
7967
|
+
which does nothing on any iPhone. It takes the same pattern and returns the
|
|
7968
|
+
same boolean, calls `navigator.vibrate` itself where that works, and asks
|
|
7969
|
+
the arcade to vibrate where it does not.
|
|
7970
|
+
|
|
7971
|
+
Read [VibrationApi.available](https://gemarcade.com/developer/api/game#vibrationapi-available) live, after `awaitBridge()`, to decide
|
|
7972
|
+
whether to show your game's vibration toggle — on an iPhone it is `false`
|
|
7973
|
+
until the bridge connects — and check that toggle before every call:
|
|
7974
|
+
|
|
7975
|
+
```js
|
|
7976
|
+
await gem.awaitBridge();
|
|
7977
|
+
vibrationRow.hidden = !gem.vibration.available;
|
|
7978
|
+
|
|
7979
|
+
function onHit() {
|
|
7980
|
+
if (settings.vibration) gem.vibration.vibrate(30);
|
|
7981
|
+
}
|
|
7982
|
+
```
|
|
7983
|
+
|
|
7984
|
+
- **Never call `navigator.vibrate`**, guarded or not, and never fall back
|
|
7985
|
+
to it: this already does.
|
|
7986
|
+
- **Nothing vibrates before the player's first tap** in your game; the
|
|
7987
|
+
call returns `false` until then.
|
|
7988
|
+
- **Stop on pause** with `gem.vibration.vibrate(0)`.
|
|
7989
|
+
|
|
7990
|
+
**Guides.** [vibration](https://gemarcade.com/developer/docs/vibration)
|
|
7991
|
+
|
|
7408
7992
|
## Gem.analytics
|
|
7409
7993
|
|
|
7410
7994
|
[Gem.analytics](https://gemarcade.com/developer/api/game#gem-analytics)
|
|
@@ -7499,12 +8083,17 @@ readonly friends: FriendsApi
|
|
|
7499
8083
|
The signed-in player's accepted friends — display identity only,
|
|
7500
8084
|
read-only, paginated — and the one ask that reaches past them:
|
|
7501
8085
|
[FriendsApi.challenge](https://gemarcade.com/developer/api/game#friendsapi-challenge), which asks the Arcade to send a friend a
|
|
7502
|
-
challenge after the player confirms it in the Arcade's own sheet.
|
|
8086
|
+
challenge after the player confirms it in the Arcade's own sheet. The
|
|
8087
|
+
receiving side reads the challenges this player holds in this game:
|
|
8088
|
+
[FriendsApi.launchedChallenge](https://gemarcade.com/developer/api/game#friendsapi-launchedchallenge) for the one this frame was launched
|
|
8089
|
+
with, [FriendsApi.inbox](https://gemarcade.com/developer/api/game#friendsapi-inbox) for all of them, and
|
|
8090
|
+
[FriendsApi.markAnswered](https://gemarcade.com/developer/api/game#friendsapi-markanswered) once one has been played.
|
|
7503
8091
|
|
|
7504
8092
|
Every accepted friend appears whether or not they have played this game;
|
|
7505
8093
|
an invite-a-friend flow needs exactly the ones who have not. The graph
|
|
7506
8094
|
itself — requests, accepts, blocks, presence — stays on the Arcade: there
|
|
7507
|
-
is nothing here to mutate
|
|
8095
|
+
is nothing here to mutate but a challenge's own answered mark, and nothing
|
|
8096
|
+
that says who is online.
|
|
7508
8097
|
|
|
7509
8098
|
A player surface only. In a context with no player — a game server — the
|
|
7510
8099
|
platform refuses the read; a server has no friend list.
|
|
@@ -7698,6 +8287,51 @@ arcade just said is not sent.
|
|
|
7698
8287
|
| --- | --- |
|
|
7699
8288
|
| `muted` | `boolean` |
|
|
7700
8289
|
|
|
8290
|
+
## Gem.showPortraitBorders
|
|
8291
|
+
|
|
8292
|
+
[Gem.showPortraitBorders](https://gemarcade.com/developer/api/game#gem-showportraitborders)
|
|
8293
|
+
|
|
8294
|
+
```ts
|
|
8295
|
+
showPortraitBorders(name: string | null): void
|
|
8296
|
+
```
|
|
8297
|
+
|
|
8298
|
+
Choose which of your store page's **portrait borders** the arcade paints on
|
|
8299
|
+
either side of your game, or `null` for none.
|
|
8300
|
+
|
|
8301
|
+
On a desktop browser, the arcade's desktop app or a television, the arcade
|
|
8302
|
+
shows a portrait-only game as a centred portrait column. With borders
|
|
8303
|
+
declared on your store page (`media.portrait_borders`), it paints one of
|
|
8304
|
+
those images beside the column instead of its own bars. Call this at
|
|
8305
|
+
start-up and on every theme change, with the border that goes with what is
|
|
8306
|
+
on screen:
|
|
8307
|
+
|
|
8308
|
+
```js
|
|
8309
|
+
const BORDERS = new Set(['meadow', 'alpine']); // the names your store page declares
|
|
8310
|
+
|
|
8311
|
+
function applyTheme(id) {
|
|
8312
|
+
document.body.dataset.theme = id;
|
|
8313
|
+
gem.showPortraitBorders(BORDERS.has(id) ? id : null); // null: a theme with no art
|
|
8314
|
+
}
|
|
8315
|
+
```
|
|
8316
|
+
|
|
8317
|
+
Until your first call the arcade shows the page's first border. Whether
|
|
8318
|
+
your game is letterboxed at all is decided by your store page's
|
|
8319
|
+
`orientations`, never by this call, so call it unconditionally: before the
|
|
8320
|
+
connection exists your choice is held and sent when it connects, a burst of
|
|
8321
|
+
changes (a picker previewing on hover) reaches the arcade as its final
|
|
8322
|
+
value, and an arcade that paints no borders is simply not told. The arcade
|
|
8323
|
+
owns the change itself — when and how the image swaps — and sends no
|
|
8324
|
+
acknowledgement.
|
|
8325
|
+
|
|
8326
|
+
A name is 1 to 40 characters of `a-z`, `0-9`, `_` and `-`, starting with a
|
|
8327
|
+
letter or digit — the store page's own rule. Anything else is refused with
|
|
8328
|
+
a console warning and never sent; a valid name your page does not declare
|
|
8329
|
+
is ignored by the arcade, which keeps the image it is showing.
|
|
8330
|
+
|
|
8331
|
+
| Parameter | Type |
|
|
8332
|
+
| --- | --- |
|
|
8333
|
+
| `name` | `string \| null` |
|
|
8334
|
+
|
|
7701
8335
|
## Gem.cueReward
|
|
7702
8336
|
|
|
7703
8337
|
[Gem.cueReward](https://gemarcade.com/developer/api/game#gem-cuereward)
|
|
@@ -8798,7 +9432,8 @@ interface PlayerBadge
|
|
|
8798
9432
|
One badge a player has equipped — an icon the platform shows beside their
|
|
8799
9433
|
name.
|
|
8800
9434
|
|
|
8801
|
-
|
|
9435
|
+
A player wears at most two, in the order they arranged them. Each is an icon
|
|
9436
|
+
they own — an achievement in any game may have awarded it.
|
|
8802
9437
|
|
|
8803
9438
|
## PlayerBadge.itemId
|
|
8804
9439
|
|
|
@@ -8922,8 +9557,9 @@ draw your own placeholder rather than a broken image. Pass it through
|
|
|
8922
9557
|
readonly badges: readonly PlayerBadge[]
|
|
8923
9558
|
```
|
|
8924
9559
|
|
|
8925
|
-
The player's equipped badges,
|
|
8926
|
-
|
|
9560
|
+
The player's equipped badges, at most two, in the order the player
|
|
9561
|
+
arranged them. Always a list — empty when they wear none, or when their
|
|
9562
|
+
identity is withheld — so draw what is there and key on `itemId`.
|
|
8927
9563
|
|
|
8928
9564
|
## mapIdentity
|
|
8929
9565
|
|
|
@@ -11247,6 +11883,61 @@ A unit test that constructs the SDK in a simulated DOM has to give its
|
|
|
11247
11883
|
window a parent of its own, because a simulated top-level window is exactly
|
|
11248
11884
|
what this refuses.
|
|
11249
11885
|
|
|
11886
|
+
## VibrationApi
|
|
11887
|
+
|
|
11888
|
+
[VibrationApi](https://gemarcade.com/developer/api/game#vibrationapi)
|
|
11889
|
+
|
|
11890
|
+
```ts
|
|
11891
|
+
interface VibrationApi
|
|
11892
|
+
```
|
|
11893
|
+
|
|
11894
|
+
`gem.vibration` — vibrate the player's device, on every platform that can.
|
|
11895
|
+
Call it instead of `navigator.vibrate`, which does nothing on any iPhone.
|
|
11896
|
+
|
|
11897
|
+
## VibrationApi.available
|
|
11898
|
+
|
|
11899
|
+
[VibrationApi.available](https://gemarcade.com/developer/api/game#vibrationapi-available)
|
|
11900
|
+
|
|
11901
|
+
```ts
|
|
11902
|
+
readonly available: boolean
|
|
11903
|
+
```
|
|
11904
|
+
|
|
11905
|
+
Whether this device can vibrate for your game: decide from this whether to
|
|
11906
|
+
show your game's vibration toggle. Read it live, every time — after
|
|
11907
|
+
`awaitBridge()`, and again when your settings screen opens — never once at
|
|
11908
|
+
boot: on an iPhone it is `false` until the bridge connects.
|
|
11909
|
+
|
|
11910
|
+
`false` on a desktop with a mouse, in iOS Safari, in Firefox, before the
|
|
11911
|
+
bridge is up where vibration goes through the arcade, and after the
|
|
11912
|
+
session ends there.
|
|
11913
|
+
|
|
11914
|
+
## VibrationApi.vibrate
|
|
11915
|
+
|
|
11916
|
+
[VibrationApi.vibrate](https://gemarcade.com/developer/api/game#vibrationapi-vibrate)
|
|
11917
|
+
|
|
11918
|
+
```ts
|
|
11919
|
+
vibrate(pattern: number | readonly number[]): boolean
|
|
11920
|
+
```
|
|
11921
|
+
|
|
11922
|
+
Vibrate: takes what `navigator.vibrate` takes and returns what it returns.
|
|
11923
|
+
`pattern` is a duration in milliseconds, or an array of alternating
|
|
11924
|
+
vibration and pause durations, starting with a vibration. `0` or `[]`
|
|
11925
|
+
stops a pattern that is still playing, and every call replaces it.
|
|
11926
|
+
|
|
11927
|
+
The pattern is capped at 99 entries of at most 10 000 ms each, and at
|
|
11928
|
+
10 000 ms in all: a longer one is cut short, not refused.
|
|
11929
|
+
|
|
11930
|
+
Check your game's own vibration toggle before every call. Returns `false`,
|
|
11931
|
+
and vibrates nothing, when [VibrationApi.available](https://gemarcade.com/developer/api/game#vibrationapi-available) is false, before
|
|
11932
|
+
the player's first tap or key press in your game, for a pattern with a
|
|
11933
|
+
negative or non-finite entry, and past 600 calls a minute; each of the last
|
|
11934
|
+
three says so once a session through your `log`. `true` means the pattern
|
|
11935
|
+
was accepted, never that a motor moved.
|
|
11936
|
+
|
|
11937
|
+
| Parameter | Type |
|
|
11938
|
+
| --- | --- |
|
|
11939
|
+
| `pattern` | `number \| readonly number[]` |
|
|
11940
|
+
|
|
11250
11941
|
## LeaderboardPeriod.id
|
|
11251
11942
|
|
|
11252
11943
|
[LeaderboardPeriod.id](https://gemarcade.com/developer/api/game#leaderboardperiod-id)
|
|
@@ -11415,8 +12106,9 @@ draw your own placeholder rather than a broken image. Pass it through
|
|
|
11415
12106
|
readonly badges: readonly PlayerBadge[]
|
|
11416
12107
|
```
|
|
11417
12108
|
|
|
11418
|
-
The player's equipped badges,
|
|
11419
|
-
|
|
12109
|
+
The player's equipped badges, at most two, in the order the player
|
|
12110
|
+
arranged them. Always a list — empty when they wear none, or when their
|
|
12111
|
+
identity is withheld — so draw what is there and key on `itemId`.
|
|
11420
12112
|
|
|
11421
12113
|
## LeaderboardEntry.rank
|
|
11422
12114
|
|
|
@@ -12856,8 +13548,9 @@ draw your own placeholder rather than a broken image. Pass it through
|
|
|
12856
13548
|
readonly badges: readonly PlayerBadge[]
|
|
12857
13549
|
```
|
|
12858
13550
|
|
|
12859
|
-
The player's equipped badges,
|
|
12860
|
-
|
|
13551
|
+
The player's equipped badges, at most two, in the order the player
|
|
13552
|
+
arranged them. Always a list — empty when they wear none, or when their
|
|
13553
|
+
identity is withheld — so draw what is there and key on `itemId`.
|
|
12861
13554
|
|
|
12862
13555
|
## PlayerProfile.playerId
|
|
12863
13556
|
|
|
@@ -14890,8 +15583,9 @@ draw your own placeholder rather than a broken image. Pass it through
|
|
|
14890
15583
|
readonly badges: readonly PlayerBadge[]
|
|
14891
15584
|
```
|
|
14892
15585
|
|
|
14893
|
-
The player's equipped badges,
|
|
14894
|
-
|
|
15586
|
+
The player's equipped badges, at most two, in the order the player
|
|
15587
|
+
arranged them. Always a list — empty when they wear none, or when their
|
|
15588
|
+
identity is withheld — so draw what is there and key on `itemId`.
|
|
14895
15589
|
|
|
14896
15590
|
## RoomMember.playerId
|
|
14897
15591
|
|
|
@@ -15654,8 +16348,9 @@ draw your own placeholder rather than a broken image. Pass it through
|
|
|
15654
16348
|
readonly badges: readonly PlayerBadge[]
|
|
15655
16349
|
```
|
|
15656
16350
|
|
|
15657
|
-
The player's equipped badges,
|
|
15658
|
-
|
|
16351
|
+
The player's equipped badges, at most two, in the order the player
|
|
16352
|
+
arranged them. Always a list — empty when they wear none, or when their
|
|
16353
|
+
identity is withheld — so draw what is there and key on `itemId`.
|
|
15659
16354
|
|
|
15660
16355
|
## ShowcaseFriendEntry.playerId
|
|
15661
16356
|
|