Skip to content
Open
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
81 changes: 80 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,82 @@ 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 report = createIDKitDebugReport({
transport: "bridge",
requestPayload: payload,
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,
});
expect(report).not.toHaveProperty("response_payload");
});

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 = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this v1 i would avoid strong typing here, to start with you can try:

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;
}
98 changes: 68 additions & 30 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;
Comment thread
cursor[bot] marked this conversation as resolved.
}

/**
Expand Down Expand Up @@ -112,26 +119,51 @@ 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,
): IDKitDebugReport | undefined {
if (!isDebug()) return undefined;

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bridge debug payload not cached

Medium Severity

Bridge getDebugReport() loads request_payload via live debugPayload() while request_id and connector_uri are fixed at construction. During an in-flight pollOnce(), WASM temporarily clears the connection, debugPayload() fails, and the report can omit request_payload even in debug mode.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 41beb9c. Configure here.

}

/**
* Internal request implementation (bridge/WASM path)
*/
class IDKitRequestImpl implements IDKitRequest {
private wasmRequest: WasmModule.IDKitRequest;
private _connectorURI: string;
private _requestId: string;
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> {
Expand All @@ -141,6 +173,14 @@ class IDKitRequestImpl implements IDKitRequest {
pollUntilCompletion(options?: WaitOptions): Promise<IDKitCompletionResult> {
return pollUntilCompletionLoop(() => this.pollOnce(), options);
}

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

/**
Expand All @@ -163,6 +203,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,27 +214,15 @@ export interface IDKitInviteCodeRequest {
*/
class IDKitInviteCodeRequestImpl implements IDKitInviteCodeRequest {
private wasmRequest: WasmModule.IDKitInviteCodeRequest;
private _connectorURI: string;
private _expiresAt: number;
private _requestId: string;
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> {
Expand All @@ -202,6 +232,14 @@ class IDKitInviteCodeRequestImpl implements IDKitInviteCodeRequest {
pollUntilCompletion(options?: WaitOptions): Promise<IDKitCompletionResult> {
return pollUntilCompletionLoop(() => this.pollOnce(), options);
}

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

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