Skip to content

Commit e4b11ef

Browse files
committed
Integrate interactions with @bedrock/notify.
1 parent aff0596 commit e4b11ef

File tree

5 files changed

+75
-49
lines changed

5 files changed

+75
-49
lines changed

lib/config.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,8 @@ config.ensureConfigOverride.fields.push(meterServiceName);
5050

5151
// optional interaction config
5252
cfg.interactions = {
53+
// FIXME: add qr-code route fallback for non-accept-json "protocols" requests
5354
enabled: false,
54-
caches: {
55-
// FIXME: remove; replace with bedrock-notify
56-
exchangePolling: {
57-
// each cache value is only a boolean (the key is ~64 bytes); one entry
58-
// per exchange being actively polled, 1M = ~60 MiB
59-
max: 1000000,
60-
// polling allowed no more than once per second by default
61-
ttl: 1000
62-
}
63-
},
6455
// named workflows for interactions
6556
/* Spec:
6657
{

lib/interactions.js

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
/*!
2-
* Copyright (c) 2020-2024 Digital Bazaar, Inc. All rights reserved.
2+
* Copyright (c) 2020-2025 Digital Bazaar, Inc. All rights reserved.
33
*/
44
import * as bedrock from '@bedrock/core';
55
import * as schemas from '../schemas/bedrock-profile-http.js';
6+
import {poll, pollers} from '@bedrock/notify';
7+
import {agent} from '@bedrock/https-agent';
68
import {asyncHandler} from '@bedrock/express';
79
import {ensureAuthenticated} from '@bedrock/passport';
8-
import {LRUCache} from 'lru-cache';
10+
import {httpClient} from '@digitalbazaar/http-client';
911
import {createValidateMiddleware as validate} from '@bedrock/validation';
10-
import {ZCAP_CLIENT} from './zcapClient.js';
12+
import {ZCAP_CLIENT as zcapClient} from './zcapClient.js';
1113

1214
const {config, util: {BedrockError}} = bedrock;
1315

1416
let WORKFLOWS_BY_NAME_MAP;
1517
let WORKFLOWS_BY_ID_MAP;
16-
let EXCHANGE_POLLING_CACHE;
1718

1819
bedrock.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

6158
bedrock.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
});

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@
3131
"dependencies": {
3232
"@digitalbazaar/ed25519-signature-2020": "^5.4.0",
3333
"@digitalbazaar/ezcap": "^4.1.0",
34-
"lru-cache": "^10.2.2"
34+
"@digitalbazaar/http-client": "^4.2.0"
3535
},
3636
"peerDependencies": {
3737
"@bedrock/app-identity": "^4.0.0",
3838
"@bedrock/core": "^6.3.0",
3939
"@bedrock/express": "^8.3.1",
4040
"@bedrock/https-agent": "^4.1.0",
41+
"@bedrock/notify": "^1.1.0",
4142
"@bedrock/passport": "^12.0.0",
4243
"@bedrock/profile": "^26.0.0",
4344
"@bedrock/validation": "^7.1.1"

test/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@bedrock/meter-http": "^14.0.0",
2828
"@bedrock/meter-usage-reporter": "^10.0.0",
2929
"@bedrock/mongodb": "^11.0.0",
30+
"@bedrock/notify": "^1.1.0",
3031
"@bedrock/package-manager": "^3.0.0",
3132
"@bedrock/passport": "^12.0.0",
3233
"@bedrock/profile": "^26.0.0",

test/test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*!
2-
* Copyright (c) 2020-2022 Digital Bazaar, Inc. All rights reserved.
2+
* Copyright (c) 2020-2025 Digital Bazaar, Inc. All rights reserved.
33
*/
44
import * as bedrock from '@bedrock/core';
55
import {handlers} from '@bedrock/meter-http';
@@ -13,6 +13,7 @@ import '@bedrock/https-agent';
1313
import '@bedrock/jsonld-document-loader';
1414
import '@bedrock/meter';
1515
import '@bedrock/meter-usage-reporter';
16+
import '@bedrock/notify';
1617
import '@bedrock/passport';
1718
import '@bedrock/server';
1819
import '@bedrock/kms-http';

0 commit comments

Comments
 (0)