11/*!
2- * Copyright (c) 2020-2024 Digital Bazaar, Inc. All rights reserved.
2+ * Copyright (c) 2020-2025 Digital Bazaar, Inc. All rights reserved.
33 */
44import * as bedrock from '@bedrock/core' ;
55import * as schemas from '../schemas/bedrock-profile-http.js' ;
6+ import { poll , pollers } from '@bedrock/notify' ;
7+ import { agent } from '@bedrock/https-agent' ;
68import { asyncHandler } from '@bedrock/express' ;
79import { ensureAuthenticated } from '@bedrock/passport' ;
8- import { LRUCache } from 'lru-cache ' ;
10+ import { httpClient } from '@digitalbazaar/http-client ' ;
911import { createValidateMiddleware as validate } from '@bedrock/validation' ;
10- import { ZCAP_CLIENT } from './zcapClient.js' ;
12+ import { ZCAP_CLIENT as zcapClient } from './zcapClient.js' ;
1113
1214const { config, util : { BedrockError} } = bedrock ;
1315
1416let WORKFLOWS_BY_NAME_MAP ;
1517let WORKFLOWS_BY_ID_MAP ;
16- let EXCHANGE_POLLING_CACHE ;
1718
1819bedrock . events . on ( 'bedrock.init' , ( ) => {
1920 const cfg = config [ 'profile-http' ] ;
@@ -52,10 +53,6 @@ bedrock.events.on('bedrock.init', () => {
5253 WORKFLOWS_BY_NAME_MAP . set ( workflowName , workflow ) ;
5354 WORKFLOWS_BY_ID_MAP . set ( localInteractionId , workflow ) ;
5455 }
55-
56- // setup caches
57- const { caches} = interactions ;
58- EXCHANGE_POLLING_CACHE = new LRUCache ( caches . exchangePolling ) ;
5956} ) ;
6057
6158bedrock . events . on ( 'bedrock-express.configure.routes' , app => {
@@ -73,8 +70,6 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
7370 interaction : `${ interactionsPath } /:localInteractionId/:localExchangeId`
7471 } ;
7572
76- const retryAfter = Math . ceil ( EXCHANGE_POLLING_CACHE . ttl / 1000 ) ;
77-
7873 // create an interaction to exchange VCs
7974 app . post (
8075 routes . interactions ,
@@ -97,6 +92,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
9792
9893 // create exchange with given variables
9994 const exchange = {
95+ // FIXME: use `expires` instead of now-deprecated `ttl`
10096 // 15 minute expiry in seconds
10197 ttl : 60 * 15 ,
10298 // template variables
@@ -106,7 +102,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
106102 }
107103 } ;
108104 const capability = workflow . zcaps . get ( 'readWriteExchanges' ) ;
109- const response = await ZCAP_CLIENT . write ( { json : exchange , capability} ) ;
105+ const response = await zcapClient . write ( { json : exchange , capability} ) ;
110106 const exchangeId = response . headers . get ( 'location' ) ;
111107 const { localInteractionId} = workflow ;
112108 // reuse `localExchangeId` in path
@@ -120,9 +116,13 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
120116 app . get (
121117 routes . interaction ,
122118 ensureAuthenticated ,
119+ // FIXME: add URL query validator that requires no query or `iuv=1` only
123120 asyncHandler ( async ( req , res ) => {
124121 const { id : accountId } = req . user . account || { } ;
125- const { localInteractionId, localExchangeId} = req . params ;
122+ const {
123+ params : { localInteractionId, localExchangeId} ,
124+ query : { iuv}
125+ } = req ;
126126
127127 const workflow = WORKFLOWS_BY_ID_MAP . get ( localInteractionId ) ;
128128 if ( ! workflow ) {
@@ -136,34 +136,66 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
136136 } ) ;
137137 }
138138
139- // check if entry is in exchange polling cache, if so, return
140- // "too many requests" error response
141- const key = `${ localExchangeId } /${ localExchangeId } ` ;
142- if ( EXCHANGE_POLLING_CACHE . get ( key ) ) {
143- res . set ( 'retry-after' , retryAfter ) ;
144- res . status ( 429 ) . json ( { retryAfter} ) ;
145- return ;
139+ // determine full exchange ID based on related capability
140+ const capability = workflow . zcaps . get ( 'readWriteExchanges' ) ;
141+ const exchangeId = `${ capability . invocationTarget } /${ localExchangeId } ` ;
142+
143+ // if an "Interaction URL Version" is present send "protocols"
144+ // (note: validation requires it to be `1`, so no need to check its value)
145+ if ( iuv ) {
146+ // FIXME: send to a QR-code page if supported
147+ // FIXME: check config for supported QR code route and use it
148+ // instead of hard-coded value
149+ if ( req . accepts ( 'html' ) || ! req . accepts ( 'json' ) ) {
150+ return res . redirect ( `${ req . originalUrl } /qr-code` ) ;
151+ }
152+ try {
153+ const url = `${ exchangeId } /protocols` ;
154+ const { data : protocols } = await httpClient . get ( url , { agent} ) ;
155+ res . json ( protocols ) ;
156+ } catch ( cause ) {
157+ throw new BedrockError (
158+ 'Unable to serve protocols object: ' + cause . message , {
159+ name : 'OperationError' ,
160+ details : { httpStatusCode : 500 , public : true } ,
161+ cause
162+ } ) ;
163+ }
146164 }
147165
148- // fetch exchange
149- const capability = workflow . zcaps . get ( 'readWriteExchanges' ) ;
150- const response = await ZCAP_CLIENT . read ( {
151- url : `${ capability . invocationTarget } /${ localExchangeId } ` ,
152- capability
166+ // poll the exchange...
167+ const result = await poll ( {
168+ id : exchangeId ,
169+ poller : pollers . createExchangePoller ( {
170+ zcapClient,
171+ capability,
172+ filterExchange ( { exchange/*, previousPollResult*/ } ) {
173+ // ensure `accountId` matches exchange variables
174+ if ( exchange ?. variables . accountId !== accountId ) {
175+ throw new BedrockError (
176+ 'Not authorized.' ,
177+ 'NotAllowedError' ,
178+ { httpStatusCode : 403 , public : true } ) ;
179+ }
180+ // return only information that should be accessible to client
181+ return {
182+ exchange
183+ // FIXME: filter info once final step name and info is determined
184+ /*
185+ exchange: {
186+ state: exchange.state,
187+ result: exchange.variables.results?.finish
188+ }*/
189+ } ;
190+ }
191+ } ) ,
192+ // set a TTL of 1 seconds to account for the case where a push
193+ // notification isn't received by the same instance that the client
194+ // hits, but prevent requests from triggering a hit to the backend more
195+ // frequently than 1 second
196+ ttl : 1000
153197 } ) ;
154198
155- // prevent more polling on the same exchange for another second
156- EXCHANGE_POLLING_CACHE . set ( key , true ) ;
157-
158- // ensure `accountId` matches exchange variables
159- const { exchange : { state, variables} } = response ;
160- if ( variables . accountId !== accountId ) {
161- throw new BedrockError (
162- 'The "account" is not authorized.' ,
163- 'NotAllowedError' ,
164- { httpStatusCode : 403 , public : true } ) ;
165- }
166-
167- res . json ( { exchange : { state, variables} } ) ;
199+ res . json ( result ) ;
168200 } ) ) ;
169201} ) ;
0 commit comments