@@ -5,48 +5,57 @@ import * as bedrock from '@bedrock/core';
5
5
import * as schemas from '../schemas/bedrock-profile-http.js' ;
6
6
import { asyncHandler } from '@bedrock/express' ;
7
7
import { ensureAuthenticated } from '@bedrock/passport' ;
8
+ import { LRUCache } from 'lru-cache' ;
8
9
import { createValidateMiddleware as validate } from '@bedrock/validation' ;
9
10
import { ZCAP_CLIENT } from './zcapClient.js' ;
10
11
11
12
const { config, util : { BedrockError} } = bedrock ;
12
13
13
14
let WORKFLOWS_BY_NAME_MAP ;
14
15
let WORKFLOWS_BY_ID_MAP ;
16
+ let EXCHANGE_POLLING_CACHE ;
15
17
16
18
bedrock . events . on ( 'bedrock.init' , ( ) => {
17
- const cfg = bedrock . config [ 'profile-http' ] ;
19
+ const cfg = config [ 'profile-http' ] ;
18
20
19
- // parse workflow configs when interactions are enabled
21
+ // interactions feature is optional, return early if not enabled
20
22
const { interactions} = cfg ;
21
- if ( interactions ?. enabled ) {
22
- const { workflows = { } } = interactions ;
23
- WORKFLOWS_BY_NAME_MAP = new Map ( ) ;
24
- WORKFLOWS_BY_ID_MAP = new Map ( ) ;
25
- for ( const workflowName in workflows ) {
26
- const { localInteractionId, zcaps} = workflows [ workflowName ] ;
27
- if ( ! localInteractionId ) {
28
- throw new TypeError (
29
- '"bedrock.config.profile-http.interactions.workflows" must each ' +
30
- 'have "localInteractionId".' ) ;
31
- }
32
- const workflow = {
33
- localInteractionId,
34
- name : workflowName ,
35
- zcaps : new Map ( )
36
- } ;
37
- for ( const zcapName in zcaps ) {
38
- const zcap = zcaps [ zcapName ] ;
39
- workflow . zcaps . set ( zcapName , JSON . parse ( zcap ) ) ;
40
- }
41
- if ( ! workflow . zcaps . has ( 'readWriteExchanges' ) ) {
42
- throw new TypeError (
43
- '"bedrock.config.profile-http.interactions.workflows" must each ' +
44
- 'have "zcaps.readWriteExchanges".' ) ;
45
- }
46
- WORKFLOWS_BY_NAME_MAP . set ( workflowName , workflow ) ;
47
- WORKFLOWS_BY_ID_MAP . set ( localInteractionId , workflow ) ;
23
+ if ( ! interactions ?. enabled ) {
24
+ return ;
25
+ }
26
+
27
+ // parse workflow configs when interactions are enabled
28
+ const { workflows = { } } = interactions ;
29
+ WORKFLOWS_BY_NAME_MAP = new Map ( ) ;
30
+ WORKFLOWS_BY_ID_MAP = new Map ( ) ;
31
+ for ( const workflowName in workflows ) {
32
+ const { localInteractionId, zcaps} = workflows [ workflowName ] ;
33
+ if ( ! localInteractionId ) {
34
+ throw new TypeError (
35
+ '"bedrock.config.profile-http.interactions.workflows" must each ' +
36
+ 'have "localInteractionId".' ) ;
37
+ }
38
+ const workflow = {
39
+ localInteractionId,
40
+ name : workflowName ,
41
+ zcaps : new Map ( )
42
+ } ;
43
+ for ( const zcapName in zcaps ) {
44
+ const zcap = zcaps [ zcapName ] ;
45
+ workflow . zcaps . set ( zcapName , JSON . parse ( zcap ) ) ;
46
+ }
47
+ if ( ! workflow . zcaps . has ( 'readWriteExchanges' ) ) {
48
+ throw new TypeError (
49
+ '"bedrock.config.profile-http.interactions.workflows" must each ' +
50
+ 'have "zcaps.readWriteExchanges".' ) ;
48
51
}
52
+ WORKFLOWS_BY_NAME_MAP . set ( workflowName , workflow ) ;
53
+ WORKFLOWS_BY_ID_MAP . set ( localInteractionId , workflow ) ;
49
54
}
55
+
56
+ // setup caches
57
+ const { caches} = interactions ;
58
+ EXCHANGE_POLLING_CACHE = new LRUCache ( caches . exchangePolling ) ;
50
59
} ) ;
51
60
52
61
bedrock . events . on ( 'bedrock-express.configure.routes' , app => {
@@ -64,6 +73,8 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
64
73
interaction : `${ interactionsPath } /:localInteractionId/:localExchangeId`
65
74
} ;
66
75
76
+ const retryAfter = Math . ceil ( EXCHANGE_POLLING_CACHE . ttl / 1000 ) ;
77
+
67
78
// create an interaction to exchange VCs
68
79
app . post (
69
80
routes . interactions ,
@@ -125,8 +136,14 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
125
136
} ) ;
126
137
}
127
138
128
- // FIXME: use in-memory cache to return exchange state if it was
129
- // polled recently
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 ;
146
+ }
130
147
131
148
// fetch exchange
132
149
const capability = workflow . zcaps . get ( 'readWriteExchanges' ) ;
@@ -135,6 +152,9 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
135
152
capability
136
153
} ) ;
137
154
155
+ // prevent more polling on the same exchange for another second
156
+ EXCHANGE_POLLING_CACHE . set ( key , true ) ;
157
+
138
158
// ensure `accountId` matches exchange variables
139
159
const { exchange : { state, variables} } = response ;
140
160
if ( variables . accountId !== accountId ) {
0 commit comments