UNPKG

@keplr-ewallet/ewallet-sdk-core

Version:
441 lines (422 loc) 14.1 kB
import '@keplr-ewallet/stdlib-js'; function registerMsgListener() { if (window.__keplr_ewallet_ev) { return Promise.resolve(false); } const callback = []; const prom = new Promise((resolve) => { callback.push(resolve); }); async function msgHandler(event) { const message = event.data; switch (message.msg_type) { case "init": { if (callback.length > 1) { throw new Error("Callback should exist"); } const cb = callback[0]; cb(true); } } } window.addEventListener("message", msgHandler); window.__keplr_ewallet_ev = msgHandler; console.debug("[keplr] msg listener registered"); return prom; } const EWALLET_ATTACHED_TARGET = "keplr_ewallet_attached"; function sendMsgToIframe(msg) { return new Promise((resolve, reject) => { if (this.iframe.contentWindow === null) { reject("iframe contentWindow is null"); return; } const contentWindow = this.iframe.contentWindow; const channel = new MessageChannel(); channel.port1.onmessage = (obj) => { const data = obj.data; console.debug("[keplr] reply recv", data); if (data.hasOwnProperty("payload")) { resolve(data); } else { console.error("[keplr] unknown msg type"); resolve({ target: "keplr_ewallet_sdk", msg_type: "unknown_msg_type", payload: JSON.stringify(data), }); } }; contentWindow.postMessage(msg, this.sdkEndpoint, [channel.port2]); }); } const WAIT_TIME = 300000; async function showModal(msg) { let timeoutId = null; const timeout = new Promise((_, reject) => { timeoutId = setTimeout(() => reject(new Error("Show modal timeout")), WAIT_TIME); }); try { this.iframe.style.display = "block"; const showModalAck = await Promise.race([ this.sendMsgToIframe(msg), timeout, ]); if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } if (showModalAck.msg_type !== "show_modal_ack") { throw new Error("Unreachable"); } if (!showModalAck.payload.success) { throw new Error(showModalAck.payload.err); } return showModalAck.payload.data; } catch (error) { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } if (error instanceof Error && error.message === "Show modal timeout") { await this.hideModal(); throw new Error("Show modal timeout"); } throw error; } } const RedirectUriSearchParamsKey = { STATE: "state", }; const GoogleClientId = "239646646986-8on7ql1vmbcshbjk12bdtopmto99iipm.apps.googleusercontent.com"; async function tryGoogleSignIn(sdkEndpoint, apiKey, sendMsgToIframe) { const clientId = GoogleClientId; const redirectUri = `${new URL(sdkEndpoint).origin}/google/callback`; console.debug("[keplr] window host: %s", window.location.host); console.debug("[keplr] redirectUri: %s", redirectUri); const nonce = Array.from(crypto.getRandomValues(new Uint8Array(8))) .map((b) => b.toString(16).padStart(2, "0")) .join(""); const ackPromise = sendMsgToIframe({ target: EWALLET_ATTACHED_TARGET, msg_type: "set_oauth_nonce", payload: nonce, }); const oauthState = { apiKey, targetOrigin: window.location.origin, }; const oauthStateString = JSON.stringify(oauthState); console.debug("[keplr] oauthStateString: %s", oauthStateString); const authUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth"); authUrl.searchParams.set("client_id", clientId); authUrl.searchParams.set("redirect_uri", redirectUri); authUrl.searchParams.set("response_type", "token id_token"); authUrl.searchParams.set("scope", "openid email profile"); authUrl.searchParams.set("prompt", "login"); authUrl.searchParams.set("nonce", nonce); authUrl.searchParams.set(RedirectUriSearchParamsKey.STATE, oauthStateString); const popup = window.open(authUrl.toString(), "google_oauth", "width=1200,height=800"); if (!popup) { throw new Error("Failed to open new window for google oauth sign in"); } const ack = await ackPromise; if (ack.msg_type !== "set_oauth_nonce_ack" || !ack.payload.success) { popup.close(); throw new Error("Failed to set nonce for google oauth sign in"); } return new Promise((resolve, reject) => { let focusTimer; let timeoutId; function onFocus(e) { focusTimer = window.setTimeout(() => { if (popup && popup.closed) { cleanup(); reject(new Error("Window closed by user")); } }, 200); } window.addEventListener("focus", onFocus); function onMessage(e) { const data = e.data; if (data.msg_type === "oauth_sign_in_ack") { cleanup(); if (data.payload.success) { resolve(); } else { reject(new Error(data.payload.err)); } } } window.addEventListener("message", onMessage); timeoutId = window.setTimeout(() => { cleanup(); reject(new Error("Timeout: no response within 5 minutes")); }, 5 * 60 * 1000); function cleanup() { window.clearTimeout(focusTimer); window.clearTimeout(timeoutId); window.removeEventListener("focus", onFocus); window.removeEventListener("message", onMessage); if (popup && !popup.closed) { popup.close(); } } }); } async function signIn(type) { switch (type) { case "google": { return await tryGoogleSignIn(this.sdkEndpoint, this.apiKey, this.sendMsgToIframe); } default: throw Error(`invalid type: ${type}`); } } async function signOut() { await this.sendMsgToIframe({ target: EWALLET_ATTACHED_TARGET, msg_type: "sign_out", payload: null, }); } async function getPublicKey() { try { const res = await this.sendMsgToIframe({ target: EWALLET_ATTACHED_TARGET, msg_type: "get_public_key", payload: null, }); if (res.msg_type === "get_public_key_ack" && res.payload.success) { return res.payload.data; } return null; } catch (error) { console.error("[core] getPublicKey failed with error:", error); return null; } } async function getEmail() { try { const res = await this.sendMsgToIframe({ target: EWALLET_ATTACHED_TARGET, msg_type: "get_email", payload: null, }); if (res.msg_type === "get_email_ack" && res.payload.success) { return res.payload.data; } return null; } catch (error) { console.error("[core] getEmail failed with error:", error); return null; } } async function hideModal() { this.iframe.style.display = "none"; } async function makeSignature(msg) { const res = await this.sendMsgToIframe(msg); if (res.msg_type !== "make_signature_ack") { throw new Error("Unreachable"); } if (!res.payload.success) { throw new Error(res.payload.err); } return res.payload.data; } async function initState(hostOrigin) { try { console.debug("[keplr] init core, origin: %s", hostOrigin); const res = await this.sendMsgToIframe({ target: "keplr_ewallet_attached", msg_type: "init_state", payload: hostOrigin, }); if (res.msg_type === "init_state_ack") { return { success: true, data: true, }; } return { success: false, err: "init_state_ack not received", }; } catch (error) { console.error("[keplr] initState failed with error:", error); return { success: false, err: error instanceof Error ? error.message : String(error), }; } } async function on(eventType, handler) { if (this.eventEmitter) { this.eventEmitter.on("accountsChanged", () => { console.log(123); }); } } class EventEmitter2 { constructor() { this.listeners = {}; this.listeners = {}; } on(eventName, listener) { if (!this.listeners[eventName]) { this.listeners[eventName] = []; } this.listeners[eventName].push(listener); } emit(eventName, ...args) { if (this.listeners[eventName]) { this.listeners[eventName].forEach((listener) => listener(...args)); } } } class KeplrEWallet { constructor(apiKey, iframe, sdkEndpoint) { this.showModal = showModal.bind(this); this.hideModal = hideModal.bind(this); this.sendMsgToIframe = sendMsgToIframe.bind(this); this.signIn = signIn.bind(this); this.signOut = signOut.bind(this); this.getPublicKey = getPublicKey.bind(this); this.getEmail = getEmail.bind(this); this.makeSignature = makeSignature.bind(this); this.initState = initState.bind(this); this.on = on.bind(this); this.apiKey = apiKey; this.iframe = iframe; this.sdkEndpoint = sdkEndpoint; this.origin = window.location.origin; this.eventEmitter = new EventEmitter2(); } } const KEPLR_IFRAME = "keplr-ewallet-attached"; function setupIframeElement(url) { const bodyEls = document.getElementsByTagName("body"); if (bodyEls[0] === undefined) { console.error("body element not found"); return { success: false, err: "body element not found", }; } const oldEl = document.getElementById(KEPLR_IFRAME); if (oldEl !== null) { console.warn("[keplr] iframe already exists"); return { success: true, data: oldEl, }; } const bodyEl = bodyEls[0]; console.debug("[keplr] appending iframe"); const iframe = document.createElement("iframe"); iframe.src = url; iframe.id = KEPLR_IFRAME; iframe.style.position = "fixed"; iframe.style.top = "0"; iframe.style.left = "0"; iframe.style.width = "100vw"; iframe.style.height = "100vh"; iframe.style.border = "none"; iframe.style.display = "none"; iframe.style.backgroundColor = "transparent"; iframe.style.overflow = "hidden"; iframe.style.zIndex = "1000000"; bodyEl.appendChild(iframe); return { success: true, data: iframe }; } const SDK_ENDPOINT = `https://attached.embed.keplr.app`; const KEPLR_EWALLET_ELEM_ID = "keplr-ewallet"; async function initKeplrEwalletCore(args) { console.debug("[keplr] init"); console.debug("[keplr] sdk endpoint: %s", args.sdk_endpoint); if (window === undefined) { console.error("[keplr] EWallet can only be initialized in the browser"); return { success: false, err: "Not in the browser context", }; } if (window.__keplr_ewallet) { const el = document.getElementById(KEPLR_EWALLET_ELEM_ID); if (el !== null) { return { success: false, err: "Some problem occurred during Keplr eWallet initialization", }; } console.debug("[keplr] already initialized"); return { success: true, data: window.__keplr_ewallet }; } const checkURLRes = await checkURL(args.sdk_endpoint); if (!checkURLRes.success) { return checkURLRes; } const registering = registerMsgListener(); const sdkEndpoint = checkURLRes.data; console.log("[keplr] resolved sdk endpoint: %s", sdkEndpoint); const iframeRes = setupIframeElement(sdkEndpoint); if (!iframeRes.success) { return iframeRes; } const iframe = iframeRes.data; await registering; const ewalletCore = new KeplrEWallet(args.api_key, iframe, sdkEndpoint); window.__keplr_ewallet = ewalletCore; const hostOriginRes = await getHostOrigin(); if (!hostOriginRes.success) { return hostOriginRes; } const hostOrigin = hostOriginRes.data; const initStateRes = await ewalletCore.initState(hostOrigin); if (!initStateRes.success) { return initStateRes; } return { success: true, data: ewalletCore }; } async function checkURL(url) { const _url = url ?? SDK_ENDPOINT; try { const response = await fetch(_url, { mode: "no-cors" }); if (!response.ok) { return { success: true, data: _url }; } else { return { success: false, err: `SDK endpoint, resp contains err, url: ${_url}`, }; } } catch (err) { console.error("[keplr] check url fail, url: %s", _url); return { success: false, err: `check url fail, ${err.toString()}` }; } } async function getHostOrigin() { try { const myUrl = window.location.toString(); const normalizedIframeOrigin = new URL(myUrl).origin; return { success: true, data: normalizedIframeOrigin }; } catch (err) { console.error("[keplr] get host origin fail"); return { success: false, err: `get host origin fail, ${err.toString()}`, }; } } export { EventEmitter2, KeplrEWallet, RedirectUriSearchParamsKey, initKeplrEwalletCore }; //# sourceMappingURL=index.js.map