idkit-core
Version:
The identity SDK. Privacy-preserving identity and proof of personhood with World ID.
292 lines (283 loc) • 10.1 kB
JavaScript
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
};