@indiegems/gem-web-sdk 7.1.0 → 7.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/brand/ticket-hero-128.png +0 -0
- package/brand/ticket-hero-256.png +0 -0
- package/brand/ticket-hero-384.png +0 -0
- package/brand/ticket-hero-512.png +0 -0
- package/brand/ticket-hero-768.png +0 -0
- package/brand/ticket-icon-20-1.5x.png +0 -0
- package/brand/ticket-icon-20-1x.png +0 -0
- package/brand/ticket-icon-20-2x.png +0 -0
- package/brand/ticket-icon-20-3x.png +0 -0
- package/brand/ticket-icon-32-1.5x.png +0 -0
- package/brand/ticket-icon-32-1x.png +0 -0
- package/brand/ticket-icon-32-2x.png +0 -0
- package/brand/ticket-icon-32-3x.png +0 -0
- package/dist/game.d.ts +9 -1
- package/dist/gem-web-sdk.bridge.js +1 -1
- package/dist/gem-web-sdk.esm.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 +4 -2
- package/dist/sdk.d.ts +9 -1
- package/package.json +1 -1
- package/schema/bridge.v1.json +159 -29
package/schema/bridge.v1.json
CHANGED
|
@@ -2318,14 +2318,20 @@
|
|
|
2318
2318
|
"the following is not offering a degraded store, it is offering a game a",
|
|
2319
2319
|
"button that charges a player:",
|
|
2320
2320
|
"",
|
|
2321
|
-
" EARNED CURRENCIES ONLY. `currencySlug` is frame-chosen and this
|
|
2322
|
-
" not constrain which currency it names. The argument that
|
|
2323
|
-
" this without a top-up flow binds a currency a player EARNS
|
|
2324
|
-
" Gems
|
|
2325
|
-
"
|
|
2326
|
-
"
|
|
2327
|
-
"
|
|
2328
|
-
"
|
|
2321
|
+
" PAYABLE, EARNED CURRENCIES ONLY. `currencySlug` is frame-chosen and this",
|
|
2322
|
+
" schema does not constrain which currency it names. The argument that let",
|
|
2323
|
+
" this ship without a top-up flow binds a currency a player EARNS: short of",
|
|
2324
|
+
" Gems or Tickets the answer is to go and play, and neither is sold for",
|
|
2325
|
+
" money. It does not bind a currency a player BUYS. So resolve the slug",
|
|
2326
|
+
" against the platform's own currency records - an active currency the",
|
|
2327
|
+
" platform marks payable in games - and refuse the rest, `declined` with",
|
|
2328
|
+
" `unknown_currency` or `currency_not_in_game_payable`. Resolve it EXACTLY,",
|
|
2329
|
+
" with no",
|
|
2330
|
+
" alias table of your own: the retired `TICKETS` meant GEMS, and resolving",
|
|
2331
|
+
" it charged Gems to games that meant Tickets. An item may carry prices in",
|
|
2332
|
+
" more than one payable currency, so",
|
|
2333
|
+
" the frame does choose which wallet pays; the prompt names that currency",
|
|
2334
|
+
" and its price from platform records, which keeps the choice the player's.",
|
|
2329
2335
|
"",
|
|
2330
2336
|
" THE FRAME CHOOSES THE AMOUNT. Not the unit price - but `targetId` x",
|
|
2331
2337
|
" `currencySlug` x `quantity` is nevertheless the total, up to ten times the",
|
|
@@ -2333,12 +2339,26 @@
|
|
|
2333
2339
|
" price, quantity and total distinctly so a 10x multiplier is never hidden",
|
|
2334
2340
|
" behind one figure.",
|
|
2335
2341
|
"",
|
|
2342
|
+
" ONLY ON USER ACTIVATION. Render nothing, and answer `declined` with",
|
|
2343
|
+
" reason `busy`, unless `navigator.userActivation.isActive` is true when",
|
|
2344
|
+
" the ask arrives. A click or key press in the game frame activates the",
|
|
2345
|
+
" parent page too, so a Buy button works and a timer does not. It is the",
|
|
2346
|
+
" browser's own signal, so keyboard and assistive-technology activation",
|
|
2347
|
+
" count. This is the control that makes `cancelled` (gem.purchaseResult)",
|
|
2348
|
+
" safe to disclose: a prompt costs a real player's gesture.",
|
|
2349
|
+
"",
|
|
2350
|
+
" BACK OFF AFTER A CANCEL. After a `cancelled`, answer further asks from",
|
|
2351
|
+
" that game `declined` / `busy` without rendering, for an interval that",
|
|
2352
|
+
" grows with each consecutive cancel. Without it, 'ask, learn cancelled,",
|
|
2353
|
+
" ask again' is a re-prompt loop a game can write in three lines.",
|
|
2354
|
+
"",
|
|
2336
2355
|
" ONE PROMPT AT A TIME, and not instantly actionable. A gem.requestPurchase",
|
|
2337
|
-
" arriving while a prompt is open is answered `declined` without
|
|
2338
|
-
" anything. Twelve modals a minute is inside the rate limit
|
|
2339
|
-
" successful consent-fatigue attack; and because the frame
|
|
2340
|
-
" it asks, a confirm control that is actionable the
|
|
2341
|
-
" tap-jacking target - quiet input on it briefly
|
|
2356
|
+
" arriving while a prompt is open is answered `declined` / `busy` without",
|
|
2357
|
+
" rendering anything. Twelve modals a minute is inside the rate limit",
|
|
2358
|
+
" below and is a successful consent-fatigue attack; and because the frame",
|
|
2359
|
+
" chooses the moment it asks, a confirm control that is actionable the",
|
|
2360
|
+
" instant it paints is a tap-jacking target - quiet input on it briefly",
|
|
2361
|
+
" after paint.",
|
|
2342
2362
|
"",
|
|
2343
2363
|
" ONE PURCHASE PER ASK. Each gem.requestPurchase is a distinct purchase and",
|
|
2344
2364
|
" carries its own idempotency key. Do NOT collapse repeated asks: a player",
|
|
@@ -2393,7 +2413,7 @@
|
|
|
2393
2413
|
"currencySlug": {
|
|
2394
2414
|
"kind": "string",
|
|
2395
2415
|
"maxLength": 32,
|
|
2396
|
-
"$comment": "Which platform currency to pay in,
|
|
2416
|
+
"$comment": "Which platform currency to pay in, by its exact slug: GEMS or ARCADE_TICKETS. The item must carry a price in it; the parent resolves that price itself and refuses when there is none (`no_price_in_currency`). There are no aliases: `TICKETS` is a retired name for GEMS and is answered `unknown_currency`, never resolved. FRAME-CHOSEN AND UNCONSTRAINED HERE: nothing in this schema limits it to a currency the player earned rather than bought, so the parent checks it against the platform's own currency records. See the PAYABLE, EARNED CURRENCIES ONLY obligation above - this field is where that whole argument lands."
|
|
2397
2417
|
},
|
|
2398
2418
|
"quantity": {
|
|
2399
2419
|
"kind": "int",
|
|
@@ -2421,11 +2441,32 @@
|
|
|
2421
2441
|
"$comment": [
|
|
2422
2442
|
"The answer to one gem.requestPurchase. Gated by the `purchases` capability.",
|
|
2423
2443
|
"",
|
|
2424
|
-
"
|
|
2425
|
-
"
|
|
2426
|
-
"
|
|
2427
|
-
"
|
|
2428
|
-
"
|
|
2444
|
+
"THREE OUTCOMES, AND A REASON ON THE TWO THAT ARE NOT `ok`. `outcome` says",
|
|
2445
|
+
"what the game should DO;",
|
|
2446
|
+
"`reason` says WHY, so a developer can fix a config mistake instead of",
|
|
2447
|
+
"reading 'failed'. The mapping from condition to reason is normative and is",
|
|
2448
|
+
"written on the `reason` field below.",
|
|
2449
|
+
"",
|
|
2450
|
+
"WHY THIS MESSAGE NOW CARRIES A REASON, WHEN IT USED TO FORBID ONE. The old",
|
|
2451
|
+
"rule - no reason, 'no insufficient funds and there will not be one' -",
|
|
2452
|
+
"protected the player's wallet from untrusted game code: a decline that",
|
|
2453
|
+
"said 'could not afford' let a game walk a balance down a 1/2/4/8 price",
|
|
2454
|
+
"ladder, one prompt at a time. Platform-currency access for games made that",
|
|
2455
|
+
"wallet readable directly - `gem.platform.currencies.get()` hands a game",
|
|
2456
|
+
"the exact balance of every active platform currency - and spendable",
|
|
2457
|
+
"without a sheet through `gem.me.mutations.purchase`, whose refusals",
|
|
2458
|
+
"already say `insufficient_funds`. The owner accepted that as risk M3",
|
|
2459
|
+
"(2026-09-21). So `insufficient_funds` and the config-mistake reasons tell",
|
|
2460
|
+
"a game nothing it cannot already read.",
|
|
2461
|
+
"",
|
|
2462
|
+
"THE ONE ADDED DISCLOSURE IS `cancelled`: what the player chose in",
|
|
2463
|
+
"platform UI. It is behavioural data, and it is what makes an instant",
|
|
2464
|
+
"re-prompt loop cheap - ask, learn 'cancelled', ask again. It ships with",
|
|
2465
|
+
"two parent-side controls, stated as obligations on gem.requestPurchase:",
|
|
2466
|
+
"ONLY ON USER ACTIVATION (no prompt without a real gesture) and BACK OFF",
|
|
2467
|
+
"AFTER A CANCEL (further asks answered `busy`, unrendered, for a growing",
|
|
2468
|
+
"interval). A parent that cannot enforce both must not send `cancelled`;",
|
|
2469
|
+
"it sends `error` in its place.",
|
|
2429
2470
|
"",
|
|
2430
2471
|
"EVERY OUTCOME RESOLVES. Only a protocol failure rejects. A player tapping",
|
|
2431
2472
|
"Cancel is an ordinary thing that happens in every shop, and rejecting there",
|
|
@@ -2436,16 +2477,21 @@
|
|
|
2436
2477
|
" ok - the purchase completed and the grant is on the player's",
|
|
2437
2478
|
" game-scope inventory. Re-read inventory rather than assuming",
|
|
2438
2479
|
" what landed; `purchaseId` names the server's record of it.",
|
|
2439
|
-
" declined -
|
|
2440
|
-
"
|
|
2480
|
+
" declined - a DEFINITE refusal before any debit. Return to the shop",
|
|
2481
|
+
" SILENTLY: no dialog, no 'purchase failed' copy. The player",
|
|
2482
|
+
" already saw the parent's answer where there was one, and an",
|
|
2441
2483
|
" error toast after a deliberate Cancel reads as a bug.",
|
|
2442
|
-
"
|
|
2443
|
-
"
|
|
2444
|
-
"
|
|
2445
|
-
"
|
|
2446
|
-
"
|
|
2447
|
-
" '
|
|
2448
|
-
"
|
|
2484
|
+
" `reason` is for the game's logs and shop state - grey out a",
|
|
2485
|
+
" `recipe_locked` row, fix an `unknown_currency` slug - never",
|
|
2486
|
+
" for a top-up or payment screen of the game's own.",
|
|
2487
|
+
" unavailable - no reliable answer. One neutral 'store unavailable'. Never",
|
|
2488
|
+
" auto-retried; only a fresh player action may ask again. AND",
|
|
2489
|
+
" RE-READ INVENTORY: 'unavailable' means no answer arrived,",
|
|
2490
|
+
" not that nothing happened - the parent may have prompted,",
|
|
2491
|
+
" charged and answered while the answer was lost at a gate or",
|
|
2492
|
+
" a timeout. A reason never makes it more certain. 'declined'",
|
|
2493
|
+
" is the only outcome that says a purchase definitely did not",
|
|
2494
|
+
" happen."
|
|
2449
2495
|
],
|
|
2450
2496
|
"fields": {
|
|
2451
2497
|
"requestId": {
|
|
@@ -2456,7 +2502,91 @@
|
|
|
2456
2502
|
"outcome": {
|
|
2457
2503
|
"kind": "enum",
|
|
2458
2504
|
"values": ["ok", "declined", "unavailable"],
|
|
2459
|
-
"$comment": "THREE VALUES, AND THAT IS THE COMPLETE VOCABULARY
|
|
2505
|
+
"$comment": "THREE VALUES, AND THAT IS THE COMPLETE VOCABULARY OF WHAT TO DO. The WHY is `reason`, a bounded string, so the explanations can grow without this enum growing - which matters because `check-schema-freeze` treats a new enum value as breaking (an N-1 receiver refuses it, and a refused `declined` becomes a two-minute timeout and an `unavailable`). A game branches its money logic on this field and on a re-read of inventory, never on `reason`. THE TIMING CHANNEL: a refusal the parent reaches without rendering anything is separable from a human Cancel by latency. That used to be the gap the no-reason rule left open; with `reason` sent, it tells a game nothing `reason` and `gem.platform.currencies.get()` do not already say. What still matters is how fast a game can ask, and user activation plus the cancel back-off bound that at human speed."
|
|
2506
|
+
},
|
|
2507
|
+
"reason": {
|
|
2508
|
+
"kind": "string",
|
|
2509
|
+
"maxLength": 64,
|
|
2510
|
+
"format": "code",
|
|
2511
|
+
"optional": true,
|
|
2512
|
+
"$comment": [
|
|
2513
|
+
"WHY a purchase did not complete. Sent ONLY with `declined` and",
|
|
2514
|
+
"`unavailable`, never with `ok`; a receiver ignores one on `ok`. OPTIONAL:",
|
|
2515
|
+
"a parent that predates it sends none, and a receiver reads an absent",
|
|
2516
|
+
"reason as `error`.",
|
|
2517
|
+
"",
|
|
2518
|
+
"A BOUNDED STRING, NOT AN ENUM, deliberately. An enum here would be closed",
|
|
2519
|
+
"for good: `check-schema-freeze` refuses an added value, and an older SDK",
|
|
2520
|
+
"validating a new one refuses the WHOLE message, turning a `declined` into a",
|
|
2521
|
+
"two-minute timeout and an `unavailable`. `format: code` is `^[a-z_]+$`, so",
|
|
2522
|
+
"with the bound this is `^[a-z_]{1,64}$`: a machine code, never prose or",
|
|
2523
|
+
"markup. Precedent: gem.exit's `reason`. A receiver maps a value it does not",
|
|
2524
|
+
"know to `error`, so a new code costs an old game an `error`, not a hang.",
|
|
2525
|
+
"",
|
|
2526
|
+
"ONE VOCABULARY FOR BOTH PURCHASE PATHS. The value equals the `code` the",
|
|
2527
|
+
"game gets from `gem.me.mutations.purchase` for the same refusal, so a game",
|
|
2528
|
+
"handles both with one switch. gem-api's purchase wire codes pass VERBATIM;",
|
|
2529
|
+
"`unknown_currency`, `no_price_in_currency`, `busy`, `cancelled` and `error`",
|
|
2530
|
+
"are the parent's (and the SDK's) own. `currency_not_in_game_payable` and",
|
|
2531
|
+
"`not_purchasable` are gem-api's, which a parent may also detect first.",
|
|
2532
|
+
"",
|
|
2533
|
+
"THE MAPPING IS NORMATIVE, AND IT IS AN ALLOW-LIST. A parent sends exactly",
|
|
2534
|
+
"these. Anything it meets that is not listed is sent as `error`, never",
|
|
2535
|
+
"forwarded - so a new gem-api code, a detail field or an account state",
|
|
2536
|
+
"cannot leak through by default:",
|
|
2537
|
+
"",
|
|
2538
|
+
" CONDITION OUTCOME REASON",
|
|
2539
|
+
" the slug is not an active platform declined unknown_currency",
|
|
2540
|
+
" currency - including the retired TICKETS",
|
|
2541
|
+
" the currency is not payable in games declined currency_not_in_game_payable",
|
|
2542
|
+
" the target is not in the purchase catalog, declined not_purchasable",
|
|
2543
|
+
" or is not purchasable",
|
|
2544
|
+
" the target has no price in that currency declined no_price_in_currency",
|
|
2545
|
+
" a sheet is already open, a purchase is in declined busy",
|
|
2546
|
+
" flight, a cancel back-off is running, or",
|
|
2547
|
+
" the ask arrived without user activation",
|
|
2548
|
+
" the player dismissed a sheet they could declined cancelled",
|
|
2549
|
+
" afford",
|
|
2550
|
+
" a shortfall shown on the sheet, or gem-api declined insufficient_funds",
|
|
2551
|
+
" 400 insufficient_funds",
|
|
2552
|
+
" gem-api price_mismatch or declined the code, verbatim",
|
|
2553
|
+
" config_version_stale (both refused",
|
|
2554
|
+
" before any debit)",
|
|
2555
|
+
" gem-api platform_currency_unavailable declined the code, verbatim",
|
|
2556
|
+
" gem-api not_available (the recipe is declined the code, verbatim",
|
|
2557
|
+
" outside its availability window, or",
|
|
2558
|
+
" inactive)",
|
|
2559
|
+
" gem-api recipe_locked declined recipe_locked - the",
|
|
2560
|
+
" CODE ONLY; the",
|
|
2561
|
+
" response's",
|
|
2562
|
+
" details.lock_reasons",
|
|
2563
|
+
" is never forwarded",
|
|
2564
|
+
" gem-api recipe_on_cooldown, declined the code, verbatim",
|
|
2565
|
+
" recipe_max_uses_exceeded,",
|
|
2566
|
+
" item_max_stack_exceeded,",
|
|
2567
|
+
" recipe_costs_unmet, quantity_invalid",
|
|
2568
|
+
" gem-api confirmation_required - NEVER REACHES THE",
|
|
2569
|
+
" GAME: the parent",
|
|
2570
|
+
" handles it itself,",
|
|
2571
|
+
" because it reveals",
|
|
2572
|
+
" the player's age",
|
|
2573
|
+
" gem-api 429 (its limiter is per IP, so it declined error",
|
|
2574
|
+
" would describe other people's activity),",
|
|
2575
|
+
" 403, nonce conflicts, and every account",
|
|
2576
|
+
" state - sanction, age, region",
|
|
2577
|
+
" 5xx, a network failure, a timeout, an `ok` unavailable error, or absent",
|
|
2578
|
+
" the frame could not verify, and a charge",
|
|
2579
|
+
" whose refund failed",
|
|
2580
|
+
"",
|
|
2581
|
+
"`declined` is for a definite refusal before any debit; a parent that",
|
|
2582
|
+
"answers such a refusal `unavailable` is wrong, because `unavailable` means",
|
|
2583
|
+
"'no reliable answer'. A reason never makes `unavailable` more certain: the",
|
|
2584
|
+
"game re-reads inventory after it, whatever the reason says.",
|
|
2585
|
+
"",
|
|
2586
|
+
"ADVISORY, AND NO DEFENCE AGAINST A HOSTILE PARENT. A lying parent can",
|
|
2587
|
+
"mislead a game's copy, never its ledger: games branch money on `outcome`",
|
|
2588
|
+
"and on inventory, and the SDK never retries because of a reason."
|
|
2589
|
+
]
|
|
2460
2590
|
},
|
|
2461
2591
|
"purchaseId": {
|
|
2462
2592
|
"kind": "string",
|