@chaindaddy/js 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.d.ts CHANGED
@@ -4899,9 +4899,24 @@ export interface operations {
4899
4899
  };
4900
4900
  };
4901
4901
  400: components["responses"]["IapBadRequest"];
4902
+ /**
4903
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all. The buyer signs in, or your server sends a store key
4904
+ * (`Authorization: Bearer cd_iap_…`); an order for a checkout session is the signed-in buyer's only.
4905
+ * `INVALID_STORE_KEY`: the key is unknown or revoked
4906
+ */
4907
+ 401: {
4908
+ headers: {
4909
+ [name: string]: unknown;
4910
+ };
4911
+ content: {
4912
+ "application/json": components["schemas"]["IapError"];
4913
+ };
4914
+ };
4902
4915
  /**
4903
4916
  * @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
4917
+ * app asked for another app's item, or an app's item was asked for outside its app. For a checkout
4918
+ * session: `SESSION_REQUIRED` (a store key tried to pay it) or `CHECKOUT_SESSION_WALLET` (the session
4919
+ * names another wallet)
4905
4920
  */
4906
4921
  403: {
4907
4922
  headers: {
@@ -4911,12 +4926,26 @@ export interface operations {
4911
4926
  "application/json": components["schemas"]["IapError"];
4912
4927
  };
4913
4928
  };
4929
+ /**
4930
+ * @description `NOT_FOUND`: no such store, or no such checkout session. `SKU_NOT_FOR_SALE`: no such item in the store.
4931
+ * `CHECKOUT_SESSIONS_DISABLED`: the order names a checkout session where sessions are not switched on
4932
+ */
4933
+ 404: {
4934
+ headers: {
4935
+ [name: string]: unknown;
4936
+ };
4937
+ content: {
4938
+ "application/json": components["schemas"]["IapError"];
4939
+ };
4940
+ };
4914
4941
  /**
4915
4942
  * @description Sold out, or the wallet has reached the SKU's per-wallet limit. For an app's item: `APP_TERMS_CHANGED`
4916
4943
  * (its developer changed its terms and the page's owner has not accepted them yet), `APP_NOT_INSTALLED`,
4917
4944
  * `APP_NOT_SELLING`, `APP_ORDER_LIMIT` (above the per-purchase limit) or `APP_DAILY_LIMIT` (the wallet
4918
4945
  * 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`
4946
+ * `APP_ITEM_NOT_SOLD_HERE`, with a `reason`. For any checkout session: `CHECKOUT_SESSION_EXPIRED`,
4947
+ * `CHECKOUT_SESSION_COMPLETE` (already paid) or `CHECKOUT_SESSION_IN_USE` (another wallet is paying it
4948
+ * right now)
4920
4949
  */
4921
4950
  409: {
4922
4951
  headers: {
@@ -5136,7 +5165,11 @@ export interface operations {
5136
5165
  "application/json": components["schemas"]["IapError"];
5137
5166
  };
5138
5167
  };
5139
- /** @description No store key, or one that is unknown or revoked (`INVALID_STORE_KEY`) */
5168
+ /**
5169
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all; send the store key as
5170
+ * `Authorization: Bearer cd_iap_…`. `INVALID_STORE_KEY`: the key is unknown or revoked.
5171
+ * `TEST_MODE_DISABLED`: a test key where test mode is not available
5172
+ */
5140
5173
  401: {
5141
5174
  headers: {
5142
5175
  [name: string]: unknown;
@@ -5217,7 +5250,11 @@ export interface operations {
5217
5250
  "application/json": components["schemas"]["IapCheckoutSession"];
5218
5251
  };
5219
5252
  };
5220
- /** @description No store key, or one that is unknown or revoked */
5253
+ /**
5254
+ * @description `AUTHENTICATION_REQUIRED`: no credential at all; send the store key as
5255
+ * `Authorization: Bearer cd_iap_…`. `INVALID_STORE_KEY`: the key is unknown or revoked.
5256
+ * `TEST_MODE_DISABLED`: a test key where test mode is not available
5257
+ */
5221
5258
  401: {
5222
5259
  headers: {
5223
5260
  [name: string]: unknown;
@@ -5235,7 +5272,18 @@ export interface operations {
5235
5272
  "application/json": components["schemas"]["IapError"];
5236
5273
  };
5237
5274
  };
5238
- 404: components["responses"]["IapNotFound"];
5275
+ /**
5276
+ * @description `NOT_FOUND`: no such session, or it belongs to another store. `CHECKOUT_SESSIONS_DISABLED`: checkout
5277
+ * sessions are not switched on here, or are open to test stores only and the key is a live store's
5278
+ */
5279
+ 404: {
5280
+ headers: {
5281
+ [name: string]: unknown;
5282
+ };
5283
+ content: {
5284
+ "application/json": components["schemas"]["IapError"];
5285
+ };
5286
+ };
5239
5287
  };
5240
5288
  };
5241
5289
  getIapPublicCheckoutSession: {
@@ -5262,7 +5310,18 @@ export interface operations {
5262
5310
  "application/json": components["schemas"]["IapPublicCheckoutSession"];
5263
5311
  };
5264
5312
  };
5265
- 404: components["responses"]["IapNotFound"];
5313
+ /**
5314
+ * @description `NOT_FOUND`: no such session. `CHECKOUT_SESSIONS_DISABLED`: checkout sessions are not switched on
5315
+ * here, or are open to test stores only and the session is a live store's
5316
+ */
5317
+ 404: {
5318
+ headers: {
5319
+ [name: string]: unknown;
5320
+ };
5321
+ content: {
5322
+ "application/json": components["schemas"]["IapError"];
5323
+ };
5324
+ };
5266
5325
  };
5267
5326
  };
