3
3
*/
4
4
import * as bedrock from '@bedrock/core' ;
5
5
import * as schemas from '../schemas/bedrock-profile-http.js' ;
6
- import { poll , pollers } from '@bedrock/notify' ;
6
+ import { poll , pollers , push } from '@bedrock/notify' ;
7
7
import { agent } from '@bedrock/https-agent' ;
8
8
import { asyncHandler } from '@bedrock/express' ;
9
9
import { ensureAuthenticated } from '@bedrock/passport' ;
@@ -16,6 +16,12 @@ const {config, util: {BedrockError}} = bedrock;
16
16
let DEFINITIONS_BY_TYPE_MAP ;
17
17
let DEFINITIONS_BY_ID_MAP ;
18
18
19
+ // use a TTL of 1 second to account for the case where a push notification
20
+ // isn't received by the same instance that the client hits, but prevent
21
+ // requests from triggering a hit to the workflow service backend more
22
+ // frequently than 1 second
23
+ const POLL_TTL = 1000 ;
24
+
19
25
bedrock . events . on ( 'bedrock.init' , ( ) => {
20
26
const cfg = config [ 'profile-http' ] ;
21
27
@@ -65,9 +71,13 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
65
71
const interactionsPath = '/interactions' ;
66
72
const routes = {
67
73
interactions : interactionsPath ,
68
- interaction : `${ interactionsPath } /:localInteractionId/:localExchangeId`
74
+ interaction : `${ interactionsPath } /:localInteractionId/:localExchangeId` ,
75
+ callback : `${ interactionsPath } /:localInteractionId/callbacks/:pushToken`
69
76
} ;
70
77
78
+ // base URL for server
79
+ const { baseUri} = bedrock . config . server ;
80
+
71
81
// create an interaction to exchange VCs
72
82
app . post (
73
83
routes . interactions ,
@@ -88,6 +98,15 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
88
98
} ) ;
89
99
}
90
100
101
+ // create a push token
102
+ const { token} = await push . createPushToken ( { event : 'exchangeUpdated' } ) ;
103
+
104
+ // compute callback URL
105
+ const { localInteractionId} = definition ;
106
+ const callbackUrl =
107
+ `${ baseUri } ${ interactionsPath } /${ localInteractionId } ` +
108
+ `/callbacks/${ token } ` ;
109
+
91
110
// create exchange with given variables
92
111
const exchange = {
93
112
// FIXME: use `expires` instead of now-deprecated `ttl`
@@ -96,13 +115,15 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
96
115
// template variables
97
116
variables : {
98
117
...variables ,
118
+ callback : {
119
+ url : callbackUrl
120
+ } ,
99
121
accountId
100
122
}
101
123
} ;
102
124
const capability = definition . zcaps . get ( 'readWriteExchanges' ) ;
103
125
const response = await zcapClient . write ( { json : exchange , capability} ) ;
104
126
const exchangeId = response . headers . get ( 'location' ) ;
105
- const { localInteractionId} = definition ;
106
127
// reuse `localExchangeId` in path
107
128
const localExchangeId = exchangeId . slice ( exchangeId . lastIndexOf ( '/' ) ) ;
108
129
const id = `${ config . server . baseUri } /${ routes . interactions } /` +
@@ -123,14 +144,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
123
144
} = req ;
124
145
125
146
// get interaction definition
126
- const definition = DEFINITIONS_BY_ID_MAP . get ( localInteractionId ) ;
127
- if ( ! definition ) {
128
- throw new BedrockError (
129
- `Interaction type for "${ localInteractionId } " not found.` , {
130
- name : 'NotFoundError' ,
131
- details : { httpStatusCode : 404 , public : true }
132
- } ) ;
133
- }
147
+ const definition = _getInteractionDefinition ( { localInteractionId} ) ;
134
148
135
149
// determine full exchange ID based on related capability
136
150
const capability = definition . zcaps . get ( 'readWriteExchanges' ) ;
@@ -162,36 +176,79 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
162
176
// poll the exchange...
163
177
const result = await poll ( {
164
178
id : exchangeId ,
165
- poller : pollers . createExchangePoller ( {
166
- zcapClient,
167
- capability,
168
- filterExchange ( { exchange/*, previousPollResult*/ } ) {
169
- // ensure `accountId` matches exchange variables
170
- if ( exchange ?. variables . accountId !== accountId ) {
171
- throw new BedrockError ( 'Not authorized.' , {
172
- name : 'NotAllowedError' ,
173
- details : { httpStatusCode : 403 , public : true }
174
- } ) ;
175
- }
176
- // return only information that should be accessible to client
177
- return {
178
- exchange
179
- // FIXME: filter info once final step name and info is determined
180
- /*
181
- exchange: {
182
- state: exchange.state,
183
- result: exchange.variables.results?.finish
184
- }*/
185
- } ;
186
- }
187
- } ) ,
188
- // set a TTL of 1 seconds to account for the case where a push
189
- // notification isn't received by the same instance that the client
190
- // hits, but prevent requests from triggering a hit to the backend more
191
- // frequently than 1 second
192
- ttl : 1000
179
+ poller : _createExchangePoller ( { accountId, capability} ) ,
180
+ ttl : POLL_TTL
193
181
} ) ;
194
182
195
183
res . json ( result ) ;
196
184
} ) ) ;
185
+
186
+ // push event handler
187
+ app . post (
188
+ routes . callback ,
189
+ push . createVerifyPushTokenMiddleware ( { event : 'exchangeUpdated' } ) ,
190
+ asyncHandler ( async ( req , res ) => {
191
+ const { event : { data : { exchangeId : id } } } = req . body ;
192
+ const { localInteractionId} = req . params ;
193
+
194
+ // get interaction definition
195
+ const definition = _getInteractionDefinition ( { localInteractionId} ) ;
196
+
197
+ // get capability for fetching exchange and verify its invocation target
198
+ // matches the exchange ID passed
199
+ const capability = definition . zcaps . get ( 'readWriteExchanges' ) ;
200
+ if ( ! id . startsWith ( capability . invocationTarget ) ) {
201
+ throw new BedrockError ( 'Not authorized.' , {
202
+ name : 'NotAllowedError' ,
203
+ details : { httpStatusCode : 403 , public : true }
204
+ } ) ;
205
+ }
206
+
207
+ // poll (and clear cache)
208
+ await poll ( {
209
+ id,
210
+ poller : _createExchangePoller ( { capability} ) ,
211
+ ttl : POLL_TTL ,
212
+ useCache : false
213
+ } ) ;
214
+ res . sendStatus ( 204 ) ;
215
+ } ) ) ;
197
216
} ) ;
217
+
218
+ function _createExchangePoller ( { accountId, capability} ) {
219
+ return pollers . createExchangePoller ( {
220
+ zcapClient,
221
+ capability,
222
+ filterExchange ( { exchange/*, previousPollResult*/ } ) {
223
+ // if `accountId` given, ensure it matches exchange variables
224
+ if ( accountId && exchange ?. variables . accountId !== accountId ) {
225
+ throw new BedrockError ( 'Not authorized.' , {
226
+ name : 'NotAllowedError' ,
227
+ details : { httpStatusCode : 403 , public : true }
228
+ } ) ;
229
+ }
230
+ // return only information that should be accessible to client
231
+ return {
232
+ exchange
233
+ // FIXME: filter info once final step name and info is determined
234
+ /*
235
+ exchange: {
236
+ state: exchange.state,
237
+ result: exchange.variables.results?.finish
238
+ }*/
239
+ } ;
240
+ }
241
+ } ) ;
242
+ }
243
+
244
+ function _getInteractionDefinition ( { localInteractionId} ) {
245
+ const definition = DEFINITIONS_BY_ID_MAP . get ( localInteractionId ) ;
246
+ if ( ! definition ) {
247
+ throw new BedrockError (
248
+ `Interaction type for "${ localInteractionId } " not found.` , {
249
+ name : 'NotFoundError' ,
250
+ details : { httpStatusCode : 404 , public : true }
251
+ } ) ;
252
+ }
253
+ return definition ;
254
+ }
0 commit comments