Skip to content
Merged
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
1 change: 1 addition & 0 deletions app.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const APP_BASE_URL = process.env.NEXT_PUBLIC_APP_BASE_URL || 'http://localhost:3000'
2 changes: 2 additions & 0 deletions app/ClientLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,6 +50,7 @@ export default function ClientLayout({
: '#F0F4F8'
}}
>
<AppDidInitializer />
Copy link
Member Author

@0marSalah 0marSalah Jul 30, 2025

Choose a reason for hiding this comment

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

@dmitrizagidulin - I added <AppDidInitializer /> because putting the getOrCreateAppInstanceDid() logic directly in ClientLayout runs it only after the user is authenticated. This way, the App DID is initialized earlier, before any auth checks, without using a wrapper.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Perfect, thanks.

{children}
</Box>
<Footer />
Expand Down
90 changes: 54 additions & 36 deletions app/api/exchanges/[txid]/route.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -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'
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions app/components/AppDidInitializer.tsx
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 4 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -95,7 +95,9 @@ export default function RootLayout({
<link rel='icon' type='image/png' sizes='32x32' href='/icons/favicon-32x32.png' />
<link rel='icon' type='image/png' sizes='16x16' href='/icons/favicon-16x16.png' />
</head>
<ClientLayout>{children}</ClientLayout>
<ClientLayout>
{children}
</ClientLayout>
</html>
)
}