@chaindaddy/js 0.0.0-stage → 0.2.0

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.js ADDED
File without changes
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ /** Defines <cd-signin-button> once. cd.js calls it for you. */
2
+ export declare function defineSignInButton(): void;
package/dist/cd.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";(()=>{var w="0.2.0";function b(t){let e="";for(let i of t)e+=String.fromCharCode(i);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function g(){return b(crypto.getRandomValues(new Uint8Array(32)))}function v(t,e,i,s){let c=a=>{if(a&&typeof a=="object"&&a.type===t)try{i(a)}catch{}},u=a=>{a.origin===e&&c(a.data)};window.addEventListener("message",u);let d=null;if(s&&typeof BroadcastChannel=="function"&&e===location.origin)try{d=new BroadcastChannel(s),d.onmessage=a=>c(a.data)}catch{d=null}return()=>{window.removeEventListener("message",u),d?.close()}}var h={get(t){try{return sessionStorage.getItem(t)}catch{return null}},set(t,e){try{sessionStorage.setItem(t,e)}catch{}},remove(t){try{sessionStorage.removeItem(t)}catch{}}};var A="https://api.chaindaddy.io",T={"https://api.chaindaddy.io":"https://chaindaddy.io/_oauth/authorize","https://staging-api.chaindaddy.io":"https://staging.chaindaddy.io/_oauth/authorize"},p="cd-signin",E="cd:signin",m="cd.signin";async function U(t){let e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t));return b(new Uint8Array(e))}function M(t="openid"){let e=t.split(/\s+/).filter(Boolean);return(e.includes("openid")?e:["openid",...e]).join(" ")}async function L(t,e,i){let s=new URLSearchParams({response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUri,scope:M(t.scope),state:e.state,nonce:e.nonce,code_challenge:await U(e.verifier),code_challenge_method:"S256"});return t.claims&&s.set("claims",typeof t.claims=="string"?t.claims:JSON.stringify(t.claims)),`${i}?${s}`}function x(t,e){return{code:e,codeVerifier:t.verifier,state:t.state,nonce:t.nonce,issuer:t.issuer,clientId:t.clientId,redirectUri:t.redirectUri}}function f(t){let e=(t.issuer??A).replace(/\/$/,""),i=t.authorizeUrl??T[e];if(!i)return Promise.reject(new Error("authorizeUrl is required for this issuer"));if(!t.clientId||!t.redirectUri)return Promise.reject(new Error("clientId and redirectUri are required"));let s={state:g(),nonce:g(),verifier:g(),issuer:e,clientId:t.clientId,redirectUri:t.redirectUri};h.set(m,JSON.stringify(s));let c=window.open("",p,"popup,width=480,height=720");return(async()=>{let u=await L(t,s,i);if(!c)return location.assign(u),null;c.location.href=u;let d=new URL(t.redirectUri,location.href).origin;return new Promise((a,n)=>{let o=v(E,d,r=>{r.state===s.state&&(o(),h.remove(m),typeof r.error=="string"&&r.error?n(new Error(r.error)):r.iss!==e?n(new Error("the answer came from another issuer")):typeof r.code!="string"||!r.code?n(new Error("the answer carried no code")):a(x(s,r.code)))},p);t.signal?.addEventListener("abort",()=>{o(),n(new Error("aborted"))})})})()}async function S(){let t=new URLSearchParams(location.search),e={type:E,code:t.get("code"),state:t.get("state"),iss:t.get("iss"),error:t.get("error")};if(window.name===p){try{window.opener?.postMessage(e,location.origin)}catch{}try{let c=new BroadcastChannel(p);c.postMessage(e),c.close()}catch{}return window.close(),null}let i=h.get(m);h.remove(m);let s=i?JSON.parse(i):null;if(!s||e.state!==s.state)throw new Error("the sign-in state does not match: start again");if(e.error)throw new Error(e.error);if(e.iss!==s.issuer)throw new Error("the answer came from another issuer");if(!e.code)throw new Error("the answer carried no code");return x(s,e.code)}async function k(t){let e=await fetch(`${t.issuer}/api/v2/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:t.code,redirect_uri:t.redirectUri,client_id:t.clientId,code_verifier:t.codeVerifier})}),i=await e.json();if(!e.ok)throw new Error(`${i.error??e.status}: ${i.error_description??""}`.trim());return i}var O='<svg viewBox="0 0 24 24" fill="#FFD700" width="20" height="20" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><path d="M5 16L3 5l5.5 5L12 4l3.5 6L21 5l-2 11H5z" stroke="#B8860B" stroke-width="0.5"/><path d="M5 18h14v2H5v-2z" stroke="#B8860B" stroke-width="0.5"/></svg>',P=`
2
+ :host { display: inline-block; }
3
+ button { display: inline-flex; align-items: center; justify-content: center; gap: 10px; box-sizing: border-box;
4
+ min-height: 40px; padding: 0 16px; border-radius: 10px; font: 600 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
5
+ cursor: pointer; border: 1px solid #d0d5dd; background: #ffffff; color: #101828; }
6
+ button[data-theme="dark"] { background: #101828; color: #ffffff; border-color: #101828; }
7
+ button:focus-visible { outline: 2px solid #f59e0b; outline-offset: 2px; }
8
+ button:disabled { opacity: .6; cursor: default; }
9
+ svg { flex: none; }
10
+ `;function I(){if(typeof customElements>"u"||customElements.get("cd-signin-button"))return;class t extends HTMLElement{constructor(){super(),this.root=this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(){this.isConnected&&this.render()}render(){let i=this.getAttribute("text")==="continue"?"Continue with Chain Daddy":"Sign in with Chain Daddy",s=this.getAttribute("theme")==="dark"?"dark":"light";this.root.innerHTML=`<style>${P}</style><button type="button" part="button" data-theme="${s}">${O}<span></span></button>`;let c=this.root.querySelector("button");c.querySelector("span").textContent=i,c.addEventListener("click",()=>this.start())}start(){let i=s=>this.getAttribute(s)||void 0;f({clientId:i("client-id")??"",redirectUri:i("redirect-uri")??"",scope:i("scope"),claims:i("claims"),issuer:i("issuer"),authorizeUrl:i("authorize-url")}).then(s=>s&&this.dispatchEvent(new CustomEvent("cd-signin",{detail:s,bubbles:!0,composed:!0})),s=>this.dispatchEvent(new CustomEvent("cd-signin-error",{detail:{message:String(s?.message??s)},bubbles:!0,composed:!0})))}}t.observedAttributes=["theme","text"],customElements.define("cd-signin-button",t)}var C="https://embed.chaindaddy.io",y="https://embed-staging.chaindaddy.io";var H=["--cd-accent","--cd-surface","--cd-text","--cd-text-muted","--cd-border"];function _(t){try{if(t&&new URL(t).hostname==="js.staging.chaindaddy.io")return y}catch{}return C}var D=`
11
+ :host { display: block; }
12
+ iframe { display: block; width: 100%; border: 0; height: 1px; opacity: 0; color-scheme: normal; }
13
+ :host([data-cd-ready]) iframe { opacity: 1; }
14
+ :host([data-cd-ready]) slot { display: none; }
15
+ :host([data-cd-unavailable]) iframe { display: none; }
16
+ `,z=`
17
+ :host { display: inline-block; }
18
+ button { box-sizing: border-box; min-height: 40px; padding: 0 16px; border-radius: 10px; cursor: pointer;
19
+ font: 600 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
20
+ border: 1px solid var(--cd-border, #d0d5dd); background: var(--cd-accent, #f59e0b); color: var(--cd-text, #101828); }
21
+ button:focus-visible { outline: 2px solid var(--cd-accent, #f59e0b); outline-offset: 2px; }
22
+ `;function R(t={}){try{if(typeof customElements>"u"||typeof HTMLElement>"u")return;let e=t.origin===y?y:C;class i extends HTMLElement{constructor(){super(...arguments);this.kind="app";this.frame=null;this.onMessage=n=>this.receive(n)}connectedCallback(){try{this.shadowRoot||this.attachShadow({mode:"open"}),this.mount()}catch{this.unavailable()}}disconnectedCallback(){try{window.removeEventListener("message",this.onMessage),clearTimeout(this.timer)}catch{}}attributeChangedCallback(){try{this.isConnected&&this.shadowRoot&&this.mount()}catch{this.unavailable()}}src(){let n=(this.getAttribute("crown")??"").trim(),o=(this.getAttribute("chain")??"").trim(),r=(this.getAttribute("app")??"").trim();if(!/^[0-9]{1,18}$/.test(n)||!o||this.kind==="app"&&!r)return null;let l=new URLSearchParams({crown:n,chain:o});return this.kind==="app"?l.set("app",r):l.set("kind","coin"),l.set("mode",this.mode()),l.set("v","1"),`${e}/v1/frame?${l.toString()}`}mode(){let n=this.getAttribute("mode");if(n==="light"||n==="dark")return n;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}catch{return"light"}}mount(){let n=this.shadowRoot;window.removeEventListener("message",this.onMessage),clearTimeout(this.timer),this.removeAttribute("data-cd-ready"),this.removeAttribute("data-cd-unavailable");let o=this.src();if(!o){n.innerHTML="<slot></slot>",this.frame=null;return}n.innerHTML=`<style>${D}</style><slot></slot>`;let r=document.createElement("iframe");r.setAttribute("loading","lazy"),r.setAttribute("referrerpolicy","strict-origin-when-cross-origin"),r.setAttribute("allow","clipboard-write; fullscreen"),r.setAttribute("title",this.kind==="coin"?"Coin on Chain Daddy":"App on Chain Daddy"),r.src=o,n.appendChild(r),this.frame=r,window.addEventListener("message",this.onMessage),this.timer=setTimeout(()=>{this.hasAttribute("data-cd-ready")||this.unavailable()},8e3)}receive(n){try{if(n.origin!==e||!this.frame||!n.source||n.source!==this.frame.contentWindow)return;let o=n.data;if(!o||typeof o!="object")return;if(o.type==="cd:ready")clearTimeout(this.timer),this.removeAttribute("data-cd-unavailable"),this.setAttribute("data-cd-ready",""),this.sendTheme(),this.dispatchEvent(new CustomEvent("cd:ready"));else if(o.type==="cd:resize"){let r=Number(o.height);Number.isFinite(r)&&(this.frame.style.height=`${Math.max(1,Math.min(4e3,Math.ceil(r)))}px`)}else o.type==="cd:unavailable"&&this.unavailable()}catch{}}sendTheme(){let n={},o=getComputedStyle(this);for(let r of H){let l=o.getPropertyValue(r).trim();l&&(n[r]=l)}this.frame?.contentWindow?.postMessage({type:"cd:theme",tokens:n},e)}unavailable(){try{if(clearTimeout(this.timer),this.removeAttribute("data-cd-ready"),this.hasAttribute("data-cd-unavailable"))return;this.setAttribute("data-cd-unavailable",""),this.dispatchEvent(new CustomEvent("cd:unavailable"))}catch{}}}i.observedAttributes=["crown","chain","app","mode"];class s extends i{}class c extends i{constructor(){super(...arguments);this.kind="coin"}}class u extends HTMLElement{connectedCallback(){try{let a=this.querySelector("a[href]"),n=a?new URL(a.getAttribute("href")??"",document.baseURI):null;if(!n||n.protocol!=="https:")return;let o=this.shadowRoot??this.attachShadow({mode:"open"});o.innerHTML=`<style>${z}</style><button type="button" part="button"></button>`;let r=o.querySelector("button");r.textContent=(a.textContent??"").trim()||"Buy on Chain Daddy",r.addEventListener("click",()=>{try{window.open(n.toString(),"cd-buy","popup,width=460,height=760")||window.location.assign(n.toString())}catch{}})}catch{}}}for(let[d,a]of[["cd-app",s],["cd-coin",c],["cd-buy",u]])customElements.get(d)||customElements.define(d,a)}catch{}}var N={version:w,signIn:f,handleCallback:S,exchangeCode:k};window.ChainDaddy=Object.assign(window.ChainDaddy??{},N);I();R({origin:_(document.currentScript?.src)});})();
package/dist/cd.js.sri ADDED
@@ -0,0 +1 @@
1
+ sha384-KKpjQXvfR3Rs+FsdOW+hfEJcZpfuYVXR+TcyRX0nbup+s/xRhLx102TTXL29eFE1
package/dist/core.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ export declare const VERSION: string;
2
+ /** base64url without padding. */
3
+ export declare function b64url(bytes: Uint8Array): string;
4
+ /** 32 random bytes, base64url: 43 characters. */
5
+ export declare function randomToken(): string;
6
+ /**
7
+ * Listens for messages of `type` from exactly `origin`, on window and on a
8
+ * same-origin BroadcastChannel (which still works when a COOP header has
9
+ * severed window.opener). Returns a function that stops listening.
10
+ */
11
+ export declare function listen(type: string, origin: string, onMessage: (data: Record<string, unknown>) => void, channel?: string): () => void;
12
+ /** sessionStorage that never throws (private windows, blocked storage). */
13
+ export declare const store: {
14
+ get(key: string): string | null;
15
+ set(key: string, value: string): void;
16
+ remove(key: string): void;
17
+ };
@@ -0,0 +1,8 @@
1
+ /** The embed origin for a build loaded from scriptSrc: staging's CDN frames staging, anything else prod. */
2
+ export declare function embedOriginFor(scriptSrc?: string | null): string;
3
+ export interface EmbedOptions {
4
+ /** The embed origin. Only the two Chain Daddy embed origins are taken; anything else is prod. */
5
+ origin?: string;
6
+ }
7
+ /** Defines <cd-app>, <cd-coin> and <cd-buy>, each once. cd.js calls it for you. */
8
+ export declare function defineEmbedElements(options?: EmbedOptions): void;
package/dist/embed.js ADDED
@@ -0,0 +1,188 @@
1
+ // src/embed.ts
2
+ var PROD_ORIGIN = "https://embed.chaindaddy.io";
3
+ var STAGING_ORIGIN = "https://embed-staging.chaindaddy.io";
4
+ var READY_TIMEOUT_MS = 8e3;
5
+ var MAX_HEIGHT = 4e3;
6
+ var THEME_TOKENS = ["--cd-accent", "--cd-surface", "--cd-text", "--cd-text-muted", "--cd-border"];
7
+ function embedOriginFor(scriptSrc) {
8
+ try {
9
+ if (scriptSrc && new URL(scriptSrc).hostname === "js.staging.chaindaddy.io") return STAGING_ORIGIN;
10
+ } catch {
11
+ }
12
+ return PROD_ORIGIN;
13
+ }
14
+ var STYLE = `
15
+ :host { display: block; }
16
+ iframe { display: block; width: 100%; border: 0; height: 1px; opacity: 0; color-scheme: normal; }
17
+ :host([data-cd-ready]) iframe { opacity: 1; }
18
+ :host([data-cd-ready]) slot { display: none; }
19
+ :host([data-cd-unavailable]) iframe { display: none; }
20
+ `;
21
+ var BUY_STYLE = `
22
+ :host { display: inline-block; }
23
+ button { box-sizing: border-box; min-height: 40px; padding: 0 16px; border-radius: 10px; cursor: pointer;
24
+ font: 600 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
25
+ border: 1px solid var(--cd-border, #d0d5dd); background: var(--cd-accent, #f59e0b); color: var(--cd-text, #101828); }
26
+ button:focus-visible { outline: 2px solid var(--cd-accent, #f59e0b); outline-offset: 2px; }
27
+ `;
28
+ function defineEmbedElements(options = {}) {
29
+ try {
30
+ if (typeof customElements === "undefined" || typeof HTMLElement === "undefined") return;
31
+ const origin = options.origin === STAGING_ORIGIN ? STAGING_ORIGIN : PROD_ORIGIN;
32
+ class CdFrame extends HTMLElement {
33
+ constructor() {
34
+ super(...arguments);
35
+ this.kind = "app";
36
+ this.frame = null;
37
+ this.onMessage = (event) => this.receive(event);
38
+ }
39
+ connectedCallback() {
40
+ try {
41
+ if (!this.shadowRoot) this.attachShadow({ mode: "open" });
42
+ this.mount();
43
+ } catch {
44
+ this.unavailable();
45
+ }
46
+ }
47
+ disconnectedCallback() {
48
+ try {
49
+ window.removeEventListener("message", this.onMessage);
50
+ clearTimeout(this.timer);
51
+ } catch {
52
+ }
53
+ }
54
+ attributeChangedCallback() {
55
+ try {
56
+ if (this.isConnected && this.shadowRoot) this.mount();
57
+ } catch {
58
+ this.unavailable();
59
+ }
60
+ }
61
+ /** The frame's address, or null when the element does not name enough. */
62
+ src() {
63
+ const crown = (this.getAttribute("crown") ?? "").trim();
64
+ const chain = (this.getAttribute("chain") ?? "").trim();
65
+ const app = (this.getAttribute("app") ?? "").trim();
66
+ if (!/^[0-9]{1,18}$/.test(crown) || !chain || this.kind === "app" && !app) return null;
67
+ const q = new URLSearchParams({ crown, chain });
68
+ if (this.kind === "app") q.set("app", app);
69
+ else q.set("kind", "coin");
70
+ q.set("mode", this.mode());
71
+ q.set("v", "1");
72
+ return `${origin}/v1/frame?${q.toString()}`;
73
+ }
74
+ mode() {
75
+ const attr = this.getAttribute("mode");
76
+ if (attr === "light" || attr === "dark") return attr;
77
+ try {
78
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
79
+ } catch {
80
+ return "light";
81
+ }
82
+ }
83
+ mount() {
84
+ const root = this.shadowRoot;
85
+ window.removeEventListener("message", this.onMessage);
86
+ clearTimeout(this.timer);
87
+ this.removeAttribute("data-cd-ready");
88
+ this.removeAttribute("data-cd-unavailable");
89
+ const src = this.src();
90
+ if (!src) {
91
+ root.innerHTML = "<slot></slot>";
92
+ this.frame = null;
93
+ return;
94
+ }
95
+ root.innerHTML = `<style>${STYLE}</style><slot></slot>`;
96
+ const frame = document.createElement("iframe");
97
+ frame.setAttribute("loading", "lazy");
98
+ frame.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
99
+ frame.setAttribute("allow", "clipboard-write; fullscreen");
100
+ frame.setAttribute("title", this.kind === "coin" ? "Coin on Chain Daddy" : "App on Chain Daddy");
101
+ frame.src = src;
102
+ root.appendChild(frame);
103
+ this.frame = frame;
104
+ window.addEventListener("message", this.onMessage);
105
+ this.timer = setTimeout(() => {
106
+ if (!this.hasAttribute("data-cd-ready")) this.unavailable();
107
+ }, READY_TIMEOUT_MS);
108
+ }
109
+ receive(event) {
110
+ try {
111
+ if (event.origin !== origin || !this.frame || !event.source || event.source !== this.frame.contentWindow) return;
112
+ const data = event.data;
113
+ if (!data || typeof data !== "object") return;
114
+ if (data.type === "cd:ready") {
115
+ clearTimeout(this.timer);
116
+ this.removeAttribute("data-cd-unavailable");
117
+ this.setAttribute("data-cd-ready", "");
118
+ this.sendTheme();
119
+ this.dispatchEvent(new CustomEvent("cd:ready"));
120
+ } else if (data.type === "cd:resize") {
121
+ const h = Number(data.height);
122
+ if (Number.isFinite(h)) this.frame.style.height = `${Math.max(1, Math.min(MAX_HEIGHT, Math.ceil(h)))}px`;
123
+ } else if (data.type === "cd:unavailable") {
124
+ this.unavailable();
125
+ }
126
+ } catch {
127
+ }
128
+ }
129
+ /** The page's --cd-* colours, for an app that takes them. */
130
+ sendTheme() {
131
+ const tokens = {};
132
+ const style = getComputedStyle(this);
133
+ for (const token of THEME_TOKENS) {
134
+ const value = style.getPropertyValue(token).trim();
135
+ if (value) tokens[token] = value;
136
+ }
137
+ this.frame?.contentWindow?.postMessage({ type: "cd:theme", tokens }, origin);
138
+ }
139
+ unavailable() {
140
+ try {
141
+ clearTimeout(this.timer);
142
+ this.removeAttribute("data-cd-ready");
143
+ if (this.hasAttribute("data-cd-unavailable")) return;
144
+ this.setAttribute("data-cd-unavailable", "");
145
+ this.dispatchEvent(new CustomEvent("cd:unavailable"));
146
+ } catch {
147
+ }
148
+ }
149
+ }
150
+ CdFrame.observedAttributes = ["crown", "chain", "app", "mode"];
151
+ class CdApp extends CdFrame {
152
+ }
153
+ class CdCoin extends CdFrame {
154
+ constructor() {
155
+ super(...arguments);
156
+ this.kind = "coin";
157
+ }
158
+ }
159
+ class CdBuy extends HTMLElement {
160
+ connectedCallback() {
161
+ try {
162
+ const link = this.querySelector("a[href]");
163
+ const href = link ? new URL(link.getAttribute("href") ?? "", document.baseURI) : null;
164
+ if (!href || href.protocol !== "https:") return;
165
+ const root = this.shadowRoot ?? this.attachShadow({ mode: "open" });
166
+ root.innerHTML = `<style>${BUY_STYLE}</style><button type="button" part="button"></button>`;
167
+ const button = root.querySelector("button");
168
+ button.textContent = (link.textContent ?? "").trim() || "Buy on Chain Daddy";
169
+ button.addEventListener("click", () => {
170
+ try {
171
+ if (!window.open(href.toString(), "cd-buy", "popup,width=460,height=760")) window.location.assign(href.toString());
172
+ } catch {
173
+ }
174
+ });
175
+ } catch {
176
+ }
177
+ }
178
+ }
179
+ for (const [name, ctor] of [["cd-app", CdApp], ["cd-coin", CdCoin], ["cd-buy", CdBuy]]) {
180
+ if (!customElements.get(name)) customElements.define(name, ctor);
181
+ }
182
+ } catch {
183
+ }
184
+ }
185
+ export {
186
+ defineEmbedElements,
187
+ embedOriginFor
188
+ };
@@ -0,0 +1,6 @@
1
+ export { VERSION } from './core';
2
+ export { DEFAULT_ISSUER, challengeFor, exchangeCode, handleCallback, signIn } from './signin';
3
+ export type { SignInOptions, SignInResult } from './signin';
4
+ export { defineSignInButton } from './button';
5
+ export { defineEmbedElements, embedOriginFor } from './embed';
6
+ export type { EmbedOptions } from './embed';