@keplr-ewallet/ewallet-sdk-core
Version:
441 lines (422 loc) • 14.1 kB
JavaScript
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