@chaindaddy/js 0.2.0 → 0.3.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/dist/api.d.ts CHANGED
@@ -15,8 +15,8 @@ export interface paths {
15
15
  * @description A verified coin page by chain and ticker: its name, token address, registration ID, logo, price,
16
16
  * market cap, 24-hour volume, liquidity, 24-hour price change and holder count. A figure Chain Daddy
17
17
  * does not have is `null`, never `0`. The logo is `null` for a page marked NSFW. Answers 404 for a
18
- * ticker with no verified page on that chain. Keyless, the same for every caller, and cached for
19
- * about a minute.
18
+ * ticker with no verified page on that chain, and for a page Chain Daddy removed. Keyless, the same
19
+ * for every caller, and cached for about a minute.
20
20
  */
21
21
  get: operations["getPublicCoin"];
22
22
  put?: never;
@@ -38,9 +38,10 @@ 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. Answers 404 for
42
- * a ticker with no verified page on that chain. Keyless, the same for every caller, and cached for
43
- * about a minute.
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
43
+ * with no verified page on that chain, and for a page Chain Daddy removed. Keyless, the same for
44
+ * every caller, and cached for about a minute.
44
45
  */
45
46
  get: operations["listPublicCoinApps"];
46
47
  put?: never;
@@ -64,7 +65,8 @@ export interface paths {
64
65
  * smallest unit as a decimal string, and `holds` (whether it is above zero). Use it to re-check a
65
66
  * holding after someone signed in to your site with Chain Daddy, without an API key. Works for any
66
67
  * token on a chain Chain Daddy serves. Each caller gets 60 requests a minute; past that it
67
- * answers 429. Cached for about 30 seconds.
68
+ * answers 429. Cached for about 30 seconds. All callers together share a limit on chain reads:
69
+ * in a busy moment it answers 503 with `Retry-After`.
68
70
  */
69
71
  get: operations["getPublicHolding"];
70
72
  put?: never;
@@ -86,10 +88,11 @@ export interface paths {
86
88
  * List a registration's verified sites
87
89
  * @description The websites a coin's registration has verified with a DNS record. `badgeHosts` show the coin's
88
90
  * verified badge on the Chain Daddy sign-in page. `embedHosts` may also run the coin's apps and take
89
- * its checkout. A host covers its subdomains. `interactive` is `false` when there are no
91
+ * its checkout. A host covers its subdomains, except one Chain Daddy has blocked. `interactive` is `false` when there are no
90
92
  * `embedHosts` or Chain Daddy has turned the coin's embeds off. A registration with no verified
91
- * sites answers 200 with empty lists. Keyless, the same for every caller, and cached for about a
92
- * minute.
93
+ * sites answers 200 with empty lists. Only the registration a verified coin page shows answers
94
+ * (the `crownId` the coin read gives); any other ID answers 404. Keyless, the same for every
95
+ * caller, and cached for about a minute.
93
96
  */
94
97
  get: operations["getPublicCrownSites"];
95
98
  put?: never;
@@ -833,6 +836,12 @@ export interface paths {
833
836
  * A buyer paying a checkout session sends `checkoutSessionId` from their own session on chaindaddy.io. The
834
837
  * session then fixes the store, the SKU, the quantity and `appAccountToken`, so `storeId` and `sku` may be
835
838
  * left out. The amount and the fee are quoted exactly as for any other order of that item.
839
+ *
840
+ * When the session's item belongs to an installed app, the order is that app's order: it pays the page, the
841
+ * fee and the app's developer, needs `acknowledgeFinalSale: true`, and stays inside the app's limits. The app
842
+ * is the item's own; an `appId` sent with a session is ignored. Everything is checked again when the order is
843
+ * made, so a session opened while the item could be sold answers 409 `APP_ITEM_NOT_SOLD_HERE` once its
844
+ * developer turns that off, pauses, or changes the item's terms.
836
845
  */
837
846
  post: operations["createIapOrder"];
838
847
  delete?: never;
@@ -864,6 +873,76 @@ export interface paths {
864
873
  patch?: never;
865
874
  trace?: never;
866
875
  };
876
+ "/api/v2/iap/checkout/sessions": {
877
+ parameters: {
878
+ query?: never;
879
+ header?: never;
880
+ path?: never;
881
+ cookie?: never;
882
+ };
883
+ get?: never;
884
+ put?: never;
885
+ /**
886
+ * Open a checkout session
887
+ * @description Take a payment for your coin's store on your own website. Your server calls this with a store key
888
+ * (scope `orders:write`) and gets back `url`: send the buyer there with a plain link or a redirect. The
889
+ * buyer pays on chaindaddy.io and returns to `successUrl`, or to `cancelUrl` if they leave.
890
+ *
891
+ * A session sells one item (`lineItems` takes exactly one entry). The price, the fee and where the money
892
+ * goes are the item's own, exactly as when it is bought on chaindaddy.io: a session changes none of them.
893
+ *
894
+ * `successUrl` and `cancelUrl` must be `https` pages on one of the coin's verified sites, or a subdomain of
895
+ * one (400 `RETURN_URL_NOT_VERIFIED`, which lists the sites that pass). The wallet that opened the store
896
+ * must have signed the Seller Terms (403 `SELLER_TERMS_REQUIRED`).
897
+ *
898
+ * An item of an app installed on the coin's page (its `sku` starts with `app.`) is sold through a session
899
+ * only when the app's developer has allowed that item to be sold outside the app; otherwise the call answers
900
+ * 409 `APP_ITEM_NOT_SOLD_HERE`, with a `reason`. The sale pays the same wallets as inside the app: the page,
901
+ * the fee and the developer's share. Such a session takes at most the app's per-order quantity.
902
+ *
903
+ * Send an `Idempotency-Key`. A retry of the same request returns the session it opened, with status 200
904
+ * and the header `Idempotent-Replayed: true`.
905
+ *
906
+ * Learn that the session was paid from the `iap.order.confirmed` webhook (its payload carries
907
+ * `checkoutSessionId`), or by reading the session. Never from the buyer arriving on `successUrl`.
908
+ *
909
+ * Answers 404 `CHECKOUT_SESSIONS_DISABLED` where checkout sessions are not switched on, or are open to
910
+ * test stores only and the key is a live store's. `GET /api/v2/iap/config` says which (`checkoutSessions`).
911
+ * Guide: https://docs.chaindaddy.io/developer/take-payments-on-your-site
912
+ */
913
+ post: operations["createIapCheckoutSession"];
914
+ delete?: never;
915
+ options?: never;
916
+ head?: never;
917
+ patch?: never;
918
+ trace?: never;
919
+ };
920
+ "/api/v2/iap/checkout/sessions/{sessionId}": {
921
+ parameters: {
922
+ query?: never;
923
+ header?: never;
924
+ path: {
925
+ /** @description The checkout session's id, `cs_` followed by 48 hex characters */
926
+ sessionId: components["parameters"]["IapCheckoutSessionId"];
927
+ };
928
+ cookie?: never;
929
+ };
930
+ /**
931
+ * Get a checkout session
932
+ * @description Your server reads a session it opened, with a store key of the same store (scope `orders:read`).
933
+ * `status` is `open`, `complete` once the buyer's payment is confirmed, or `expired`. `order` is the order
934
+ * that paid it (or the buyer's latest attempt while it is open), and `receipt` is that order's signed
935
+ * receipt once the session is complete. A key of any other store gets 404.
936
+ */
937
+ get: operations["getIapCheckoutSession"];
938
+ put?: never;
939
+ post?: never;
940
+ delete?: never;
941
+ options?: never;
942
+ head?: never;
943
+ patch?: never;
944
+ trace?: never;
945
+ };
867
946
  "/api/v2/iap/checkout/sessions/{sessionId}/public": {
868
947
  parameters: {
869
948
  query?: never;
@@ -880,8 +959,16 @@ export interface paths {
880
959
  * needs no credential (the session id in the buyer's link is enough) and never carries the session's
881
960
  * `metadata` or `clientReferenceId`. The answer is never cached.
882
961
  *
962
+ * When the item belongs to an app installed on the coin's page, `sku` names the app (`appId`) and carries
963
+ * nothing else of the app's: no `appBps`, `appTermsVersion`, `appItemKey` or `appRole`, and an empty
964
+ * `metadata`.
965
+ *
883
966
  * Pass `origin` to learn whether a page on that origin may open this checkout in a popup
884
967
  * (`openerAllowed`): it must be `https` on one of the coin's verified sites.
968
+ *
969
+ * `returnAllowed` says whether the buyer may still be sent to `successUrl` and `cancelUrl`. It is checked
970
+ * again on every read, so a coin whose site stopped being verified after the session was opened no longer
971
+ * gets its buyers sent there; the pay page then shows the result itself.
885
972
  */
886
973
  get: operations["getIapPublicCheckoutSession"];
887
974
  put?: never;
@@ -949,7 +1036,8 @@ export interface paths {
949
1036
  * List the event types a webhook can subscribe to
950
1037
  * @description Returns every subscribable event type with a one-line description: registration, heartbeat, holder, trade
951
1038
  * and alert events, API quota events, deal lifecycle events (`deal.created`, `deal.accepted`,
952
- * `deal.rejected`, `deal.cancelled`, `deal.completed`, `deal.failed`) and creator-store events (`iap.*`).
1039
+ * `deal.rejected`, `deal.cancelled`, `deal.completed`, `deal.failed`), creator-store events (`iap.*`) and
1040
+ * `app.order.confirmed`, which tells a paid app's developer of each confirmed order made through the app.
953
1041
  * This list is exactly what create and update accept. Read it before creating a webhook rather than
954
1042
  * guessing names: a misspelled type is refused with 400.
955
1043
  *
@@ -1273,6 +1361,18 @@ export interface components {
1273
1361
  error: string;
1274
1362
  /** @description Machine-readable error code (e.g. `IAP_PLAN_REQUIRED`, `INSUFFICIENT_QUANTITY`) */
1275
1363
  code?: string;
1364
+ /**
1365
+ * @description Which of a code's causes this is, where a code has several. Sent with `APP_NOT_SELLING`:
1366
+ * `app_not_published`, `selling_switched_off` (stopped by Chain Daddy), `developer_paused`
1367
+ * (paused by the app's developer), `developer_unavailable` (the developer cannot sell right
1368
+ * now; why is between them and Chain Daddy, and their own portal tells them), `no_payout_on_chain`
1369
+ * or `payout_contract_missing` (the developer is not paid on this chain). Sent with
1370
+ * `APP_ITEM_NOT_SOLD_HERE` (an app's item and a checkout session): any of those, or
1371
+ * `not_allowed_outside_app` (the app's developer has not allowed the item to be sold outside the app),
1372
+ * `app_not_installed`, `terms_changed` or `item_not_for_sale`. Absent on every other error. Expect new
1373
+ * values.
1374
+ */
1375
+ reason?: string;
1276
1376
  };
1277
1377
  /** @description One token prize. Amounts are base units, as strings. */
1278
1378
  IapAirdropAward: {
@@ -1852,6 +1952,46 @@ export interface components {
1852
1952
  wallet?: string;
1853
1953
  entitlements?: components["schemas"]["IapEntitlement"][];
1854
1954
  };
1955
+ /** @description A checkout session, as the store's own server sees it */
1956
+ IapCheckoutSession: {
1957
+ /** @description `cs_` followed by 48 hex characters */
1958
+ id?: string;
1959
+ /**
1960
+ * @description The page the buyer pays on. Link or redirect the buyer to it
1961
+ * @example https://chaindaddy.io/_pay/cs_0f3a9c1e5b7d2a4c6e8f0b1d3a5c7e9f1b3d5a7c9e0f2a4c
1962
+ */
1963
+ url?: string;
1964
+ /**
1965
+ * @description `complete` once the payment is confirmed; `expired` when it was not paid by `expiresAt`
1966
+ * @enum {string}
1967
+ */
1968
+ status?: "open" | "complete" | "expired";
1969
+ /** @description False for a test store's session */
1970
+ livemode?: boolean;
1971
+ /** Format: date-time */
1972
+ expiresAt?: string;
1973
+ /** Format: date-time */
1974
+ completedAt?: string | null;
1975
+ /** Format: date-time */
1976
+ createdAt?: string;
1977
+ storeId?: string;
1978
+ lineItems?: {
1979
+ sku?: string;
1980
+ quantity?: number;
1981
+ }[];
1982
+ successUrl?: string;
1983
+ cancelUrl?: string;
1984
+ clientReferenceId?: string | null;
1985
+ metadata?: {
1986
+ [key: string]: unknown;
1987
+ };
1988
+ /** @description The only wallet that can pay, when the session names one */
1989
+ wallet?: string | null;
1990
+ /** @description The order that paid the session, or the buyer's latest attempt while it is open; null until the buyer starts paying */
1991
+ order?: components["schemas"]["IapOrder"] | null;
1992
+ /** @description The paying order's signed receipt (a JWS), once the session is complete */
1993
+ receipt?: string | null;
1994
+ };
1855
1995
  /** @description A checkout session, as the buyer's pay page sees it. Never carries `metadata` or `clientReferenceId` */
1856
1996
  IapPublicCheckoutSession: {
1857
1997
  id?: string;
@@ -1869,6 +2009,11 @@ export interface components {
1869
2009
  wallet?: string | null;
1870
2010
  /** @description Whether a page on the `origin` passed may open this checkout in a popup. False when no `origin` is passed */
1871
2011
  openerAllowed?: boolean;
2012
+ /**
2013
+ * @description Whether `successUrl` and `cancelUrl` are both still on the coin's verified sites, as of this read.
2014
+ * When false the buyer is not sent to either
2015
+ */
2016
+ returnAllowed?: boolean;
1872
2017
  };
1873
2018
  IapSkuOwners: {
1874
2019
  entries?: {
@@ -2345,8 +2490,8 @@ export interface components {
2345
2490
  iat: number;
2346
2491
  /** @description Five minutes after `iat` */
2347
2492
  exp: number;
2348
- /** @description When the Chain Daddy session this sign-in rides on began */
2349
- auth_time: number;
2493
+ /** @description When the Chain Daddy session this sign-in rides on began. Only for a client whose `sub` is the wallet; a client with a per-site `sub` gets none, because the same value at every site would link its ids */
2494
+ auth_time?: number;
2350
2495
  /** @description Your `nonce`, when you sent one */
2351
2496
  nonce?: string;
2352
2497
  jti: string;
@@ -2625,7 +2770,7 @@ export interface operations {
2625
2770
  /** @description The registration's ID on that chain */
2626
2771
  crownId: number;
2627
2772
  tokenAddress: string;
2628
- /** @description The page's logo; `null` when it has none or the page is marked NSFW */
2773
+ /** @description The page's logo; `null` when it has none, the page is marked NSFW, or Chain Daddy removed the image */
2629
2774
  logoUrl: string | null;
2630
2775
  /** @description Whether the page carries the NSFW label */
2631
2776
  nsfw: boolean;
@@ -2643,6 +2788,21 @@ export interface operations {
2643
2788
  };
2644
2789
  };
2645
2790
  };
2791
+ /** @description The request came with the user agent of an AI or LLM crawler, and no cached answer was there to serve instead. Never sent to a browser or to a server using its own user agent. */
2792
+ 403: {
2793
+ headers: {
2794
+ [name: string]: unknown;
2795
+ };
2796
+ content: {
2797
+ /**
2798
+ * @example {
2799
+ * "error": "ai_bot_blocked",
2800
+ * "message": "AI/LLM scraping is not permitted. Use our API with a developer-tier API key for programmatic access."
2801
+ * }
2802
+ */
2803
+ "application/json": components["schemas"]["Error"];
2804
+ };
2805
+ };
2646
2806
  /** @description No verified page for that ticker on that chain, or an unknown chain */
2647
2807
  404: {
2648
2808
  headers: {
@@ -2659,6 +2819,24 @@ export interface operations {
2659
2819
  "application/json": components["schemas"]["Error"];
2660
2820
  };
2661
2821
  };
2822
+ /** @description More than 400 requests in a minute from one IP address, across the whole API. Never cached. */
2823
+ 429: {
2824
+ headers: {
2825
+ /** @description Seconds to wait before trying again */
2826
+ "Retry-After"?: number;
2827
+ [name: string]: unknown;
2828
+ };
2829
+ content: {
2830
+ /**
2831
+ * @example {
2832
+ * "error": "Too many requests. Please try again later.",
2833
+ * "code": "rate_limit_exceeded",
2834
+ * "message": "Too many requests. Please try again later."
2835
+ * }
2836
+ */
2837
+ "application/json": components["schemas"]["Error"];
2838
+ };
2839
+ };
2662
2840
  /** @description The read could not be answered right now. Never cached; try again. */
2663
2841
  503: {
2664
2842
  headers: {
@@ -2737,6 +2915,21 @@ export interface operations {
2737
2915
  };
2738
2916
  };
2739
2917
  };
2918
+ /** @description The request came with the user agent of an AI or LLM crawler, and no cached answer was there to serve instead. Never sent to a browser or to a server using its own user agent. */
2919
+ 403: {
2920
+ headers: {
2921
+ [name: string]: unknown;
2922
+ };
2923
+ content: {
2924
+ /**
2925
+ * @example {
2926
+ * "error": "ai_bot_blocked",
2927
+ * "message": "AI/LLM scraping is not permitted. Use our API with a developer-tier API key for programmatic access."
2928
+ * }
2929
+ */
2930
+ "application/json": components["schemas"]["Error"];
2931
+ };
2932
+ };
2740
2933
  /** @description No verified page for that ticker on that chain, or an unknown chain */
2741
2934
  404: {
2742
2935
  headers: {
@@ -2753,6 +2946,24 @@ export interface operations {
2753
2946
  "application/json": components["schemas"]["Error"];
2754
2947
  };
2755
2948
  };
2949
+ /** @description More than 400 requests in a minute from one IP address, across the whole API. Never cached. */
2950
+ 429: {
2951
+ headers: {
2952
+ /** @description Seconds to wait before trying again */
2953
+ "Retry-After"?: number;
2954
+ [name: string]: unknown;
2955
+ };
2956
+ content: {
2957
+ /**
2958
+ * @example {
2959
+ * "error": "Too many requests. Please try again later.",
2960
+ * "code": "rate_limit_exceeded",
2961
+ * "message": "Too many requests. Please try again later."
2962
+ * }
2963
+ */
2964
+ "application/json": components["schemas"]["Error"];
2965
+ };
2966
+ };
2756
2967
  /** @description The read could not be answered right now. Never cached; try again. */
2757
2968
  503: {
2758
2969
  headers: {
@@ -2817,6 +3028,21 @@ export interface operations {
2817
3028
  };
2818
3029
  };
2819
3030
  };
3031
+ /** @description The request came with the user agent of an AI or LLM crawler, and no cached answer was there to serve instead. Never sent to a browser or to a server using its own user agent. */
3032
+ 403: {
3033
+ headers: {
3034
+ [name: string]: unknown;
3035
+ };
3036
+ content: {
3037
+ /**
3038
+ * @example {
3039
+ * "error": "ai_bot_blocked",
3040
+ * "message": "AI/LLM scraping is not permitted. Use our API with a developer-tier API key for programmatic access."
3041
+ * }
3042
+ */
3043
+ "application/json": components["schemas"]["Error"];
3044
+ };
3045
+ };
2820
3046
  /** @description An unknown chain, or an address that is not one on that chain */
2821
3047
  404: {
2822
3048
  headers: {
@@ -2851,9 +3077,15 @@ export interface operations {
2851
3077
  "application/json": components["schemas"]["Error"];
2852
3078
  };
2853
3079
  };
2854
- /** @description The read could not be answered right now. Never cached; try again. */
3080
+ /**
3081
+ * @description The read could not be answered right now. Never cached; try again. It never means the wallet
3082
+ * holds nothing. In a busy moment, when Chain Daddy is making as many chain reads as it allows,
3083
+ * it carries `Retry-After`.
3084
+ */
2855
3085
  503: {
2856
3086
  headers: {
3087
+ /** @description Seconds to wait before trying again. Sent only in a busy moment */
3088
+ "Retry-After"?: number;
2857
3089
  [name: string]: unknown;
2858
3090
  };
2859
3091
  content: {
@@ -2908,7 +3140,7 @@ export interface operations {
2908
3140
  chain: string;
2909
3141
  chainId: string;
2910
3142
  crownId: number;
2911
- /** @description The coin's ticker; empty when the registration could not be read */
3143
+ /** @description The coin's ticker */
2912
3144
  symbol: string;
2913
3145
  badgeHosts: string[];
2914
3146
  embedHosts: string[];
@@ -2916,7 +3148,22 @@ export interface operations {
2916
3148
  };
2917
3149
  };
2918
3150
  };
2919
- /** @description An unknown chain, or an ID that is not a positive whole number */
3151
+ /** @description The request came with the user agent of an AI or LLM crawler, and no cached answer was there to serve instead. Never sent to a browser or to a server using its own user agent. */
3152
+ 403: {
3153
+ headers: {
3154
+ [name: string]: unknown;
3155
+ };
3156
+ content: {
3157
+ /**
3158
+ * @example {
3159
+ * "error": "ai_bot_blocked",
3160
+ * "message": "AI/LLM scraping is not permitted. Use our API with a developer-tier API key for programmatic access."
3161
+ * }
3162
+ */
3163
+ "application/json": components["schemas"]["Error"];
3164
+ };
3165
+ };
3166
+ /** @description An unknown chain, an ID that is not a positive whole number, or a registration that is not a verified coin page's */
2920
3167
  404: {
2921
3168
  headers: {
2922
3169
  [name: string]: unknown;
@@ -2932,6 +3179,24 @@ export interface operations {
2932
3179
  "application/json": components["schemas"]["Error"];
2933
3180
  };
2934
3181
  };
3182
+ /** @description More than 400 requests in a minute from one IP address, across the whole API. Never cached. */
3183
+ 429: {
3184
+ headers: {
3185
+ /** @description Seconds to wait before trying again */
3186
+ "Retry-After"?: number;
3187
+ [name: string]: unknown;
3188
+ };
3189
+ content: {
3190
+ /**
3191
+ * @example {
3192
+ * "error": "Too many requests. Please try again later.",
3193
+ * "code": "rate_limit_exceeded",
3194
+ * "message": "Too many requests. Please try again later."
3195
+ * }
3196
+ */
3197
+ "application/json": components["schemas"]["Error"];
3198
+ };
3199
+ };
2935
3200
  /** @description The read could not be answered right now. Never cached; try again. */
2936
3201
  503: {
2937
3202
  headers: {
@@ -4256,7 +4521,9 @@ export interface operations {
4256
4521
  /**
4257
4522
  * @description The checkout session the buyer is paying. A buyer's session only: a store key gets 403
4258
4523
  * `SESSION_REQUIRED`. 409 `CHECKOUT_SESSION_EXPIRED` or `CHECKOUT_SESSION_COMPLETE` when the
4259
- * session can no longer be paid; 403 `CHECKOUT_SESSION_WALLET` when it names another wallet
4524
+ * session can no longer be paid; 403 `CHECKOUT_SESSION_WALLET` when it names another wallet.
4525
+ * A session takes one buyer at a time: 409 `CHECKOUT_SESSION_IN_USE` while another wallet has
4526
+ * an order for it that can still be paid, or is paid and being confirmed
4260
4527
  */
4261
4528
  checkoutSessionId?: string;
4262
4529
  /** @description Required with a store key; the wallet that receives the items */
@@ -4295,7 +4562,8 @@ export interface operations {
4295
4562
  * @description Sold out, or the wallet has reached the SKU's per-wallet limit. For an app's item: `APP_TERMS_CHANGED`
4296
4563
  * (its developer changed its terms and the page's owner has not accepted them yet), `APP_NOT_INSTALLED`,
4297
4564
  * `APP_NOT_SELLING`, `APP_ORDER_LIMIT` (above the per-purchase limit) or `APP_DAILY_LIMIT` (the wallet
4298
- * reached what it may pay that app in 24 hours)
4565
+ * 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`
4299
4567
  */
4300
4568
  409: {
4301
4569
  headers: {
@@ -4367,6 +4635,192 @@ export interface operations {
4367
4635
  404: components["responses"]["IapNotFound"];
4368
4636
  };
4369
4637
  };
4638
+ createIapCheckoutSession: {
4639
+ parameters: {
4640
+ query?: never;
4641
+ header?: {
4642
+ /**
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.
4645
+ */
4646
+ "Idempotency-Key"?: components["parameters"]["IdempotencyKey"];
4647
+ };
4648
+ path?: never;
4649
+ cookie?: never;
4650
+ };
4651
+ requestBody: {
4652
+ content: {
4653
+ "application/json": {
4654
+ /** @description What is sold. Exactly one item */
4655
+ lineItems: {
4656
+ /** @description The item's SKU key in your store */
4657
+ sku: string;
4658
+ /** @default 1 */
4659
+ quantity?: number;
4660
+ }[];
4661
+ /**
4662
+ * Format: uri
4663
+ * @description Where the buyer returns after paying. `https`, on one of the coin's verified sites
4664
+ */
4665
+ successUrl: string;
4666
+ /**
4667
+ * Format: uri
4668
+ * @description Where the buyer returns if they leave without paying. Same rule as `successUrl`
4669
+ */
4670
+ cancelUrl: string;
4671
+ /**
4672
+ * @description Your own id for this checkout (a cart or user id). Echoed as `appAccountToken` on the order,
4673
+ * its events and its receipt, which the buyer can read: a reference, not a secret
4674
+ */
4675
+ clientReferenceId?: string;
4676
+ /** @description Your own data, returned when you read the session. At most 20 keys and 4 KB. Never shown to the buyer */
4677
+ metadata?: {
4678
+ [key: string]: unknown;
4679
+ };
4680
+ /** @description When set, only this wallet can pay the session. An address on the store's chain */
4681
+ wallet?: string;
4682
+ /**
4683
+ * @description How long the session can be paid
4684
+ * @default 3600
4685
+ */
4686
+ expiresInSeconds?: number;
4687
+ };
4688
+ };
4689
+ };
4690
+ responses: {
4691
+ /** @description A retry of a request already made under this `Idempotency-Key`; the session it opened */
4692
+ 200: {
4693
+ headers: {
4694
+ "Idempotent-Replayed"?: "true";
4695
+ [name: string]: unknown;
4696
+ };
4697
+ content: {
4698
+ "application/json": components["schemas"]["IapCheckoutSession"];
4699
+ };
4700
+ };
4701
+ /** @description The session. Send the buyer to `url` */
4702
+ 201: {
4703
+ headers: {
4704
+ [name: string]: unknown;
4705
+ };
4706
+ content: {
4707
+ "application/json": components["schemas"]["IapCheckoutSession"];
4708
+ };
4709
+ };
4710
+ /**
4711
+ * @description `INVALID_REQUEST`, `IDEMPOTENCY_KEY_REQUIRED`, or `RETURN_URL_NOT_VERIFIED`: a return URL is not an
4712
+ * `https` page on one of the coin's verified sites
4713
+ */
4714
+ 400: {
4715
+ headers: {
4716
+ [name: string]: unknown;
4717
+ };
4718
+ content: {
4719
+ "application/json": components["schemas"]["IapError"];
4720
+ };
4721
+ };
4722
+ /** @description No store key, or one that is unknown or revoked (`INVALID_STORE_KEY`) */
4723
+ 401: {
4724
+ headers: {
4725
+ [name: string]: unknown;
4726
+ };
4727
+ content: {
4728
+ "application/json": components["schemas"]["IapError"];
4729
+ };
4730
+ };
4731
+ /**
4732
+ * @description `STORE_KEY_REQUIRED`: called with a signed-in session instead of a store key. `SCOPE_REQUIRED`: the key
4733
+ * lacks `orders:write`. `STORE_NOT_LIVE`: the store is paused or its plan lapsed. `SELLER_TERMS_REQUIRED`:
4734
+ * the wallet that opened the store has not signed the Seller Terms
4735
+ */
4736
+ 403: {
4737
+ headers: {
4738
+ [name: string]: unknown;
4739
+ };
4740
+ content: {
4741
+ "application/json": components["schemas"]["IapError"];
4742
+ };
4743
+ };
4744
+ /** @description `SKU_NOT_FOR_SALE`: no such item in the store. `CHECKOUT_SESSIONS_DISABLED`: not switched on here */
4745
+ 404: {
4746
+ headers: {
4747
+ [name: string]: unknown;
4748
+ };
4749
+ content: {
4750
+ "application/json": components["schemas"]["IapError"];
4751
+ };
4752
+ };
4753
+ /**
4754
+ * @description `SKU_NOT_FOR_SALE`: the item is archived, has no price or is an unlock. `APP_ITEM_NOT_SOLD_HERE`: the
4755
+ * item belongs to an app installed on the coin's page and cannot be sold from the coin's own site now.
4756
+ * `reason` says why: `not_allowed_outside_app` (the app's developer has not turned it on for this item),
4757
+ * `app_not_installed`, `terms_changed` (the coin's owner must accept the app's new terms on the coin's
4758
+ * page), `item_not_for_sale` (the app no longer sells it), or one of the `APP_NOT_SELLING` reasons
4759
+ */
4760
+ 409: {
4761
+ headers: {
4762
+ [name: string]: unknown;
4763
+ };
4764
+ content: {
4765
+ "application/json": components["schemas"]["IapError"];
4766
+ };
4767
+ };
4768
+ /**
4769
+ * @description `IDEMPOTENCY_KEY_REUSED`: this `Idempotency-Key` already opened a session for a different request.
4770
+ * Nothing is returned or created; use a new key for a new session
4771
+ */
4772
+ 422: {
4773
+ headers: {
4774
+ [name: string]: unknown;
4775
+ };
4776
+ content: {
4777
+ "application/json": components["schemas"]["IapError"];
4778
+ };
4779
+ };
4780
+ };
4781
+ };
4782
+ getIapCheckoutSession: {
4783
+ parameters: {
4784
+ query?: never;
4785
+ header?: never;
4786
+ path: {
4787
+ /** @description The checkout session's id, `cs_` followed by 48 hex characters */
4788
+ sessionId: components["parameters"]["IapCheckoutSessionId"];
4789
+ };
4790
+ cookie?: never;
4791
+ };
4792
+ requestBody?: never;
4793
+ responses: {
4794
+ /** @description The session */
4795
+ 200: {
4796
+ headers: {
4797
+ [name: string]: unknown;
4798
+ };
4799
+ content: {
4800
+ "application/json": components["schemas"]["IapCheckoutSession"];
4801
+ };
4802
+ };
4803
+ /** @description No store key, or one that is unknown or revoked */
4804
+ 401: {
4805
+ headers: {
4806
+ [name: string]: unknown;
4807
+ };
4808
+ content: {
4809
+ "application/json": components["schemas"]["IapError"];
4810
+ };
4811
+ };
4812
+ /** @description `STORE_KEY_REQUIRED` (a signed-in session instead of a store key) or `SCOPE_REQUIRED` (the key lacks `orders:read`) */
4813
+ 403: {
4814
+ headers: {
4815
+ [name: string]: unknown;
4816
+ };
4817
+ content: {
4818
+ "application/json": components["schemas"]["IapError"];
4819
+ };
4820
+ };
4821
+ 404: components["responses"]["IapNotFound"];
4822
+ };
4823
+ };
4370
4824
  getIapPublicCheckoutSession: {
4371
4825
  parameters: {
4372
4826
  query?: {
package/dist/cd.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";(()=>{var w="0.2.0";function b(t){let e="";for(let i of t)e+=String.fromCharCode(i);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function g(){return b(crypto.getRandomValues(new Uint8Array(32)))}function v(t,e,i,s){let c=a=>{if(a&&typeof a=="object"&&a.type===t)try{i(a)}catch{}},u=a=>{a.origin===e&&c(a.data)};window.addEventListener("message",u);let d=null;if(s&&typeof BroadcastChannel=="function"&&e===location.origin)try{d=new BroadcastChannel(s),d.onmessage=a=>c(a.data)}catch{d=null}return()=>{window.removeEventListener("message",u),d?.close()}}var h={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 A="https://api.chaindaddy.io",T={"https://api.chaindaddy.io":"https://chaindaddy.io/_oauth/authorize","https://staging-api.chaindaddy.io":"https://staging.chaindaddy.io/_oauth/authorize"},p="cd-signin",E="cd:signin",m="cd.signin";async function U(t){let e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t));return b(new Uint8Array(e))}function M(t="openid"){let e=t.split(/\s+/).filter(Boolean);return(e.includes("openid")?e:["openid",...e]).join(" ")}async function L(t,e,i){let s=new URLSearchParams({response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUri,scope:M(t.scope),state:e.state,nonce:e.nonce,code_challenge:await U(e.verifier),code_challenge_method:"S256"});return t.claims&&s.set("claims",typeof t.claims=="string"?t.claims:JSON.stringify(t.claims)),`${i}?${s}`}function x(t,e){return{code:e,codeVerifier:t.verifier,state:t.state,nonce:t.nonce,issuer:t.issuer,clientId:t.clientId,redirectUri:t.redirectUri}}function f(t){let e=(t.issuer??A).replace(/\/$/,""),i=t.authorizeUrl??T[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"));let s={state:g(),nonce:g(),verifier:g(),issuer:e,clientId:t.clientId,redirectUri:t.redirectUri};h.set(m,JSON.stringify(s));let c=window.open("",p,"popup,width=480,height=720");return(async()=>{let u=await L(t,s,i);if(!c)return location.assign(u),null;c.location.href=u;let d=new URL(t.redirectUri,location.href).origin;return new Promise((a,n)=>{let o=v(E,d,r=>{r.state===s.state&&(o(),h.remove(m),typeof r.error=="string"&&r.error?n(new Error(r.error)):r.iss!==e?n(new Error("the answer came from another issuer")):typeof r.code!="string"||!r.code?n(new Error("the answer carried no code")):a(x(s,r.code)))},p);t.signal?.addEventListener("abort",()=>{o(),n(new Error("aborted"))})})})()}async function S(){let t=new URLSearchParams(location.search),e={type:E,code:t.get("code"),state:t.get("state"),iss:t.get("iss"),error:t.get("error")};if(window.name===p){try{window.opener?.postMessage(e,location.origin)}catch{}try{let c=new BroadcastChannel(p);c.postMessage(e),c.close()}catch{}return window.close(),null}let i=h.get(m);h.remove(m);let s=i?JSON.parse(i):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 x(s,e.code)}async function k(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 O='<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>',P=`
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=`
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 I(){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",s=this.getAttribute("theme")==="dark"?"dark":"light";this.root.innerHTML=`<style>${P}</style><button type="button" part="button" data-theme="${s}">${O}<span></span></button>`;let c=this.root.querySelector("button");c.querySelector("span").textContent=i,c.addEventListener("click",()=>this.start())}start(){let i=s=>this.getAttribute(s)||void 0;f({clientId:i("client-id")??"",redirectUri:i("redirect-uri")??"",scope:i("scope"),claims:i("claims"),issuer:i("issuer"),authorizeUrl:i("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 C="https://embed.chaindaddy.io",y="https://embed-staging.chaindaddy.io";var H=["--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 y}catch{}return C}var D=`
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=`
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
- `,z=`
16
+ `,Q=`
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 R(t={}){try{if(typeof customElements>"u"||typeof HTMLElement>"u")return;let e=t.origin===y?y:C;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)}catch{}}attributeChangedCallback(){try{this.isConnected&&this.shadowRoot&&this.mount()}catch{this.unavailable()}}src(){let n=(this.getAttribute("crown")??"").trim(),o=(this.getAttribute("chain")??"").trim(),r=(this.getAttribute("app")??"").trim();if(!/^[0-9]{1,18}$/.test(n)||!o||this.kind==="app"&&!r)return null;let l=new URLSearchParams({crown:n,chain:o});return this.kind==="app"?l.set("app",r):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 o=this.src();if(!o){n.innerHTML="<slot></slot>",this.frame=null;return}n.innerHTML=`<style>${D}</style><slot></slot>`;let r=document.createElement("iframe");r.setAttribute("loading","lazy"),r.setAttribute("referrerpolicy","strict-origin-when-cross-origin"),r.setAttribute("allow","clipboard-write; fullscreen"),r.setAttribute("title",this.kind==="coin"?"Coin on Chain Daddy":"App on Chain Daddy"),r.src=o,n.appendChild(r),this.frame=r,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 o=n.data;if(!o||typeof o!="object")return;if(o.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(o.type==="cd:resize"){let r=Number(o.height);Number.isFinite(r)&&(this.frame.style.height=`${Math.max(1,Math.min(4e3,Math.ceil(r)))}px`)}else o.type==="cd:unavailable"&&this.unavailable()}catch{}}sendTheme(){let n={},o=getComputedStyle(this);for(let r of H){let l=o.getPropertyValue(r).trim();l&&(n[r]=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{}}}i.observedAttributes=["crown","chain","app","mode"];class s extends i{}class c 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 o=this.shadowRoot??this.attachShadow({mode:"open"});o.innerHTML=`<style>${z}</style><button type="button" part="button"></button>`;let r=o.querySelector("button");r.textContent=(a.textContent??"").trim()||"Buy on Chain Daddy",r.addEventListener("click",()=>{try{window.open(n.toString(),"cd-buy","popup,width=460,height=760")||window.location.assign(n.toString())}catch{}})}catch{}}}for(let[d,a]of[["cd-app",s],["cd-coin",c],["cd-buy",u]])customElements.get(d)||customElements.define(d,a)}catch{}}var N={version:w,signIn:f,handleCallback:S,exchangeCode:k};window.ChainDaddy=Object.assign(window.ChainDaddy??{},N);I();R({origin:_(document.currentScript?.src)});})();
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();})();
package/dist/cd.js.sri CHANGED
@@ -1 +1 @@
1
- sha384-KKpjQXvfR3Rs+FsdOW+hfEJcZpfuYVXR+TcyRX0nbup+s/xRhLx102TTXL29eFE1
1
+ sha384-EF2XlOLUJ0J2U3uy/hlkmXdqyRP0WVm2LYjWdTseg/+QC4El1HD+qMhMlandYcSF
@@ -0,0 +1,73 @@
1
+ export interface CheckoutOptions {
2
+ /**
3
+ * The session's `url`, or a function that fetches it. With a function the
4
+ * popup opens blank at once (inside the click) and goes to the link when it
5
+ * arrives.
6
+ */
7
+ url: string | (() => string | Promise<string>);
8
+ /**
9
+ * Where your page goes once the buyer has paid, with `session_id` added, as
10
+ * the checkout's own return does. Left out, the call only resolves. The
11
+ * checkout never tells this page where to go.
12
+ */
13
+ successUrl?: string | null;
14
+ }
15
+ export type CheckoutResult =
16
+ /** The buyer paid. `session.status` is "complete" (confirmed) or "paid" (sent, still confirming). */
17
+ {
18
+ status: 'completed';
19
+ session: {
20
+ id: string;
21
+ status: 'complete' | 'paid';
22
+ orderId?: string;
23
+ };
24
+ }
25
+ /** The buyer left the checkout without paying. */
26
+ | {
27
+ status: 'cancelled';
28
+ session: {
29
+ id: string;
30
+ };
31
+ }
32
+ /** The popup closed and said nothing: read the session on your server to know. */
33
+ | {
34
+ status: 'closed';
35
+ }
36
+ /** The popup was blocked, so this page is going to the session by link. */
37
+ | {
38
+ status: 'redirected';
39
+ };
40
+ /** A session page: its address as parsed, its origin, and the session's id. */
41
+ export interface CheckoutTarget {
42
+ href: string;
43
+ origin: string;
44
+ id: string;
45
+ }
46
+ /**
47
+ * The session page `url` names, or null when it is anything else. Exact
48
+ * origin (so https, our host, no port), no credentials, and the path
49
+ * /_pay/<id> and nothing under it, all read from the parsed URL: never a
50
+ * prefix test on the string, which "https://chaindaddy.io@evil.example/_pay/x"
51
+ * passes. `href` is the page and nothing more: a query or fragment on the
52
+ * link is dropped, so nothing rides into our site on a checkout link.
53
+ */
54
+ export declare function parseCheckoutUrl(url: unknown): CheckoutTarget | null;
55
+ /**
56
+ * Opens a checkout session in a popup. Call it from a click handler: the popup
57
+ * opens at once, or a browser blocks it. Resolves with how it ended; rejects,
58
+ * before any window opens or moves, when `url` is not a checkout session link.
59
+ * When the popup is blocked, the page goes to the session by link instead.
60
+ */
61
+ export declare function checkout(o: CheckoutOptions): Promise<CheckoutResult>;
62
+ /**
63
+ * Makes every element with `data-cd-checkout` open its session in the popup:
64
+ * a link by its `href`, anything else by the attribute's value. One listener
65
+ * on the document, so elements added later work too. On a link this only adds
66
+ * to what the link already does: without this script, with a modifier key, or
67
+ * when the address is not a session link, the link is followed as usual.
68
+ *
69
+ * When the visit ends the element fires `cd:checkout` (bubbling) with the
70
+ * result as `detail`; once the buyer has paid, the page goes to the element's
71
+ * `data-cd-success`, if it has one.
72
+ */
73
+ export declare function bindCheckoutButtons(): void;
@@ -0,0 +1,168 @@
1
+ // src/core.ts
2
+ function b64url(bytes) {
3
+ let s = "";
4
+ for (const b of bytes) s += String.fromCharCode(b);
5
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6
+ }
7
+ function randomToken() {
8
+ return b64url(crypto.getRandomValues(new Uint8Array(32)));
9
+ }
10
+
11
+ // src/checkout.ts
12
+ var ORIGINS = ["https://chaindaddy.io", "https://staging.chaindaddy.io"];
13
+ var windowName = () => "cd-checkout-" + randomToken();
14
+ var FEATURES = "popup,width=480,height=820";
15
+ var TICK_MS = 300;
16
+ var QUIET_TICKS = 7;
17
+ function parseCheckoutUrl(url) {
18
+ if (typeof url !== "string") return null;
19
+ let u;
20
+ try {
21
+ u = new URL(url);
22
+ } catch {
23
+ return null;
24
+ }
25
+ const m = /^\/_pay\/([\w-]+)$/.exec(u.pathname);
26
+ return m && ORIGINS.includes(u.origin) && !u.username && !u.password ? { href: u.origin + u.pathname, origin: u.origin, id: m[1] } : null;
27
+ }
28
+ function successTarget(successUrl, id) {
29
+ if (typeof successUrl !== "string") return null;
30
+ let u;
31
+ try {
32
+ u = new URL(successUrl, location.href);
33
+ } catch {
34
+ return null;
35
+ }
36
+ if (!/^https?:$/.test(u.protocol) || u.username || u.password) return null;
37
+ const key = (pair) => {
38
+ const raw = pair.split("=")[0];
39
+ try {
40
+ return decodeURIComponent(raw.replace(/\+/g, " "));
41
+ } catch {
42
+ return raw;
43
+ }
44
+ };
45
+ const pairs = u.search.slice(1).split("&").filter((pair) => pair && key(pair) !== "session_id");
46
+ pairs.push("session_id=" + encodeURIComponent(id));
47
+ return u.origin + u.pathname + "?" + pairs.join("&") + u.hash;
48
+ }
49
+ var current;
50
+ function wait(popup, target, success) {
51
+ return new Promise((resolve) => {
52
+ let ready = false;
53
+ let quiet = 0;
54
+ let last;
55
+ const done = (result) => {
56
+ clearInterval(timer);
57
+ clearTimeout(last);
58
+ window.removeEventListener("message", onMessage);
59
+ if (success && result.status === "completed") location.assign(success);
60
+ resolve(result);
61
+ };
62
+ const onMessage = (e) => {
63
+ if (e.origin !== target.origin || e.source !== popup) return;
64
+ const data = e.data;
65
+ if (!data || typeof data !== "object") return;
66
+ if (data.type === "cd:checkout.ready") ready = true;
67
+ const s = data.session;
68
+ if (!s || typeof s !== "object" || s.id !== target.id) return;
69
+ if (data.type === "cd:checkout.cancelled") done({ status: "cancelled", session: { id: target.id } });
70
+ else if (data.type === "cd:checkout.completed" && (s.status === "complete" || s.status === "paid")) {
71
+ const session = { id: target.id, status: s.status };
72
+ if (typeof s.orderId === "string" && /^[\w-]{1,128}$/.test(s.orderId)) session.orderId = s.orderId;
73
+ done({ status: "completed", session });
74
+ }
75
+ };
76
+ const hello = () => {
77
+ try {
78
+ popup.postMessage({ type: "cd:checkout.hello" }, target.origin);
79
+ } catch {
80
+ }
81
+ };
82
+ const timer = setInterval(() => {
83
+ if (popup.closed) {
84
+ clearInterval(timer);
85
+ last = setTimeout(() => done({ status: "closed" }), TICK_MS);
86
+ } else if (!ready || ++quiet % QUIET_TICKS === 0) hello();
87
+ }, TICK_MS);
88
+ window.addEventListener("message", onMessage);
89
+ hello();
90
+ });
91
+ }
92
+ function checkout(o) {
93
+ const refused = () => new Error("checkout: url is not a Chain Daddy checkout session link");
94
+ const track = (popup, result, href) => {
95
+ current = { popup, result, href };
96
+ const over = () => {
97
+ if (current && current.popup === popup) current = void 0;
98
+ };
99
+ result.then(over, over);
100
+ return result;
101
+ };
102
+ const byLink = (target) => {
103
+ location.assign(target.href);
104
+ return { status: "redirected" };
105
+ };
106
+ try {
107
+ const { url, successUrl } = o;
108
+ if (successUrl && !successTarget(successUrl, "")) throw new Error("checkout: successUrl is not a web address");
109
+ const success = (t) => successUrl ? successTarget(successUrl, t.id) : null;
110
+ const target = typeof url === "function" ? null : parseCheckoutUrl(url);
111
+ if (typeof url !== "function" && !target) throw refused();
112
+ if (current && !current.popup.closed && (!target || current.href === target.href)) {
113
+ current.popup.focus();
114
+ return current.result;
115
+ }
116
+ if (target) {
117
+ const popup2 = window.open(target.href, windowName(), FEATURES);
118
+ return popup2 ? track(popup2, wait(popup2, target, success(target)), target.href) : Promise.resolve(byLink(target));
119
+ }
120
+ const popup = window.open("", windowName(), FEATURES);
121
+ const result = new Promise((resolve) => resolve(url())).then(
122
+ (fetched) => {
123
+ const t = parseCheckoutUrl(fetched);
124
+ if (!t) {
125
+ popup?.close();
126
+ throw refused();
127
+ }
128
+ if (!popup) return byLink(t);
129
+ popup.location.href = t.href;
130
+ return wait(popup, t, success(t));
131
+ },
132
+ (e) => {
133
+ popup?.close();
134
+ throw e;
135
+ }
136
+ );
137
+ return popup ? track(popup, result) : result;
138
+ } catch (e) {
139
+ return Promise.reject(e);
140
+ }
141
+ }
142
+ var bound = false;
143
+ var reporting;
144
+ function bindCheckoutButtons() {
145
+ if (bound || typeof document === "undefined") return;
146
+ bound = true;
147
+ document.addEventListener("click", (e) => {
148
+ const el = e.target instanceof Element ? e.target.closest("[data-cd-checkout]") : null;
149
+ if (!el || e.defaultPrevented || e.button || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
150
+ const url = el.getAttribute("data-cd-checkout") || el.href;
151
+ const successUrl = el.getAttribute("data-cd-success");
152
+ if (!parseCheckoutUrl(url) || successUrl && !successTarget(successUrl, "")) return;
153
+ e.preventDefault();
154
+ const visit = checkout({ url, successUrl });
155
+ if (visit === reporting) return;
156
+ reporting = visit;
157
+ visit.then(
158
+ (detail) => void el.dispatchEvent(new CustomEvent("cd:checkout", { detail, bubbles: true })),
159
+ () => {
160
+ }
161
+ );
162
+ });
163
+ }
164
+ export {
165
+ bindCheckoutButtons,
166
+ checkout,
167
+ parseCheckoutUrl
168
+ };
package/dist/core.d.ts CHANGED
@@ -6,9 +6,10 @@ export declare function randomToken(): string;
6
6
  /**
7
7
  * Listens for messages of `type` from exactly `origin`, on window and on a
8
8
  * same-origin BroadcastChannel (which still works when a COOP header has
9
- * severed window.opener). Returns a function that stops listening.
9
+ * severed window.opener). With `from`, a window message counts only when
10
+ * that window sent it. Returns a function that stops listening.
10
11
  */
11
- export declare function listen(type: string, origin: string, onMessage: (data: Record<string, unknown>) => void, channel?: string): () => void;
12
+ export declare function listen(type: string, origin: string, onMessage: (data: Record<string, unknown>) => void, channel?: string, from?: Window | null): () => void;
12
13
  /** sessionStorage that never throws (private windows, blocked storage). */
13
14
  export declare const store: {
14
15
  get(key: string): string | null;
package/dist/index.d.ts CHANGED
@@ -4,3 +4,5 @@ export type { SignInOptions, SignInResult } from './signin';
4
4
  export { defineSignInButton } from './button';
5
5
  export { defineEmbedElements, embedOriginFor } from './embed';
6
6
  export type { EmbedOptions } from './embed';
7
+ export { bindCheckoutButtons, checkout, parseCheckoutUrl } from './checkout';
8
+ export type { CheckoutOptions, CheckoutResult, CheckoutTarget } from './checkout';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/core.ts
2
- var VERSION = true ? "0.2.0" : "0.0.0";
2
+ var VERSION = true ? "0.3.1" : "0.0.0";
3
3
  function b64url(bytes) {
4
4
  let s = "";
5
5
  for (const b of bytes) s += String.fromCharCode(b);
@@ -8,7 +8,7 @@ function b64url(bytes) {
8
8
  function randomToken() {
9
9
  return b64url(crypto.getRandomValues(new Uint8Array(32)));
10
10
  }
11
- function listen(type, origin, onMessage, channel) {
11
+ function listen(type, origin, onMessage, channel, from) {
12
12
  const accept = (data) => {
13
13
  if (data && typeof data === "object" && data.type === type) {
14
14
  try {
@@ -18,7 +18,7 @@ function listen(type, origin, onMessage, channel) {
18
18
  }
19
19
  };
20
20
  const onWindow = (e) => {
21
- if (e.origin === origin) accept(e.data);
21
+ if (e.origin === origin && (!from || e.source === from)) accept(e.data);
22
22
  };
23
23
  window.addEventListener("message", onWindow);
24
24
  let bc = null;
@@ -116,7 +116,7 @@ function signIn(o) {
116
116
  else if (m.iss !== issuer) reject(new Error("the answer came from another issuer"));
117
117
  else if (typeof m.code !== "string" || !m.code) reject(new Error("the answer carried no code"));
118
118
  else resolve(result(p, m.code));
119
- }, POPUP_NAME);
119
+ }, POPUP_NAME, popup);
120
120
  o.signal?.addEventListener("abort", () => {
121
121
  stop();
122
122
  reject(new Error("aborted"));
@@ -397,14 +397,171 @@ function defineEmbedElements(options = {}) {
397
397
  } catch {
398
398
  }
399
399
  }
400
+
401
+ // src/checkout.ts
402
+ var ORIGINS = ["https://chaindaddy.io", "https://staging.chaindaddy.io"];
403
+ var windowName = () => "cd-checkout-" + randomToken();
404
+ var FEATURES = "popup,width=480,height=820";
405
+ var TICK_MS = 300;
406
+ var QUIET_TICKS = 7;
407
+ function parseCheckoutUrl(url) {
408
+ if (typeof url !== "string") return null;
409
+ let u;
410
+ try {
411
+ u = new URL(url);
412
+ } catch {
413
+ return null;
414
+ }
415
+ const m = /^\/_pay\/([\w-]+)$/.exec(u.pathname);
416
+ return m && ORIGINS.includes(u.origin) && !u.username && !u.password ? { href: u.origin + u.pathname, origin: u.origin, id: m[1] } : null;
417
+ }
418
+ function successTarget(successUrl, id) {
419
+ if (typeof successUrl !== "string") return null;
420
+ let u;
421
+ try {
422
+ u = new URL(successUrl, location.href);
423
+ } catch {
424
+ return null;
425
+ }
426
+ if (!/^https?:$/.test(u.protocol) || u.username || u.password) return null;
427
+ const key = (pair) => {
428
+ const raw = pair.split("=")[0];
429
+ try {
430
+ return decodeURIComponent(raw.replace(/\+/g, " "));
431
+ } catch {
432
+ return raw;
433
+ }
434
+ };
435
+ const pairs = u.search.slice(1).split("&").filter((pair) => pair && key(pair) !== "session_id");
436
+ pairs.push("session_id=" + encodeURIComponent(id));
437
+ return u.origin + u.pathname + "?" + pairs.join("&") + u.hash;
438
+ }
439
+ var current;
440
+ function wait(popup, target, success) {
441
+ return new Promise((resolve) => {
442
+ let ready = false;
443
+ let quiet = 0;
444
+ let last;
445
+ const done = (result2) => {
446
+ clearInterval(timer);
447
+ clearTimeout(last);
448
+ window.removeEventListener("message", onMessage);
449
+ if (success && result2.status === "completed") location.assign(success);
450
+ resolve(result2);
451
+ };
452
+ const onMessage = (e) => {
453
+ if (e.origin !== target.origin || e.source !== popup) return;
454
+ const data = e.data;
455
+ if (!data || typeof data !== "object") return;
456
+ if (data.type === "cd:checkout.ready") ready = true;
457
+ const s = data.session;
458
+ if (!s || typeof s !== "object" || s.id !== target.id) return;
459
+ if (data.type === "cd:checkout.cancelled") done({ status: "cancelled", session: { id: target.id } });
460
+ else if (data.type === "cd:checkout.completed" && (s.status === "complete" || s.status === "paid")) {
461
+ const session = { id: target.id, status: s.status };
462
+ if (typeof s.orderId === "string" && /^[\w-]{1,128}$/.test(s.orderId)) session.orderId = s.orderId;
463
+ done({ status: "completed", session });
464
+ }
465
+ };
466
+ const hello = () => {
467
+ try {
468
+ popup.postMessage({ type: "cd:checkout.hello" }, target.origin);
469
+ } catch {
470
+ }
471
+ };
472
+ const timer = setInterval(() => {
473
+ if (popup.closed) {
474
+ clearInterval(timer);
475
+ last = setTimeout(() => done({ status: "closed" }), TICK_MS);
476
+ } else if (!ready || ++quiet % QUIET_TICKS === 0) hello();
477
+ }, TICK_MS);
478
+ window.addEventListener("message", onMessage);
479
+ hello();
480
+ });
481
+ }
482
+ function checkout(o) {
483
+ const refused = () => new Error("checkout: url is not a Chain Daddy checkout session link");
484
+ const track = (popup, result2, href) => {
485
+ current = { popup, result: result2, href };
486
+ const over = () => {
487
+ if (current && current.popup === popup) current = void 0;
488
+ };
489
+ result2.then(over, over);
490
+ return result2;
491
+ };
492
+ const byLink = (target) => {
493
+ location.assign(target.href);
494
+ return { status: "redirected" };
495
+ };
496
+ try {
497
+ const { url, successUrl } = o;
498
+ if (successUrl && !successTarget(successUrl, "")) throw new Error("checkout: successUrl is not a web address");
499
+ const success = (t) => successUrl ? successTarget(successUrl, t.id) : null;
500
+ const target = typeof url === "function" ? null : parseCheckoutUrl(url);
501
+ 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;
505
+ }
506
+ if (target) {
507
+ const popup2 = window.open(target.href, windowName(), FEATURES);
508
+ return popup2 ? track(popup2, wait(popup2, target, success(target)), target.href) : Promise.resolve(byLink(target));
509
+ }
510
+ const popup = window.open("", windowName(), FEATURES);
511
+ const result2 = new Promise((resolve) => resolve(url())).then(
512
+ (fetched) => {
513
+ const t = parseCheckoutUrl(fetched);
514
+ if (!t) {
515
+ popup?.close();
516
+ throw refused();
517
+ }
518
+ if (!popup) return byLink(t);
519
+ popup.location.href = t.href;
520
+ return wait(popup, t, success(t));
521
+ },
522
+ (e) => {
523
+ popup?.close();
524
+ throw e;
525
+ }
526
+ );
527
+ return popup ? track(popup, result2) : result2;
528
+ } catch (e) {
529
+ return Promise.reject(e);
530
+ }
531
+ }
532
+ var bound = false;
533
+ var reporting;
534
+ function bindCheckoutButtons() {
535
+ if (bound || typeof document === "undefined") return;
536
+ bound = true;
537
+ document.addEventListener("click", (e) => {
538
+ const el = e.target instanceof Element ? e.target.closest("[data-cd-checkout]") : null;
539
+ if (!el || e.defaultPrevented || e.button || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
540
+ const url = el.getAttribute("data-cd-checkout") || el.href;
541
+ const successUrl = el.getAttribute("data-cd-success");
542
+ if (!parseCheckoutUrl(url) || successUrl && !successTarget(successUrl, "")) return;
543
+ e.preventDefault();
544
+ const visit = checkout({ url, successUrl });
545
+ if (visit === reporting) return;
546
+ reporting = visit;
547
+ visit.then(
548
+ (detail) => void el.dispatchEvent(new CustomEvent("cd:checkout", { detail, bubbles: true })),
549
+ () => {
550
+ }
551
+ );
552
+ });
553
+ }
400
554
  export {
401
555
  DEFAULT_ISSUER,
402
556
  VERSION,
557
+ bindCheckoutButtons,
403
558
  challengeFor,
559
+ checkout,
404
560
  defineEmbedElements,
405
561
  defineSignInButton,
406
562
  embedOriginFor,
407
563
  exchangeCode,
408
564
  handleCallback,
565
+ parseCheckoutUrl,
409
566
  signIn
410
567
  };
package/dist/signin.js CHANGED
@@ -7,7 +7,7 @@ function b64url(bytes) {
7
7
  function randomToken() {
8
8
  return b64url(crypto.getRandomValues(new Uint8Array(32)));
9
9
  }
10
- function listen(type, origin, onMessage, channel) {
10
+ function listen(type, origin, onMessage, channel, from) {
11
11
  const accept = (data) => {
12
12
  if (data && typeof data === "object" && data.type === type) {
13
13
  try {
@@ -17,7 +17,7 @@ function listen(type, origin, onMessage, channel) {
17
17
  }
18
18
  };
19
19
  const onWindow = (e) => {
20
- if (e.origin === origin) accept(e.data);
20
+ if (e.origin === origin && (!from || e.source === from)) accept(e.data);
21
21
  };
22
22
  window.addEventListener("message", onWindow);
23
23
  let bc = null;
@@ -115,7 +115,7 @@ function signIn(o) {
115
115
  else if (m.iss !== issuer) reject(new Error("the answer came from another issuer"));
116
116
  else if (typeof m.code !== "string" || !m.code) reject(new Error("the answer carried no code"));
117
117
  else resolve(result(p, m.code));
118
- }, POPUP_NAME);
118
+ }, POPUP_NAME, popup);
119
119
  o.signal?.addEventListener("abort", () => {
120
120
  stop();
121
121
  reject(new Error("aborted"));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chaindaddy/js",
3
- "version": "0.2.0",
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>), and a server-side check that a store order was paid on chain, with checkout to follow",
3
+ "version": "0.3.1",
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": {
7
7
  "type": "git",
@@ -30,6 +30,10 @@
30
30
  "types": "./dist/embed.d.ts",
31
31
  "default": "./dist/embed.js"
32
32
  },
33
+ "./checkout": {
34
+ "types": "./dist/checkout.d.ts",
35
+ "default": "./dist/checkout.js"
36
+ },
33
37
  "./verify": {
34
38
  "types": "./dist/verify.d.ts",
35
39
  "default": "./dist/verify.js"
@@ -56,6 +60,7 @@
56
60
  "devDependencies": {
57
61
  "esbuild": "^0.25.0",
58
62
  "jsdom": "^26.0.0",
63
+ "openapi-typescript": "7.13.0",
59
64
  "typescript": "^5.7.0",
60
65
  "vitest": "^3.2.0"
61
66
  },