@chaindaddy/js 0.3.1 → 0.3.3

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/dist/api.d.ts CHANGED
@@ -38,8 +38,9 @@ export interface paths {
38
38
  * List the apps on a verified coin's page
39
39
  * @description The apps a verified coin's page shows, in page order: each app's ID, name, description, version,
40
40
  * category, icon and whether it is a built-in. It names the apps; it does not include what the owner
41
- * set up inside them. Apps turned off are left out, and so is every app rated NSFW and every app the
42
- * page itself no longer shows (not reviewed yet, suspended or removed). Answers 404 for a ticker
41
+ * set up inside them. Apps turned off are left out, and so is every app rated NSFW, every app the
42
+ * page itself no longer shows (not reviewed yet, suspended or removed) and the built-in parts of
43
+ * the page that are not apps (its header, its market panel). Answers 404 for a ticker
43
44
  * with no verified page on that chain, and for a page Chain Daddy removed. Keyless, the same for
44
45
  * every caller, and cached for about a minute.
45
46
  */
@@ -103,6 +104,30 @@ export interface paths {
103
104
  patch?: never;
104
105
  trace?: never;
105
106
  };
107
+ "/api/v2/iap/config": {
108
+ parameters: {
109
+ query?: never;
110
+ header?: never;
111
+ path?: never;
112
+ cookie?: never;
113
+ };
114
+ /**
115
+ * Store configuration
116
+ * @description How creator stores work right now: whether they are switched on, the chains a store can run
117
+ * on, what an item on each chain can be priced in besides the store's own token (the chain's
118
+ * coin, a dollar stablecoin), and where the receipt signing keys are published. It also says
119
+ * whether test stores are available here (`testMode`) and whether checkout sessions are
120
+ * (`checkoutSessions`, with the limits on how long one may stay open).
121
+ */
122
+ get: operations["getIapConfig"];
123
+ put?: never;
124
+ post?: never;
125
+ delete?: never;
126
+ options?: never;
127
+ head?: never;
128
+ patch?: never;
129
+ trace?: never;
130
+ };
106
131
  "/api/v2/iap/jwks": {
107
132
  parameters: {
108
133
  query?: never;
@@ -139,6 +164,10 @@ export interface paths {
139
164
  * amount paid, when) with its current status. A refunded order's receipt reads `revoked`: offline
140
165
  * verification proves a receipt was issued, this proves it still stands. The receipt is the compact JWS
141
166
  * exactly as issued (three base64url parts joined by dots).
167
+ *
168
+ * The answer is `200` whatever the receipt is. Act on `valid`, which is true only for status `valid`.
169
+ * A string that is not a receipt Chain Daddy signed (a bad signature, a changed payload, an unknown
170
+ * key, not a JWS at all) answers `{"valid": false, "status": "invalid", "error": "…"}` with no `payload`.
142
171
  */
143
172
  post: operations["verifyIapReceipt"];
144
173
  delete?: never;
@@ -167,6 +196,43 @@ export interface paths {
167
196
  patch?: never;
168
197
  trace?: never;
169
198
  };
199
+ "/api/v2/iap/stores/{storeId}/test-store": {
200
+ parameters: {
201
+ query?: never;
202
+ header?: never;
203
+ path: {
204
+ /** @description The store's ID (a UUID) */
205
+ storeId: components["parameters"]["IapStoreId"];
206
+ };
207
+ cookie?: never;
208
+ };
209
+ /**
210
+ * Get a store's test store
211
+ * @description The test store that stands beside a live store: the same coin page and payout, its own
212
+ * catalog, keys, orders and webhook, and `mode: test`. Its orders are paid with
213
+ * `POST /api/v2/iap/orders/{orderId}/test-pay`; nothing is sent on chain and no fee is taken. Pass the
214
+ * live store's id (a test store's own id returns that test store). Your account credentials, as a wallet
215
+ * that manages the coin's page; a store key is refused (403 `SESSION_REQUIRED`).
216
+ * Guide: https://docs.chaindaddy.io/developer/test-your-integration#test-store
217
+ */
218
+ get: operations["getIapTestStore"];
219
+ put?: never;
220
+ /**
221
+ * Create a store's test store
222
+ * @description Creates the live store's test store with a copy of its catalog as it is now, drafts included and
223
+ * nothing sold (201), or returns the one it already has (200). Safe to call again. From then on the
224
+ * two catalogs are separate. Make its keys with `POST /api/v2/iap/stores/{storeId}/keys` on the test
225
+ * store's id: they start with `cd_iap_test_` and work only on it. It takes no body. Your account
226
+ * credentials, as a wallet that manages the coin's page and has the plan a store needs.
227
+ * Guide: https://docs.chaindaddy.io/developer/test-your-integration#test-store
228
+ */
229
+ post: operations["openIapTestStore"];
230
+ delete?: never;
231
+ options?: never;
232
+ head?: never;
233
+ patch?: never;
234
+ trace?: never;
235
+ };
170
236
  "/api/v2/iap/stores/{storeId}/skus": {
171
237
  parameters: {
172
238
  query?: never;
@@ -714,9 +780,8 @@ export interface paths {
714
780
  * (200, secret unchanged; also turns back on a webhook turned off after repeated failures). Events default
715
781
  * to every `iap.*` type there is at the time of the save (a type added later needs another save); names
716
782
  * may omit the `iap.` prefix. The URL must be https and must not resolve to a
717
- * private address. Needs the store's own plan (a Developer plan or developer program membership), not
718
- * the Developer plan the general `/api/v2/webhooks` routes need. Account credentials, or a store key
719
- * with `webhooks:write`.
783
+ * private address. Needs only the plan the store itself needs (any paid plan), not the Developer plan the
784
+ * general `/api/v2/webhooks` routes need. Account credentials, or a store key with `webhooks:write`.
720
785
  */
721
786
  put: operations["setIapStoreWebhook"];
722
787
  post?: never;
@@ -873,6 +938,36 @@ export interface paths {
873
938
  patch?: never;
874
939
  trace?: never;
875
940
  };
941
+ "/api/v2/iap/orders/{orderId}/test-pay": {
942
+ parameters: {
943
+ query?: never;
944
+ header?: never;
945
+ path: {
946
+ /** @description `0x` followed by 64 hex characters */
947
+ orderId: components["parameters"]["IapOrderId"];
948
+ };
949
+ cookie?: never;
950
+ };
951
+ get?: never;
952
+ put?: never;
953
+ /**
954
+ * Pay a test order
955
+ * @description Pays a pending order of a test store with a simulated payment and confirms it at once: the items
956
+ * are granted, the receipt is signed and `order.confirmed` is written, exactly as for a real payment.
957
+ * No chain is read or written and no money moves. Where a transaction hash would be, the order carries
958
+ * `test:` and an id. Calling it again on a confirmed order returns that order and changes nothing.
959
+ *
960
+ * Call it with a key of the order's test store (`cd_iap_test_…`, scope `orders:write`), or signed in as
961
+ * a wallet that manages the coin's page. It takes no body. A live store's order is only ever paid on
962
+ * chain (409 `LIVE_ORDER`). Guide: https://docs.chaindaddy.io/developer/test-your-integration#test-store
963
+ */
964
+ post: operations["testPayIapOrder"];
965
+ delete?: never;
966
+ options?: never;
967
+ head?: never;
968
+ patch?: never;
969
+ trace?: never;
970
+ };
876
971
  "/api/v2/iap/checkout/sessions": {
877
972
  parameters: {
878
973
  query?: never;
@@ -1446,6 +1541,112 @@ export interface components {
1446
1541
  award?: components["schemas"]["IapAirdropAward"];
1447
1542
  campaign?: components["schemas"]["IapAwardAirdrop"];
1448
1543
  };
1544
+ IapChain: {
1545
+ /**
1546
+ * @description CAIP-2 chain id
1547
+ * @example eip155:8453
1548
+ */
1549
+ chainId?: string;
1550
+ /** @example base */
1551
+ chainKey?: string;
1552
+ /**
1553
+ * @description The checkout contract an order with a fee is paid through. Absent on Solana. A hint only: the
1554
+ * contract to pay is always the one in the order's own `payment` block.
1555
+ */
1556
+ checkout?: string;
1557
+ /**
1558
+ * @description The checkout contract an order with no fee is paid through (a store whose `feeBps` is 0). Absent on
1559
+ * Solana and where the chain has none. A hint only, like `checkout`.
1560
+ */
1561
+ checkoutNoFee?: string;
1562
+ /** @description Confirmations an order waits for before it is `confirmed` */
1563
+ confirmations?: number;
1564
+ /**
1565
+ * @description What a SKU on this chain may be paid in besides the store's own token (always allowed, and the
1566
+ * default): the chain's coin and, where the chain has one, its dollar stable.
1567
+ */
1568
+ currencies?: components["schemas"]["IapCurrency"][];
1569
+ };
1570
+ IapCurrency: {
1571
+ /** @enum {string} */
1572
+ currency?: "native" | "usdc";
1573
+ /**
1574
+ * @description The zero address for an EVM chain's coin, `11111111111111111111111111111111` for SOL, otherwise
1575
+ * the stable's contract or mint
1576
+ */
1577
+ address?: string;
1578
+ /** @example USDC */
1579
+ symbol?: string;
1580
+ /** @example 6 */
1581
+ decimals?: number;
1582
+ };
1583
+ IapConfig: {
1584
+ enabled?: boolean;
1585
+ /**
1586
+ * @description Whether test stores are available here: a store's test store, its `cd_iap_test_` keys, and paying a
1587
+ * test order with no money
1588
+ */
1589
+ testMode?: boolean;
1590
+ chains?: components["schemas"]["IapChain"][];
1591
+ /** @description The read-only account every Solana store payment references */
1592
+ solanaStaticKey?: string;
1593
+ /** @example https://api.chaindaddy.io/api/v2/iap/jwks */
1594
+ jwksUrl?: string;
1595
+ /**
1596
+ * @description The limits on what an app can make a buyer pay (paid apps). Chain Daddy may change them at any time.
1597
+ * Amounts are in millionths of a dollar.
1598
+ */
1599
+ paidApps?: {
1600
+ /**
1601
+ * Format: int64
1602
+ * @description The most one purchase through an app may cost; an app item priced above it cannot be saved
1603
+ * @example 250000000
1604
+ */
1605
+ maxOrderUsdMicros?: number;
1606
+ /**
1607
+ * Format: int64
1608
+ * @description The most one buyer wallet may pay one app in any 24 hours
1609
+ * @example 1000000000
1610
+ */
1611
+ walletAppDayUsdMicros?: number;
1612
+ /**
1613
+ * Format: int64
1614
+ * @description Above this, the purchase sheet asks the buyer to press and hold
1615
+ * @example 50000000
1616
+ */
1617
+ confirmAboveUsdMicros?: number;
1618
+ /**
1619
+ * @description The most units in one order through an app
1620
+ * @example 10
1621
+ */
1622
+ maxQuantity?: number;
1623
+ /**
1624
+ * @description The most orders one buyer wallet may open through one app in a minute
1625
+ * @example 5
1626
+ */
1627
+ ordersPerMinute?: number;
1628
+ };
1629
+ /** @description Present only where checkout sessions are switched on */
1630
+ checkoutSessions?: {
1631
+ /** @description When true, only a test store's key can open a checkout session here */
1632
+ testOnly?: boolean;
1633
+ /**
1634
+ * @description The least `expiresInSeconds` a session may ask for
1635
+ * @example 900
1636
+ */
1637
+ minExpiresInSeconds?: number;
1638
+ /**
1639
+ * @description The most `expiresInSeconds` a session may ask for
1640
+ * @example 86400
1641
+ */
1642
+ maxExpiresInSeconds?: number;
1643
+ /**
1644
+ * @description How long a session stays open when it does not say
1645
+ * @example 3600
1646
+ */
1647
+ defaultExpiresInSeconds?: number;
1648
+ };
1649
+ };
1449
1650
  IapStore: {
1450
1651
  /** Format: uuid */
1451
1652
  id?: string;
@@ -1483,6 +1684,12 @@ export interface components {
1483
1684
  * for its own items; items from apps installed on the page keep selling.
1484
1685
  */
1485
1686
  live?: boolean;
1687
+ /**
1688
+ * @description `test` for a test store: its orders are paid by the test-pay call instead of a wallet, nothing is sent
1689
+ * on chain and no fee is taken. Unrelated to `live` above.
1690
+ * @enum {string}
1691
+ */
1692
+ mode?: "live" | "test";
1486
1693
  /** Format: date-time */
1487
1694
  createdAt?: string;
1488
1695
  /**
@@ -1887,13 +2094,24 @@ export interface components {
1887
2094
  refundedAt?: string | null;
1888
2095
  /** @description Signed receipt (JWS) once confirmed */
1889
2096
  receipt?: string | null;
1890
- /** @description Present while pending; how to pay */
1891
- payment?: components["schemas"]["IapEvmPayment"] | components["schemas"]["IapSolanaPayment"];
2097
+ /**
2098
+ * @description Present while pending; how to pay. A test store's order carries `{"kind": "test"}`: nothing is signed,
2099
+ * and it is paid with `POST /api/v2/iap/orders/{orderId}/test-pay`.
2100
+ */
2101
+ payment?: components["schemas"]["IapEvmPayment"] | components["schemas"]["IapSolanaPayment"] | components["schemas"]["IapTestPayment"];
1892
2102
  /** @description How to check this order's payment on chain without trusting the API. EVM: call hashOrder(order) on `checkout`, then paidBy(hash) at a block `confirmations` deep; `checkout` is the contract the order was (or will be) paid through, which hashOrder is bound to. Solana: find the transfer by its `reference` key and `memo`. Absent on test orders, and on EVM orders paid before the contract was recorded. */
1893
2103
  verification?: components["schemas"]["IapEvmVerification"] | components["schemas"]["IapSolanaVerification"];
1894
2104
  /** Format: date-time */
1895
2105
  createdAt?: string;
1896
2106
  };
2107
+ /** @description The payment block of a test store's pending order. There is nothing to sign or send */
2108
+ IapTestPayment: {
2109
+ /**
2110
+ * @description discriminator enum property added by openapi-typescript
2111
+ * @enum {string}
2112
+ */
2113
+ kind: "test";
2114
+ };
1897
2115
  IapEvmVerification: {
1898
2116
  /**
1899
2117
  * @description discriminator enum property added by openapi-typescript
@@ -2149,12 +2367,18 @@ export interface components {
2149
2367
  deliveredAt?: string | null;
2150
2368
  };
2151
2369
  IapReceiptVerification: {
2370
+ /** @description True only when `status` is `valid`. Deliver on this */
2371
+ valid?: boolean;
2152
2372
  /**
2153
- * @description `revoked` once the order is refunded
2373
+ * @description `valid`: the receipt is ours and its order stands. `revoked`: a refund was recorded since.
2374
+ * `invalid`: not a receipt Chain Daddy signed; `error` says why and there is no `payload`.
2375
+ * `unknown`: the signature is ours, but this API has no record of the receipt.
2154
2376
  * @enum {string}
2155
2377
  */
2156
- status?: "valid" | "revoked";
2157
- /** @description The receipt's decoded claims */
2378
+ status?: "valid" | "revoked" | "invalid" | "unknown";
2379
+ /** @description Why the receipt is `invalid`. Absent otherwise */
2380
+ error?: string;
2381
+ /** @description The receipt's decoded claims. Absent when `status` is `invalid` */
2158
2382
  payload?: {
2159
2383
  /** @example https://chaindaddy.io */
2160
2384
  iss?: string;
@@ -2563,8 +2787,9 @@ export interface components {
2563
2787
  };
2564
2788
  /**
2565
2789
  * @description Not allowed. `IAP_PLAN_REQUIRED` when the wallet (for a store key, the wallet that opened the store)
2566
- * has neither a Developer plan nor Developer Beta program membership in good standing. Otherwise the
2567
- * caller does not manage this token page, or the store key lacks the scope.
2790
+ * cannot run a store. That takes any paid plan with the Seller Terms signed, or Developer Beta Program
2791
+ * membership in good standing. Otherwise the caller does not manage this token page, or the store key
2792
+ * lacks the scope.
2568
2793
  */
2569
2794
  IapForbidden: {
2570
2795
  headers: {
@@ -2573,13 +2798,28 @@ export interface components {
2573
2798
  content: {
2574
2799
  /**
2575
2800
  * @example {
2576
- * "error": "a Developer plan is required to run a store",
2801
+ * "error": "running a store needs a paid plan or Developer Beta Program membership (the Developer plan is in beta and free while it is): https://chaindaddy.io/_developer",
2577
2802
  * "code": "IAP_PLAN_REQUIRED"
2578
2803
  * }
2579
2804
  */
2580
2805
  "application/json": components["schemas"]["IapError"];
2581
2806
  };
2582
2807
  };
2808
+ /** @description No wallet session */
2809
+ IapUnauthorized: {
2810
+ headers: {
2811
+ [name: string]: unknown;
2812
+ };
2813
+ content: {
2814
+ /**
2815
+ * @example {
2816
+ * "error": "sign in first",
2817
+ * "code": "UNAUTHORIZED"
2818
+ * }
2819
+ */
2820
+ "application/json": components["schemas"]["IapError"];
2821
+ };
2822
+ };
2583
2823
  /** @description `AIRDROPS_DISABLED` or `IAP_DISABLED`: the feature is switched off */
2584
2824
  IapAirdropsUnavailable: {
2585
2825
  headers: {
@@ -2695,6 +2935,12 @@ export interface components {
2695
2935
  * and never sells, grants or consumes twice.
2696
2936
  */
2697
2937
  IdempotencyKey: string;
2938
+ /**
2939
+ * @description Required: a call without it answers 400 `IDEMPOTENCY_KEY_REQUIRED`. Send one value per attempt (a UUID,
2940
+ * or an id of your own such as `cart-8842-attempt-1`), up to 255 characters. Retrying with the same key
2941
+ * and the same request returns the first result and never sells, grants or consumes twice.
2942
+ */
2943
+ IdempotencyKeyRequired: string;
2698
2944
  /** @description The store's ID (a UUID) */
2699
2945
  IapStoreId: string;
2700
2946
  /** @description The SKU key, e.g. `badge.gold` */
@@ -3215,6 +3461,26 @@ export interface operations {
3215
3461
  };
3216
3462
  };
3217
3463
  };
3464
+ getIapConfig: {
3465
+ parameters: {
3466
+ query?: never;
3467
+ header?: never;
3468
+ path?: never;
3469
+ cookie?: never;
3470
+ };
3471
+ requestBody?: never;
3472
+ responses: {
3473
+ /** @description Configuration */
3474
+ 200: {
3475
+ headers: {
3476
+ [name: string]: unknown;
3477
+ };
3478
+ content: {
3479
+ "application/json": components["schemas"]["IapConfig"];
3480
+ };
3481
+ };
3482
+ };
3483
+ };
3218
3484
  getIapJwks: {
3219
3485
  parameters: {
3220
3486
  query?: never;
@@ -3271,7 +3537,7 @@ export interface operations {
3271
3537
  };
3272
3538
  };
3273
3539
  responses: {
3274
- /** @description Verified receipt */
3540
+ /** @description The check's answer, also for a receipt that is not valid (read `valid` and `status`) */
3275
3541
  200: {
3276
3542
  headers: {
3277
3543
  [name: string]: unknown;
@@ -3308,6 +3574,89 @@ export interface operations {
3308
3574
  404: components["responses"]["IapNotFound"];
3309
3575
  };
3310
3576
  };
3577
+ getIapTestStore: {
3578
+ parameters: {
3579
+ query?: never;
3580
+ header?: never;
3581
+ path: {
3582
+ /** @description The store's ID (a UUID) */
3583
+ storeId: components["parameters"]["IapStoreId"];
3584
+ };
3585
+ cookie?: never;
3586
+ };
3587
+ requestBody?: never;
3588
+ responses: {
3589
+ /** @description The test store */
3590
+ 200: {
3591
+ headers: {
3592
+ [name: string]: unknown;
3593
+ };
3594
+ content: {
3595
+ "application/json": components["schemas"]["IapStore"];
3596
+ };
3597
+ };
3598
+ 401: components["responses"]["IapUnauthorized"];
3599
+ 403: components["responses"]["IapForbidden"];
3600
+ /**
3601
+ * @description `NOT_FOUND`: no such store, or it has no test store yet (POST here to create it).
3602
+ * `TEST_MODE_DISABLED`: test stores are not available here (`testMode` in `GET /api/v2/iap/config`).
3603
+ */
3604
+ 404: {
3605
+ headers: {
3606
+ [name: string]: unknown;
3607
+ };
3608
+ content: {
3609
+ "application/json": components["schemas"]["IapError"];
3610
+ };
3611
+ };
3612
+ };
3613
+ };
3614
+ openIapTestStore: {
3615
+ parameters: {
3616
+ query?: never;
3617
+ header?: never;
3618
+ path: {
3619
+ /** @description The store's ID (a UUID) */
3620
+ storeId: components["parameters"]["IapStoreId"];
3621
+ };
3622
+ cookie?: never;
3623
+ };
3624
+ requestBody?: never;
3625
+ responses: {
3626
+ /** @description The test store the live store already had */
3627
+ 200: {
3628
+ headers: {
3629
+ [name: string]: unknown;
3630
+ };
3631
+ content: {
3632
+ "application/json": components["schemas"]["IapStore"];
3633
+ };
3634
+ };
3635
+ /** @description The test store, just created */
3636
+ 201: {
3637
+ headers: {
3638
+ [name: string]: unknown;
3639
+ };
3640
+ content: {
3641
+ "application/json": components["schemas"]["IapStore"];
3642
+ };
3643
+ };
3644
+ 401: components["responses"]["IapUnauthorized"];
3645
+ 403: components["responses"]["IapForbidden"];
3646
+ /**
3647
+ * @description `NOT_FOUND`: no such store. `TEST_MODE_DISABLED`: test stores are not available here (`testMode` in
3648
+ * `GET /api/v2/iap/config`).
3649
+ */
3650
+ 404: {
3651
+ headers: {
3652
+ [name: string]: unknown;
3653
+ };
3654
+ content: {
3655
+ "application/json": components["schemas"]["IapError"];
3656
+ };
3657
+ };
3658
+ };
3659
+ };
3311
3660
  listIapSkus: {
3312
3661
  parameters: {
3313
3662
  query?: never;
@@ -3577,12 +3926,13 @@ export interface operations {
3577
3926
  grantIapItems: {
3578
3927
  parameters: {
3579
3928
  query?: never;
3580
- header?: {
3929
+ header: {
3581
3930
  /**
3582
- * @description Send a unique value (a UUID) with every POST. Retrying with the same key returns the first result
3583
- * and never sells, grants or consumes twice.
3931
+ * @description Required: a call without it answers 400 `IDEMPOTENCY_KEY_REQUIRED`. Send one value per attempt (a UUID,
3932
+ * or an id of your own such as `cart-8842-attempt-1`), up to 255 characters. Retrying with the same key
3933
+ * and the same request returns the first result and never sells, grants or consumes twice.
3584
3934
  */
3585
- "Idempotency-Key"?: components["parameters"]["IdempotencyKey"];
3935
+ "Idempotency-Key": components["parameters"]["IdempotencyKeyRequired"];
3586
3936
  };
3587
3937
  path: {
3588
3938
  /** @description The store's ID (a UUID) */
@@ -3617,12 +3967,13 @@ export interface operations {
3617
3967
  consumeIapItems: {
3618
3968
  parameters: {
3619
3969
  query?: never;
3620
- header?: {
3970
+ header: {
3621
3971
  /**
3622
- * @description Send a unique value (a UUID) with every POST. Retrying with the same key returns the first result
3623
- * and never sells, grants or consumes twice.
3972
+ * @description Required: a call without it answers 400 `IDEMPOTENCY_KEY_REQUIRED`. Send one value per attempt (a UUID,
3973
+ * or an id of your own such as `cart-8842-attempt-1`), up to 255 characters. Retrying with the same key
3974
+ * and the same request returns the first result and never sells, grants or consumes twice.
3624
3975
  */
3625
- "Idempotency-Key"?: components["parameters"]["IdempotencyKey"];
3976
+ "Idempotency-Key": components["parameters"]["IdempotencyKeyRequired"];
3626
3977
  };
3627
3978
  path: {
3628
3979
  /** @description The store's ID (a UUID) */
@@ -3940,12 +4291,13 @@ export interface operations {
3940
4291
  awardIapDrop: {
3941
4292
  parameters: {
3942
4293
  query?: never;
3943
- header?: {
4294
+ header: {
3944
4295
  /**
3945
- * @description Send a unique value (a UUID) with every POST. Retrying with the same key returns the first result
3946
- * and never sells, grants or consumes twice.
4296
+ * @description Required: a call without it answers 400 `IDEMPOTENCY_KEY_REQUIRED`. Send one value per attempt (a UUID,
4297
+ * or an id of your own such as `cart-8842-attempt-1`), up to 255 characters. Retrying with the same key
4298
+ * and the same request returns the first result and never sells, grants or consumes twice.
3947
4299
  */
3948
- "Idempotency-Key"?: components["parameters"]["IdempotencyKey"];
4300
+ "Idempotency-Key": components["parameters"]["IdempotencyKeyRequired"];
3949
4301
  };
3950
4302
  path: {
3951
4303
  /** @description The store's ID (a UUID) */
@@ -4497,12 +4849,13 @@ export interface operations {
4497
4849
  createIapOrder: {
4498
4850
  parameters: {
4499
4851
  query?: never;
4500
- header?: {
4852
+ header: {
4501
4853
  /**
4502
- * @description Send a unique value (a UUID) with every POST. Retrying with the same key returns the first result
4503
- * and never sells, grants or consumes twice.
4854
+ * @description Required: a call without it answers 400 `IDEMPOTENCY_KEY_REQUIRED`. Send one value per attempt (a UUID,
4855
+ * or an id of your own such as `cart-8842-attempt-1`), up to 255 characters. Retrying with the same key
4856
+ * and the same request returns the first result and never sells, grants or consumes twice.
4504
4857
  */
4505
- "Idempotency-Key"?: components["parameters"]["IdempotencyKey"];
4858
+ "Idempotency-Key": components["parameters"]["IdempotencyKeyRequired"];
4506
4859
  };
4507
4860
  path?: never;
4508
4861
  cookie?: never;
@@ -4546,9 +4899,24 @@ export interface operations {
4546
4899
  };
4547
4900
  };
4548
4901
  400: components["responses"]["IapBadRequest"];
4902
+ /**
4903
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all. The buyer signs in, or your server sends a store key
4904
+ * (`Authorization: Bearer cd_iap_…`); an order for a checkout session is the signed-in buyer's only.
4905
+ * `INVALID_STORE_KEY`: the key is unknown or revoked
4906
+ */
4907
+ 401: {
4908
+ headers: {
4909
+ [name: string]: unknown;
4910
+ };
4911
+ content: {
4912
+ "application/json": components["schemas"]["IapError"];
4913
+ };
4914
+ };
4549
4915
  /**
4550
4916
  * @description Not allowed, the purchase rule is not met, or the store is not taking orders. `APP_ITEM_NOT_YOURS`: an
4551
- * app asked for another app's item, or an app's item was asked for outside its app
4917
+ * app asked for another app's item, or an app's item was asked for outside its app. For a checkout
4918
+ * session: `SESSION_REQUIRED` (a store key tried to pay it) or `CHECKOUT_SESSION_WALLET` (the session
4919
+ * names another wallet)
4552
4920
  */
4553
4921
  403: {
4554
4922
  headers: {
@@ -4558,12 +4926,26 @@ export interface operations {
4558
4926
  "application/json": components["schemas"]["IapError"];
4559
4927
  };
4560
4928
  };
4929
+ /**
4930
+ * @description `NOT_FOUND`: no such store, or no such checkout session. `SKU_NOT_FOR_SALE`: no such item in the store.
4931
+ * `CHECKOUT_SESSIONS_DISABLED`: the order names a checkout session where sessions are not switched on
4932
+ */
4933
+ 404: {
4934
+ headers: {
4935
+ [name: string]: unknown;
4936
+ };
4937
+ content: {
4938
+ "application/json": components["schemas"]["IapError"];
4939
+ };
4940
+ };
4561
4941
  /**
4562
4942
  * @description Sold out, or the wallet has reached the SKU's per-wallet limit. For an app's item: `APP_TERMS_CHANGED`
4563
4943
  * (its developer changed its terms and the page's owner has not accepted them yet), `APP_NOT_INSTALLED`,
4564
4944
  * `APP_NOT_SELLING`, `APP_ORDER_LIMIT` (above the per-purchase limit) or `APP_DAILY_LIMIT` (the wallet
4565
4945
  * reached what it may pay that app in 24 hours). For an app's item paid through a checkout session:
4566
- * `APP_ITEM_NOT_SOLD_HERE`, with a `reason`
4946
+ * `APP_ITEM_NOT_SOLD_HERE`, with a `reason`. For any checkout session: `CHECKOUT_SESSION_EXPIRED`,
4947
+ * `CHECKOUT_SESSION_COMPLETE` (already paid) or `CHECKOUT_SESSION_IN_USE` (another wallet is paying it
4948
+ * right now)
4567
4949
  */
4568
4950
  409: {
4569
4951
  headers: {
@@ -4635,15 +5017,79 @@ export interface operations {
4635
5017
  404: components["responses"]["IapNotFound"];
4636
5018
  };
4637
5019
  };
5020
+ testPayIapOrder: {
5021
+ parameters: {
5022
+ query?: never;
5023
+ header?: never;
5024
+ path: {
5025
+ /** @description `0x` followed by 64 hex characters */
5026
+ orderId: components["parameters"]["IapOrderId"];
5027
+ };
5028
+ cookie?: never;
5029
+ };
5030
+ requestBody?: never;
5031
+ responses: {
5032
+ /** @description The order, confirmed, with its receipt */
5033
+ 200: {
5034
+ headers: {
5035
+ [name: string]: unknown;
5036
+ };
5037
+ content: {
5038
+ "application/json": components["schemas"]["IapOrder"];
5039
+ };
5040
+ };
5041
+ /** @description No credential, or a store key that is unknown or revoked (`INVALID_STORE_KEY`) */
5042
+ 401: {
5043
+ headers: {
5044
+ [name: string]: unknown;
5045
+ };
5046
+ content: {
5047
+ "application/json": components["schemas"]["IapError"];
5048
+ };
5049
+ };
5050
+ 403: components["responses"]["IapForbidden"];
5051
+ /**
5052
+ * @description `NOT_FOUND`: no such order, or it is not an order of the caller's store or coin page.
5053
+ * `TEST_MODE_DISABLED`: test stores are not available here (`testMode` in `GET /api/v2/iap/config`).
5054
+ */
5055
+ 404: {
5056
+ headers: {
5057
+ [name: string]: unknown;
5058
+ };
5059
+ content: {
5060
+ "application/json": components["schemas"]["IapError"];
5061
+ };
5062
+ };
5063
+ /**
5064
+ * @description `LIVE_ORDER`: the order belongs to a live store. `ORDER_EXPIRED`: the order's deadline has passed;
5065
+ * create a new one.
5066
+ */
5067
+ 409: {
5068
+ headers: {
5069
+ [name: string]: unknown;
5070
+ };
5071
+ content: {
5072
+ /**
5073
+ * @example {
5074
+ * "error": "this is a live order: it is paid on chain, and test-pay pays test orders only",
5075
+ * "code": "LIVE_ORDER"
5076
+ * }
5077
+ */
5078
+ "application/json": components["schemas"]["IapError"];
5079
+ };
5080
+ };
5081
+ };
5082
+ };
4638
5083
  createIapCheckoutSession: {
4639
5084
  parameters: {
4640
5085
  query?: never;
4641
- header?: {
5086
+ header: {
4642
5087
  /**
4643
- * @description Send a unique value (a UUID) with every POST. Retrying with the same key returns the first result
4644
- * and never sells, grants or consumes twice.
5088
+ * @description Required: a call without it answers 400 `IDEMPOTENCY_KEY_REQUIRED`. Send one value per attempt (a UUID,
5089
+ * or an id of your own such as `cart-8842-attempt-1`), up to 255 characters. Retrying with the same key
5090
+ * and the same request returns the first result and never sells, grants or consumes twice.
4645
5091
  */
4646
- "Idempotency-Key"?: components["parameters"]["IdempotencyKey"];
5092
+ "Idempotency-Key": components["parameters"]["IdempotencyKeyRequired"];
4647
5093
  };
4648
5094
  path?: never;
4649
5095
  cookie?: never;
@@ -4719,7 +5165,11 @@ export interface operations {
4719
5165
  "application/json": components["schemas"]["IapError"];
4720
5166
  };
4721
5167
  };
4722
- /** @description No store key, or one that is unknown or revoked (`INVALID_STORE_KEY`) */
5168
+ /**
5169
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all; send the store key as
5170
+ * `Authorization: Bearer cd_iap_…`. `INVALID_STORE_KEY`: the key is unknown or revoked.
5171
+ * `TEST_MODE_DISABLED`: a test key where test mode is not available
5172
+ */
4723
5173
  401: {
4724
5174
  headers: {
4725
5175
  [name: string]: unknown;
@@ -4800,7 +5250,11 @@ export interface operations {
4800
5250
  "application/json": components["schemas"]["IapCheckoutSession"];
4801
5251
  };
4802
5252
  };
4803
- /** @description No store key, or one that is unknown or revoked */
5253
+ /**
5254
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all; send the store key as
5255
+ * `Authorization: Bearer cd_iap_…`. `INVALID_STORE_KEY`: the key is unknown or revoked.
5256
+ * `TEST_MODE_DISABLED`: a test key where test mode is not available
5257
+ */
4804
5258
  401: {
4805
5259
  headers: {
4806
5260
  [name: string]: unknown;
@@ -4818,7 +5272,18 @@ export interface operations {
4818
5272
  "application/json": components["schemas"]["IapError"];
4819
5273
  };
4820
5274
  };
4821
- 404: components["responses"]["IapNotFound"];
5275
+ /**
5276
+ * @description `NOT_FOUND`: no such session, or it belongs to another store. `CHECKOUT_SESSIONS_DISABLED`: checkout
5277
+ * sessions are not switched on here, or are open to test stores only and the key is a live store's
5278
+ */
5279
+ 404: {
5280
+ headers: {
5281
+ [name: string]: unknown;
5282
+ };
5283
+ content: {
5284
+ "application/json": components["schemas"]["IapError"];
5285
+ };
5286
+ };
4822
5287
  };
4823
5288
  };
4824
5289
  getIapPublicCheckoutSession: {
@@ -4845,7 +5310,18 @@ export interface operations {
4845
5310
  "application/json": components["schemas"]["IapPublicCheckoutSession"];
4846
5311
  };
4847
5312
  };
4848
- 404: components["responses"]["IapNotFound"];
5313
+ /**
5314
+ * @description `NOT_FOUND`: no such session. `CHECKOUT_SESSIONS_DISABLED`: checkout sessions are not switched on
5315
+ * here, or are open to test stores only and the session is a live store's
5316
+ */
5317
+ 404: {
5318
+ headers: {
5319
+ [name: string]: unknown;
5320
+ };
5321
+ content: {
5322
+ "application/json": components["schemas"]["IapError"];
5323
+ };
5324
+ };
4849
5325
  };
4850
5326
  };
4851
5327
  listWebhooks: {
package/dist/cd.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";(()=>{var S="0.3.1";function v(t){let e="";for(let r of t)e+=String.fromCharCode(r);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function f(){return v(crypto.getRandomValues(new Uint8Array(32)))}function R(t,e,r,s,o){let d=n=>{if(n&&typeof n=="object"&&n.type===t)try{r(n)}catch{}},u=n=>{n.origin===e&&(!o||n.source===o)&&d(n.data)};window.addEventListener("message",u);let c=null;if(s&&typeof BroadcastChannel=="function"&&e===location.origin)try{c=new BroadcastChannel(s),c.onmessage=n=>d(n.data)}catch{c=null}return()=>{window.removeEventListener("message",u),c?.close()}}var m={get(t){try{return sessionStorage.getItem(t)}catch{return null}},set(t,e){try{sessionStorage.setItem(t,e)}catch{}},remove(t){try{sessionStorage.removeItem(t)}catch{}}};var q="https://api.chaindaddy.io",V={"https://api.chaindaddy.io":"https://chaindaddy.io/_oauth/authorize","https://staging-api.chaindaddy.io":"https://staging.chaindaddy.io/_oauth/authorize"},w="cd-signin",T="cd:signin",y="cd.signin";async function F(t){let e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t));return v(new Uint8Array(e))}function G(t="openid"){let e=t.split(/\s+/).filter(Boolean);return(e.includes("openid")?e:["openid",...e]).join(" ")}async function K(t,e,r){let s=new URLSearchParams({response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUri,scope:G(t.scope),state:e.state,nonce:e.nonce,code_challenge:await F(e.verifier),code_challenge_method:"S256"});return t.claims&&s.set("claims",typeof t.claims=="string"?t.claims:JSON.stringify(t.claims)),`${r}?${s}`}function _(t,e){return{code:e,codeVerifier:t.verifier,state:t.state,nonce:t.nonce,issuer:t.issuer,clientId:t.clientId,redirectUri:t.redirectUri}}function b(t){let e=(t.issuer??q).replace(/\/$/,""),r=t.authorizeUrl??V[e];if(!r)return Promise.reject(new Error("authorizeUrl is required for this issuer"));if(!t.clientId||!t.redirectUri)return Promise.reject(new Error("clientId and redirectUri are required"));let s={state:f(),nonce:f(),verifier:f(),issuer:e,clientId:t.clientId,redirectUri:t.redirectUri};m.set(y,JSON.stringify(s));let o=window.open("",w,"popup,width=480,height=720");return(async()=>{let d=await K(t,s,r);if(!o)return location.assign(d),null;o.location.href=d;let u=new URL(t.redirectUri,location.href).origin;return new Promise((c,n)=>{let a=R(T,u,i=>{i.state===s.state&&(a(),m.remove(y),typeof i.error=="string"&&i.error?n(new Error(i.error)):i.iss!==e?n(new Error("the answer came from another issuer")):typeof i.code!="string"||!i.code?n(new Error("the answer carried no code")):c(_(s,i.code)))},w,o);t.signal?.addEventListener("abort",()=>{a(),n(new Error("aborted"))})})})()}async function U(){let t=new URLSearchParams(location.search),e={type:T,code:t.get("code"),state:t.get("state"),iss:t.get("iss"),error:t.get("error")};if(window.name===w){try{window.opener?.postMessage(e,location.origin)}catch{}try{let o=new BroadcastChannel(w);o.postMessage(e),o.close()}catch{}return window.close(),null}let r=m.get(y);m.remove(y);let s=r?JSON.parse(r):null;if(!s||e.state!==s.state)throw new Error("the sign-in state does not match: start again");if(e.error)throw new Error(e.error);if(e.iss!==s.issuer)throw new Error("the answer came from another issuer");if(!e.code)throw new Error("the answer carried no code");return _(s,e.code)}async function A(t){let e=await fetch(`${t.issuer}/api/v2/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:t.code,redirect_uri:t.redirectUri,client_id:t.clientId,code_verifier:t.codeVerifier})}),r=await e.json();if(!e.ok)throw new Error(`${r.error??e.status}: ${r.error_description??""}`.trim());return r}var W='<svg viewBox="0 0 24 24" fill="#FFD700" width="20" height="20" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><path d="M5 16L3 5l5.5 5L12 4l3.5 6L21 5l-2 11H5z" stroke="#B8860B" stroke-width="0.5"/><path d="M5 18h14v2H5v-2z" stroke="#B8860B" stroke-width="0.5"/></svg>',Y=`
1
+ "use strict";(()=>{var R="0.3.3";function C(t){let e="";for(let i of t)e+=String.fromCharCode(i);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function w(){return C(crypto.getRandomValues(new Uint8Array(32)))}function T(t,e,i,o,r){let u=n=>{if(n&&typeof n=="object"&&n.type===t)try{i(n)}catch{}},l=n=>{n.origin===e&&(!r||n.source===r)&&u(n.data)};window.addEventListener("message",l);let a=null;if(o&&typeof BroadcastChannel=="function"&&e===location.origin)try{a=new BroadcastChannel(o),a.onmessage=n=>u(n.data)}catch{a=null}return()=>{window.removeEventListener("message",l),a?.close()}}var y={get(t){try{return sessionStorage.getItem(t)}catch{return null}},set(t,e){try{sessionStorage.setItem(t,e)}catch{}},remove(t){try{sessionStorage.removeItem(t)}catch{}}};var F="https://api.chaindaddy.io",G={"https://api.chaindaddy.io":"https://chaindaddy.io/_oauth/authorize","https://staging-api.chaindaddy.io":"https://staging.chaindaddy.io/_oauth/authorize"},b="cd-signin",U="cd:signin",v="cd.signin",_=300;async function K(t){let e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t));return C(new Uint8Array(e))}function W(t="openid"){let e=t.split(/\s+/).filter(Boolean);return(e.includes("openid")?e:["openid",...e]).join(" ")}async function Y(t,e,i){let o=new URLSearchParams({response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUri,scope:W(t.scope),state:e.state,nonce:e.nonce,code_challenge:await K(e.verifier),code_challenge_method:"S256"});return t.claims&&o.set("claims",typeof t.claims=="string"?t.claims:JSON.stringify(t.claims)),`${i}?${o}`}function A(t,e){return{code:e,codeVerifier:t.verifier,state:t.state,nonce:t.nonce,issuer:t.issuer,clientId:t.clientId,redirectUri:t.redirectUri}}var m;function k(t){let e=(t.issuer??F).replace(/\/$/,""),i=t.authorizeUrl??G[e];if(!i)return Promise.reject(new Error("authorizeUrl is required for this issuer"));if(!t.clientId||!t.redirectUri)return Promise.reject(new Error("clientId and redirectUri are required"));if(m&&!m.popup.closed)return m.popup.focus(),m.waiting;let o={state:w(),nonce:w(),verifier:w(),issuer:e,clientId:t.clientId,redirectUri:t.redirectUri};y.set(v,JSON.stringify(o));let r=window.open("",b,"popup,width=480,height=720"),u=(async()=>{let l=await Y(t,o,i);if(!r)return location.assign(l),null;r.location.href=l;let a=new URL(t.redirectUri,location.href).origin;return new Promise((n,c)=>{let s,d=()=>{g(),clearInterval(h),clearTimeout(s)},g=T(U,a,p=>{p.state===o.state&&(d(),y.remove(v),typeof p.error=="string"&&p.error?c(new Error(p.error)):p.iss!==e?c(new Error("the answer came from another issuer")):typeof p.code!="string"||!p.code?c(new Error("the answer carried no code")):n(A(o,p.code)))},b,r),h=setInterval(()=>{r.closed&&(clearInterval(h),s=setTimeout(()=>{d(),c(new Error("closed"))},_))},_);t.signal?.addEventListener("abort",()=>{d(),c(new Error("aborted"))})})})();if(r){m={popup:r,waiting:u};let l=()=>{m&&m.popup===r&&(m=void 0)};u.then(l,l)}return u}async function L(){let t=new URLSearchParams(location.search),e={type:U,code:t.get("code"),state:t.get("state"),iss:t.get("iss"),error:t.get("error")};if(window.name===b){try{window.opener?.postMessage(e,location.origin)}catch{}try{let r=new BroadcastChannel(b);r.postMessage(e),r.close()}catch{}return window.close(),null}let i=y.get(v);y.remove(v);let o=i?JSON.parse(i):null;if(!o||e.state!==o.state)throw new Error("the sign-in state does not match: start again");if(e.error)throw new Error(e.error);if(e.iss!==o.issuer)throw new Error("the answer came from another issuer");if(!e.code)throw new Error("the answer carried no code");return A(o,e.code)}async function M(t){let e=await fetch(`${t.issuer}/api/v2/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:t.code,redirect_uri:t.redirectUri,client_id:t.clientId,code_verifier:t.codeVerifier})}),i=await e.json();if(!e.ok)throw new Error(`${i.error??e.status}: ${i.error_description??""}`.trim());return i}var J='<svg viewBox="0 0 24 24" fill="#FFD700" width="20" height="20" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><path d="M5 16L3 5l5.5 5L12 4l3.5 6L21 5l-2 11H5z" stroke="#B8860B" stroke-width="0.5"/><path d="M5 18h14v2H5v-2z" stroke="#B8860B" stroke-width="0.5"/></svg>',X=`
2
2
  :host { display: inline-block; }
3
3
  button { display: inline-flex; align-items: center; justify-content: center; gap: 10px; box-sizing: border-box;
4
4
  min-height: 40px; padding: 0 16px; border-radius: 10px; font: 600 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
@@ -7,16 +7,16 @@ button[data-theme="dark"] { background: #101828; color: #ffffff; border-color: #
7
7
  button:focus-visible { outline: 2px solid #f59e0b; outline-offset: 2px; }
8
8
  button:disabled { opacity: .6; cursor: default; }
9
9
  svg { flex: none; }
10
- `;function L(){if(typeof customElements>"u"||customElements.get("cd-signin-button"))return;class t extends HTMLElement{constructor(){super(),this.root=this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(){this.isConnected&&this.render()}render(){let r=this.getAttribute("text")==="continue"?"Continue with Chain Daddy":"Sign in with Chain Daddy",s=this.getAttribute("theme")==="dark"?"dark":"light";this.root.innerHTML=`<style>${Y}</style><button type="button" part="button" data-theme="${s}">${W}<span></span></button>`;let o=this.root.querySelector("button");o.querySelector("span").textContent=r,o.addEventListener("click",()=>this.start())}start(){let r=s=>this.getAttribute(s)||void 0;b({clientId:r("client-id")??"",redirectUri:r("redirect-uri")??"",scope:r("scope"),claims:r("claims"),issuer:r("issuer"),authorizeUrl:r("authorize-url")}).then(s=>s&&this.dispatchEvent(new CustomEvent("cd-signin",{detail:s,bubbles:!0,composed:!0})),s=>this.dispatchEvent(new CustomEvent("cd-signin-error",{detail:{message:String(s?.message??s)},bubbles:!0,composed:!0})))}}t.observedAttributes=["theme","text"],customElements.define("cd-signin-button",t)}var M="https://embed.chaindaddy.io",k="https://embed-staging.chaindaddy.io";var J=["--cd-accent","--cd-surface","--cd-text","--cd-text-muted","--cd-border"];function P(t){try{if(t&&new URL(t).hostname==="js.staging.chaindaddy.io")return k}catch{}return M}var X=`
10
+ `;function P(){if(typeof customElements>"u"||customElements.get("cd-signin-button"))return;class t extends HTMLElement{constructor(){super(),this.root=this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(){this.isConnected&&this.render()}render(){let i=this.getAttribute("text")==="continue"?"Continue with Chain Daddy":"Sign in with Chain Daddy",o=this.getAttribute("theme")==="dark"?"dark":"light";this.root.innerHTML=`<style>${X}</style><button type="button" part="button" data-theme="${o}">${J}<span></span></button>`;let r=this.root.querySelector("button");r.querySelector("span").textContent=i,r.addEventListener("click",()=>this.start())}start(){let i=r=>this.getAttribute(r)||void 0,o=k({clientId:i("client-id")??"",redirectUri:i("redirect-uri")??"",scope:i("scope"),claims:i("claims"),issuer:i("issuer"),authorizeUrl:i("authorize-url")});o!==this.reporting&&(this.reporting=o,o.then(r=>r&&this.dispatchEvent(new CustomEvent("cd-signin",{detail:r,bubbles:!0,composed:!0})),r=>this.dispatchEvent(new CustomEvent("cd-signin-error",{detail:{message:String(r?.message??r)},bubbles:!0,composed:!0}))))}}t.observedAttributes=["theme","text"],customElements.define("cd-signin-button",t)}var O="https://embed.chaindaddy.io",E="https://embed-staging.chaindaddy.io";var Q="1500px 0px",Z="docs.chaindaddy.io/token/apps-on-your-site#if-the-link-stays";var tt=["--cd-accent","--cd-surface","--cd-text","--cd-text-muted","--cd-border"];function $(t){try{if(t&&new URL(t).hostname==="js.staging.chaindaddy.io")return E}catch{}return O}var et=`
11
11
  :host { display: block; }
12
12
  iframe { display: block; width: 100%; border: 0; height: 1px; opacity: 0; color-scheme: normal; }
13
13
  :host([data-cd-ready]) iframe { opacity: 1; }
14
14
  :host([data-cd-ready]) slot { display: none; }
15
15
  :host([data-cd-unavailable]) iframe { display: none; }
16
- `,Q=`
16
+ `,nt=`
17
17
  :host { display: inline-block; }
18
18
  button { box-sizing: border-box; min-height: 40px; padding: 0 16px; border-radius: 10px; cursor: pointer;
19
19
  font: 600 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
20
20
  border: 1px solid var(--cd-border, #d0d5dd); background: var(--cd-accent, #f59e0b); color: var(--cd-text, #101828); }
21
21
  button:focus-visible { outline: 2px solid var(--cd-accent, #f59e0b); outline-offset: 2px; }
22
- `;function O(t={}){try{if(typeof customElements>"u"||typeof HTMLElement>"u")return;let e=t.origin===k?k:M;class r extends HTMLElement{constructor(){super(...arguments);this.kind="app";this.frame=null;this.onMessage=n=>this.receive(n)}connectedCallback(){try{this.shadowRoot||this.attachShadow({mode:"open"}),this.mount()}catch{this.unavailable()}}disconnectedCallback(){try{window.removeEventListener("message",this.onMessage),clearTimeout(this.timer)}catch{}}attributeChangedCallback(){try{this.isConnected&&this.shadowRoot&&this.mount()}catch{this.unavailable()}}src(){let n=(this.getAttribute("crown")??"").trim(),a=(this.getAttribute("chain")??"").trim(),i=(this.getAttribute("app")??"").trim();if(!/^[0-9]{1,18}$/.test(n)||!a||this.kind==="app"&&!i)return null;let l=new URLSearchParams({crown:n,chain:a});return this.kind==="app"?l.set("app",i):l.set("kind","coin"),l.set("mode",this.mode()),l.set("v","1"),`${e}/v1/frame?${l.toString()}`}mode(){let n=this.getAttribute("mode");if(n==="light"||n==="dark")return n;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}catch{return"light"}}mount(){let n=this.shadowRoot;window.removeEventListener("message",this.onMessage),clearTimeout(this.timer),this.removeAttribute("data-cd-ready"),this.removeAttribute("data-cd-unavailable");let a=this.src();if(!a){n.innerHTML="<slot></slot>",this.frame=null;return}n.innerHTML=`<style>${X}</style><slot></slot>`;let i=document.createElement("iframe");i.setAttribute("loading","lazy"),i.setAttribute("referrerpolicy","strict-origin-when-cross-origin"),i.setAttribute("allow","clipboard-write; fullscreen"),i.setAttribute("title",this.kind==="coin"?"Coin on Chain Daddy":"App on Chain Daddy"),i.src=a,n.appendChild(i),this.frame=i,window.addEventListener("message",this.onMessage),this.timer=setTimeout(()=>{this.hasAttribute("data-cd-ready")||this.unavailable()},8e3)}receive(n){try{if(n.origin!==e||!this.frame||!n.source||n.source!==this.frame.contentWindow)return;let a=n.data;if(!a||typeof a!="object")return;if(a.type==="cd:ready")clearTimeout(this.timer),this.removeAttribute("data-cd-unavailable"),this.setAttribute("data-cd-ready",""),this.sendTheme(),this.dispatchEvent(new CustomEvent("cd:ready"));else if(a.type==="cd:resize"){let i=Number(a.height);Number.isFinite(i)&&(this.frame.style.height=`${Math.max(1,Math.min(4e3,Math.ceil(i)))}px`)}else a.type==="cd:unavailable"&&this.unavailable()}catch{}}sendTheme(){let n={},a=getComputedStyle(this);for(let i of J){let l=a.getPropertyValue(i).trim();l&&(n[i]=l)}this.frame?.contentWindow?.postMessage({type:"cd:theme",tokens:n},e)}unavailable(){try{if(clearTimeout(this.timer),this.removeAttribute("data-cd-ready"),this.hasAttribute("data-cd-unavailable"))return;this.setAttribute("data-cd-unavailable",""),this.dispatchEvent(new CustomEvent("cd:unavailable"))}catch{}}}r.observedAttributes=["crown","chain","app","mode"];class s extends r{}class o extends r{constructor(){super(...arguments);this.kind="coin"}}class d extends HTMLElement{connectedCallback(){try{let c=this.querySelector("a[href]"),n=c?new URL(c.getAttribute("href")??"",document.baseURI):null;if(!n||n.protocol!=="https:")return;let a=this.shadowRoot??this.attachShadow({mode:"open"});a.innerHTML=`<style>${Q}</style><button type="button" part="button"></button>`;let i=a.querySelector("button");i.textContent=(c.textContent??"").trim()||"Buy on Chain Daddy",i.addEventListener("click",()=>{try{window.open(n.toString(),"cd-buy","popup,width=460,height=760")||window.location.assign(n.toString())}catch{}})}catch{}}}for(let[u,c]of[["cd-app",s],["cd-coin",o],["cd-buy",d]])customElements.get(u)||customElements.define(u,c)}catch{}}var Z=["https://chaindaddy.io","https://staging.chaindaddy.io"],D=()=>"cd-checkout-"+f(),H="popup,width=480,height=820",$=300,tt=7;function E(t){if(typeof t!="string")return null;let e;try{e=new URL(t)}catch{return null}let r=/^\/_pay\/([\w-]+)$/.exec(e.pathname);return r&&Z.includes(e.origin)&&!e.username&&!e.password?{href:e.origin+e.pathname,origin:e.origin,id:r[1]}:null}function C(t,e){if(typeof t!="string")return null;let r;try{r=new URL(t,location.href)}catch{return null}if(!/^https?:$/.test(r.protocol)||r.username||r.password)return null;let s=d=>{let u=d.split("=")[0];try{return decodeURIComponent(u.replace(/\+/g," "))}catch{return u}},o=r.search.slice(1).split("&").filter(d=>d&&s(d)!=="session_id");return o.push("session_id="+encodeURIComponent(e)),r.origin+r.pathname+"?"+o.join("&")+r.hash}var p;function N(t,e,r){return new Promise(s=>{let o=!1,d=0,u,c=l=>{clearInterval(i),clearTimeout(u),window.removeEventListener("message",n),r&&l.status==="completed"&&location.assign(r),s(l)},n=l=>{if(l.origin!==e.origin||l.source!==t)return;let g=l.data;if(!g||typeof g!="object")return;g.type==="cd:checkout.ready"&&(o=!0);let h=g.session;if(!(!h||typeof h!="object"||h.id!==e.id)){if(g.type==="cd:checkout.cancelled")c({status:"cancelled",session:{id:e.id}});else if(g.type==="cd:checkout.completed"&&(h.status==="complete"||h.status==="paid")){let I={id:e.id,status:h.status};typeof h.orderId=="string"&&/^[\w-]{1,128}$/.test(h.orderId)&&(I.orderId=h.orderId),c({status:"completed",session:I})}}},a=()=>{try{t.postMessage({type:"cd:checkout.hello"},e.origin)}catch{}},i=setInterval(()=>{t.closed?(clearInterval(i),u=setTimeout(()=>c({status:"closed"}),$)):(!o||++d%tt===0)&&a()},$);window.addEventListener("message",n),a()})}function x(t){let e=()=>new Error("checkout: url is not a Chain Daddy checkout session link"),r=(o,d,u)=>{p={popup:o,result:d,href:u};let c=()=>{p&&p.popup===o&&(p=void 0)};return d.then(c,c),d},s=o=>(location.assign(o.href),{status:"redirected"});try{let{url:o,successUrl:d}=t;if(d&&!C(d,""))throw new Error("checkout: successUrl is not a web address");let u=i=>d?C(d,i.id):null,c=typeof o=="function"?null:E(o);if(typeof o!="function"&&!c)throw e();if(p&&!p.popup.closed&&(!c||p.href===c.href))return p.popup.focus(),p.result;if(c){let i=window.open(c.href,D(),H);return i?r(i,N(i,c,u(c)),c.href):Promise.resolve(s(c))}let n=window.open("",D(),H),a=new Promise(i=>i(o())).then(i=>{let l=E(i);if(!l)throw n?.close(),e();return n?(n.location.href=l.href,N(n,l,u(l))):s(l)},i=>{throw n?.close(),i});return n?r(n,a):a}catch(o){return Promise.reject(o)}}var B=!1,z;function j(){B||typeof document>"u"||(B=!0,document.addEventListener("click",t=>{let e=t.target instanceof Element?t.target.closest("[data-cd-checkout]"):null;if(!e||t.defaultPrevented||t.button||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)return;let r=e.getAttribute("data-cd-checkout")||e.href,s=e.getAttribute("data-cd-success");if(!E(r)||s&&!C(s,""))return;t.preventDefault();let o=x({url:r,successUrl:s});o!==z&&(z=o,o.then(d=>void e.dispatchEvent(new CustomEvent("cd:checkout",{detail:d,bubbles:!0})),()=>{}))}))}var et={version:S,signIn:b,handleCallback:U,exchangeCode:A,checkout:x};window.ChainDaddy=Object.assign(window.ChainDaddy??{},et);L();O({origin:P(document.currentScript?.src)});j();})();
22
+ `;function D(t={}){try{if(typeof customElements>"u"||typeof HTMLElement>"u")return;let e=t.origin===E?E:O;class i extends HTMLElement{constructor(){super(...arguments);this.kind="app";this.frame=null;this.onMessage=n=>this.receive(n)}connectedCallback(){try{this.shadowRoot||this.attachShadow({mode:"open"}),this.mount()}catch{this.unavailable()}}disconnectedCallback(){try{window.removeEventListener("message",this.onMessage),clearTimeout(this.timer),this.watch?.disconnect()}catch{}}attributeChangedCallback(){try{this.isConnected&&this.shadowRoot&&this.mount()}catch{this.unavailable()}}src(){let n=(this.getAttribute("crown")??"").trim(),c=(this.getAttribute("chain")??"").trim(),s=(this.getAttribute("app")??"").trim();if(!/^[0-9]{1,18}$/.test(n)||!c||this.kind==="app"&&!s)return null;let d=new URLSearchParams({crown:n,chain:c});return this.kind==="app"?d.set("app",s):d.set("kind","coin"),d.set("mode",this.mode()),d.set("v","1"),`${e}/v1/frame?${d.toString()}`}mode(){let n=this.getAttribute("mode");if(n==="light"||n==="dark")return n;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}catch{return"light"}}mount(){let n=this.shadowRoot;window.removeEventListener("message",this.onMessage),clearTimeout(this.timer),this.watch?.disconnect(),this.removeAttribute("data-cd-ready"),this.removeAttribute("data-cd-unavailable");let c=this.src();if(!c){n.innerHTML="<slot></slot>",this.frame=null;return}n.innerHTML=`<style>${et}</style><slot></slot>`;let s=document.createElement("iframe");s.setAttribute("referrerpolicy","strict-origin-when-cross-origin"),s.setAttribute("allow","clipboard-write; fullscreen"),s.setAttribute("title",this.kind==="coin"?"Coin on Chain Daddy":"App on Chain Daddy"),n.appendChild(s),this.frame=s,window.addEventListener("message",this.onMessage);let d=()=>{s.src=c,this.timer=setTimeout(()=>{this.hasAttribute("data-cd-ready")||this.unavailable()},8e3)};if(typeof IntersectionObserver>"u")return d();this.watch=new IntersectionObserver(g=>{g.some(h=>h.isIntersecting)&&(this.watch?.disconnect(),d())},{rootMargin:Q}),this.watch.observe(this)}why(){try{let n=new URL(this.frame.src).searchParams,c=n.get("app");n.delete("mode"),n.delete("v"),fetch(`https://${e===E?"staging-api":"api"}.chaindaddy.io/api/v2/embed/status?${n}`,{credentials:"omit"}).then(s=>s.json()).then(s=>{let d=s.embeddable?s.interactive?"not a verified site of the coin":"not https, or the app did not start":s.reason;console.info(`[Chain Daddy] cd-${this.kind} ${c??""} cannot show here: ${d}. ${Z}`)}).catch(()=>{})}catch{}}receive(n){try{if(n.origin!==e||!this.frame||!n.source||n.source!==this.frame.contentWindow)return;let c=n.data;if(!c||typeof c!="object")return;if(c.type==="cd:ready")clearTimeout(this.timer),this.removeAttribute("data-cd-unavailable"),this.setAttribute("data-cd-ready",""),this.sendTheme(),this.dispatchEvent(new CustomEvent("cd:ready"));else if(c.type==="cd:resize"){let s=Number(c.height);Number.isFinite(s)&&(this.frame.style.height=`${Math.max(1,Math.min(4e3,Math.ceil(s)))}px`)}else c.type==="cd:unavailable"&&this.unavailable()}catch{}}sendTheme(){let n={},c=getComputedStyle(this);for(let s of tt){let d=c.getPropertyValue(s).trim();d&&(n[s]=d)}this.frame?.contentWindow?.postMessage({type:"cd:theme",tokens:n},e)}unavailable(){try{if(clearTimeout(this.timer),this.removeAttribute("data-cd-ready"),this.hasAttribute("data-cd-unavailable"))return;this.setAttribute("data-cd-unavailable",""),this.dispatchEvent(new CustomEvent("cd:unavailable")),this.why()}catch{}}}i.observedAttributes=["crown","chain","app","mode"];class o extends i{}class r extends i{constructor(){super(...arguments);this.kind="coin"}}class u extends HTMLElement{connectedCallback(){try{let a=this.querySelector("a[href]"),n=a?new URL(a.getAttribute("href")??"",document.baseURI):null;if(!n||n.protocol!=="https:")return;let c=this.shadowRoot??this.attachShadow({mode:"open"});c.innerHTML=`<style>${nt}</style><button type="button" part="button"></button>`;let s=c.querySelector("button");s.textContent=(a.textContent??"").trim()||"Buy on Chain Daddy",s.addEventListener("click",()=>{try{window.open(n.toString(),"cd-buy","popup,width=460,height=760")||window.location.assign(n.toString())}catch{}})}catch{}}}for(let[l,a]of[["cd-app",o],["cd-coin",r],["cd-buy",u]])customElements.get(l)||customElements.define(l,a)}catch{}}var it=["https://chaindaddy.io","https://staging.chaindaddy.io"],H=()=>"cd-checkout-"+w(),N="popup,width=480,height=820",B=300,rt=7;function I(t){if(typeof t!="string")return null;let e;try{e=new URL(t)}catch{return null}let i=/^\/_pay\/([\w-]+)$/.exec(e.pathname);return i&&it.includes(e.origin)&&!e.username&&!e.password?{href:e.origin+e.pathname,origin:e.origin,id:i[1]}:null}function x(t,e){if(typeof t!="string")return null;let i;try{i=new URL(t,location.href)}catch{return null}if(!/^https?:$/.test(i.protocol)||i.username||i.password)return null;let o=u=>{let l=u.split("=")[0];try{return decodeURIComponent(l.replace(/\+/g," "))}catch{return l}},r=i.search.slice(1).split("&").filter(u=>u&&o(u)!=="session_id");return r.push("session_id="+encodeURIComponent(e)),i.origin+i.pathname+"?"+r.join("&")+i.hash}var f;function z(t,e,i){return new Promise(o=>{let r=!1,u=0,l,a=d=>{clearInterval(s),clearTimeout(l),window.removeEventListener("message",n),i&&d.status==="completed"&&location.assign(i),o(d)},n=d=>{if(d.origin!==e.origin||d.source!==t)return;let g=d.data;if(!g||typeof g!="object")return;g.type==="cd:checkout.ready"&&(r=!0);let h=g.session;if(!(!h||typeof h!="object"||h.id!==e.id)){if(g.type==="cd:checkout.cancelled")a({status:"cancelled",session:{id:e.id}});else if(g.type==="cd:checkout.completed"&&(h.status==="complete"||h.status==="paid")){let p={id:e.id,status:h.status};typeof h.orderId=="string"&&/^[\w-]{1,128}$/.test(h.orderId)&&(p.orderId=h.orderId),a({status:"completed",session:p})}}},c=()=>{try{t.postMessage({type:"cd:checkout.hello"},e.origin)}catch{}},s=setInterval(()=>{t.closed?(clearInterval(s),l=setTimeout(()=>a({status:"closed"}),B)):(!r||++u%rt===0)&&c()},B);window.addEventListener("message",n),c()})}function S(t){let e=()=>new Error("checkout: url is not a Chain Daddy checkout session link"),i=(r,u,l)=>{f={popup:r,result:u,href:l};let a=()=>{f&&f.popup===r&&(f=void 0)};return u.then(a,a),u},o=r=>(location.assign(r.href),{status:"redirected"});try{let{url:r,successUrl:u}=t;if(u&&!x(u,""))throw new Error("checkout: successUrl is not a web address");let l=s=>u?x(u,s.id):null,a=typeof r=="function"?null:I(r);if(typeof r!="function"&&!a)throw e();if(f&&!f.popup.closed&&(!a||f.href===a.href))return f.popup.focus(),f.result;if(a){let s=window.open(a.href,H(),N);return s?i(s,z(s,a,l(a)),a.href):Promise.resolve(o(a))}let n=window.open("",H(),N),c=new Promise(s=>s(r())).then(s=>{let d=I(s);if(!d)throw n?.close(),e();return n?(n.location.href=d.href,z(n,d,l(d))):o(d)},s=>{throw n?.close(),s});return n?i(n,c):c}catch(r){return Promise.reject(r)}}var j=!1,q;function V(){j||typeof document>"u"||(j=!0,document.addEventListener("click",t=>{let e=t.target instanceof Element?t.target.closest("[data-cd-checkout]"):null;if(!e||t.defaultPrevented||t.button||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)return;let i=e.getAttribute("data-cd-checkout")||e.href,o=e.getAttribute("data-cd-success");if(!I(i)||o&&!x(o,""))return;t.preventDefault();let r=S({url:i,successUrl:o});r!==q&&(q=r,r.then(u=>void e.dispatchEvent(new CustomEvent("cd:checkout",{detail:u,bubbles:!0})),()=>{}))}))}var st={version:R,signIn:k,handleCallback:L,exchangeCode:M,checkout:S};window.ChainDaddy=Object.assign(window.ChainDaddy??{},st);P();D({origin:$(document.currentScript?.src)});V();})();
package/dist/cd.js.sri CHANGED
@@ -1 +1 @@
1
- sha384-EF2XlOLUJ0J2U3uy/hlkmXdqyRP0WVm2LYjWdTseg/+QC4El1HD+qMhMlandYcSF
1
+ sha384-x7XiOLUzANtLnqFmv/hWd2uhGI7c+jlhVivakZ7LYHklw9zgK/diic0+fJom/mXU
package/dist/embed.js CHANGED
@@ -2,6 +2,8 @@
2
2
  var PROD_ORIGIN = "https://embed.chaindaddy.io";
3
3
  var STAGING_ORIGIN = "https://embed-staging.chaindaddy.io";
4
4
  var READY_TIMEOUT_MS = 8e3;
5
+ var NEAR = "1500px 0px";
6
+ var DOCS = "docs.chaindaddy.io/token/apps-on-your-site#if-the-link-stays";
5
7
  var MAX_HEIGHT = 4e3;
6
8
  var THEME_TOKENS = ["--cd-accent", "--cd-surface", "--cd-text", "--cd-text-muted", "--cd-border"];
7
9
  function embedOriginFor(scriptSrc) {
@@ -48,6 +50,7 @@ function defineEmbedElements(options = {}) {
48
50
  try {
49
51
  window.removeEventListener("message", this.onMessage);
50
52
  clearTimeout(this.timer);
53
+ this.watch?.disconnect();
51
54
  } catch {
52
55
  }
53
56
  }
@@ -84,6 +87,7 @@ function defineEmbedElements(options = {}) {
84
87
  const root = this.shadowRoot;
85
88
  window.removeEventListener("message", this.onMessage);
86
89
  clearTimeout(this.timer);
90
+ this.watch?.disconnect();
87
91
  this.removeAttribute("data-cd-ready");
88
92
  this.removeAttribute("data-cd-unavailable");
89
93
  const src = this.src();
@@ -94,17 +98,43 @@ function defineEmbedElements(options = {}) {
94
98
  }
95
99
  root.innerHTML = `<style>${STYLE}</style><slot></slot>`;
96
100
  const frame = document.createElement("iframe");
97
- frame.setAttribute("loading", "lazy");
98
101
  frame.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
99
102
  frame.setAttribute("allow", "clipboard-write; fullscreen");
100
103
  frame.setAttribute("title", this.kind === "coin" ? "Coin on Chain Daddy" : "App on Chain Daddy");
101
- frame.src = src;
102
104
  root.appendChild(frame);
103
105
  this.frame = frame;
104
106
  window.addEventListener("message", this.onMessage);
105
- this.timer = setTimeout(() => {
106
- if (!this.hasAttribute("data-cd-ready")) this.unavailable();
107
- }, READY_TIMEOUT_MS);
107
+ const load = () => {
108
+ frame.src = src;
109
+ this.timer = setTimeout(() => {
110
+ if (!this.hasAttribute("data-cd-ready")) this.unavailable();
111
+ }, READY_TIMEOUT_MS);
112
+ };
113
+ if (typeof IntersectionObserver === "undefined") return load();
114
+ this.watch = new IntersectionObserver(
115
+ (entries) => {
116
+ if (!entries.some((e) => e.isIntersecting)) return;
117
+ this.watch?.disconnect();
118
+ load();
119
+ },
120
+ { rootMargin: NEAR }
121
+ );
122
+ this.watch.observe(this);
123
+ }
124
+ /** Says in the console why the link stayed: the page sees only that it did. */
125
+ why() {
126
+ try {
127
+ const q = new URL(this.frame.src).searchParams;
128
+ const app = q.get("app");
129
+ q.delete("mode");
130
+ q.delete("v");
131
+ fetch(`https://${origin === STAGING_ORIGIN ? "staging-api" : "api"}.chaindaddy.io/api/v2/embed/status?${q}`, { credentials: "omit" }).then((r) => r.json()).then((s) => {
132
+ const reason = !s.embeddable ? s.reason : s.interactive ? "not a verified site of the coin" : "not https, or the app did not start";
133
+ console.info(`[Chain Daddy] cd-${this.kind} ${app ?? ""} cannot show here: ${reason}. ${DOCS}`);
134
+ }).catch(() => {
135
+ });
136
+ } catch {
137
+ }
108
138
  }
109
139
  receive(event) {
110
140
  try {
@@ -143,6 +173,7 @@ function defineEmbedElements(options = {}) {
143
173
  if (this.hasAttribute("data-cd-unavailable")) return;
144
174
  this.setAttribute("data-cd-unavailable", "");
145
175
  this.dispatchEvent(new CustomEvent("cd:unavailable"));
176
+ this.why();
146
177
  } catch {
147
178
  }
148
179
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/core.ts
2
- var VERSION = true ? "0.3.1" : "0.0.0";
2
+ var VERSION = true ? "0.3.3" : "0.0.0";
3
3
  function b64url(bytes) {
4
4
  let s = "";
5
5
  for (const b of bytes) s += String.fromCharCode(b);
@@ -66,6 +66,7 @@ var AUTHORIZE = {
66
66
  var POPUP_NAME = "cd-signin";
67
67
  var MESSAGE = "cd:signin";
68
68
  var STORAGE_KEY = "cd.signin";
69
+ var TICK_MS = 300;
69
70
  async function challengeFor(verifier) {
70
71
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
71
72
  return b64url(new Uint8Array(digest));
@@ -91,15 +92,20 @@ async function authorizeURL(o, p, authorize) {
91
92
  function result(p, code) {
92
93
  return { code, codeVerifier: p.verifier, state: p.state, nonce: p.nonce, issuer: p.issuer, clientId: p.clientId, redirectUri: p.redirectUri };
93
94
  }
95
+ var current;
94
96
  function signIn(o) {
95
97
  const issuer = (o.issuer ?? DEFAULT_ISSUER).replace(/\/$/, "");
96
98
  const authorize = o.authorizeUrl ?? AUTHORIZE[issuer];
97
99
  if (!authorize) return Promise.reject(new Error("authorizeUrl is required for this issuer"));
98
100
  if (!o.clientId || !o.redirectUri) return Promise.reject(new Error("clientId and redirectUri are required"));
101
+ if (current && !current.popup.closed) {
102
+ current.popup.focus();
103
+ return current.waiting;
104
+ }
99
105
  const p = { state: randomToken(), nonce: randomToken(), verifier: randomToken(), issuer, clientId: o.clientId, redirectUri: o.redirectUri };
100
106
  store.set(STORAGE_KEY, JSON.stringify(p));
101
107
  const popup = window.open("", POPUP_NAME, "popup,width=480,height=720");
102
- return (async () => {
108
+ const waiting = (async () => {
103
109
  const url = await authorizeURL(o, p, authorize);
104
110
  if (!popup) {
105
111
  location.assign(url);
@@ -108,21 +114,43 @@ function signIn(o) {
108
114
  popup.location.href = url;
109
115
  const callbackOrigin = new URL(o.redirectUri, location.href).origin;
110
116
  return new Promise((resolve, reject) => {
117
+ let last;
118
+ const end = () => {
119
+ stop();
120
+ clearInterval(timer);
121
+ clearTimeout(last);
122
+ };
111
123
  const stop = listen(MESSAGE, callbackOrigin, (m) => {
112
124
  if (m.state !== p.state) return;
113
- stop();
125
+ end();
114
126
  store.remove(STORAGE_KEY);
115
127
  if (typeof m.error === "string" && m.error) reject(new Error(m.error));
116
128
  else if (m.iss !== issuer) reject(new Error("the answer came from another issuer"));
117
129
  else if (typeof m.code !== "string" || !m.code) reject(new Error("the answer carried no code"));
118
130
  else resolve(result(p, m.code));
119
131
  }, POPUP_NAME, popup);
132
+ const timer = setInterval(() => {
133
+ if (!popup.closed) return;
134
+ clearInterval(timer);
135
+ last = setTimeout(() => {
136
+ end();
137
+ reject(new Error("closed"));
138
+ }, TICK_MS);
139
+ }, TICK_MS);
120
140
  o.signal?.addEventListener("abort", () => {
121
- stop();
141
+ end();
122
142
  reject(new Error("aborted"));
123
143
  });
124
144
  });
125
145
  })();
146
+ if (popup) {
147
+ current = { popup, waiting };
148
+ const over = () => {
149
+ if (current && current.popup === popup) current = void 0;
150
+ };
151
+ waiting.then(over, over);
152
+ }
153
+ return waiting;
126
154
  }
127
155
  async function handleCallback() {
128
156
  const q = new URLSearchParams(location.search);
@@ -196,14 +224,17 @@ function defineSignInButton() {
196
224
  }
197
225
  start() {
198
226
  const attr = (n) => this.getAttribute(n) || void 0;
199
- signIn({
227
+ const visit = signIn({
200
228
  clientId: attr("client-id") ?? "",
201
229
  redirectUri: attr("redirect-uri") ?? "",
202
230
  scope: attr("scope"),
203
231
  claims: attr("claims"),
204
232
  issuer: attr("issuer"),
205
233
  authorizeUrl: attr("authorize-url")
206
- }).then(
234
+ });
235
+ if (visit === this.reporting) return;
236
+ this.reporting = visit;
237
+ visit.then(
207
238
  (r) => r && this.dispatchEvent(new CustomEvent("cd-signin", { detail: r, bubbles: true, composed: true })),
208
239
  (e) => this.dispatchEvent(new CustomEvent("cd-signin-error", { detail: { message: String(e?.message ?? e) }, bubbles: true, composed: true }))
209
240
  );
@@ -217,6 +248,8 @@ function defineSignInButton() {
217
248
  var PROD_ORIGIN = "https://embed.chaindaddy.io";
218
249
  var STAGING_ORIGIN = "https://embed-staging.chaindaddy.io";
219
250
  var READY_TIMEOUT_MS = 8e3;
251
+ var NEAR = "1500px 0px";
252
+ var DOCS = "docs.chaindaddy.io/token/apps-on-your-site#if-the-link-stays";
220
253
  var MAX_HEIGHT = 4e3;
221
254
  var THEME_TOKENS = ["--cd-accent", "--cd-surface", "--cd-text", "--cd-text-muted", "--cd-border"];
222
255
  function embedOriginFor(scriptSrc) {
@@ -263,6 +296,7 @@ function defineEmbedElements(options = {}) {
263
296
  try {
264
297
  window.removeEventListener("message", this.onMessage);
265
298
  clearTimeout(this.timer);
299
+ this.watch?.disconnect();
266
300
  } catch {
267
301
  }
268
302
  }
@@ -299,6 +333,7 @@ function defineEmbedElements(options = {}) {
299
333
  const root = this.shadowRoot;
300
334
  window.removeEventListener("message", this.onMessage);
301
335
  clearTimeout(this.timer);
336
+ this.watch?.disconnect();
302
337
  this.removeAttribute("data-cd-ready");
303
338
  this.removeAttribute("data-cd-unavailable");
304
339
  const src = this.src();
@@ -309,17 +344,43 @@ function defineEmbedElements(options = {}) {
309
344
  }
310
345
  root.innerHTML = `<style>${STYLE2}</style><slot></slot>`;
311
346
  const frame = document.createElement("iframe");
312
- frame.setAttribute("loading", "lazy");
313
347
  frame.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
314
348
  frame.setAttribute("allow", "clipboard-write; fullscreen");
315
349
  frame.setAttribute("title", this.kind === "coin" ? "Coin on Chain Daddy" : "App on Chain Daddy");
316
- frame.src = src;
317
350
  root.appendChild(frame);
318
351
  this.frame = frame;
319
352
  window.addEventListener("message", this.onMessage);
320
- this.timer = setTimeout(() => {
321
- if (!this.hasAttribute("data-cd-ready")) this.unavailable();
322
- }, READY_TIMEOUT_MS);
353
+ const load = () => {
354
+ frame.src = src;
355
+ this.timer = setTimeout(() => {
356
+ if (!this.hasAttribute("data-cd-ready")) this.unavailable();
357
+ }, READY_TIMEOUT_MS);
358
+ };
359
+ if (typeof IntersectionObserver === "undefined") return load();
360
+ this.watch = new IntersectionObserver(
361
+ (entries) => {
362
+ if (!entries.some((e) => e.isIntersecting)) return;
363
+ this.watch?.disconnect();
364
+ load();
365
+ },
366
+ { rootMargin: NEAR }
367
+ );
368
+ this.watch.observe(this);
369
+ }
370
+ /** Says in the console why the link stayed: the page sees only that it did. */
371
+ why() {
372
+ try {
373
+ const q = new URL(this.frame.src).searchParams;
374
+ const app = q.get("app");
375
+ q.delete("mode");
376
+ q.delete("v");
377
+ fetch(`https://${origin === STAGING_ORIGIN ? "staging-api" : "api"}.chaindaddy.io/api/v2/embed/status?${q}`, { credentials: "omit" }).then((r) => r.json()).then((s) => {
378
+ const reason = !s.embeddable ? s.reason : s.interactive ? "not a verified site of the coin" : "not https, or the app did not start";
379
+ console.info(`[Chain Daddy] cd-${this.kind} ${app ?? ""} cannot show here: ${reason}. ${DOCS}`);
380
+ }).catch(() => {
381
+ });
382
+ } catch {
383
+ }
323
384
  }
324
385
  receive(event) {
325
386
  try {
@@ -358,6 +419,7 @@ function defineEmbedElements(options = {}) {
358
419
  if (this.hasAttribute("data-cd-unavailable")) return;
359
420
  this.setAttribute("data-cd-unavailable", "");
360
421
  this.dispatchEvent(new CustomEvent("cd:unavailable"));
422
+ this.why();
361
423
  } catch {
362
424
  }
363
425
  }
@@ -402,7 +464,7 @@ function defineEmbedElements(options = {}) {
402
464
  var ORIGINS = ["https://chaindaddy.io", "https://staging.chaindaddy.io"];
403
465
  var windowName = () => "cd-checkout-" + randomToken();
404
466
  var FEATURES = "popup,width=480,height=820";
405
- var TICK_MS = 300;
467
+ var TICK_MS2 = 300;
406
468
  var QUIET_TICKS = 7;
407
469
  function parseCheckoutUrl(url) {
408
470
  if (typeof url !== "string") return null;
@@ -436,7 +498,7 @@ function successTarget(successUrl, id) {
436
498
  pairs.push("session_id=" + encodeURIComponent(id));
437
499
  return u.origin + u.pathname + "?" + pairs.join("&") + u.hash;
438
500
  }
439
- var current;
501
+ var current2;
440
502
  function wait(popup, target, success) {
441
503
  return new Promise((resolve) => {
442
504
  let ready = false;
@@ -472,9 +534,9 @@ function wait(popup, target, success) {
472
534
  const timer = setInterval(() => {
473
535
  if (popup.closed) {
474
536
  clearInterval(timer);
475
- last = setTimeout(() => done({ status: "closed" }), TICK_MS);
537
+ last = setTimeout(() => done({ status: "closed" }), TICK_MS2);
476
538
  } else if (!ready || ++quiet % QUIET_TICKS === 0) hello();
477
- }, TICK_MS);
539
+ }, TICK_MS2);
478
540
  window.addEventListener("message", onMessage);
479
541
  hello();
480
542
  });
@@ -482,9 +544,9 @@ function wait(popup, target, success) {
482
544
  function checkout(o) {
483
545
  const refused = () => new Error("checkout: url is not a Chain Daddy checkout session link");
484
546
  const track = (popup, result2, href) => {
485
- current = { popup, result: result2, href };
547
+ current2 = { popup, result: result2, href };
486
548
  const over = () => {
487
- if (current && current.popup === popup) current = void 0;
549
+ if (current2 && current2.popup === popup) current2 = void 0;
488
550
  };
489
551
  result2.then(over, over);
490
552
  return result2;
@@ -499,9 +561,9 @@ function checkout(o) {
499
561
  const success = (t) => successUrl ? successTarget(successUrl, t.id) : null;
500
562
  const target = typeof url === "function" ? null : parseCheckoutUrl(url);
501
563
  if (typeof url !== "function" && !target) throw refused();
502
- if (current && !current.popup.closed && (!target || current.href === target.href)) {
503
- current.popup.focus();
504
- return current.result;
564
+ if (current2 && !current2.popup.closed && (!target || current2.href === target.href)) {
565
+ current2.popup.focus();
566
+ return current2.result;
505
567
  }
506
568
  if (target) {
507
569
  const popup2 = window.open(target.href, windowName(), FEATURES);
package/dist/signin.d.ts CHANGED
@@ -12,7 +12,7 @@ export interface SignInOptions {
12
12
  issuer?: string;
13
13
  /** The consent page; derived from the issuer for Chain Daddy's own. */
14
14
  authorizeUrl?: string;
15
- /** Stops waiting. Closing the popup does not: a closed-looking popup is not a cancel. */
15
+ /** Stops waiting: the call rejects "aborted". */
16
16
  signal?: AbortSignal;
17
17
  }
18
18
  /** What your server exchanges at the token endpoint (or exchangeCode does here). */
@@ -32,8 +32,9 @@ export declare function challengeFor(verifier: string): Promise<string>;
32
32
  * before anything is awaited, or a browser blocks it. Resolves with the code
33
33
  * once the visitor approves; resolves null when the popup was blocked and the
34
34
  * page is redirecting instead (your callback page then calls handleCallback).
35
- * Rejects when they cancel (access_denied) or the answer is not for this
36
- * request.
35
+ * Rejects when they cancel (access_denied), when they close the popup without
36
+ * answering (closed), or when the answer is not for this request. While one
37
+ * sign-in is open, another call shows its popup and returns the same promise.
37
38
  */
38
39
  export declare function signIn(o: SignInOptions): Promise<SignInResult | null>;
39
40
  /**
package/dist/signin.js CHANGED
@@ -65,6 +65,7 @@ var AUTHORIZE = {
65
65
  var POPUP_NAME = "cd-signin";
66
66
  var MESSAGE = "cd:signin";
67
67
  var STORAGE_KEY = "cd.signin";
68
+ var TICK_MS = 300;
68
69
  async function challengeFor(verifier) {
69
70
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
70
71
  return b64url(new Uint8Array(digest));
@@ -90,15 +91,20 @@ async function authorizeURL(o, p, authorize) {
90
91
  function result(p, code) {
91
92
  return { code, codeVerifier: p.verifier, state: p.state, nonce: p.nonce, issuer: p.issuer, clientId: p.clientId, redirectUri: p.redirectUri };
92
93
  }
94
+ var current;
93
95
  function signIn(o) {
94
96
  const issuer = (o.issuer ?? DEFAULT_ISSUER).replace(/\/$/, "");
95
97
  const authorize = o.authorizeUrl ?? AUTHORIZE[issuer];
96
98
  if (!authorize) return Promise.reject(new Error("authorizeUrl is required for this issuer"));
97
99
  if (!o.clientId || !o.redirectUri) return Promise.reject(new Error("clientId and redirectUri are required"));
100
+ if (current && !current.popup.closed) {
101
+ current.popup.focus();
102
+ return current.waiting;
103
+ }
98
104
  const p = { state: randomToken(), nonce: randomToken(), verifier: randomToken(), issuer, clientId: o.clientId, redirectUri: o.redirectUri };
99
105
  store.set(STORAGE_KEY, JSON.stringify(p));
100
106
  const popup = window.open("", POPUP_NAME, "popup,width=480,height=720");
101
- return (async () => {
107
+ const waiting = (async () => {
102
108
  const url = await authorizeURL(o, p, authorize);
103
109
  if (!popup) {
104
110
  location.assign(url);
@@ -107,21 +113,43 @@ function signIn(o) {
107
113
  popup.location.href = url;
108
114
  const callbackOrigin = new URL(o.redirectUri, location.href).origin;
109
115
  return new Promise((resolve, reject) => {
116
+ let last;
117
+ const end = () => {
118
+ stop();
119
+ clearInterval(timer);
120
+ clearTimeout(last);
121
+ };
110
122
  const stop = listen(MESSAGE, callbackOrigin, (m) => {
111
123
  if (m.state !== p.state) return;
112
- stop();
124
+ end();
113
125
  store.remove(STORAGE_KEY);
114
126
  if (typeof m.error === "string" && m.error) reject(new Error(m.error));
115
127
  else if (m.iss !== issuer) reject(new Error("the answer came from another issuer"));
116
128
  else if (typeof m.code !== "string" || !m.code) reject(new Error("the answer carried no code"));
117
129
  else resolve(result(p, m.code));
118
130
  }, POPUP_NAME, popup);
131
+ const timer = setInterval(() => {
132
+ if (!popup.closed) return;
133
+ clearInterval(timer);
134
+ last = setTimeout(() => {
135
+ end();
136
+ reject(new Error("closed"));
137
+ }, TICK_MS);
138
+ }, TICK_MS);
119
139
  o.signal?.addEventListener("abort", () => {
120
- stop();
140
+ end();
121
141
  reject(new Error("aborted"));
122
142
  });
123
143
  });
124
144
  })();
145
+ if (popup) {
146
+ current = { popup, waiting };
147
+ const over = () => {
148
+ if (current && current.popup === popup) current = void 0;
149
+ };
150
+ waiting.then(over, over);
151
+ }
152
+ return waiting;
125
153
  }
126
154
  async function handleCallback() {
127
155
  const q = new URLSearchParams(location.search);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaindaddy/js",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Chain Daddy for any website: Sign in with Chain Daddy (a popup and a button), a coin's apps on its own site (<cd-app>), checkout for a coin's store in a popup, and a server-side check that a store order was paid on chain",
5
5
  "homepage": "https://docs.chaindaddy.io/developer/javascript-sdk",
6
6
  "repository": {