diff --git a/app.config.js b/app.config.js
new file mode 100644
index 00000000..25b4d827
--- /dev/null
+++ b/app.config.js
@@ -0,0 +1 @@
+export const APP_BASE_URL = process.env.NEXT_PUBLIC_APP_BASE_URL || 'http://localhost:3000'
diff --git a/app/ClientLayout.tsx b/app/ClientLayout.tsx
index 6afe41fb..e65f7a34 100644
--- a/app/ClientLayout.tsx
+++ b/app/ClientLayout.tsx
@@ -8,6 +8,7 @@ import { StepProvider } from './credentialForm/form/StepContext'
import { usePathname } from 'next/navigation'
import background from './Assets/Images/Background.svg'
import Providers from './components/signing/Providers'
+import AppDidInitializer from './components/AppDidInitializer'
export default function ClientLayout({
children
@@ -49,6 +50,7 @@ export default function ClientLayout({
: '#F0F4F8'
}}
>
+
{children}
diff --git a/app/api/exchanges/[txid]/route.ts b/app/api/exchanges/[txid]/route.ts
index fa55a3e2..067307fe 100644
--- a/app/api/exchanges/[txid]/route.ts
+++ b/app/api/exchanges/[txid]/route.ts
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'
-import { exchanges } from '../../../lib/exchanges' // Adjust path if needed
+import { exchanges } from '../../../lib/exchanges'
+import { APP_BASE_URL } from '../../../../app.config'
// Set CORS headers
const corsHeaders = {
@@ -58,57 +59,74 @@ export async function POST(
request: NextRequest,
{ params }: { params: { txId: string } }
) {
- const { txId } = params
- console.log('🚀 ~ txId:', txId)
+ const { txId } = params;
+ console.log('[POST] Incoming txId:', txId);
try {
- const body = await request.json()
- const payload = JSON.stringify(body)
-
- console.log('Incoming POST:', body)
-
- if (payload === '{}') {
- // Initial POST by the wallet, send the VP Request query
- const query = vprQuery()
- return NextResponse.json(query, { headers: corsHeaders })
- } else {
- // Requested credentials sent by the wallet
- // Store in the exchanges cache
- console.log('Storing txId', txId, payload)
- exchanges.set(txId, payload)
- return NextResponse.json({ status: 'received' }, { headers: corsHeaders })
+ // Try to parse body — gracefully handle empty body
+ const body = await request.json().catch(() => ({}));
+ console.log('[POST] Parsed body:', body);
+
+ const appInstanceDid = body.appInstanceDid;
+ const existing = exchanges.get(txId);
+
+ if (appInstanceDid) {
+ // Initial POST from the web app
+ console.log('[POST] Received appInstanceDid from resume-author:', appInstanceDid);
+ exchanges.set(txId, JSON.stringify({ appInstanceDid }));
+ return NextResponse.json(
+ { status: '✅ App DID received, waiting for wallet to connect' },
+ { headers: corsHeaders }
+ );
}
- } catch (error: any) {
- console.error(error)
+ if (existing) {
+ // LCW second POST with empty body — retrieve session and return VPR
+ const { appInstanceDid } = JSON.parse(existing);
+ console.log('[POST] LCW connected, found stored appInstanceDid:', appInstanceDid);
+
+ const query = vprQuery({ txId, appInstanceDid });
+ return NextResponse.json(query, { headers: corsHeaders });
+ }
+
+ // Neither new DID nor existing session
+ console.warn('[POST] ❌ Missing appInstanceDid and no cached session found for txId.');
return NextResponse.json(
- {
- status: error.statusText || 'Invalid request',
- error: error.message
- },
- {
- status: error.statusCode || 400,
- headers: corsHeaders
- }
- )
+ { error: 'Missing appInstanceDid and no session initialized' },
+ { status: 400, headers: corsHeaders }
+ );
+
+ } catch (error: any) {
+ console.error('[POST] ❌ Error handling request:', error);
+ return NextResponse.json({ error: 'Invalid request' }, { status: 400, headers: corsHeaders });
}
}
+
/**
* Returns the Verifiable Presentation Request Query
*/
-function vprQuery() {
+function vprQuery({ txId, appInstanceDid }: { txId: string; appInstanceDid: string }) {
+ const pollingExchangeEndpoint = `${APP_BASE_URL}/api/exchanges/${txId}`
+
return {
verifiablePresentationRequest: {
+ interact: {
+ type: 'UnmediatedHttpPresentationService2021',
+ serviceEndpoint: pollingExchangeEndpoint
+ },
query: [
{
- type: 'QueryByExample',
- credentialQuery: {
+ type: 'ZcapQuery',
+ capabilityQuery: {
reason:
- 'Please present your Verifiable Credential to complete the verification process.',
- example: {
- '@context': ['https://www.w3.org/2018/credentials/v1'],
- type: ['VerifiableCredential']
+ 'Linked Creds Author is requesting the permission to read and write to the Verifiable Credentials and VC Evidence collections.',
+ allowedAction: ['GET', 'PUT', 'POST'],
+ controller: appInstanceDid,
+ invocationTarget: {
+ type: 'urn:was:collection',
+ contentType: 'application/vc',
+ name: 'VerifiableCredential collection'
}
}
}
diff --git a/app/components/AppDidInitializer.tsx b/app/components/AppDidInitializer.tsx
new file mode 100644
index 00000000..0c15bc36
--- /dev/null
+++ b/app/components/AppDidInitializer.tsx
@@ -0,0 +1,14 @@
+'use client'
+import { useEffect } from 'react'
+import { getOrCreateAppInstanceDid } from '@cooperation/vc-storage'
+
+export default function AppDidInitializer() {
+ useEffect(() => {
+ ;(async () => {
+ const did = await getOrCreateAppInstanceDid()
+ console.log('App DID ready', did)
+ })()
+ }, [])
+
+ return null
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 72b67804..2709f18b 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -3,7 +3,7 @@ import { Metadata } from 'next'
import ClientLayout from './ClientLayout'
import { Inter } from 'next/font/google'
import './globals.css'
-// test
+
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'LinkedCreds',
@@ -95,7 +95,9 @@ export default function RootLayout({
- {children}
+
+ {children}
+