33 */
44import * as bedrock from '@bedrock/core' ;
55import * as schemas from '../schemas/bedrock-profile-http.js' ;
6- import { poll , pollers } from '@bedrock/notify' ;
6+ import { poll , pollers , push } from '@bedrock/notify' ;
77import { agent } from '@bedrock/https-agent' ;
88import { asyncHandler } from '@bedrock/express' ;
99import { ensureAuthenticated } from '@bedrock/passport' ;
@@ -16,6 +16,12 @@ const {config, util: {BedrockError}} = bedrock;
1616let DEFINITIONS_BY_TYPE_MAP ;
1717let DEFINITIONS_BY_ID_MAP ;
1818
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+
1925bedrock . events . on ( 'bedrock.init' , ( ) => {
2026 const cfg = config [ 'profile-http' ] ;
2127
@@ -65,9 +71,13 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
6571 const interactionsPath = '/interactions' ;
6672 const routes = {
6773 interactions : interactionsPath ,
68- interaction : `${ interactionsPath } /:localInteractionId/:localExchangeId`
74+ interaction : `${ interactionsPath } /:localInteractionId/:localExchangeId` ,
75+ callback : `${ interactionsPath } /:localInteractionId/callbacks/:pushToken`
6976 } ;
7077
78+ // base URL for server
79+ const { baseUri} = bedrock . config . server ;
80+
7181 // create an interaction to exchange VCs
7282 app . post (
7383 routes . interactions ,
@@ -88,6 +98,15 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
8898 } ) ;
8999 }
90100
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+
91110 // create exchange with given variables
92111 const exchange = {
93112 // FIXME: use `expires` instead of now-deprecated `ttl`
@@ -96,13 +115,15 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
96115 // template variables
97116 variables : {
98117 ...variables ,
118+ callback : {
119+ url : callbackUrl
120+ } ,
99121 accountId
100122 }
101123 } ;
102124 const capability = definition . zcaps . get ( 'readWriteExchanges' ) ;
103125 const response = await zcapClient . write ( { json : exchange , capability} ) ;
104126 const exchangeId = response . headers . get ( 'location' ) ;
105- const { localInteractionId} = definition ;
106127 // reuse `localExchangeId` in path
107128 const localExchangeId = exchangeId . slice ( exchangeId . lastIndexOf ( '/' ) ) ;
108129 const id = `${ config . server . baseUri } /${ routes . interactions } /` +
@@ -123,14 +144,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
123144 } = req ;
124145
125146 // 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} ) ;
134148
135149 // determine full exchange ID based on related capability
136150 const capability = definition . zcaps . get ( 'readWriteExchanges' ) ;
@@ -162,36 +176,79 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
162176 // poll the exchange...
163177 const result = await poll ( {
164178 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
193181 } ) ;
194182
195183 res . json ( result ) ;
196184 } ) ) ;
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+ } ) ) ;
197216} ) ;
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