Skip to content
Draft
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
4 changes: 2 additions & 2 deletions js/examples/nextjs/app/api/rp-signature/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { signRequest } from "@worldcoin/idkit/signing";
export async function POST(request: Request): Promise<Response> {
try {
const body = (await request.json()) as {
action: string;
action?: string;
ttl?: number;
};

const signingKey = process.env.RP_SIGNING_KEY;
const { sig, nonce, createdAt, expiresAt } = signRequest({
action: body.action,
...(body.action ? { action: body.action } : {}),
signingKeyHex: signingKey!,
ttl: body.ttl,
});
Expand Down
131 changes: 112 additions & 19 deletions js/examples/nextjs/app/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import {
deviceLegacy,
selfieCheckLegacy,
IDKitRequestWidget,
IDKitSessionWidget,
orbLegacy,
secureDocumentLegacy,
setDebug,
type ConstraintNode,
type IDKitResult,
type IDKitResultSession,
type RpContext,
Preset,
} from "@worldcoin/idkit";
Expand Down Expand Up @@ -71,11 +73,11 @@ function createPreset(kind: PresetKind, signal: string) {
}
}

async function fetchRpContext(action: string): Promise<RpContext> {
async function fetchRpContext(action?: string): Promise<RpContext> {
const response = await fetch("/api/rp-signature", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ action }),
body: JSON.stringify(action ? { action } : {}),
});

if (!response.ok) {
Expand Down Expand Up @@ -149,6 +151,12 @@ export function DemoClient(): ReactElement {
const [genesisEnabled, setGenesisEnabled] = useState(false);
const [genesisDate, setGenesisDate] = useState("");
const [isGenesisTooltipOpen, setIsGenesisTooltipOpen] = useState(false);
const [flowType, setFlowType] = useState<"uniqueness" | "session">(
"uniqueness",
);
const [sessionId, setSessionId] = useState("");
const [widgetSessionResult, setWidgetSessionResult] =
useState<IDKitResultSession | null>(null);
const [useReturnTo, setUseReturnTo] = useState(false);
const [returnTo, setReturnTo] = useState("");
const [isReturnToTooltipOpen, setIsReturnToTooltipOpen] = useState(false);
Expand Down Expand Up @@ -208,6 +216,8 @@ export function DemoClient(): ReactElement {
if (worldIdVersion !== "4.0") {
setGenesisEnabled(false);
setGenesisDate("");
setFlowType("uniqueness");
setSessionId("");
}
}, [worldIdVersion]);

Expand All @@ -223,13 +233,18 @@ export function DemoClient(): ReactElement {
);
}, []);

const isSessionFlow = worldIdVersion === "4.0" && flowType === "session";

