UNPKG

@hot-labs/near-connect

Version:

![ezgif-26d74832f88c3c](https://github.com/user-attachments/assets/c4422057-38bb-4cd9-8bd0-568e29f46280)

342 lines 16.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NearConnector = void 0; const events_1 = require("./helpers/events"); const NearWalletsPopup_1 = require("./popups/NearWalletsPopup"); const storage_1 = require("./helpers/storage"); const indexdb_1 = __importDefault(require("./helpers/indexdb")); const ParentFrameWallet_1 = require("./ParentFrameWallet"); const InjectedWallet_1 = require("./InjectedWallet"); const SandboxedWallet_1 = require("./SandboxedWallet"); const defaultManifests = [ "https://raw.githubusercontent.com/hot-dao/near-selector/refs/heads/main/repository/manifest.json", "https://cdn.jsdelivr.net/gh/azbang/hot-connector/repository/manifest.json", ]; function createFilterForWalletFeatures(features) { return (wallet) => { if (Object.entries(features).length === 0) return true; return Object.entries(features) .filter(([_, value]) => value === true) .every(([key]) => { return wallet.manifest.features?.[key] === true; }); }; } class NearConnector { storage; events; db; logger; wallets = []; manifest = { wallets: [], version: "1.0.0" }; features = {}; network = "mainnet"; providers = { mainnet: [], testnet: [] }; walletConnect; footerBranding; excludedWallets = []; autoConnect; cspNonce; whenManifestLoaded; constructor(options) { this.db = new indexdb_1.default("hot-connector", "wallets"); this.storage = options?.storage ?? new storage_1.LocalStorage(); this.events = options?.events ?? new events_1.EventEmitter(); this.logger = options?.logger; this.cspNonce = options?.cspNonce; this.network = options?.network ?? "mainnet"; this.walletConnect = options?.walletConnect; this.autoConnect = options?.autoConnect ?? true; this.providers = options?.providers ?? { mainnet: [], testnet: [] }; this.excludedWallets = options?.excludedWallets ?? []; this.features = options?.features ?? {}; if (options?.footerBranding !== undefined) { this.footerBranding = options?.footerBranding; } else { this.footerBranding = { icon: "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%20512%20512%22%20class%3D%22size-7%22%3E%3Crect%20width%3D%22512%22%20height%3D%22512%22%20fill%3D%22%2300ec97%22%20rx%3D%22110%22%2F%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M373.89%20106.199a31.95%2031.95%200%200%200-27.213%2015.207l-62.631%2092.979a6.67%206.67%200%200%200-.989%205.001%206.66%206.66%200%200%200%202.837%204.235%206.67%206.67%200%200%200%208.032-.494l61.643-53.47c1.02-.924%202.599-.827%203.523.193.419.473.644%201.074.644%201.697v167.402a2.49%202.49%200%200%201-2.502%202.491%202.48%202.48%200%200%201-1.912-.891L168.976%20117.497a31.93%2031.93%200%200%200-24.356-11.298h-6.508c-17.623%200-31.917%2014.294-31.917%2031.917v235.767c0%2017.623%2014.294%2031.917%2031.917%2031.917a31.94%2031.94%200%200%200%2027.213-15.206l62.631-92.98a6.66%206.66%200%200%200-1.847-9.236%206.67%206.67%200%200%200-8.033.494l-61.643%2053.471c-1.02.923-2.599.826-3.522-.194a2.5%202.5%200%200%201-.634-1.697V173.008a2.49%202.49%200%200%201%202.502-2.492c.731%200%201.439.322%201.912.891l186.313%20223.096a31.95%2031.95%200%200%200%2024.357%2011.297h6.508c17.623%200%2031.927-14.272%2031.938-31.895V138.116c0-17.623-14.294-31.917-31.917-31.917%22%2F%3E%3C%2Fsvg%3E", heading: "NEAR Connector", link: "https://wallet.near.org", linkText: "Don't have a wallet?", }; } this.whenManifestLoaded = new Promise(async (resolve) => { if (options?.manifest == null || typeof options.manifest === "string") { this.manifest = await this._loadManifest(options?.manifest).catch(() => ({ wallets: [], version: "1.0.0" })); } else { this.manifest = options?.manifest ?? { wallets: [], version: "1.0.0" }; } const set = new Set(this.excludedWallets); set.delete("hot-wallet"); // always include hot-wallet this.manifest.wallets = this.manifest.wallets.filter((wallet) => { // Remove wallet with walletConnect permission but no projectId is provided if (wallet.permissions.walletConnect && !this.walletConnect) return false; if (set.has(wallet.id)) return false; // excluded wallets return true; }); await new Promise((resolve) => setTimeout(resolve, 100)); resolve(); }); if (typeof window !== "undefined") { window.addEventListener("near-wallet-injected", this._handleNearWalletInjected); window.dispatchEvent(new Event("near-selector-ready")); window.addEventListener("message", async (event) => { if (event.data.type === "near-wallet-injected") { await this.whenManifestLoaded.catch(() => { }); this.wallets = this.wallets.filter((wallet) => wallet.manifest.id !== event.data.manifest.id); this.wallets.unshift(new ParentFrameWallet_1.ParentFrameWallet(this, event.data.manifest)); this.events.emit("selector:walletsChanged", {}); if (this.autoConnect) { this.connect({ walletId: event.data.manifest.id }); } } }); } this.whenManifestLoaded.then(() => { if (typeof window !== "undefined") { window.parent.postMessage({ type: "near-selector-ready" }, "*"); } this.manifest.wallets.forEach((wallet) => this.registerWallet(wallet)); this.storage.get("debug-wallets").then((json) => { const debugWallets = JSON.parse(json ?? "[]"); debugWallets.forEach((wallet) => this.registerDebugWallet(wallet)); }); }); } get availableWallets() { const wallets = this.wallets.filter((wallet) => { return Object.entries(this.features).every(([key, value]) => { if (value && !wallet.manifest.features?.[key]) return false; return true; }); }); return wallets.filter((wallet) => { if (this.network === "testnet" && !wallet.manifest.features?.testnet) return false; return true; }); } _handleNearWalletInjected = (event) => { this.wallets = this.wallets.filter((wallet) => wallet.manifest.id !== event.detail.manifest.id); this.wallets.unshift(new InjectedWallet_1.InjectedWallet(this, event.detail)); this.events.emit("selector:walletsChanged", {}); }; async _loadManifest(manifestUrl) { const manifestEndpoints = manifestUrl ? [manifestUrl] : defaultManifests; for (const endpoint of manifestEndpoints) { const res = await fetch(endpoint).catch(() => null); if (!res || !res.ok) continue; return await res.json(); // TODO: Validate this } throw new Error("Failed to load manifest"); } async switchNetwork(network, connectOptions) { if (this.network === network) return; await this.disconnect().catch(() => { }); this.network = network; await this.connect(connectOptions); } async registerWallet(manifest) { if (manifest.type !== "sandbox") throw new Error("Only sandbox wallets are supported"); if (this.wallets.find((wallet) => wallet.manifest.id === manifest.id)) return; this.wallets.push(new SandboxedWallet_1.SandboxWallet(this, manifest)); this.events.emit("selector:walletsChanged", {}); } async registerDebugWallet(json) { const manifest = typeof json === "string" ? JSON.parse(json) : json; if (manifest.type !== "sandbox") throw new Error("Only sandbox wallets type are supported"); if (!manifest.id) throw new Error("Manifest must have an id"); if (!manifest.name) throw new Error("Manifest must have a name"); if (!manifest.icon) throw new Error("Manifest must have an icon"); if (!manifest.website) throw new Error("Manifest must have a website"); if (!manifest.version) throw new Error("Manifest must have a version"); if (!manifest.executor) throw new Error("Manifest must have an executor"); if (!manifest.features) throw new Error("Manifest must have features"); if (!manifest.permissions) throw new Error("Manifest must have permissions"); if (this.wallets.find((wallet) => wallet.manifest.id === manifest.id)) throw new Error("Wallet already registered"); manifest.debug = true; this.wallets.unshift(new SandboxedWallet_1.SandboxWallet(this, manifest)); this.events.emit("selector:walletsChanged", {}); const debugWallets = this.wallets.filter((wallet) => wallet.manifest.debug).map((wallet) => wallet.manifest); this.storage.set("debug-wallets", JSON.stringify(debugWallets)); return manifest; } async removeDebugWallet(id) { this.wallets = this.wallets.filter((wallet) => wallet.manifest.id !== id); const debugWallets = this.wallets.filter((wallet) => wallet.manifest.debug).map((wallet) => wallet.manifest); this.storage.set("debug-wallets", JSON.stringify(debugWallets)); this.events.emit("selector:walletsChanged", {}); } async selectWallet({ features = {} } = {}) { await this.whenManifestLoaded.catch(() => { }); return new Promise((resolve, reject) => { const popup = new NearWalletsPopup_1.NearWalletsPopup({ footer: this.footerBranding, wallets: this.availableWallets.filter(createFilterForWalletFeatures(features)).map((wallet) => wallet.manifest), onRemoveDebugManifest: async (id) => this.removeDebugWallet(id), onAddDebugManifest: async (wallet) => this.registerDebugWallet(wallet), onReject: () => (reject(new Error("User rejected")), popup.destroy()), onSelect: (id) => (resolve(id), popup.destroy()), }); popup.create(); }); } async connect(input = {}) { let walletId = input.walletId; const signMessageParams = input.signMessageParams; await this.whenManifestLoaded.catch(() => { }); if (!walletId) { walletId = await this.selectWallet({ features: { signInAndSignMessage: input.signMessageParams != null ? true : undefined, signInWithFunctionCallKey: input.addFunctionCallKey != null ? true : undefined, }, }); } try { const wallet = await this.wallet(walletId); this.logger?.log(`Wallet available to connect`, wallet); await this.storage.set("selected-wallet", walletId); this.logger?.log(`Set preferred wallet, try to signIn${signMessageParams != null ? " (with signed message)" : ""}`, walletId); let addFunctionCallKey = undefined; if (input.addFunctionCallKey != null) { this.logger?.log(`Adding function call access key during sign in with params`, input.addFunctionCallKey); // Set default gas allowance if not provided addFunctionCallKey = { ...input.addFunctionCallKey, gasAllowance: input.addFunctionCallKey.gasAllowance ?? { amount: "250000000000000000000000", // 0.25 NEAR in yoctoNEAR kind: "limited", }, }; } if (signMessageParams != null) { const accounts = await wallet.signInAndSignMessage({ addFunctionCallKey, messageParams: signMessageParams, network: this.network, }); if (!accounts?.length) throw new Error("Failed to sign in"); this.logger?.log(`Signed in to wallet (with signed message)`, walletId, accounts); this.events.emit("wallet:signInAndSignMessage", { wallet, accounts, success: true }); this.events.emit("wallet:signIn", { wallet, accounts: accounts.map((account) => ({ accountId: account.accountId, publicKey: account.publicKey, })), success: true, source: "signInAndSignMessage", }); } else { const accounts = await wallet.signIn({ addFunctionCallKey, network: this.network, }); if (!accounts?.length) throw new Error("Failed to sign in"); this.logger?.log(`Signed in to wallet`, walletId, accounts); this.events.emit("wallet:signIn", { wallet, accounts, success: true, source: "signIn" }); } return wallet; } catch (e) { this.logger?.log("Failed to connect to wallet", e); throw e; } } async disconnect(wallet) { if (!wallet) wallet = await this.wallet(); await wallet.signOut({ network: this.network }); await this.storage.remove("selected-wallet"); this.events.emit("wallet:signOut", { success: true }); } async getConnectedWallet() { await this.whenManifestLoaded.catch(() => { }); const id = await this.storage.get("selected-wallet"); const wallet = this.wallets.find((wallet) => wallet.manifest.id === id); if (!wallet) throw new Error("No wallet selected"); const accounts = await wallet.getAccounts(); if (!accounts?.length) throw new Error("No accounts found"); return { wallet, accounts }; } async wallet(id) { await this.whenManifestLoaded.catch(() => { }); if (!id) { return this.getConnectedWallet() .then(({ wallet }) => wallet) .catch(async () => { await this.storage.remove("selected-wallet"); throw new Error("No accounts found"); }); } const wallet = this.wallets.find((wallet) => wallet.manifest.id === id); if (!wallet) throw new Error("Wallet not found"); return wallet; } async use(plugin) { await this.whenManifestLoaded.catch(() => { }); this.wallets = this.wallets.map((wallet) => { return new Proxy(wallet, { get(target, prop, receiver) { const originalValue = Reflect.get(target, prop, receiver); // If plugin has this method and it's a function on the wallet if (prop in plugin && typeof originalValue === "function") { const pluginMethod = plugin[prop]; // Act as middleware, can call next method in line via next() return function (...args) { const next = () => originalValue.apply(target, args); // Pass all args if any exist, otherwise undefined // this ensures next is always the last param return args.length > 0 ? pluginMethod.call(this, ...args, next) : pluginMethod.call(this, undefined, next); }; } return originalValue; }, }); }); } on(event, callback) { this.events.on(event, callback); } once(event, callback) { this.events.once(event, callback); } off(event, callback) { this.events.off(event, callback); } removeAllListeners(event) { this.events.removeAllListeners(event); } } exports.NearConnector = NearConnector; //# sourceMappingURL=NearConnector.js.map