@worldcoin/idkit-core
Version:
The identity SDK. Privacy-preserving identity and proof of personhood with World ID.
359 lines (348 loc) • 12.3 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/platform.ts
import { isBrowser, isNode as isNodeBoolean } from "browser-or-node";
var isReactNative = () => {
return typeof navigator !== "undefined" && navigator.product === "ReactNative";
};
var isWeb = () => {
return isBrowser;
};
var getGlobalObject = () => {
if (typeof globalThis !== "undefined") return globalThis;
if (typeof self !== "undefined") return self;
if (typeof window !== "undefined") return window;
throw new Error("Unable to locate global object");
};
var getCrypto = () => {
const globalObj = getGlobalObject();
if (typeof globalObj.crypto !== "undefined") {
return globalObj.crypto;
}
throw new Error("Crypto API not available. For React Native, ensure polyfills are set up properly.");
};
// 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/adapters/web-crypto-adapter.ts
var WebCryptoAdapter = class {
constructor() {
this.encoder = new TextEncoder();
this.decoder = new TextDecoder();
}
async generateKey() {
return {
iv: window.crypto.getRandomValues(new Uint8Array(12)),
key: await window.crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"])
};
}
async exportKey(key) {
return buffer_encode(await window.crypto.subtle.exportKey("raw", key));
}
async encryptRequest(key, iv, request) {
return {
iv: buffer_encode(iv),
payload: buffer_encode(
await window.crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, this.encoder.encode(request))
)
};
}
async decryptResponse(key, iv, payload) {
return this.decoder.decode(
await window.crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, buffer_decode(payload))
);
}
};
// src/lib/adapters/react-native-crypto-adapter.ts
var ReactNativeCryptoAdapter = class {
constructor() {
this.encoder = new TextEncoder();
this.decoder = new TextDecoder();
}
async generateKey() {
const crypto = getCrypto();
return {
iv: crypto.getRandomValues(new Uint8Array(12)),
key: await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"])
};
}
async exportKey(key) {
const crypto = getCrypto();
return buffer_encode(await crypto.subtle.exportKey("raw", key));
}
async encryptRequest(key, iv, request) {
const crypto = getCrypto();
return {
iv: buffer_encode(iv),
payload: buffer_encode(
await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, this.encoder.encode(request))
)
};
}
async decryptResponse(key, iv, payload) {
const crypto = getCrypto();
return this.decoder.decode(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, buffer_decode(payload)));
}
};
// src/lib/crypto.ts
var cryptoAdapterInstance = null;
var getCryptoAdapter = () => {
if (cryptoAdapterInstance) {
return cryptoAdapterInstance;
}
if (isWeb()) {
cryptoAdapterInstance = new WebCryptoAdapter();
return cryptoAdapterInstance;
}
if (isReactNative()) {
cryptoAdapterInstance = new ReactNativeCryptoAdapter();
return cryptoAdapterInstance;
}
throw new Error("Unsupported platform");
};
var generateKey = async () => {
return getCryptoAdapter().generateKey();
};
var exportKey = async (key) => {
return getCryptoAdapter().exportKey(key);
};
var encryptRequest = async (key, iv, request) => {
return getCryptoAdapter().encryptRequest(key, iv, request);
};
var decryptResponse = async (key, iv, payload) => {
return getCryptoAdapter().decryptResponse(key, iv, payload);
};
// src/bridge.ts
var DEFAULT_BRIDGE_URL = "https://bridge.worldcoin.org";
var createStoreImplementation = (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 */
});
}
});
var useWorldBridgeStore = create(createStoreImplementation);
var createWorldBridgeStore = () => create(createStoreImplementation);
export {
AppErrorCodes,
CredentialType,
DEFAULT_VERIFICATION_LEVEL,
VerificationLevel,
VerificationState,
createWorldBridgeStore,
useWorldBridgeStore,
verification_level_to_credential_types
};