Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 85 additions & 1 deletion js/packages/core/src/__tests__/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* These tests verify that the WASM integration and core APIs are functional
*/

import { describe, it, expect } from "vitest";
import { afterEach, describe, it, expect } from "vitest";
import {
IDKit,
CredentialRequest,
Expand All @@ -19,7 +19,10 @@ import {
IDKitErrorCodes,
signRequest,
hashSignal,
setDebug,
type IDKitDebugTransport,
} from "../index";
import { createIDKitDebugReport } from "../lib/debug";
import { initIDKit, WasmModule } from "../lib/wasm";

const TEST_SESSION_ID = `session_${"11".repeat(64)}` as const;
Expand Down Expand Up @@ -292,6 +295,87 @@ describe("IDKitRequest API", () => {
});
});

describe("Debug reports", () => {
afterEach(() => {
setDebug(false);
delete (globalThis as any).window;
});

it("does not build reports when debug mode is disabled", () => {
setDebug(false);

expect(
createIDKitDebugReport({
transport: "bridge",
requestPayload: { proof_request: { id: "req_1" } },
}),
).toBeUndefined();
});

it("builds a bridge snapshot from the provided request data", () => {
setDebug(true);

const payload = { proof_request: { id: "req_1" } };
const response = {
type: "confirmed",
result: { protocol_version: "4.0", verification_level: "Orb" },
};

const report = createIDKitDebugReport({
transport: "bridge",
requestPayload: payload,
responsePayload: response,
requestId: "req_1",
connectorURI: "https://world.org/verify?t=wld&i=req_1",
});

expect(report).toMatchObject({
package_version: expect.any(String),
transport: "bridge",
timestamps: { generated_at: expect.any(String) },
request_id: "req_1",
connector_uri: "https://world.org/verify?t=wld&i=req_1",
request_payload: payload,
response_payload: response,
});
});

it("only includes Mini App metadata on Mini App reports", () => {
setDebug(true);
(globalThis as any).window = {
WorldApp: {
world_app_version: "2026.6.16",
device_os: "ios",
},
};

expect(
createIDKitDebugReport({
transport: "bridge",
requestPayload: { proof_request: { id: "req_1" } },
}),
).not.toHaveProperty("mini_app");

expect(
createIDKitDebugReport({
transport: "mini_app",
requestPayload: { command: "verify" },
}),
).toMatchObject({
mini_app: {
world_app_version: "2026.6.16",
platform: "ios",
},
});
});

it("exports debug transport type from the package entrypoint", () => {
const transport: IDKitDebugTransport = "bridge";

expect(transport).toBe("bridge");
});
});

