@keplr-ewallet/ewallet-sdk-core
Version:
536 lines (516 loc) • 17.4 kB
JavaScript
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