UNPKG

idkit-core

Version:

The identity SDK. Privacy-preserving identity and proof of personhood with World ID.

292 lines (283 loc) 10.1 kB
import { encodeAction, generateSignal } from "./chunk-HZ2SQA5V.js"; // src/types/bridge.ts var AppErrorCodes = /* @__PURE__ */ ((AppErrorCodes2) => { AppErrorCodes2["ConnectionFailed"] = "connection_failed"; AppErrorCodes2["VerificationRejected"] = "verification_rejected"; AppErrorCodes2["MaxVerificationsReached"] = "max_verifications_reached"; AppErrorCodes2["CredentialUnavailable"] = "credential_unavailable"; AppErrorCodes2["MalformedRequest"] = "malformed_request"; AppErrorCodes2["InvalidNetwork"] = "invalid_network"; AppErrorCodes2["InclusionProofFailed"] = "inclusion_proof_failed"; AppErrorCodes2["InclusionProofPending"] = "inclusion_proof_pending"; AppErrorCodes2["UnexpectedResponse"] = "unexpected_response"; AppErrorCodes2["FailedByHostApp"] = "failed_by_host_app"; AppErrorCodes2["GenericError"] = "generic_error"; return AppErrorCodes2; })(AppErrorCodes || {}); var VerificationState = /* @__PURE__ */ ((VerificationState2) => { VerificationState2["PreparingClient"] = "loading_widget"; VerificationState2["WaitingForConnection"] = "awaiting_connection"; VerificationState2["WaitingForApp"] = "awaiting_app"; VerificationState2["Confirmed"] = "confirmed"; VerificationState2["Failed"] = "failed"; return VerificationState2; })(VerificationState || {}); // src/types/config.ts var CredentialType = /* @__PURE__ */ ((CredentialType2) => { CredentialType2["Orb"] = "orb"; CredentialType2["SecureDocument"] = "secure_document"; CredentialType2["Document"] = "document"; CredentialType2["Device"] = "device"; return CredentialType2; })(CredentialType || {}); var VerificationLevel = /* @__PURE__ */ ((VerificationLevel2) => { VerificationLevel2["Orb"] = "orb"; VerificationLevel2["SecureDocument"] = "secure_document"; VerificationLevel2["Document"] = "document"; VerificationLevel2["Device"] = "device"; return VerificationLevel2; })(VerificationLevel || {}); // src/bridge.ts import { create } from "zustand"; // src/lib/validation.ts function validate_bridge_url(bridge_url, is_staging) { try { new URL(bridge_url); } catch (e) { return { valid: false, errors: ["Failed to parse Bridge URL."] }; } const test_url = new URL(bridge_url); const errors = []; if (is_staging && ["localhost", "127.0.0.1"].includes(test_url.hostname)) { console.log("Using staging app_id with localhost bridge_url. Skipping validation."); return { valid: true }; } if (test_url.protocol !== "https:") { errors.push("Bridge URL must use HTTPS."); } if (test_url.port) { errors.push("Bridge URL must use the default port (443)."); } if (test_url.pathname !== "/") { errors.push("Bridge URL must not have a path."); } if (test_url.search) { errors.push("Bridge URL must not have query parameters."); } if (test_url.hash) { errors.push("Bridge URL must not have a fragment."); } if (!test_url.hostname.endsWith(".worldcoin.org") && !test_url.hostname.endsWith(".toolsforhumanity.com")) { console.warn( "Bridge URL should be a subdomain of worldcoin.org or toolsforhumanity.com. The user's identity wallet may refuse to connect. This is a temporary measure and may be removed in the future." ); } if (errors.length) { return { valid: false, errors }; } return { valid: true }; } // src/lib/crypto.ts import { isBrowser } from "browser-or-node"; // src/lib/utils.ts import { Buffer } from "buffer/index.js"; var DEFAULT_VERIFICATION_LEVEL = "orb" /* Orb */; var buffer_encode = (buffer) => { return Buffer.from(buffer).toString("base64"); }; var buffer_decode = (encoded) => { return Buffer.from(encoded, "base64"); }; var verification_level_to_credential_types = (verification_level) => { switch (verification_level) { case "device" /* Device */: return ["orb" /* Orb */, "device" /* Device */]; case "document" /* Document */: return ["document" /* Document */, "secure_document" /* SecureDocument */, "orb" /* Orb */]; case "secure_document" /* SecureDocument */: return ["secure_document" /* SecureDocument */, "orb" /* Orb */]; case "orb" /* Orb */: return ["orb" /* Orb */]; default: throw new Error(`Unknown verification level: ${verification_level}`); } }; var credential_type_to_verification_level = (credential_type) => { switch (credential_type) { case "orb" /* Orb */: return "orb" /* Orb */; case "secure_document" /* SecureDocument */: return "secure_document" /* SecureDocument */; case "document" /* Document */: return "document" /* Document */; case "device" /* Device */: return "device" /* Device */; default: throw new Error(`Unknown credential_type: ${credential_type}`); } }; // src/lib/crypto.ts var getTextEncoderDecoder = () => { try { return { encoder: new TextEncoder(), decoder: new TextDecoder() }; } catch (e) { throw new Error( "TextEncoder/TextDecoder not available. For React Native, install and import the text-encoding polyfill" ); } }; var { encoder, decoder } = getTextEncoderDecoder(); var getCrypto = () => { if (isBrowser) { return window.crypto; } if (typeof globalThis !== "undefined" && "crypto" in globalThis) { return globalThis.crypto; } throw new Error("Web Crypto API not available. For React Native, install and import react-native-get-random-values"); }; var generateKey = async () => { const crypto = getCrypto(); return { iv: crypto.getRandomValues(new Uint8Array(12)), key: await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]) }; }; var exportKey = async (key) => { const crypto = getCrypto(); return buffer_encode(await crypto.subtle.exportKey("raw", key)); }; var encryptRequest = async (key, iv, request) => { const crypto = getCrypto(); return { iv: buffer_encode(iv), payload: buffer_encode(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoder.encode(request))) }; }; var decryptResponse = async (key, iv, payload) => { const crypto = getCrypto(); return decoder.decode(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, buffer_decode(payload))); }; // src/bridge.ts var DEFAULT_BRIDGE_URL = "https://bridge.worldcoin.org"; var useWorldBridgeStore = create((set, get) => ({ iv: null, key: null, result: null, errorCode: null, requestId: null, connectorURI: null, bridge_url: DEFAULT_BRIDGE_URL, verificationState: "loading_widget" /* PreparingClient */, createClient: async ({ bridge_url, app_id, verification_level, action_description, action, signal, partner }) => { const { key, iv } = await generateKey(); if (bridge_url) { const validation = validate_bridge_url(bridge_url, app_id.includes("staging")); if (!validation.valid) { console.error(validation.errors.join("\n")); set({ verificationState: "failed" /* Failed */ }); throw new Error("Invalid bridge_url. Please check the console for more details."); } } const res = await fetch(new URL("/request", bridge_url ?? DEFAULT_BRIDGE_URL), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify( await encryptRequest( key, iv, JSON.stringify({ app_id, action_description, action: encodeAction(action), signal: generateSignal(signal).digest, credential_types: verification_level_to_credential_types( verification_level ?? DEFAULT_VERIFICATION_LEVEL ), verification_level: verification_level ?? DEFAULT_VERIFICATION_LEVEL }) ) ) }); if (!res.ok) { set({ verificationState: "failed" /* Failed */ }); throw new Error("Failed to create client"); } const { request_id } = await res.json(); set({ iv, key, requestId: request_id, bridge_url: bridge_url ?? DEFAULT_BRIDGE_URL, verificationState: "awaiting_connection" /* WaitingForConnection */, connectorURI: `https://world.org/verify?t=wld&i=${request_id}&k=${encodeURIComponent( await exportKey(key) )}${bridge_url && bridge_url !== DEFAULT_BRIDGE_URL ? `&b=${encodeURIComponent(bridge_url)}` : ""}${partner ? `&partner=${encodeURIComponent(true)}` : ""}` }); }, pollForUpdates: async () => { const key = get().key; if (!key) throw new Error("No keypair found. Please call `createClient` first."); const res = await fetch(new URL(`/response/${get().requestId}`, get().bridge_url)); if (!res.ok) { return set({ errorCode: "connection_failed" /* ConnectionFailed */, verificationState: "failed" /* Failed */ }); } const { response, status } = await res.json(); if (status != "completed" /* Completed */) { return set({ verificationState: status == "retrieved" /* Retrieved */ ? "awaiting_app" /* WaitingForApp */ : "awaiting_connection" /* WaitingForConnection */ }); } let result = JSON.parse( await decryptResponse(key, buffer_decode(response.iv), response.payload) ); if ("error_code" in result) { return set({ errorCode: result.error_code, verificationState: "failed" /* Failed */ }); } if ("credential_type" in result) { result = { verification_level: credential_type_to_verification_level(result.credential_type), ...result }; } set({ result, key: null, requestId: null, connectorURI: null, verificationState: "confirmed" /* Confirmed */ }); }, reset: () => { set({ iv: null, key: null, result: null, errorCode: null, requestId: null, connectorURI: null, verificationState: "loading_widget" /* PreparingClient */ }); } })); export { AppErrorCodes, CredentialType, DEFAULT_VERIFICATION_LEVEL, VerificationLevel, VerificationState, useWorldBridgeStore, verification_level_to_credential_types };