5268
5327
  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.3";function C(t){let e="";for(let i of t)e+=String.fromCharCode(i);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function w(){return C(crypto.getRandomValues(new Uint8Array(32)))}function T(t,e,i,o,r){let u=n=>{if(n&&typeof n=="object"&&n.type===t)try{i(n)}catch{}},l=n=>{n.origin===e&&(!r||n.source===r)&&u(n.data)};window.addEventListener("message",l);let a=null;if(o&&typeof BroadcastChannel=="function"&&e===location.origin)try{a=new BroadcastChannel(o),a.onmessage=n=>u(n.data)}catch{a=null}return()=>{window.removeEventListener("message",l),a?.close()}}var y={get(t){try{return sessionStorage.getItem(t)}catch{return null}},set(t,e){try{sessionStorage.setItem(t,e)}catch{}},remove(t){try{sessionStorage.removeItem(t)}catch{}}};var F="https://api.chaindaddy.io",G={"https://api.chaindaddy.io":"https://chaindaddy.io/_oauth/authorize","https://staging-api.chaindaddy.io":"https://staging.chaindaddy.io/_oauth/authorize"},b="cd-signin",U="cd:signin",v="cd.signin",_=300;async function K(t){let e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t));return C(new Uint8Array(e))}function W(t="openid"){let e=t.split(/\s+/).filter(Boolean);return(e.includes("openid")?e:["openid",...e]).join(" ")}async function Y(t,e,i){let o=new URLSearchParams({response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUri,scope:W(t.scope),state:e.state,nonce:e.nonce,code_challenge:await K(e.verifier),code_challenge_method:"S256"});return t.claims&&o.set("claims",typeof t.claims=="string"?t.claims:JSON.stringify(t.claims)),`${i}?${o}`}function A(t,e){return{code:e,codeVerifier:t.verifier,state:t.state,nonce:t.nonce,issuer:t.issuer,clientId:t.clientId,redirectUri:t.redirectUri}}var m;function k(t){let e=(t.issuer??F).replace(/\/$/,""),i=t.authorizeUrl??G[e];if(!i)return Promise.reject(new Error("authorizeUrl is required for this issuer"));if(!t.clientId||!t.redirectUri)return Promise.reject(new Error("clientId and redirectUri are required"));if(m&&!m.popup.closed)return m.popup.focus(),m.waiting;let o={state:w(),nonce:w(),verifier:w(),issuer:e,clientId:t.clientId,redirectUri:t.redirectUri};y.set(v,JSON.stringify(o));let r=window.open("",b,"popup,width=480,height=720"),u=(async()=>{let l=await Y(t,o,i);if(!r)return location.assign(l),null;r.location.href=l;let a=new URL(t.redirectUri,location.href).origin;return new Promise((n,c)=>{let s,d=()=>{g(),clearInterval(h),clearTimeout(s)},g=T(U,a,p=>{p.state===o.state&&(d(),y.remove(v),typeof p.error=="string"&&p.error?c(new Error(p.error)):p.iss!==e?c(new Error("the answer came from another issuer")):typeof p.code!="string"||!p.code?c(new Error("the answer carried no code")):n(A(o,p.code)))},b,r),h=setInterval(()=>{r.closed&&(clearInterval(h),s=setTimeout(()=>{d(),c(new Error("closed"))},_))},_);t.signal?.addEventListener("abort",()=>{d(),c(new Error("aborted"))})})})();if(r){m={popup:r,waiting:u};let l=()=>{m&&m.popup===r&&(m=void 0)};u.then(l,l)}return u}async function L(){let t=new URLSearchParams(location.search),e={type:U,code:t.get("code"),state:t.get("state"),iss:t.get("iss"),error:t.get("error")};if(window.name===b){try{window.opener?.postMessage(e,location.origin)}catch{}try{let r=new BroadcastChannel(b);r.postMessage(e),r.close()}catch{}return window.close(),null}let i=y.get(v);y.remove(v);let o=i?JSON.parse(i):null;if(!o||e.state!==o.state)throw new Error("the sign-in state does not match: start again");if(e.error)throw new Error(e.error);if(e.iss!==o.issuer)throw new Error("the answer came from another issuer");if(!e.code)throw new Error("the answer carried no code");return A(o,e.code)}async function M(t){let e=await fetch(`${t.issuer}/api/v2/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:t.code,redirect_uri:t.redirectUri,client_id:t.clientId,code_verifier:t.codeVerifier})}),i=await e.json();if(!e.ok)throw new Error(`${i.error??e.status}: ${i.error_description??""}`.trim());return i}var J='<svg viewBox="0 0 24 24" fill="#FFD700" width="20" height="20" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><path d="M5 16L3 5l5.5 5L12 4l3.5 6L21 5l-2 11H5z" stroke="#B8860B" stroke-width="0.5"/><path d="M5 18h14v2H5v-2z" stroke="#B8860B" stroke-width="0.5"/></svg>',X=`