const startWidgetFlow = async () => {
setWidgetError(null);
setWidgetVerifyResult(null);
setWidgetIdkitResult(null);
setWidgetSessionResult(null);

try {
const rpContext = await fetchRpContext(action || "test-action");
const rpContext = isSessionFlow
? await fetchRpContext()
: await fetchRpContext(action || "test-action");
setWidgetSignal(`demo-signal-${Date.now()}`);
setWidgetRpContext(rpContext);
setWidgetOpen(true);
Expand Down Expand Up @@ -275,15 +290,17 @@ export function DemoClient(): ReactElement {
<label htmlFor="cfgRpId">RP ID</label>
<input type="text" id="cfgRpId" value={RP_ID} readOnly />
</div>
<div className="config-row">
<label htmlFor="cfgAction">Action</label>
<input
type="text"
id="cfgAction"
value={action}
onChange={(e) => setAction(e.target.value)}
/>
</div>
{!isSessionFlow && (
<div className="config-row">
<label htmlFor="cfgAction">Action</label>
<input
type="text"
id="cfgAction"
value={action}
onChange={(e) => setAction(e.target.value)}
/>
</div>
)}
<div className="config-row">
<label htmlFor="cfgEnv">Environment</label>
<select
Expand Down Expand Up @@ -374,6 +391,31 @@ export function DemoClient(): ReactElement {

{worldIdVersion === "4.0" && (
<>
<div className="config-row">
<label htmlFor="cfgFlowType">Flow Type</label>
<select
id="cfgFlowType"
value={flowType}
onChange={(e) =>
setFlowType(e.target.value as "uniqueness" | "session")
}
>
<option value="uniqueness">Uniqueness</option>
<option value="session">Session</option>
</select>
</div>
{flowType === "session" && (
<div className="config-row">
<label htmlFor="cfgSessionId">Session ID</label>
<input
type="text"
id="cfgSessionId"
value={sessionId}
onChange={(e) => setSessionId(e.target.value)}
placeholder="session_... (optional, for returning users)"
/>
</div>
)}
<div className="config-row">
<label htmlFor="cfgCredentialv4">Credential</label>
<select
Expand Down Expand Up @@ -496,17 +538,21 @@ export function DemoClient(): ReactElement {
</>
)}

{worldIdVersion === "4.0" && (
<>
<button onClick={startWidgetFlow}>
Verify with {V4_CREDENTIAL_TO_NAME[v4CredentialType]}
</button>
</>
{worldIdVersion === "4.0" && !isSessionFlow && (
<button onClick={startWidgetFlow}>
Verify with {V4_CREDENTIAL_TO_NAME[v4CredentialType]}
</button>
)}

{isSessionFlow && (
<button onClick={startWidgetFlow}>
{sessionId.trim() ? "Prove Session" : "Start Session"}
</button>
)}
</div>
{widgetError && <p className="status">Error: {widgetError}</p>}

{widgetRpContext && (
{widgetRpContext && !isSessionFlow && (
<IDKitRequestWidget
open={widgetOpen}
onOpenChange={setWidgetOpen}
Expand All @@ -531,6 +577,38 @@ export function DemoClient(): ReactElement {
/>
)}

{widgetRpContext && isSessionFlow && (
<IDKitSessionWidget
open={widgetOpen}
onOpenChange={setWidgetOpen}
app_id={APP_ID}
rp_context={widgetRpContext}
constraints={CredentialRequest(v4CredentialType, {
genesis_issued_at_min: genesisIssuedAtMin,
})}
{...(sessionId.trim()
? {
existing_session_id: sessionId.trim() as `session_${string}`,
}
: {})}
onSuccess={(result) => {
setWidgetSessionResult(result);
}}
handleVerify={async (result) => {
const verified = await verifyProof(
result as unknown as IDKitResult,
);
setWidgetVerifyResult(verified);
}}
onError={(errorCode) => {
setWidgetError(`Verification failed: ${errorCode}`);
}}
environment={environment}
override_connect_base_url={overrideConnectBaseUrl}
return_to={effectiveReturnTo}
/>
)}

{widgetIdkitResult && (
<>
<h3>IDKit response</h3>
Expand All @@ -540,6 +618,21 @@ export function DemoClient(): ReactElement {
</>
)}

{widgetSessionResult && (
<>
<h3>Session response</h3>
<p>
<strong>Session ID:</strong>{" "}
<code style={{ wordBreak: "break-all" }}>
{widgetSessionResult.session_id}
</code>
</p>
<pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-all" }}>
{JSON.stringify(widgetSessionResult, null, 2)}
</pre>
</>
)}

{widgetVerifyResult && (
<>
<h3>Verification response</h3>
Expand Down
6 changes: 6 additions & 0 deletions js/packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
export {
// IDKit namespace (main entry point)
IDKit,
// Session entry points
createSession,
proveSession,
CredentialRequest,
any,
all,
Expand Down Expand Up @@ -64,6 +67,9 @@ export { isReactNative, isWeb, isNode } from "./lib/platform";
export { isInWorldApp } from "./transports/native";
export { isDebug, setDebug } from "./lib/debug";

// Session utilities
export { getSessionCommitment } from "./session";

// RP Request Signing (server-side only)
export { signRequest } from "./signing";
export type { RpSignature, SignRequestParams } from "./signing";
Expand Down
9 changes: 6 additions & 3 deletions js/packages/core/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
IDKitSessionConfig,
RpContext,
} from "./types/config";
import { getSessionCommitment } from "./session";
import type {
IDKitResult,
ConstraintNode,
Expand Down Expand Up @@ -589,7 +590,7 @@ class IDKitBuilder {
* const proof = await request.pollUntilCompletion();
* ```
*/
function createRequest(config: IDKitRequestConfig): IDKitBuilder {
export function createRequest(config: IDKitRequestConfig): IDKitBuilder {
// Validate required fields
if (!config.app_id) {
throw new Error("app_id is required");
Expand Down Expand Up @@ -649,7 +650,7 @@ function createRequest(config: IDKitRequestConfig): IDKitBuilder {
* // result.responses[0].session_nullifier -> for session tracking
* ```
*/
function createSession(config: IDKitSessionConfig): IDKitBuilder {
export function createSession(config: IDKitSessionConfig): IDKitBuilder {
// Validate required fields
if (!config.app_id) {
throw new Error("app_id is required");
Expand Down Expand Up @@ -698,7 +699,7 @@ function createSession(config: IDKitSessionConfig): IDKitBuilder {
* // result.responses[0].session_nullifier -> should match for same user
* ```
*/
function proveSession(
export function proveSession(
sessionId: `session_${string}`,
config: IDKitSessionConfig,
): IDKitBuilder {
Expand Down Expand Up @@ -761,6 +762,8 @@ export const IDKit = {
createSession,
/** Prove an existing session (no action, has session_id) */
proveSession,
/** Extract the commitment from an opaque session_id */
getSessionCommitment,
/** Create a CredentialRequest for a credential type */
CredentialRequest,
/** Create an OR constraint - at least one child must be satisfied */
Expand Down
3 changes: 3 additions & 0 deletions js/packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export type { SupportedLanguage } from "./lang/types";

export {
IDKit,
createSession,
proveSession,
getSessionCommitment,
CredentialRequest,
any,
all,
Expand Down
Loading