UNPKG

@worldcoin/minikit-js

Version:

minikit-js is our SDK for building mini-apps.

613 lines (608 loc) 19 kB
import { EventManager, MiniKitInstallErrorMessage, attestation, chat, closeMiniApp, getPermissions, pay, requestPermission, sendHapticFeedback, sendTransaction, share, shareContacts, signMessage, signTypedData, walletAuth } from "./chunk-AH3WTRZY.js"; import { isInWorldApp, sendMiniKitEvent, validateCommands } from "./chunk-DZ2YRSIU.js"; // src/helpers/microphone.ts var microphoneSetupDone = false; var setupMicrophone = () => { if (microphoneSetupDone) { return; } if (typeof navigator !== "undefined" && !navigator.mediaDevices?.getUserMedia) return; const originalStop = MediaStreamTrack.prototype.stop; MediaStreamTrack.prototype.stop = function() { originalStop.call(this); if (this.readyState === "ended") { setTimeout(() => this.dispatchEvent(new Event("ended")), 0); } }; const realGUM = navigator.mediaDevices.getUserMedia.bind( navigator.mediaDevices ); const live = /* @__PURE__ */ new Set(); async function wrapped(constraints) { const stream = await realGUM(constraints); const hasAudioTrack = stream.getAudioTracks().length > 0; if (hasAudioTrack) { sendMiniKitEvent({ command: "microphone-stream-started", version: 1, payload: { streamId: stream.id } }); live.add(stream); stream.getAudioTracks().forEach((t) => { t.addEventListener("ended", () => { const allAudioTracksEnded = stream.getAudioTracks().every((track) => track.readyState === "ended"); if (allAudioTracksEnded) { sendMiniKitEvent({ command: "microphone-stream-ended", version: 1, payload: { streamId: stream.id } }); live.delete(stream); } }); }); } return stream; } Object.defineProperty(navigator.mediaDevices, "getUserMedia", { value: wrapped, writable: false, configurable: false, enumerable: true }); Object.freeze(navigator.mediaDevices); const stopAllMiniAppMicrophoneStreams = () => { live.forEach((s) => { const audioTracks = s.getAudioTracks(); if (audioTracks.length > 0) { audioTracks.forEach((t) => { t.stop(); }); sendMiniKitEvent({ command: "microphone-stream-ended", version: 1, payload: { streamId: s.id } }); } }); live.clear(); }; MiniKit.subscribe("miniapp-microphone" /* MiniAppMicrophone */, (payload) => { if (payload.status === "error" && (payload.error_code === "mini_app_permission_not_enabled" /* MiniAppPermissionNotEnabled */ || payload.error_code === "world_app_permission_not_enabled" /* WorldAppPermissionNotEnabled */)) { console.log("stopping all microphone streams", payload); stopAllMiniAppMicrophoneStreams(); } }); window.__stopAllMiniAppMicrophoneStreams = stopAllMiniAppMicrophoneStreams; microphoneSetupDone = true; }; // src/helpers/usernames.ts var getUserProfile = async (address) => { const res = await fetch("https://usernames.worldcoin.org/api/v1/query", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ addresses: [address] }) }); const usernames = await res.json(); return usernames?.[0] ?? { username: null, profile_picture_url: null }; }; // src/minikit.ts var MINIKIT_VERSION = 1; var MINIKIT_MINOR_VERSION = 96; var WORLD_APP_LAUNCH_LOCATION_MAP = { "app-store": "app-store" /* AppStore */, carousel: "app-store" /* AppStore */, explore: "app-store" /* AppStore */, app_details: "app-store" /* AppStore */, deeplink: "deep-link" /* DeepLink */, homepage: "home" /* Home */, wallet_tab: "wallet-tab" /* WalletTab */, world_chat: "chat" /* Chat */ }; function mapWorldAppLaunchLocation(location) { if (!location) return null; const raw = typeof location === "object" && "open_origin" in location ? location.open_origin : typeof location === "string" ? location : null; if (!raw) return null; console.log("MiniKit launch location mapped:", raw); return WORLD_APP_LAUNCH_LOCATION_MAP[raw.toLowerCase()] ?? null; } var _MiniKit = class _MiniKit { static getActiveMiniKit() { if (typeof window === "undefined") return this; const candidate = window.MiniKit; if (candidate && typeof candidate.trigger === "function") { return candidate; } return this; } // ============================================================================ // Unified API (auto-detects environment) // ============================================================================ /** * Authenticate user via wallet signature (SIWE) * * Works in World App (native SIWE) and web (Wagmi + SIWE fallback). * * @example * ```typescript * const result = await MiniKit.walletAuth({ nonce: 'randomnonce123' }); * console.log(result.data.address); * console.log(result.executedWith); // 'minikit' | 'wagmi' | 'fallback' * ``` */ static walletAuth(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.walletAuth(options); } return walletAuth(options, this.getContext()); } /** * Send one or more transactions * * World App: batch + permit2 + gas sponsorship * Web: sequential execution via Wagmi * * @example * ```typescript * const result = await MiniKit.sendTransaction({ * chainId: 480, * transactions: [{ * to: '0x...', * data: '0x...', * value: '0x0', * }], * }); * ``` */ static sendTransaction(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.sendTransaction(options); } return sendTransaction(options, this.getContext()); } /** * Send a payment (World App only) * * Requires custom fallback on web. * * @example * ```typescript * const result = await MiniKit.pay({ * reference: crypto.randomUUID(), * to: '0x...', * tokens: [{ symbol: Tokens.WLD, token_amount: '1.0' }], * description: 'Payment for coffee', * fallback: () => showStripeCheckout(), * }); * ``` */ static pay(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.pay(options); } return pay(options, this.getContext()); } /** * Open the contact picker (World App only) * * Requires custom fallback on web. * * @example * ```typescript * const result = await MiniKit.shareContacts({ * isMultiSelectEnabled: true, * fallback: () => showManualAddressInput(), * }); * ``` */ static shareContacts(options = {}) { const active = this.getActiveMiniKit(); if (active !== this) { return active.shareContacts(options); } return shareContacts(options, this.getContext()); } /** * Sign a message */ static signMessage(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.signMessage(options); } return signMessage(options, this.getContext()); } /** * Sign typed data (EIP-712) */ static signTypedData(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.signTypedData(options); } return signTypedData(options, this.getContext()); } /** * Send a chat message */ static chat(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.chat(options); } return chat(options, this.getContext()); } /** * Share files/text/URL */ static share(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.share(options); } return share(options, this.getContext()); } /** * Get current permission settings */ static getPermissions(options = {}) { const active = this.getActiveMiniKit(); if (active !== this) { return active.getPermissions(options); } return getPermissions(options, this.getContext()); } /** * Request a permission from the user */ static requestPermission(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.requestPermission(options); } return requestPermission(options, this.getContext()); } /** * Trigger haptic feedback */ static sendHapticFeedback(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.sendHapticFeedback(options); } return sendHapticFeedback(options, this.getContext()); } /** * Request app attestation token for a request hash */ static attestation(options) { const active = this.getActiveMiniKit(); if (active !== this) { return active.attestation(options); } return attestation(options, this.getContext()); } /** * Close the mini app */ static closeMiniApp(options = {}) { const active = this.getActiveMiniKit(); if (active !== this) { return active.closeMiniApp(options); } return closeMiniApp(options, this.getContext()); } // ============================================================================ // Public State Accessors // ============================================================================ static get appId() { return this._appId; } static set appId(value) { this._appId = value; } static get user() { return this._user; } static set user(value) { this._user = value; } static get deviceProperties() { return this._deviceProperties; } static get location() { return this._location; } // ============================================================================ // Event System // ============================================================================ static subscribe(event, handler) { const active = this.getActiveMiniKit(); if (active !== this) { active.subscribe(event, handler); return; } if (event === "miniapp-wallet-auth" /* MiniAppWalletAuth */) { const originalHandler = handler; const wrappedHandler = async (payload) => { if (payload.status === "success") { await this.updateUserFromWalletAuth(payload.address); } originalHandler(payload); }; this.eventManager.subscribe(event, wrappedHandler); } else { this.eventManager.subscribe(event, handler); } } static unsubscribe(event) { const active = this.getActiveMiniKit(); if (active !== this) { active.unsubscribe(event); return; } this.eventManager.unsubscribe(event); } static trigger(event, payload) { const active = this.getActiveMiniKit(); if (active !== this) { active.trigger(event, payload); return; } this.eventManager.trigger(event, payload); } // ============================================================================ // Installation // ============================================================================ static sendInit() { sendMiniKitEvent({ command: "init", payload: { version: MINIKIT_VERSION, minorVersion: MINIKIT_MINOR_VERSION } }); } static install(appId) { const active = this.getActiveMiniKit(); if (active !== this) { return active.install(appId); } if (typeof window === "undefined") { return { success: false, errorCode: "already_installed" /* AlreadyInstalled */, errorMessage: MiniKitInstallErrorMessage["already_installed" /* AlreadyInstalled */] }; } if (!appId) { console.warn("App ID not provided during install"); } else { this._appId = appId; } if (!window.WorldApp) { return { success: false, errorCode: "outside_of_worldapp" /* OutsideOfWorldApp */, errorMessage: MiniKitInstallErrorMessage["outside_of_worldapp" /* OutsideOfWorldApp */] }; } this.initFromWorldApp(window.WorldApp); try { window.MiniKit = this; this.sendInit(); } catch (error) { console.error( MiniKitInstallErrorMessage["unknown" /* Unknown */], error ); return { success: false, errorCode: "unknown" /* Unknown */, errorMessage: MiniKitInstallErrorMessage["unknown" /* Unknown */] }; } this._isReady = true; setupMicrophone(); if (!validateCommands(window.WorldApp.supported_commands)) { return { success: false, errorCode: "app_out_of_date" /* AppOutOfDate */, errorMessage: MiniKitInstallErrorMessage["app_out_of_date" /* AppOutOfDate */] }; } return { success: true }; } static isInstalled(debug) { const isInstalled = this._isReady && Boolean(window.MiniKit); if (!isInstalled) { console.warn( "MiniKit is not installed. Make sure you're running the application inside of World App" ); } if (debug && isInstalled) { console.log("MiniKit is alive!"); } return isInstalled; } // ============================================================================ // Internal // ============================================================================ static initFromWorldApp(worldApp) { if (!worldApp) return; this._user = {}; this._deviceProperties = {}; this._user.optedIntoOptionalAnalytics = worldApp.is_optional_analytics; this._user.preferredCurrency = worldApp.preferred_currency; this._user.pendingNotifications = worldApp.pending_notifications; this._user.walletAddress = worldApp.wallet_address; if (worldApp.verification_status) { this._user.verificationStatus = { isOrbVerified: worldApp.verification_status.is_orb_verified, isDocumentVerified: worldApp.verification_status.is_document_verified, isSecureDocumentVerified: worldApp.verification_status.is_secure_document_verified }; } this._deviceProperties.safeAreaInsets = worldApp.safe_area_insets; this._deviceProperties.deviceOS = worldApp.device_os; this._deviceProperties.worldAppVersion = worldApp.world_app_version; this._location = mapWorldAppLaunchLocation(worldApp.location); } static async updateUserFromWalletAuth(address) { this._user.walletAddress = address; try { const userProfile = await getUserProfile(address); this._user.username = userProfile.username; this._user.profilePictureUrl = userProfile.profile_picture_url; } catch (error) { console.error("Failed to fetch user profile:", error); } } static getContext() { return { events: this.eventManager, state: { deviceProperties: this._deviceProperties } }; } // ============================================================================ // Deprecated — remove in next major // ============================================================================ /** * @deprecated Use `MiniKit.pay()`, `MiniKit.walletAuth()`, etc. directly. * * Migration guide: * - `MiniKit.commands.pay(payload)` → `await MiniKit.pay(options)` * - `MiniKit.commands.walletAuth(payload)` → `await MiniKit.walletAuth(options)` * - `MiniKit.commands.sendTransaction(payload)` → `await MiniKit.sendTransaction(options)` * - `MiniKit.commands.signMessage(payload)` → `await MiniKit.signMessage(input)` * - `MiniKit.commands.signTypedData(payload)` → `await MiniKit.signTypedData(input)` * - `MiniKit.commands.shareContacts(payload)` → `await MiniKit.shareContacts(options)` * - `MiniKit.commands.chat(payload)` → `await MiniKit.chat(input)` * - `MiniKit.commands.share(payload)` → `await MiniKit.share(input)` * - `MiniKit.commands.getPermissions()` → `await MiniKit.getPermissions()` * - `MiniKit.commands.requestPermission(payload)` → `await MiniKit.requestPermission(input)` * - `MiniKit.commands.sendHapticFeedback(payload)` → `await MiniKit.sendHapticFeedback(input)` * - `MiniKit.commands.attestation(payload)` → `await MiniKit.attestation(options)` * - `MiniKit.commands.closeMiniApp()` → `await MiniKit.closeMiniApp()` */ static get commands() { throw new Error( "MiniKit.commands has been removed. Use MiniKit.pay(), MiniKit.walletAuth(), etc. directly." ); } /** * @deprecated Use `MiniKit.pay()`, `MiniKit.walletAuth()`, etc. directly. All commands are now async by default. * * See `MiniKit.commands` deprecation notice for the full migration guide. */ static get commandsAsync() { throw new Error( "MiniKit.commandsAsync has been removed. Use MiniKit.pay(), MiniKit.walletAuth(), etc. directly." ); } }; _MiniKit.eventManager = new EventManager(); // State (was MiniKitState) _MiniKit._appId = null; _MiniKit._user = {}; _MiniKit._deviceProperties = {}; _MiniKit._location = null; _MiniKit._isReady = false; /** * Check if running inside World App */ _MiniKit.isInWorldApp = isInWorldApp; // ============================================================================ // Utility Methods // ============================================================================ _MiniKit.getUserByAddress = async (address) => { const walletAddress = address ?? _MiniKit._user.walletAddress; const userProfile = await getUserProfile(walletAddress); return { walletAddress, username: userProfile.username, profilePictureUrl: userProfile.profile_picture_url }; }; _MiniKit.getUserByUsername = async (username) => { const res = await fetch( `https://usernames.worldcoin.org/api/v1/${username}`, { method: "GET", headers: { "Content-Type": "application/json" } } ); const user = await res.json(); return { walletAddress: user.address, username: user.username, profilePictureUrl: user.profile_picture_url }; }; _MiniKit.getUserInfo = _MiniKit.getUserByAddress; _MiniKit.getMiniAppUrl = (appId, path) => { const baseUrl = new URL("https://world.org/mini-app"); baseUrl.searchParams.append("app_id", appId); if (path) { const fullPath = path.startsWith("/") ? path : `/${path}`; baseUrl.searchParams.append("path", encodeURIComponent(fullPath)); } return baseUrl.toString(); }; _MiniKit.showProfileCard = (username, walletAddress) => { if (!username && !walletAddress) { console.error( "Either username or walletAddress must be provided to show profile card" ); return; } if (username) { window.open( `worldapp://profile?username=${encodeURIComponent(username)}` ); } else { window.open( `worldapp://profile?address=${encodeURIComponent(walletAddress || "")}` ); } }; var MiniKit = _MiniKit; export { MiniKit };