2
2
  :host { display: inline-block; }
3
3
  button { display: inline-flex; align-items: center; justify-content: center; gap: 10px; box-sizing: border-box;
4
4
  min-height: 40px; padding: 0 16px; border-radius: 10px; font: 600 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
@@ -7,16 +7,16 @@ button[data-theme="dark"] { background: #101828; color: #ffffff; border-color: #
7
7
  button:focus-visible { outline: 2px solid #f59e0b; outline-offset: 2px; }
8
8
  button:disabled { opacity: .6; cursor: default; }
9
9
  svg { flex: none; }
10
- `;function L(){if(typeof customElements>"u"||customElements.get("cd-signin-button"))return;class t extends HTMLElement{constructor(){super(),this.root=this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(){this.isConnected&&this.render()}render(){let 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),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-x7XiOLUzANtLnqFmv/hWd2uhGI7c+jlhVivakZ7LYHklw9zgK/diic0+fJom/mXU
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.3" : "0.0.0";
3
3
  function b64url(bytes) {
4
4
  let s = "";
5
5
  for (const b of bytes) s += String.fromCharCode(b);
@@ -66,6 +66,7 @@ var AUTHORIZE = {
66
66
  var POPUP_NAME = "cd-signin";
67
67
  var MESSAGE = "cd:signin";
68
68
  var STORAGE_KEY = "cd.signin";
69
+ var TICK_MS = 300;
69
70
  async function challengeFor(verifier) {
70
71
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
71
72
  return b64url(new Uint8Array(digest));
@@ -91,15 +92,20 @@ async function authorizeURL(o, p, authorize) {
91
92
  function result(p, code) {
92
93
  return { code, codeVerifier: p.verifier, state: p.state, nonce: p.nonce, issuer: p.issuer, clientId: p.clientId, redirectUri: p.redirectUri };
93
94
  }
95
+ var current;
94
96
  function signIn(o) {
95
97
  const issuer = (o.issuer ?? DEFAULT_ISSUER).replace(/\/$/, "");
96
98
  const authorize = o.authorizeUrl ?? AUTHORIZE[issuer];
97
99
  if (!authorize) return Promise.reject(new Error("authorizeUrl is required for this issuer"));
98
100
  if (!o.clientId || !o.redirectUri) return Promise.reject(new Error("clientId and redirectUri are required"));
101
+ if (current && !current.popup.closed) {
102
+ current.popup.focus();
103
+ return current.waiting;
104
+ }
99
105
  const p = { state: randomToken(), nonce: randomToken(), verifier: randomToken(), issuer, clientId: o.clientId, redirectUri: o.redirectUri };
100
106
  store.set(STORAGE_KEY, JSON.stringify(p));
101
107
  const popup = window.open("", POPUP_NAME, "popup,width=480,height=720");
102
- return (async () => {
108
+ const waiting = (async () => {
103
109
  const url = await authorizeURL(o, p, authorize);
104
110
  if (!popup) {
105
111
  location.assign(url);
@@ -108,21 +114,43 @@ function signIn(o) {
108
114
  popup.location.href = url;
109
115
  const callbackOrigin = new URL(o.redirectUri, location.href).origin;
110
116
  return new Promise((resolve, reject) => {
117
+ let last;
118
+ const end = () => {
119
+ stop();
120
+ clearInterval(timer);
121
+ clearTimeout(last);
122
+ };
111
123
  const stop = listen(MESSAGE, callbackOrigin, (m) => {
112
124
  if (m.state !== p.state) return;
113
- stop();
125
+ end();
114
126
  store.remove(STORAGE_KEY);
115
127
  if (typeof m.error === "string" && m.error) reject(new Error(m.error));
116
128
  else if (m.iss !== issuer) reject(new Error("the answer came from another issuer"));
117
129
  else if (typeof m.code !== "string" || !m.code) reject(new Error("the answer carried no code"));
118
130
  else resolve(result(p, m.code));
119
131
  }, POPUP_NAME, popup);
132
+ const timer = setInterval(() => {
133
+ if (!popup.closed) return;
134
+ clearInterval(timer);
135
+ last = setTimeout(() => {
136
+ end();
137
+ reject(new Error("closed"));
138
+ }, TICK_MS);
139
+ }, TICK_MS);
120
140
  o.signal?.addEventListener("abort", () => {
121
- stop();
141
+ end();
122
142
  reject(new Error("aborted"));
123
143
  });
124
144
  });
125
145
  })();
146
+ if (popup) {
147
+ current = { popup, waiting };
148
+ const over = () => {
149
+ if (current && current.popup === popup) current = void 0;
150
+ };
151
+ waiting.then(over, over);
152
+ }
153
+ return waiting;
126
154
  }
127
155
  async function handleCallback() {
128
156
  const q = new URLSearchParams(location.search);
@@ -196,14 +224,17 @@ function defineSignInButton() {
196
224
  }
197
225
  start() {
198
226
  const attr = (n) => this.getAttribute(n) || void 0;
199
- signIn({
227
+ const visit = signIn({
200
228
  clientId: attr("client-id") ?? "",
201
229
  redirectUri: attr("redirect-uri") ?? "",
202
230
  scope: attr("scope"),
203
231
  claims: attr("claims"),
204
232
  issuer: attr("issuer"),
205
233
  authorizeUrl: attr("authorize-url")
206
- }).then(
234
+ });
235
+ if (visit === this.reporting) return;
236
+ this.reporting = visit;
237
+ visit.then(
207
238
  (r) => r && this.dispatchEvent(new CustomEvent("cd-signin", { detail: r, bubbles: true, composed: true })),
208
239
  (e) => this.dispatchEvent(new CustomEvent("cd-signin-error", { detail: { message: String(e?.message ?? e) }, bubbles: true, composed: true }))
209
240
  );
@@ -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;
@@ -503,9 +534,9 @@ function wait(popup, target, success) {
503
534
  const timer = setInterval(() => {
504
535
  if (popup.closed) {
505
536
  clearInterval(timer);
506
- last = setTimeout(() => done({ status: "closed" }), TICK_MS);
537
+ last = setTimeout(() => done({ status: "closed" }), TICK_MS2);
507
538
  } else if (!ready || ++quiet % QUIET_TICKS === 0) hello();
508
- }, TICK_MS);
539
+ }, TICK_MS2);
509
540
  window.addEventListener("message", onMessage);
510
541
  hello();
511
542
  });
@@ -513,9 +544,9 @@ function wait(popup, target, success) {
513
544
  function checkout(o) {
514
545
  const refused = () => new Error("checkout: url is not a Chain Daddy checkout session link");
515
546
  const track = (popup, result2, href) => {
516
- current = { popup, result: result2, href };
547
+ current2 = { popup, result: result2, href };
517
548
  const over = () => {
518
- if (current && current.popup === popup) current = void 0;
549
+ if (current2 && current2.popup === popup) current2 = void 0;
519
550
  };
520
551
  result2.then(over, over);
521
552
  return result2;
@@ -530,9 +561,9 @@ function checkout(o) {
530
561
  const success = (t) => successUrl ? successTarget(successUrl, t.id) : null;
531
562
  const target = typeof url === "function" ? null : parseCheckoutUrl(url);
532
563
  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;
564
+ if (current2 && !current2.popup.closed && (!target || current2.href === target.href)) {
565
+ current2.popup.focus();
566
+ return current2.result;
536
567
  }
537
568
  if (target) {
538
569
  const popup2 = window.open(target.href, windowName(), FEATURES);
package/dist/signin.d.ts CHANGED
@@ -12,7 +12,7 @@ export interface SignInOptions {
12
12
  issuer?: string;
13
13
  /** The consent page; derived from the issuer for Chain Daddy's own. */
14
14
  authorizeUrl?: string;
15
- /** Stops waiting. Closing the popup does not: a closed-looking popup is not a cancel. */
15
+ /** Stops waiting: the call rejects "aborted". */
16
16
  signal?: AbortSignal;
17
17
  }
18
18
  /** What your server exchanges at the token endpoint (or exchangeCode does here). */
@@ -32,8 +32,9 @@ export declare function challengeFor(verifier: string): Promise<string>;
32
32
  * before anything is awaited, or a browser blocks it. Resolves with the code
33
33
  * once the visitor approves; resolves null when the popup was blocked and the
34
34
  * page is redirecting instead (your callback page then calls handleCallback).
35
- * Rejects when they cancel (access_denied) or the answer is not for this
36
- * request.
35
+ * Rejects when they cancel (access_denied), when they close the popup without
36
+ * answering (closed), or when the answer is not for this request. While one
37
+ * sign-in is open, another call shows its popup and returns the same promise.
37
38
  */
38
39
  export declare function signIn(o: SignInOptions): Promise<SignInResult | null>;
39
40
  /**
package/dist/signin.js CHANGED
@@ -65,6 +65,7 @@ var AUTHORIZE = {
65
65
  var POPUP_NAME = "cd-signin";
66
66
  var MESSAGE = "cd:signin";
67
67
  var STORAGE_KEY = "cd.signin";
68
+ var TICK_MS = 300;
68
69
  async function challengeFor(verifier) {
69
70
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
70
71
  return b64url(new Uint8Array(digest));
@@ -90,15 +91,20 @@ async function authorizeURL(o, p, authorize) {
90
91
  function result(p, code) {
91
92
  return { code, codeVerifier: p.verifier, state: p.state, nonce: p.nonce, issuer: p.issuer, clientId: p.clientId, redirectUri: p.redirectUri };
92
93
  }
94
+ var current;
93
95
  function signIn(o) {
94
96
  const issuer = (o.issuer ?? DEFAULT_ISSUER).replace(/\/$/, "");
95
97
  const authorize = o.authorizeUrl ?? AUTHORIZE[issuer];
96
98
  if (!authorize) return Promise.reject(new Error("authorizeUrl is required for this issuer"));
97
99
  if (!o.clientId || !o.redirectUri) return Promise.reject(new Error("clientId and redirectUri are required"));
100
+ if (current && !current.popup.closed) {
101
+ current.popup.focus();
102
+ return current.waiting;
103
+ }
98
104
  const p = { state: randomToken(), nonce: randomToken(), verifier: randomToken(), issuer, clientId: o.clientId, redirectUri: o.redirectUri };
99
105
  store.set(STORAGE_KEY, JSON.stringify(p));
100
106
  const popup = window.open("", POPUP_NAME, "popup,width=480,height=720");
101
- return (async () => {
107
+ const waiting = (async () => {
102
108
  const url = await authorizeURL(o, p, authorize);
103
109
  if (!popup) {
104
110
  location.assign(url);
@@ -107,21 +113,43 @@ function signIn(o) {
107
113
  popup.location.href = url;
108
114
  const callbackOrigin = new URL(o.redirectUri, location.href).origin;
109
115
  return new Promise((resolve, reject) => {
116
+ let last;
117
+ const end = () => {
118
+ stop();
119
+ clearInterval(timer);
120
+ clearTimeout(last);
121
+ };
110
122
  const stop = listen(MESSAGE, callbackOrigin, (m) => {
111
123
  if (m.state !== p.state) return;
112
- stop();
124
+ end();
113
125
  store.remove(STORAGE_KEY);
114
126
  if (typeof m.error === "string" && m.error) reject(new Error(m.error));
115
127
  else if (m.iss !== issuer) reject(new Error("the answer came from another issuer"));
116
128
  else if (typeof m.code !== "string" || !m.code) reject(new Error("the answer carried no code"));
117
129
  else resolve(result(p, m.code));
118
130
  }, POPUP_NAME, popup);
131
+ const timer = setInterval(() => {
132
+ if (!popup.closed) return;
133
+ clearInterval(timer);
134
+ last = setTimeout(() => {
135
+ end();
136
+ reject(new Error("closed"));
137
+ }, TICK_MS);
138
+ }, TICK_MS);
119
139
  o.signal?.addEventListener("abort", () => {
120
- stop();
140
+ end();
121
141
  reject(new Error("aborted"));
122
142
  });
123
143
  });
124
144
  })();
145
+ if (popup) {
146
+ current = { popup, waiting };
147
+ const over = () => {
148
+ if (current && current.popup === popup) current = void 0;
149
+ };
150
+ waiting.then(over, over);
151
+ }
152
+ return waiting;
125
153
  }
126
154
  async function handleCallback() {
127
155
  const q = new URLSearchParams(location.search);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaindaddy/js",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Chain Daddy for any website: Sign in with Chain Daddy (a popup and a button), a coin's apps on its own site (<cd-app>), checkout for a coin's store in a popup, and a server-side check that a store order was paid on chain",
5
5
  "homepage": "https://docs.chaindaddy.io/developer/javascript-sdk",
6
6
  "repository": {