describe("Enums", () => {
it("should export IDKitErrorCodes enum", () => {
expect(IDKitErrorCodes.ConnectionFailed).toBe("connection_failed");
Expand Down
7 changes: 6 additions & 1 deletion js/packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ export type { IDKitErrorCode } from "./types/result";
// Utilities
export { isReactNative, isWeb, isNode } from "./lib/platform";
export { isInWorldApp } from "./transports/native";
export { isDebug, setDebug } from "./lib/debug";
export {
isDebug,
setDebug,
type IDKitDebugReport,
type IDKitDebugTransport,
} from "./lib/debug";

// Session utilities
export { getSessionCommitment } from "./session";
Expand Down
63 changes: 63 additions & 0 deletions js/packages/core/src/lib/debug.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import packageJson from "../../package.json";

let _debug = false;

export type IDKitDebugTransport = "bridge" | "mini_app";

export type IDKitDebugReport = {
package_version: string;
transport: IDKitDebugTransport;
timestamps: { generated_at: string };
request_id?: string;
connector_uri?: string;
request_payload?: object;
response_payload?: object;
mini_app?: Record<string, unknown>;
};

export function isDebug(): boolean {
if (_debug) return true;
return typeof window !== "undefined" && Boolean((window as any).IDKIT_DEBUG);
Expand All @@ -8,3 +23,51 @@ export function isDebug(): boolean {
export function setDebug(enabled: boolean): void {
_debug = enabled;
}

export function createIDKitDebugReport(options: {
transport: IDKitDebugTransport;
requestPayload?: unknown;
responsePayload?: unknown;
requestId?: string;
connectorURI?: string;
}): IDKitDebugReport | undefined {
if (!isDebug()) return undefined;

const now = new Date().toISOString();
const report: IDKitDebugReport = {
package_version: packageJson.version,
transport: options.transport,
timestamps: { generated_at: now },
};

if (options.transport === "mini_app") {
const miniApp = getMiniAppDebugInfo();
if (miniApp) report.mini_app = miniApp;
}

if (options.requestPayload && typeof options.requestPayload === "object") {
report.request_payload = options.requestPayload;
}
if (options.responsePayload && typeof options.responsePayload === "object") {
report.response_payload = options.responsePayload;
}
if (options.requestId) report.request_id = options.requestId;
if (options.connectorURI) report.connector_uri = options.connectorURI;
return report;
}

function getMiniAppDebugInfo(): Record<string, unknown> | undefined {
const worldApp =
typeof window !== "undefined" ? (window as any).WorldApp : undefined;
if (!worldApp || typeof worldApp !== "object") return undefined;

const miniApp: Record<string, unknown> = {};
if (worldApp.world_app_version !== undefined) {
miniApp.world_app_version = worldApp.world_app_version;
}
if (worldApp.device_os !== undefined) {
miniApp.platform = worldApp.device_os;
}

return Object.keys(miniApp).length > 0 ? miniApp : undefined;
}
112 changes: 80 additions & 32 deletions js/packages/core/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import {
createNativeRequest,
type BuilderConfig,
} from "./transports/native";
import {
createIDKitDebugReport,
isDebug,
type IDKitDebugReport,
} from "./lib/debug";

/** Options for pollUntilCompletion() */
export interface WaitOptions {
Expand Down Expand Up @@ -70,6 +75,8 @@ export interface IDKitRequest {
pollOnce(): Promise<Status>;
/** Poll continuously until completion or timeout */
pollUntilCompletion(options?: WaitOptions): Promise<IDKitCompletionResult>;
/** Debug report for this request when debug mode is enabled. May include sensitive data. */
getDebugReport(): IDKitDebugReport | undefined;
}

/**
Expand Down Expand Up @@ -112,35 +119,74 @@ async function pollUntilCompletionLoop(
}
}

type BridgeDebugPayloadSource = {
debugPayload?: () => unknown;
};

function readBridgeDebugPayload(
wasmRequest: BridgeDebugPayloadSource,
): unknown {
if (typeof wasmRequest.debugPayload !== "function") {
return undefined;
}

try {
return wasmRequest.debugPayload();
} catch {
return undefined;
}
}

function createBridgeDebugReport(
wasmRequest: BridgeDebugPayloadSource,
requestId: string,
connectorURI: string,
responsePayload?: Status,
): IDKitDebugReport | undefined {
if (!isDebug()) return undefined;

return createIDKitDebugReport({
transport: "bridge",
requestPayload: readBridgeDebugPayload(wasmRequest),
responsePayload,
requestId,
connectorURI,
});
}

/**
* Internal request implementation (bridge/WASM path)
*/
class IDKitRequestImpl implements IDKitRequest {
private wasmRequest: WasmModule.IDKitRequest;
private _connectorURI: string;
private _requestId: string;
private latestResponsePayload?: Status;
readonly connectorURI: string;
readonly requestId: string;

constructor(wasmRequest: WasmModule.IDKitRequest) {
this.wasmRequest = wasmRequest;
this._connectorURI = wasmRequest.connectUrl();
this._requestId = wasmRequest.requestId();
}

get connectorURI(): string {
return this._connectorURI;
}

get requestId(): string {
return this._requestId;
this.connectorURI = wasmRequest.connectUrl();
this.requestId = wasmRequest.requestId();
}

async pollOnce(): Promise<Status> {
return (await this.wasmRequest.pollForStatus()) as Status;
const status = (await this.wasmRequest.pollForStatus()) as Status;
this.latestResponsePayload = status;
return status;
}

pollUntilCompletion(options?: WaitOptions): Promise<IDKitCompletionResult> {
return pollUntilCompletionLoop(() => this.pollOnce(), options);
}

getDebugReport(): IDKitDebugReport | undefined {
return createBridgeDebugReport(
this.wasmRequest,
this.requestId,
this.connectorURI,
this.latestResponsePayload,
);
}
}

/**
Expand All @@ -163,6 +209,8 @@ export interface IDKitInviteCodeRequest {
pollOnce(): Promise<Status>;
/** Poll continuously until completion or timeout */
pollUntilCompletion(options?: WaitOptions): Promise<IDKitCompletionResult>;
/** Debug report for this request when debug mode is enabled. May include sensitive data. */
getDebugReport(): IDKitDebugReport | undefined;
}

/**
Expand All @@ -172,36 +220,36 @@ export interface IDKitInviteCodeRequest {
*/
class IDKitInviteCodeRequestImpl implements IDKitInviteCodeRequest {
private wasmRequest: WasmModule.IDKitInviteCodeRequest;
private _connectorURI: string;
private _expiresAt: number;
private _requestId: string;
private latestResponsePayload?: Status;
readonly connectorURI: string;
readonly expiresAt: number;
readonly requestId: string;

constructor(wasmRequest: WasmModule.IDKitInviteCodeRequest) {
this.wasmRequest = wasmRequest;
this._connectorURI = wasmRequest.connectUrl();
this._expiresAt = wasmRequest.expiresAt();
this._requestId = wasmRequest.requestId();
}

get connectorURI(): string {
return this._connectorURI;
}

get expiresAt(): number {
return this._expiresAt;
}

get requestId(): string {
return this._requestId;
this.connectorURI = wasmRequest.connectUrl();
this.expiresAt = wasmRequest.expiresAt();
this.requestId = wasmRequest.requestId();
}

async pollOnce(): Promise<Status> {
return (await this.wasmRequest.pollForStatus()) as Status;
const status = (await this.wasmRequest.pollForStatus()) as Status;
this.latestResponsePayload = status;
return status;
}

pollUntilCompletion(options?: WaitOptions): Promise<IDKitCompletionResult> {
return pollUntilCompletionLoop(() => this.pollOnce(), options);
}

getDebugReport(): IDKitDebugReport | undefined {
return createBridgeDebugReport(
this.wasmRequest,
this.requestId,
this.connectorURI,
this.latestResponsePayload,
);
}
}

// ─────────────────────────────────────────────────────────────────────────────
Expand Down
Loading
Loading