UNPKG

@keplr-ewallet/ewallet-sdk-core

Version:
536 lines (516 loc) 17.4 kB
import '@keplr-ewallet/stdlib-js'; var RedirectUriSearchParamsKey; (function (RedirectUriSearchParamsKey) { RedirectUriSearchParamsKey["STATE"] = "state"; })(RedirectUriSearchParamsKey || (RedirectUriSearchParamsKey = {})); async function sendMsgToIframe(msg) { await this.waitUntilInitialized; const contentWindow = this.iframe.contentWindow; if (contentWindow === null) { throw new Error("iframe contentWindow is null"); } return new Promise((resolve) => { const channel = new MessageChannel(); channel.port1.onmessage = (event) => { const data = event.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 FIVE_MINS = 60 * 5 * 1000; async function openModal(msg) { await this.waitUntilInitialized; let timeoutId = null; const timeout = new Promise((_, reject) => { timeoutId = setTimeout(() => reject(new Error("Show modal timeout")), FIVE_MINS); }); try { this.iframe.style.display = "block"; const openModalAck = await Promise.race([ this.sendMsgToIframe(msg), timeout, ]); if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } if (openModalAck.msg_type !== "open_modal_ack") { return { success: false, err: { type: "invalid_ack_type", received: openModalAck.msg_type }, }; } return { success: true, data: openModalAck.payload }; } catch (error) { return { success: false, err: { type: "unknown_error", error } }; } finally { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } this.closeModal(); } } const EWALLET_ATTACHED_TARGET = "keplr_ewallet_attached"; const GOOGLE_CLIENT_ID = "421793224165-cpmbt6enqrj6ad6n4ujokham8qdmnnln.apps.googleusercontent.com"; const FIVE_MINS_MS = 5 * 60 * 1000; async function signIn(type) { await this.waitUntilInitialized; let signInRes; try { switch (type) { case "google": { signInRes = await tryGoogleSignIn(this.sdkEndpoint, this.apiKey, this.sendMsgToIframe.bind(this)); break; } default: throw new Error(`not supported sign in type, type: ${type}`); } } catch (err) { throw new Error(`Sign in error, err: ${err}`); } if (!signInRes.payload.success) { throw new Error(`sign in fail, err: ${signInRes.payload.err}`); } const publicKey = await this.getPublicKey(); const email = await this.getEmail(); if (!!publicKey && !!email) { console.log("[keplr] emit CORE__accountsChanged"); this.eventEmitter.emit({ type: "CORE__accountsChanged", email, publicKey, }); } } async function tryGoogleSignIn(sdkEndpoint, apiKey, sendMsgToIframe) { const clientId = GOOGLE_CLIENT_ID; 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 nonceAckPromise = 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 nonceAckPromise; if (ack.msg_type !== "set_oauth_nonce_ack" || !ack.payload.success) { throw new Error("Failed to set nonce for google oauth sign in"); } return new Promise(async (resolve, reject) => { let timeout; function onMessage(event) { if (event.ports.length < 1) { return; } const port = event.ports[0]; const data = event.data; if (data.msg_type === "oauth_sign_in_update") { console.log("[keplr] oauth_sign_in_update recv, %o", data); const msg = { target: "keplr_ewallet_attached", msg_type: "oauth_sign_in_update_ack", payload: null, }; port.postMessage(msg); if (data.payload.success) { resolve(data); } else { reject(new Error(data.payload.err.type)); } cleanup(); } } window.addEventListener("message", onMessage); timeout = window.setTimeout(() => { cleanup(); reject(new Error("Timeout: no response within 5 minutes")); closePopup(popup); }, FIVE_MINS_MS); function cleanup() { console.log("[keplr] clean up oauth sign in listener"); window.clearTimeout(timeout); window.removeEventListener("message", onMessage); } }); } function closePopup(popup) { if (popup && !popup.closed) { popup.close(); } } async function signOut() { await this.waitUntilInitialized; await this.sendMsgToIframe({ target: EWALLET_ATTACHED_TARGET, msg_type: "sign_out", payload: null, }); this.eventEmitter.emit({ type: "CORE__accountsChanged", email: null, publicKey: null, }); } async function getPublicKey() { await this.waitUntilInitialized; 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; } async function getEmail() { await this.waitUntilInitialized; 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; } function closeModal() { this.iframe.style.display = "none"; } function on(handlerDef) { this.eventEmitter.on(handlerDef); } const KEPLR_IFRAME_ID = "keplr-ewallet-attached"; function setUpIframeElement(url) { const oldEl = document.getElementById(KEPLR_IFRAME_ID); if (oldEl !== null) { console.warn("[keplr] iframe already exists"); return { success: true, data: oldEl, }; } const bodyEls = document.getElementsByTagName("body"); if (bodyEls.length < 1 || bodyEls[0] === undefined) { console.error("body element not found"); return { success: false, err: "body element not found", }; } const bodyEl = bodyEls[0]; console.debug("[keplr] setting up iframe"); const iframe = document.createElement("iframe"); if (document.readyState === "complete") { loadIframe(iframe, bodyEl, url); } else { window.addEventListener("load", () => loadIframe(iframe, bodyEl, url)); } return { success: true, data: iframe }; } function loadIframe(iframe, bodyEl, url) { console.log("[keplr] loading iframe"); iframe.src = url.toString(); iframe.loading = "eager"; iframe.id = KEPLR_IFRAME_ID; 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); } function registerMsgListener(_eWallet) { if (window.__keplr_ewallet_ev) { console.error("[keplr] isn't it already initailized?"); } return new Promise((resolve, reject) => { async function handler(event) { if (event.ports.length < 1) { return; } const port = event.ports[0]; const msg = event.data; if (msg.msg_type === "init") { const ack = { target: "keplr_ewallet_attached", msg_type: "init_ack", payload: { success: true, data: null }, }; port.postMessage(ack); window.removeEventListener("message", handler); resolve(msg.payload); } } window.addEventListener("message", handler); window.__keplr_ewallet_ev = handler; console.log("[keplr] msg listener registered"); }); } async function lazyInit(eWallet) { await waitUntilDocumentLoad(); const el = document.getElementById(KEPLR_IFRAME_ID); if (el === null) { return { success: false, err: "iframe not exists even after Keplr eWallet initialization", }; } const checkURLRes = await checkURL(eWallet.sdkEndpoint); if (!checkURLRes.success) { return checkURLRes; } const registerRes = await registerMsgListener(); if (registerRes.success) { const initResult = registerRes.data; const { email, public_key } = initResult; eWallet.state = { email, publicKey: public_key }; if (email && public_key) { eWallet.eventEmitter.emit({ type: "CORE__accountsChanged", email: email, publicKey: public_key, }); } return { success: true, data: eWallet.state }; } else { return { success: false, err: "msg listener register fail", }; } } async function checkURL(url) { 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 waitUntilDocumentLoad() { return new Promise((resolve) => { if (document.readyState === "complete") { Promise.resolve().then(() => { resolve(0); }); } else { window.addEventListener("load", () => { Promise.resolve().then(() => { resolve(0); }); }); } }); } class EventEmitter3 { constructor() { this.listeners = {}; } on(handlerDef) { const { handler, type } = handlerDef; if (typeof handler !== "function") { throw new TypeError(`The "handler" argument must be of type function. \ Received ${handler === null ? "null" : typeof handler}`); } if (this.listeners[type] === undefined) { this.listeners[type] = []; } this.listeners[type].push(handler); } emit(event) { const { type, ...rest } = event; console.log("[keplr] emit, type: %s", type); const handlers = this.listeners[type]; if (handlers === undefined) { return { success: false, err: { type: "handler_not_found", event_type: type, }, }; } for (let idx = 0; idx < handlers.length; idx += 1) { try { const handler = handlers[idx]; handler(rest); } catch (err) { return { success: false, err: { type: "handle_error", error: err.toString() }, }; } } return { success: true, data: void 0 }; } off(handlerDef) { const { type, handler } = handlerDef; const handlers = this.listeners[type]; if (handlers === undefined) { return; } const index = handlers.indexOf(handler); if (index === -1) { return; } handlers.splice(index, 1); if (handlers.length === 0) { delete this.listeners[type]; } } } const KeplrEWallet = function (apiKey, iframe, sdkEndpoint) { this.apiKey = apiKey; this.iframe = iframe; this.sdkEndpoint = sdkEndpoint; this.origin = window.location.origin; this.eventEmitter = new EventEmitter3(); this.state = { email: null, publicKey: null, }; this.waitUntilInitialized = lazyInit(this).then(); }; const SDK_ENDPOINT = `https://attached.embed.keplr.app`; function init(args) { try { console.log("[keplr] init"); if (window === undefined) { console.error("[keplr] EWallet can only be initialized in the browser"); return { success: false, err: { type: "not_in_browser" }, }; } if (window.__keplr_ewallet_locked === true) { console.warn("keplr ewallet init is locked. Is init being exeucted concurrently?"); return { success: false, err: { type: "is_locked" } }; } else { window.__keplr_ewallet_locked = true; } console.log("[keplr] sdk endpoint: %s", args.sdk_endpoint); if (window.__keplr_ewallet) { console.warn("[keplr] already initialized"); return { success: true, data: window.__keplr_ewallet }; } const hostOrigin = new URL(window.location.toString()).origin; if (hostOrigin.length === 0) { return { success: false, err: { type: "host_origin_empty" }, }; } const sdkEndpoint = args.sdk_endpoint ?? SDK_ENDPOINT; let sdkEndpointURL; try { sdkEndpointURL = new URL(sdkEndpoint); sdkEndpointURL.searchParams.append("host_origin", hostOrigin); } catch (err) { return { success: false, err: { type: "sdk_endpoint_invalid_url" }, }; } console.log("[keplr] resolved sdk endpoint: %s", sdkEndpoint); console.log("[keplr] host origin: %s", hostOrigin); const iframeRes = setUpIframeElement(sdkEndpointURL); if (!iframeRes.success) { return { success: false, err: { type: "iframe_setup_fail", msg: iframeRes.err.toString() }, }; } const iframe = iframeRes.data; const ewalletCore = new KeplrEWallet(args.api_key, iframe, sdkEndpoint); if (window.__keplr_ewallet) { console.warn("[keplr] ewallet has been initialized by another process"); return { success: true, data: window.__keplr_ewallet }; } else { window.__keplr_ewallet = ewalletCore; return { success: true, data: ewalletCore }; } } catch (err) { console.error("[keplr] init fail", err); throw new Error("[keplr] sdk init fail, unreachable"); } finally { if (window.__keplr_ewallet_locked === true) { window.__keplr_ewallet_locked = false; } } } KeplrEWallet.init = init; const ptype = KeplrEWallet.prototype; ptype.openModal = openModal; ptype.closeModal = closeModal; ptype.sendMsgToIframe = sendMsgToIframe; ptype.signIn = signIn; ptype.signOut = signOut; ptype.getPublicKey = getPublicKey; ptype.getEmail = getEmail; ptype.on = on; export { EventEmitter3, KEPLR_IFRAME_ID, KeplrEWallet, RedirectUriSearchParamsKey, setUpIframeElement }; //# sourceMappingURL=index.js.map