@chaindaddy/js 0.3.2 → 0.3.4

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
@@ -987,16 +987,18 @@ export interface paths {
987
987
  * goes are the item's own, exactly as when it is bought on chaindaddy.io: a session changes none of them.
988
988
  *
989
989
  * `successUrl` and `cancelUrl` must be `https` pages on one of the coin's verified sites, or a subdomain of
990
- * one (400 `RETURN_URL_NOT_VERIFIED`, which lists the sites that pass). The wallet that opened the store
991
- * must have signed the Seller Terms (403 `SELLER_TERMS_REQUIRED`).
990
+ * one (400 `RETURN_URL_NOT_VERIFIED`, which lists the sites that pass). A test store's key
991
+ * (`cd_iap_test_…`) may also use `http://localhost` or `http://127.0.0.1`, on any port: test mode works on
992
+ * localhost, live needs a verified site. The wallet that opened the store must have signed the Seller
993
+ * Terms (403 `SELLER_TERMS_REQUIRED`).
992
994
  *
993
995
  * An item of an app installed on the coin's page (its `sku` starts with `app.`) is sold through a session
994
996
  * only when the app's developer has allowed that item to be sold outside the app; otherwise the call answers
995
997
  * 409 `APP_ITEM_NOT_SOLD_HERE`, with a `reason`. The sale pays the same wallets as inside the app: the page,
996
998
  * the fee and the developer's share. Such a session takes at most the app's per-order quantity.
997
999
  *
998
- * Send an `Idempotency-Key`. A retry of the same request returns the session it opened, with status 200
999
- * and the header `Idempotent-Replayed: true`.
1000
+ * An `Idempotency-Key` is required. A retry of the same request returns the session it opened, with
1001
+ * status 200 and the header `Idempotent-Replayed: true`.
1000
1002
  *
1001
1003
  * Learn that the session was paid from the `iap.order.confirmed` webhook (its payload carries
1002
1004
  * `checkoutSessionId`), or by reading the session. Never from the buyer arriving on `successUrl`.
@@ -1054,12 +1056,13 @@ export interface paths {
1054
1056
  * needs no credential (the session id in the buyer's link is enough) and never carries the session's
1055
1057
  * `metadata` or `clientReferenceId`. The answer is never cached.
1056
1058
  *
1057
- * When the item belongs to an app installed on the coin's page, `sku` names the app (`appId`) and carries
1058
- * nothing else of the app's: no `appBps`, `appTermsVersion`, `appItemKey` or `appRole`, and an empty
1059
- * `metadata`.
1059
+ * When the item belongs to an app installed on the coin's page, `sku` names the app (`appId`) and the
1060
+ * share of the price its developer gets (`appBps`, basis points of the price after the fee), and carries
1061
+ * nothing else of the app's: no `appTermsVersion`, `appItemKey` or `appRole`, and an empty `metadata`.
1060
1062
  *
1061
1063
  * Pass `origin` to learn whether a page on that origin may open this checkout in a popup
1062
- * (`openerAllowed`): it must be `https` on one of the coin's verified sites.
1064
+ * (`openerAllowed`): it must be `https` on one of the coin's verified sites. A test store's session
1065
+ * (`livemode: false`) may also be opened from `http://localhost` or `http://127.0.0.1`, on any port.
1063
1066
  *
1064
1067
  * `returnAllowed` says whether the buyer may still be sent to `successUrl` and `cancelUrl`. It is checked
1065
1068
  * again on every read, so a coin whose site stopped being verified after the session was opened no longer
@@ -1074,6 +1077,48 @@ export interface paths {
1074
1077
  patch?: never;
1075
1078
  trace?: never;
1076
1079
  };
1080
+ "/api/v2/iap/checkout/sessions/{sessionId}/test-pay": {
1081
+ parameters: {
1082
+ query?: never;
1083
+ header?: never;
1084
+ path: {
1085
+ /** @description The checkout session's id, `cs_` followed by 48 hex characters */
1086
+ sessionId: components["parameters"]["IapCheckoutSessionId"];
1087
+ };
1088
+ cookie?: never;
1089
+ };
1090
+ get?: never;
1091
+ put?: never;
1092
+ /**
1093
+ * Complete a test checkout session
1094
+ * @description Completes a test store's checkout session from your own server or terminal, with no buyer signed in
1095
+ * and no wallet: the whole loop (open a session, pay it, get the webhook, read it `complete`) with
1096
+ * nothing but a test store's key. It makes the session's order for `wallet` and pays it as
1097
+ * `POST /api/v2/iap/orders/{orderId}/test-pay` does: the items are granted, the receipt is signed and the
1098
+ * test store's webhook gets `iap.order.confirmed` with `checkoutSessionId` and `"livemode": false`. No
1099
+ * chain is read or written and no money moves. A pay page left open on the session notices within a
1100
+ * few seconds and takes the buyer back.
1101
+ *
1102
+ * Call it with a key of the session's test store (`cd_iap_test_…`, scope `orders:write`). `wallet` is the
1103
+ * wallet the test items go to: required unless the session names one, and then it must be that wallet
1104
+ * (403 `CHECKOUT_SESSION_WALLET`). The answer is the session as `GET` returns it: `status: complete`,
1105
+ * the order and its receipt.
1106
+ *
1107
+ * `Idempotency-Key` is required. A retry of the same request returns the same order, with the header
1108
+ * `Idempotent-Replayed: true`; the key used for a different session or wallet is a 422.
1109
+ *
1110
+ * Test stores only. A live store's session is only ever paid by the buyer on chaindaddy.io: a live
1111
+ * store's key gets 409 `LIVE_ORDER` and nothing is created. A key of any other store gets 404. An item
1112
+ * of an installed app cannot be bought with a store key (403 `SESSION_REQUIRED`); pay that session on
1113
+ * its page. Guide: https://docs.chaindaddy.io/developer/take-payments-on-your-site#test-mode
1114
+ */
1115
+ post: operations["testPayIapCheckoutSession"];
1116
+ delete?: never;
1117
+ options?: never;
1118
+ head?: never;
1119
+ patch?: never;
1120
+ trace?: never;
1121
+ };
1077
1122
  "/api/v2/webhooks": {
1078
1123
  parameters: {
1079
1124
  query?: never;
@@ -2228,7 +2273,8 @@ export interface components {
2228
2273
  /** @description Whether a page on the `origin` passed may open this checkout in a popup. False when no `origin` is passed */
2229
2274
  openerAllowed?: boolean;
2230
2275
  /**
2231
- * @description Whether `successUrl` and `cancelUrl` are both still on the coin's verified sites, as of this read.
2276
+ * @description Whether `successUrl` and `cancelUrl` are both still on the coin's verified sites, as of this read
2277
+ * (for a test store's session, a URL on `http://localhost` or `http://127.0.0.1` also counts).
2232
2278
  * When false the buyer is not sent to either
2233
2279
  */
2234
2280
  returnAllowed?: boolean;
@@ -2805,7 +2851,7 @@ export interface components {
2805
2851
  "application/json": components["schemas"]["IapError"];
2806
2852
  };
2807
2853
  };
2808
- /** @description No wallet session */
2854
+ /** @description `AUTHENTICATION_REQUIRED`: no credential at all. Sign in, or send the credential the operation names */
2809
2855
  IapUnauthorized: {
2810
2856
  headers: {
2811
2857
  [name: string]: unknown;
@@ -2813,8 +2859,8 @@ export interface components {
2813
2859
  content: {
2814
2860
  /**
2815
2861
  * @example {
2816
- * "error": "sign in first",
2817
- * "code": "UNAUTHORIZED"
2862
+ * "error": "authentication required",
2863
+ * "code": "AUTHENTICATION_REQUIRED"
2818
2864
  * }
2819
2865
  */
2820
2866
  "application/json": components["schemas"]["IapError"];
@@ -4873,7 +4919,8 @@ export interface operations {
4873
4919
  appAccountToken?: string;
4874
4920
  /**
4875
4921
  * @description The checkout session the buyer is paying. A buyer's session only: a store key gets 403
4876
- * `SESSION_REQUIRED`. 409 `CHECKOUT_SESSION_EXPIRED` or `CHECKOUT_SESSION_COMPLETE` when the
4922
+ * `SESSION_REQUIRED` (a test store's key completes its own test session with
4923
+ * `POST /api/v2/iap/checkout/sessions/{sessionId}/test-pay` instead). 409 `CHECKOUT_SESSION_EXPIRED` or `CHECKOUT_SESSION_COMPLETE` when the
4877
4924
  * session can no longer be paid; 403 `CHECKOUT_SESSION_WALLET` when it names another wallet.
4878
4925
  * A session takes one buyer at a time: 409 `CHECKOUT_SESSION_IN_USE` while another wallet has
4879
4926
  * an order for it that can still be paid, or is paid and being confirmed
@@ -4899,9 +4946,24 @@ export interface operations {
4899
4946
  };
4900
4947
  };
4901
4948
  400: components["responses"]["IapBadRequest"];
4949
+ /**
4950
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all. The buyer signs in, or your server sends a store key
4951
+ * (`Authorization: Bearer cd_iap_…`); an order for a checkout session is the signed-in buyer's only.
4952
+ * `INVALID_STORE_KEY`: the key is unknown or revoked
4953
+ */
4954
+ 401: {
4955
+ headers: {
4956
+ [name: string]: unknown;
4957
+ };
4958
+ content: {
4959
+ "application/json": components["schemas"]["IapError"];
4960
+ };
4961
+ };
4902
4962
  /**
4903
4963
  * @description Not allowed, the purchase rule is not met, or the store is not taking orders. `APP_ITEM_NOT_YOURS`: an
4904
- * app asked for another app's item, or an app's item was asked for outside its app
4964
+ * app asked for another app's item, or an app's item was asked for outside its app. For a checkout
4965
+ * session: `SESSION_REQUIRED` (a store key tried to pay it) or `CHECKOUT_SESSION_WALLET` (the session
4966
+ * names another wallet)
4905
4967
  */
4906
4968
  403: {
4907
4969
  headers: {
@@ -4911,12 +4973,26 @@ export interface operations {
4911
4973
  "application/json": components["schemas"]["IapError"];
4912
4974
  };
4913
4975
  };
4976
+ /**
4977
+ * @description `NOT_FOUND`: no such store, or no such checkout session. `SKU_NOT_FOR_SALE`: no such item in the store.
4978
+ * `CHECKOUT_SESSIONS_DISABLED`: the order names a checkout session where sessions are not switched on
4979
+ */
4980
+ 404: {
4981
+ headers: {
4982
+ [name: string]: unknown;
4983
+ };
4984
+ content: {
4985
+ "application/json": components["schemas"]["IapError"];
4986
+ };
4987
+ };
4914
4988
  /**
4915
4989
  * @description Sold out, or the wallet has reached the SKU's per-wallet limit. For an app's item: `APP_TERMS_CHANGED`
4916
4990
  * (its developer changed its terms and the page's owner has not accepted them yet), `APP_NOT_INSTALLED`,
4917
4991
  * `APP_NOT_SELLING`, `APP_ORDER_LIMIT` (above the per-purchase limit) or `APP_DAILY_LIMIT` (the wallet
4918
4992
  * reached what it may pay that app in 24 hours). For an app's item paid through a checkout session:
4919
- * `APP_ITEM_NOT_SOLD_HERE`, with a `reason`
4993
+ * `APP_ITEM_NOT_SOLD_HERE`, with a `reason`. For any checkout session: `CHECKOUT_SESSION_EXPIRED`,
4994
+ * `CHECKOUT_SESSION_COMPLETE` (already paid) or `CHECKOUT_SESSION_IN_USE` (another wallet is paying it
4995
+ * right now)
4920
4996
  */
4921
4997
  409: {
4922
4998
  headers: {
@@ -5077,7 +5153,9 @@ export interface operations {
5077
5153
  }[];
5078
5154
  /**
5079
5155
  * Format: uri
5080
- * @description Where the buyer returns after paying. `https`, on one of the coin's verified sites
5156
+ * @description Where the buyer returns after paying. `https`, on one of the coin's verified sites or a
5157
+ * subdomain of one. With a test store's key it may also be `http://localhost` or
5158
+ * `http://127.0.0.1`, on any port
5081
5159
  */
5082
5160
  successUrl: string;
5083
5161
  /**
@@ -5125,8 +5203,10 @@ export interface operations {
5125
5203
  };
5126
5204
  };
5127
5205
  /**
5128
- * @description `INVALID_REQUEST`, `IDEMPOTENCY_KEY_REQUIRED`, or `RETURN_URL_NOT_VERIFIED`: a return URL is not an
5129
- * `https` page on one of the coin's verified sites
5206
+ * @description `INVALID_REQUEST`, `IDEMPOTENCY_KEY_REQUIRED`, `IDEMPOTENCY_KEY_INVALID` (a key longer than 255
5207
+ * characters), or `RETURN_URL_NOT_VERIFIED`: a return URL is not an
5208
+ * `https` page on one of the coin's verified sites (nor, with a test store's key, `http://localhost` or
5209
+ * `http://127.0.0.1`). A live store's key is always refused a localhost URL
5130
5210
  */
5131
5211
  400: {
5132
5212
  headers: {
@@ -5136,7 +5216,11 @@ export interface operations {
5136
5216
  "application/json": components["schemas"]["IapError"];
5137
5217
  };
5138
5218
  };
5139
- /** @description No store key, or one that is unknown or revoked (`INVALID_STORE_KEY`) */
5219
+ /**
5220
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all; send the store key as
5221
+ * `Authorization: Bearer cd_iap_…`. `INVALID_STORE_KEY`: the key is unknown or revoked.
5222
+ * `TEST_MODE_DISABLED`: a test key where test mode is not available
5223
+ */
5140
5224
  401: {
5141
5225
  headers: {
5142
5226
  [name: string]: unknown;
@@ -5217,7 +5301,11 @@ export interface operations {
5217
5301
  "application/json": components["schemas"]["IapCheckoutSession"];
5218
5302
  };
5219
5303
  };
5220
- /** @description No store key, or one that is unknown or revoked */
5304
+ /**
5305
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all; send the store key as
5306
+ * `Authorization: Bearer cd_iap_…`. `INVALID_STORE_KEY`: the key is unknown or revoked.
5307
+ * `TEST_MODE_DISABLED`: a test key where test mode is not available
5308
+ */
5221
5309
  401: {
5222
5310
  headers: {
5223
5311
  [name: string]: unknown;
@@ -5235,13 +5323,24 @@ export interface operations {
5235
5323
  "application/json": components["schemas"]["IapError"];
5236
5324
  };
5237
5325
  };
5238
- 404: components["responses"]["IapNotFound"];
5326
+ /**
5327
+ * @description `NOT_FOUND`: no such session, or it belongs to another store. `CHECKOUT_SESSIONS_DISABLED`: checkout
5328
+ * sessions are not switched on here, or are open to test stores only and the key is a live store's
5329
+ */
5330
+ 404: {
5331
+ headers: {
5332
+ [name: string]: unknown;
5333
+ };
5334
+ content: {
5335
+ "application/json": components["schemas"]["IapError"];
5336
+ };
5337
+ };
5239
5338
  };
5240
5339
  };
5241
5340
  getIapPublicCheckoutSession: {
5242
5341
  parameters: {
5243
5342
  query?: {
5244
- /** @description The origin of the page that opened the checkout, e.g. `https://example.com` */
5343
+ /** @description The origin of the page that opened the checkout, e.g. `https://example.com` (or `http://localhost:3000` for a test store's session) */
5245
5344
  origin?: string;
5246
5345
  };
5247
5346
  header?: never;
@@ -5262,7 +5361,147 @@ export interface operations {
5262
5361
  "application/json": components["schemas"]["IapPublicCheckoutSession"];
5263
5362
  };
5264
5363
  };
5265
- 404: components["responses"]["IapNotFound"];
5364
+ /**
5365
+ * @description `NOT_FOUND`: no such session. `CHECKOUT_SESSIONS_DISABLED`: checkout sessions are not switched on
5366
+ * here, or are open to test stores only and the session is a live store's
5367
+ */
5368
+ 404: {
5369
+ headers: {
5370
+ [name: string]: unknown;
5371
+ };
5372
+ content: {
5373
+ "application/json": components["schemas"]["IapError"];
5374
+ };
5375
+ };
5376
+ };
5377
+ };
5378
+ testPayIapCheckoutSession: {
5379
+ parameters: {
5380
+ query?: never;
5381
+ header: {
5382
+ /**
5383
+ * @description Required: a call without it answers 400 `IDEMPOTENCY_KEY_REQUIRED`. Send one value per attempt (a UUID,
5384
+ * or an id of your own such as `cart-8842-attempt-1`), up to 255 characters. Retrying with the same key
5385
+ * and the same request returns the first result and never sells, grants or consumes twice.
5386
+ */
5387
+ "Idempotency-Key": components["parameters"]["IdempotencyKeyRequired"];
5388
+ };
5389
+ path: {
5390
+ /** @description The checkout session's id, `cs_` followed by 48 hex characters */
5391
+ sessionId: components["parameters"]["IapCheckoutSessionId"];
5392
+ };
5393
+ cookie?: never;
5394
+ };
5395
+ requestBody?: {
5396
+ content: {
5397
+ /**
5398
+ * @example {
5399
+ * "wallet": "0x00000000000000000000000000000000000000d4"
5400
+ * }
5401
+ */
5402
+ "application/json": {
5403
+ /**
5404
+ * @description The wallet the test items go to: an address on the store's chain. Required unless the
5405
+ * session names a wallet; when it does, leave this out or send the same wallet
5406
+ */
5407
+ wallet?: string;
5408
+ };
5409
+ };
5410
+ };
5411
+ responses: {
5412
+ /** @description The session, complete, with the order that paid it and its receipt */
5413
+ 200: {
5414
+ headers: {
5415
+ /** @description Present, and `true`, on a retry of a request already made under this `Idempotency-Key` */
5416
+ "Idempotent-Replayed"?: "true";
5417
+ [name: string]: unknown;
5418
+ };
5419
+ content: {
5420
+ "application/json": components["schemas"]["IapCheckoutSession"];
5421
+ };
5422
+ };
5423
+ /**
5424
+ * @description `IDEMPOTENCY_KEY_REQUIRED`, or `INVALID_REQUEST`: no `wallet` and the session names none, a `wallet`
5425
+ * that is not an address on the store's chain, or a body that is not the JSON above
5426
+ */
5427
+ 400: {
5428
+ headers: {
5429
+ [name: string]: unknown;
5430
+ };
5431
+ content: {
5432
+ "application/json": components["schemas"]["IapError"];
5433
+ };
5434
+ };
5435
+ /**
5436
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all; send the test store's key as
5437
+ * `Authorization: Bearer cd_iap_test_…`. `INVALID_STORE_KEY`: the key is unknown or revoked
5438
+ */
5439
+ 401: {
5440
+ headers: {
5441
+ [name: string]: unknown;
5442
+ };
5443
+ content: {
5444
+ "application/json": components["schemas"]["IapError"];
5445
+ };
5446
+ };
5447
+ /**
5448
+ * @description `STORE_KEY_REQUIRED`: called with a signed-in session instead of a store key. `SCOPE_REQUIRED`: the
5449
+ * key lacks `orders:write`. `CHECKOUT_SESSION_WALLET`: the session names another wallet.
5450
+ * `IAP_PLAN_REQUIRED` or `STORE_NOT_LIVE`: the store's plan lapsed or the store is paused.
5451
+ * `SESSION_REQUIRED`: the session's item belongs to an installed app
5452
+ */
5453
+ 403: {
5454
+ headers: {
5455
+ [name: string]: unknown;
5456
+ };
5457
+ content: {
5458
+ "application/json": components["schemas"]["IapError"];
5459
+ };
5460
+ };
5461
+ /**
5462
+ * @description `NOT_FOUND`: no such session, or it belongs to another store. `TEST_MODE_DISABLED`: test stores are
5463
+ * not available here. `CHECKOUT_SESSIONS_DISABLED`: checkout sessions are not switched on here
5464
+ */
5465
+ 404: {
5466
+ headers: {
5467
+ [name: string]: unknown;
5468
+ };
5469
+ content: {
5470
+ "application/json": components["schemas"]["IapError"];
5471
+ };
5472
+ };
5473
+ /**
5474
+ * @description `LIVE_ORDER`: the session belongs to a live store. `CHECKOUT_SESSION_COMPLETE`: it is already paid.
5475
+ * `CHECKOUT_SESSION_EXPIRED`: it expired; open a new one. `CHECKOUT_SESSION_IN_USE`: another wallet
5476
+ * has an unpaid order for it; use that wallet, or wait a few minutes past that order's deadline.
5477
+ * `ORDER_EXPIRED`: the order this `Idempotency-Key` made has lapsed; use a new key
5478
+ */
5479
+ 409: {
5480
+ headers: {
5481
+ [name: string]: unknown;
5482
+ };
5483
+ content: {
5484
+ /**
5485
+ * @example {
5486
+ * "error": "this is a live checkout session: it is paid by the buyer on chaindaddy.io, and test-pay pays a test store's sessions only",
5487
+ * "code": "LIVE_ORDER"
5488
+ * }
5489
+ */
5490
+ "application/json": components["schemas"]["IapError"];
5491
+ };
5492
+ };
5493
+ /**
5494
+ * @description `IDEMPOTENCY_KEY_REUSED`: this `Idempotency-Key` already made an order for a different session or
5495
+ * wallet. Nothing is returned or created; use a new key
5496
+ */
5497
+ 422: {
5498
+ headers: {
5499
+ [name: string]: unknown;
5500
+ };
5501
+ content: {
5502
+ "application/json": components["schemas"]["IapError"];
5503
+ };
5504
+ };
5266
5505
  };
5267
5506
  };
5268
5507
  listWebhooks: {
package/dist/cd.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";(()=>{var S="0.3.2";function k(t){let e="";for(let i of t)e+=String.fromCharCode(i);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function f(){return k(crypto.getRandomValues(new Uint8Array(32)))}function R(t,e,i,s,o){let u=n=>{if(n&&typeof n=="object"&&n.type===t)try{i(n)}catch{}},l=n=>{n.origin===e&&(!o||n.source===o)&&u(n.data)};window.addEventListener("message",l);let a=null;if(s&&typeof BroadcastChannel=="function"&&e===location.origin)try{a=new BroadcastChannel(s),a.onmessage=n=>u(n.data)}catch{a=null}return()=>{window.removeEventListener("message",l),a?.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 k(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,i){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)),`${i}?${s}`}function U(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(/\/$/,""),i=t.authorizeUrl??V[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: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 u=await K(t,s,i);if(!o)return location.assign(u),null;o.location.href=u;let l=new URL(t.redirectUri,location.href).origin;return new Promise((a,n)=>{let c=R(T,l,r=>{r.state===s.state&&(c(),m.remove(y),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(U(s,r.code)))},w,o);t.signal?.addEventListener("abort",()=>{c(),n(new Error("aborted"))})})})()}async function _(){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 i=m.get(y);m.remove(y);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 U(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})}),i=await e.json();if(!e.ok)throw new Error(`${i.error??e.status}: ${i.error_description??""}`.trim());return i}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.4";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 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>${Y}</style><button type="button" part="button" data-theme="${s}">${W}<span></span></button>`;let o=this.root.querySelector("button");o.querySelector("span").textContent=i,o.addEventListener("click",()=>this.start())}start(){let i=s=>this.getAttribute(s)||void 0;b({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 M="https://embed.chaindaddy.io",v="https://embed-staging.chaindaddy.io";var J="1500px 0px",X="docs.chaindaddy.io/token/apps-on-your-site#if-the-link-stays";var Q=["--cd-accent","--cd-surface","--cd-text","--cd-text-muted","--cd-border"];function O(t){try{if(t&&new URL(t).hostname==="js.staging.chaindaddy.io")return v}catch{}return M}var Z=`
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
- `,tt=`
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 P(t={}){try{if(typeof customElements>"u"||typeof HTMLElement>"u")return;let e=t.origin===v?v:M;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(),r=(this.getAttribute("app")??"").trim();if(!/^[0-9]{1,18}$/.test(n)||!c||this.kind==="app"&&!r)return null;let d=new URLSearchParams({crown:n,chain:c});return this.kind==="app"?d.set("app",r):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>${Z}</style><slot></slot>`;let r=document.createElement("iframe");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"),n.appendChild(r),this.frame=r,window.addEventListener("message",this.onMessage);let d=()=>{r.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:J}),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===v?"staging-api":"api"}.chaindaddy.io/api/v2/embed/status?${n}`,{credentials:"omit"}).then(r=>r.json()).then(r=>{let d=r.embeddable?r.interactive?"not a verified site of the coin":"not https, or the app did not start":r.reason;console.info(`[Chain Daddy] cd-${this.kind} ${c??""} cannot show here: ${d}. ${X}`)}).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 r=Number(c.height);Number.isFinite(r)&&(this.frame.style.height=`${Math.max(1,Math.min(4e3,Math.ceil(r)))}px`)}else c.type==="cd:unavailable"&&this.unavailable()}catch{}}sendTheme(){let n={},c=getComputedStyle(this);for(let r of Q){let d=c.getPropertyValue(r).trim();d&&(n[r]=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 s extends i{}class o 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>${tt}</style><button type="button" part="button"></button>`;let r=c.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[l,a]of[["cd-app",s],["cd-coin",o],["cd-buy",u]])customElements.get(l)||customElements.define(l,a)}catch{}}var et=["https://chaindaddy.io","https://staging.chaindaddy.io"],$=()=>"cd-checkout-"+f(),D="popup,width=480,height=820",H=300,nt=7;function E(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&&et.includes(e.origin)&&!e.username&&!e.password?{href:e.origin+e.pathname,origin:e.origin,id:i[1]}:null}function C(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 s=u=>{let l=u.split("=")[0];try{return decodeURIComponent(l.replace(/\+/g," "))}catch{return l}},o=i.search.slice(1).split("&").filter(u=>u&&s(u)!=="session_id");return o.push("session_id="+encodeURIComponent(e)),i.origin+i.pathname+"?"+o.join("&")+i.hash}var p;function N(t,e,i){return new Promise(s=>{let o=!1,u=0,l,a=d=>{clearInterval(r),clearTimeout(l),window.removeEventListener("message",n),i&&d.status==="completed"&&location.assign(i),s(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"&&(o=!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 x={id:e.id,status:h.status};typeof h.orderId=="string"&&/^[\w-]{1,128}$/.test(h.orderId)&&(x.orderId=h.orderId),a({status:"completed",session:x})}}},c=()=>{try{t.postMessage({type:"cd:checkout.hello"},e.origin)}catch{}},r=setInterval(()=>{t.closed?(clearInterval(r),l=setTimeout(()=>a({status:"closed"}),H)):(!o||++u%nt===0)&&c()},H);window.addEventListener("message",n),c()})}function I(t){let e=()=>new Error("checkout: url is not a Chain Daddy checkout session link"),i=(o,u,l)=>{p={popup:o,result:u,href:l};let a=()=>{p&&p.popup===o&&(p=void 0)};return u.then(a,a),u},s=o=>(location.assign(o.href),{status:"redirected"});try{let{url:o,successUrl:u}=t;if(u&&!C(u,""))throw new Error("checkout: successUrl is not a web address");let l=r=>u?C(u,r.id):null,a=typeof o=="function"?null:E(o);if(typeof o!="function"&&!a)throw e();if(p&&!p.popup.closed&&(!a||p.href===a.href))return p.popup.focus(),p.result;if(a){let r=window.open(a.href,$(),D);return r?i(r,N(r,a,l(a)),a.href):Promise.resolve(s(a))}let n=window.open("",$(),D),c=new Promise(r=>r(o())).then(r=>{let d=E(r);if(!d)throw n?.close(),e();return n?(n.location.href=d.href,N(n,d,l(d))):s(d)},r=>{throw n?.close(),r});return n?i(n,c):c}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 i=e.getAttribute("data-cd-checkout")||e.href,s=e.getAttribute("data-cd-success");if(!E(i)||s&&!C(s,""))return;t.preventDefault();let o=I({url:i,successUrl:s});o!==z&&(z=o,o.then(u=>void e.dispatchEvent(new CustomEvent("cd:checkout",{detail:u,bubbles:!0})),()=>{}))}))}var it={version:S,signIn:b,handleCallback:_,exchangeCode:A,checkout:I};window.ChainDaddy=Object.assign(window.ChainDaddy??{},it);L();P({origin:O(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),typeof h.livemode=="boolean"&&(p.livemode=h.livemode),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-fJEKHZn2Glw6r/O/phO2IcSENkiPJsnY5wmjK3psuPFMg86hzPzk7/Mvq07tNiGP
1
+ sha384-Skwsv/FQCtenMYT3zex2SCnlERwJPOsytod8T05hRGAb8yUoTlD/nF2tx+0d590b
@@ -13,13 +13,17 @@ export interface CheckoutOptions {
13
13
  successUrl?: string | null;
14
14
  }
15
15
  export type CheckoutResult =
16
- /** The buyer paid. `session.status` is "complete" (confirmed) or "paid" (sent, still confirming). */
16
+ /**
17
+ * The buyer paid. `session.status` is "complete" (confirmed) or "paid" (sent, still confirming).
18
+ * `session.livemode` is false for a test store's session, where nothing was charged.
19
+ */
17
20
  {
18
21
  status: 'completed';
19
22
  session: {
20
23
  id: string;
21
24
  status: 'complete' | 'paid';
22
25
  orderId?: string;
26
+ livemode?: boolean;
23
27
  };
24
28
  }
25
29
  /** The buyer left the checkout without paying. */
package/dist/checkout.js CHANGED
@@ -70,6 +70,7 @@ function wait(popup, target, success) {
70
70
  else if (data.type === "cd:checkout.completed" && (s.status === "complete" || s.status === "paid")) {
71
71
  const session = { id: target.id, status: s.status };
72
72
  if (typeof s.orderId === "string" && /^[\w-]{1,128}$/.test(s.orderId)) session.orderId = s.orderId;
73
+ if (typeof s.livemode === "boolean") session.livemode = s.livemode;
73
74
  done({ status: "completed", session });
74
75
  }
75
76
  };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/core.ts
2
- var VERSION = true ? "0.3.2" : "0.0.0";
2
+ var VERSION = true ? "0.3.4" : "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
  );
@@ -433,7 +464,7 @@ function defineEmbedElements(options = {}) {
433
464
  var ORIGINS = ["https://chaindaddy.io", "https://staging.chaindaddy.io"];
434
465
  var windowName = () => "cd-checkout-" + randomToken();
435
466
  var FEATURES = "popup,width=480,height=820";
436
- var TICK_MS = 300;
467
+ var TICK_MS2 = 300;
437
468
  var QUIET_TICKS = 7;
438
469
  function parseCheckoutUrl(url) {
439
470
  if (typeof url !== "string") return null;
@@ -467,7 +498,7 @@ function successTarget(successUrl, id) {
467
498
  pairs.push("session_id=" + encodeURIComponent(id));
468
499
  return u.origin + u.pathname + "?" + pairs.join("&") + u.hash;
469
500
  }
470
- var current;
501
+ var current2;
471
502
  function wait(popup, target, success) {
472
503
  return new Promise((resolve) => {
473
504
  let ready = false;
@@ -491,6 +522,7 @@ function wait(popup, target, success) {
491
522
  else if (data.type === "cd:checkout.completed" && (s.status === "complete" || s.status === "paid")) {
492
523
  const session = { id: target.id, status: s.status };
493
524
  if (typeof s.orderId === "string" && /^[\w-]{1,128}$/.test(s.orderId)) session.orderId = s.orderId;
525
+ if (typeof s.livemode === "boolean") session.livemode = s.livemode;
494
526
  done({ status: "completed", session });
495
527
  }
496
528
  };
@@ -503,9 +535,9 @@ function wait(popup, target, success) {
503
535
  const timer = setInterval(() => {
504
536
  if (popup.closed) {
505
537
  clearInterval(timer);
506
- last = setTimeout(() => done({ status: "closed" }), TICK_MS);
538
+ last = setTimeout(() => done({ status: "closed" }), TICK_MS2);
507
539
  } else if (!ready || ++quiet % QUIET_TICKS === 0) hello();
508
- }, TICK_MS);
540
+ }, TICK_MS2);
509
541
  window.addEventListener("message", onMessage);
510
542
  hello();
511
543
  });
@@ -513,9 +545,9 @@ function wait(popup, target, success) {
513
545
  function checkout(o) {
514
546
  const refused = () => new Error("checkout: url is not a Chain Daddy checkout session link");
515
547
  const track = (popup, result2, href) => {
516
- current = { popup, result: result2, href };
548
+ current2 = { popup, result: result2, href };
517
549
  const over = () => {
518
- if (current && current.popup === popup) current = void 0;
550
+ if (current2 && current2.popup === popup) current2 = void 0;
519
551
  };
520
552
  result2.then(over, over);
521
553
  return result2;
@@ -530,9 +562,9 @@ function checkout(o) {
530
562
  const success = (t) => successUrl ? successTarget(successUrl, t.id) : null;
531
563
  const target = typeof url === "function" ? null : parseCheckoutUrl(url);
532
564
  if (typeof url !== "function" && !target) throw refused();
533
- if (current && !current.popup.closed && (!target || current.href === target.href)) {
534
- current.popup.focus();
535
- return current.result;
565
+ if (current2 && !current2.popup.closed && (!target || current2.href === target.href)) {
566
+ current2.popup.focus();
567
+ return current2.result;
536
568
  }
537
569
  if (target) {
538
570
  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.2",
3
+ "version": "0.3.4",
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": {