Skip to content

Commit 0f333ef

Browse files
committed
Changing response eventType - Adding revoke and refuse
1 parent 23e2d83 commit 0f333ef

9 files changed

Lines changed: 162 additions & 75 deletions

File tree

docs/hds-lib.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/hds-lib.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"description": "Health Data Safe - Library",
55
"main": "src/index.js",
66
"scripts": {
7-
"test": "mocha tests --test-reporter=spec",
8-
"test:coverage": "c8 mocha tests --test-reporter=spec",
7+
"test": "NODE_ENV=test mocha tests --test-reporter=spec",
8+
"test:coverage": "NODE_ENV=test c8 mocha tests --test-reporter=spec",
99
"build": "webpack",
1010
"lint": "eslint .",
1111
"lint:fix": "eslint . --fix",

src/appTemplates/AppClientAccount.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { HDSLibError } = require('../errors');
22
const pryv = require('../patchedPryv');
33
const Application = require('./Application');
44
const CollectorClient = require('./CollectorClient');
5+
const logger = require('../logger');
56

67
/**
78
* - applications
@@ -29,15 +30,23 @@ class AppClientAccount extends Application {
2930
*/
3031
async handleIncomingRequest (apiEndpoint, incomingEventId) {
3132
// make sure that collectorClientsMap is initialized
32-
// await this.getCollectorClients();
33+
await this.getCollectorClients();
3334

3435
const requesterConnection = new pryv.Connection(apiEndpoint);
3536
const accessInfo = await requesterConnection.accessInfo();
3637
// check if request is known
37-
if (this.cache.collectorClientsMap[CollectorClient.keyFromInfo(accessInfo)]) {
38-
const collectorClient = this.collectoClientsMap[accessInfo.name];
38+
const collectorClientKey = CollectorClient.keyFromInfo(accessInfo);
39+
logger.debug('AppClient:handleIncomingRequest', { collectorClientKey, accessInfo, incomingEventId });
40+
if (this.cache.collectorClientsMap[collectorClientKey]) {
41+
const collectorClient = this.cache.collectorClientsMap[collectorClientKey];
42+
logger.debug('AppClient:handleIncomingRequest found existing', { collectorClient });
3943
if (incomingEventId && collectorClient.requesterEventId !== incomingEventId) {
40-
throw new HDSLibError('Found existing collectorClient with a different eventId', { actual: collectorClient.requesterEventId, incoming: incomingEventId });
44+
logger.error('Found existing collectorClient with a different eventId', { actual: collectorClient.requesterEventId, incoming: incomingEventId });
45+
return await collectorClient.reset(apiEndpoint, incomingEventId, accessInfo);
46+
}
47+
if (collectorClient.apiEndpoint !== apiEndpoint) {
48+
logger.error('Found existing collectorClient with a different apiEndpoint', { actual: collectorClient.requesterApiEndpoint, incoming: apiEndpoint });
49+
return await collectorClient.reset(apiEndpoint, incomingEventId, accessInfo);
4150
}
4251
return collectorClient;
4352
}
@@ -46,7 +55,6 @@ class AppClientAccount extends Application {
4655
throw new HDSLibError('Invalid collector request, cannot find clientData.hdsCollector or wrong version', { clientData: accessInfo?.clientData });
4756
}
4857
// else create it
49-
5058
const collectorClient = await CollectorClient.create(this, apiEndpoint, incomingEventId, accessInfo);
5159
this.cache.collectorClientsMap[collectorClient.key] = collectorClient;
5260
return collectorClient;
@@ -59,7 +67,7 @@ class AppClientAccount extends Application {
5967
}
6068

6169
async getCollectorClients (forceRefresh = false) {
62-
if (!forceRefresh && this.cache.collectorClientsMapInitialized) return Object.values(this.cache.collectoClientsMap);
70+
if (!forceRefresh && this.cache.collectorClientsMapInitialized) return Object.values(this.cache.collectorClientsMap);
6371
const apiCalls = [{
6472
method: 'accesses.get',
6573
params: { includeDeletions: true }

src/appTemplates/Collector.js

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,28 +194,49 @@ class Collector {
194194
async checkInbox () {
195195
const newCollectorInvites = [];
196196

197-
const params = { types: ['credentials/collector-v1'], limit: 1, streams: [this.streamIdFor(Collector.STREAMID_SUFFIXES.inbox)] };
198-
const incomingCredentials = await this.appManaging.connection.apiOne('events.get', params, 'events');
199-
for (const incomingCredential of incomingCredentials) {
197+
const params = { types: ['response/collector-v1'], limit: 1000, streams: [this.streamIdFor(Collector.STREAMID_SUFFIXES.inbox)] };
198+
const responseEvents = await this.appManaging.connection.apiOne('events.get', params, 'events');
199+
for (const responseEvent of responseEvents) {
200200
// fetch corresponding invite
201-
const inviteEvent = await this.appManaging.connection.apiOne('events.getOne', { id: incomingCredential.content.eventId }, 'event');
202-
if (inviteEvent == null) throw new HDSLibError(`Cannot find invite event matching id: ${incomingCredential.content.eventId}`, incomingCredential);
201+
const inviteEvent = await this.appManaging.connection.apiOne('events.getOne', { id: responseEvent.content.eventId }, 'event');
202+
if (inviteEvent == null) throw new HDSLibError(`Cannot find invite event matching id: ${responseEvent.content.eventId}`, responseEvent);
203+
204+
const updateInvite = {
205+
content: structuredClone(inviteEvent.content)
206+
};
207+
updateInvite.content.sourceEventId = responseEvent.id;
208+
209+
// check type of response
210+
switch (responseEvent.content.type) {
211+
case 'accept':
212+
updateInvite.streamIds = [this.streamIdFor(Collector.STREAMID_SUFFIXES.active)];
213+
Object.assign(updateInvite.content, { apiEndpoint: responseEvent.content.apiEndpoint });
214+
break;
215+
case 'refuse':
216+
updateInvite.streamIds = [this.streamIdFor(Collector.STREAMID_SUFFIXES.error)];
217+
updateInvite.content.errorType = 'refused';
218+
break;
219+
case 'revoke':
220+
updateInvite.streamIds = [this.streamIdFor(Collector.STREAMID_SUFFIXES.error)];
221+
updateInvite.content.errorType = 'revoked';
222+
break;
223+
default:
224+
throw new HDSLibError(`Unkown or undefined ${responseEvent.content.type}`, responseEvent);
225+
}
226+
203227
// update inviteEvent and archive inbox message
204228
const apiCalls = [
205229
{
206230
method: 'events.update',
207231
params: {
208232
id: inviteEvent.id,
209-
update: {
210-
streamIds: [this.streamIdFor(Collector.STREAMID_SUFFIXES.active)],
211-
content: Object.assign(inviteEvent.content, { apiEndpoint: incomingCredential.content.apiEndpoint })
212-
}
233+
update: updateInvite
213234
}
214235
},
215236
{
216237
method: 'events.update',
217238
params: {
218-
id: incomingCredential.id,
239+
id: responseEvent.id,
219240
update: {
220241
streamIds: [this.streamIdFor(Collector.STREAMID_SUFFIXES.archive)]
221242
}
@@ -352,12 +373,14 @@ class Collector {
352373
* reverse of streamIdFor
353374
*/
354375
inviteStatusForStreamId (streamId) {
376+
// init cache if needed
355377
if (!this.#cache.inviteStatusForStreamId) {
356378
this.#cache.inviteStatusForStreamId = {};
357379
for (const status of [COLLECTOR_STREAMID_SUFFIXES.pending, COLLECTOR_STREAMID_SUFFIXES.active, COLLECTOR_STREAMID_SUFFIXES.error]) {
358380
this.#cache.inviteStatusForStreamId[this.streamIdFor(status)] = status;
359381
}
360382
}
383+
// look for status
361384
const status = this.#cache.inviteStatusForStreamId[streamId];
362385
if (status == null) throw new HDSLibError(`Cannot find status for streamId: ${streamId}`);
363386
return status;

src/appTemplates/CollectorClient.js

Lines changed: 95 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ class CollectorClient {
3030
return this.eventData.content.requesterEventId;
3131
}
3232

33+
/** @property {String} */
34+
get requesterApiEndpoint () {
35+
return this.eventData.content.apiEndpoint;
36+
}
37+
3338
/** @property {Object} - full content of the request */
3439
get requestData () {
3540
return this.eventData.content.requesterEventData.content;
@@ -38,10 +43,21 @@ class CollectorClient {
3843
/** @property {string} - one of 'Incoming', 'Active', 'Deactivated', 'Refused' */
3944
get status () {
4045
const eventStatus = this.eventData.content.status;
46+
if (eventStatus === 'Deactivated' || eventStatus === 'Refused') {
47+
if (!this.accessData?.deleted) {
48+
logger.error('>> CollectorClient.status TODO check consitency when access is still valid and deactivated or refused', this.accessData);
49+
}
50+
return eventStatus;
51+
}
52+
4153
if (this.accessData && !this.accessData.deleted && this.eventData.content.status !== CollectorClient.STATUSES.active) {
42-
console.log(this.accessData);
54+
logger.error('>> CollectorClient.status: accessData ', this.accessData);
4355
throw new HDSLibError('Should be active, try checkConsiency()');
4456
}
57+
if (!eventStatus) {
58+
logger.error('>> CollectorClient.status is null', { eventData: this.eventData, accessData: this.accessData });
59+
}
60+
4561
return eventStatus;
4662
}
4763

@@ -77,10 +93,41 @@ class CollectorClient {
7793
return new CollectorClient(app, event);
7894
}
7995

96+
/**
97+
* reset with new request Event of ApiEndpoint
98+
* Identical as create but keep current event
99+
*/
100+
async reset (apiEndpoint, requesterEventId, accessInfo) {
101+
if (this.accessData && this.accessData?.deleted != null) {
102+
logger.error('TODO try to revoke current access');
103+
}
104+
// check content of accessInfo
105+
const publicStreamId = accessInfo.clientData.hdsCollector.public.streamId;
106+
// get request event cont
107+
const requesterConnection = new pryv.Connection(apiEndpoint);
108+
const requesterEvents = await requesterConnection.apiOne('events.get', { types: ['request/collector-v1'], streams: [publicStreamId], limit: 1 }, 'events');
109+
if (!requesterEvents[0]) throw new HDSLibError('Cannot find requester event in public stream', requesterEvents);
110+
111+
const eventData = await this.app.connection.apiOne('events.update', {
112+
id: this.eventData.id,
113+
update: {
114+
content: {
115+
apiEndpoint,
116+
requesterEventId,
117+
requesterEventData: requesterEvents[0],
118+
accessInfo,
119+
status: CollectorClient.STATUSES.incoming
120+
}
121+
}
122+
}, 'event');
123+
this.eventData = eventData;
124+
return this;
125+
}
126+
80127
/**
81128
* Update business event with new status
82129
* @param {string} newStatus
83-
* @param {Object} [extraData] - if given this will be added to content ⚠️ - This can ovveride content!
130+
* @param {Object} [extraData] - if given this will be added to content ⚠️ - This can overide content!
84131
*/
85132
async #updateStatus (newStatus, extraData = null) {
86133
const newContent = structuredClone(this.eventData.content);
@@ -95,48 +142,15 @@ class CollectorClient {
95142
this.eventData = eventData;
96143
}
97144

98-
/**
99-
* Refuse current request
100-
*/
101-
async refuse () {
102-
if (this.status !== 'Incoming') throw new HDSLibError('Cannot only refuse incoming requests');
103-
// sent access credentials to requester
104-
// check content of accessInfo
105-
const publicStreamId = this.eventData.content.accessInfo.clientData.hdsCollector.inbox.streamId;
106-
const requesterEventId = this.requesterEventId;
107-
const requestrerApiEndpoint = this.eventData.content.apiEndpoint;
108-
109-
// refuseEvent to be sent to requester
110-
const refuseEvent = {
111-
type: 'refusal/collector-v1',
112-
streamIds: [publicStreamId],
113-
content: {
114-
eventId: requesterEventId
115-
}
116-
};
117-
118-
const requesterConnection = new pryv.Connection(requestrerApiEndpoint);
119-
const requesterEvent = await requesterConnection.apiOne('events.create', refuseEvent, 'event');
120-
121-
await this.#updateStatus(CollectorClient.STATUSES.refused);
122-
return { requesterEvent };
123-
}
124-
125-
/**
126-
* Revoke current request
127-
* @param {boolean} forceAndSkipAccessCreation - internal temporary option,
128-
*/
129-
async revoke () {
130-
if (this.status !== 'Active') throw new HDSLibError('Cannot only revoke an Active CollectorClient');
131-
132-
console.log('Do something for revoke');
133-
}
134-
135145
/**
136146
* Accept current request
137147
* @param {boolean} forceAndSkipAccessCreation - internal temporary option,
138148
*/
139149
async accept (forceAndSkipAccessCreation = false) {
150+
if (this.accessData && this.accessData.deleted == null && this.status !== 'Active') {
151+
forceAndSkipAccessCreation = true;
152+
logger.error('CollectorClient.accept TODO fix accept when access valid');
153+
}
140154
if (forceAndSkipAccessCreation) {
141155
if (!this.accessData?.apiEndpoint || this.accessData?.delete) throw new HDSLibError('Cannot force accept with empty or deleted accessData', this.accessData);
142156
} else {
@@ -162,36 +176,65 @@ class CollectorClient {
162176
if (!this.accessData?.apiEndpoint) throw new HDSLibError('Failed creating request access', accessData);
163177
}
164178

179+
const responseContent = {
180+
apiEndpoint: this.accessData.apiEndpoint
181+
};
182+
183+
const requesterEvent = await this.#updateRequester('accept', responseContent);
184+
if (requesterEvent != null) {
185+
await this.#updateStatus(CollectorClient.STATUSES.active);
186+
return { accessData: this.accessData, requesterEvent };
187+
}
188+
return null;
189+
}
190+
191+
async refuse () {
192+
const responseContent = { };
193+
194+
const requesterEvent = await this.#updateRequester('refuse', responseContent);
195+
if (requesterEvent != null) {
196+
await this.#updateStatus(CollectorClient.STATUSES.refused);
197+
return { requesterEvent };
198+
}
199+
return null;
200+
}
201+
202+
/**
203+
* @param {string} type - one of 'accpet', 'revoke', 'refuse'
204+
* @param {object} responseContent - content is related to type
205+
* @returns {Object} - response
206+
*/
207+
async #updateRequester (type, responseContent) {
165208
// sent access credentials to requester
166209
// check content of accessInfo
167210
const publicStreamId = this.eventData.content.accessInfo.clientData.hdsCollector.inbox.streamId;
168211
const requesterEventId = this.requesterEventId;
169212
const requestrerApiEndpoint = this.eventData.content.apiEndpoint;
170213

214+
// add eventId to content
215+
const content = Object.assign({ type, eventId: requesterEventId }, responseContent);
216+
171217
// acceptEvent to be sent to requester
172-
const acceptEvent = {
173-
type: 'credentials/collector-v1',
218+
const responseEvent = {
219+
type: 'response/collector-v1',
174220
streamIds: [publicStreamId],
175-
content: {
176-
apiEndpoint: this.accessData.apiEndpoint,
177-
eventId: requesterEventId
178-
}
221+
content
179222
};
180223

181224
try {
182225
const requesterConnection = new pryv.Connection(requestrerApiEndpoint);
183-
const requesterEvent = await requesterConnection.apiOne('events.create', acceptEvent, 'event');
184-
await this.#updateStatus(CollectorClient.STATUSES.active);
185-
return { accessData: this.accessData, requesterEvent };
226+
const requesterEvent = await requesterConnection.apiOne('events.create', responseEvent, 'event');
227+
return requesterEvent;
186228
} catch (e) {
187229
const deactivatedDetail = {
188230
type: 'error',
189231
message: e.message
190232
};
191233
if (e.innerObject) deactivatedDetail.data = e.innerObject;
192234
logger.error('Failed activating', deactivatedDetail);
193-
await this.#updateStatus(CollectorClient.STATUSES.deactivated, { deactivatedDetail });
194-
return false;
235+
const deactivatedResult = await this.#updateStatus(CollectorClient.STATUSES.deactivated, { deactivatedDetail });
236+
console.log('***** ', { deactivatedResult });
237+
return null;
195238
}
196239
}
197240

@@ -202,13 +245,13 @@ class CollectorClient {
202245
// accessData but not active
203246
if (this.accessData && this.eventData.content.status == null) {
204247
logger.info('Found discrepency with accessData and status not active, fixing it');
205-
if (this.accessData.deleted) {
248+
if (!this.accessData.deleted) {
206249
await this.accept(true);
207250
} else {
208251
await this.revoke();
209252
}
210253
} else {
211-
logger.debug('CollectorClient:checkConsistency', this.accessData);
254+
// logger.debug('CollectorClient:checkConsistency', this.accessData);
212255
}
213256
}
214257

src/appTemplates/CollectorInvite.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class CollectorInvite {
3535
return this.eventData.content.apiEndpoint;
3636
}
3737

38+
/** @type {string} - on of 'revoked', 'refused' */
39+
get errorType () {
40+
return this.eventData.content?.errorType;
41+
}
42+
3843
get dateCreation () {
3944
return new Date(this.eventData.created * 1000);
4045
}

src/logger.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module.exports = {
1111
let logger = {
1212
info: log('info'),
1313
error: log('error'),
14-
debug: log('error')
14+
debug: log('debug')
1515
};
1616

1717
function setLogger (newLogger) {
@@ -20,7 +20,9 @@ function setLogger (newLogger) {
2020

2121
function info () { logger.info(...arguments); }
2222
function error () { logger.error(...arguments); }
23-
function debug () { logger.debug(...arguments); }
23+
function debug () {
24+
// logger.debug(...arguments);
25+
}
2426

2527
function log (type) {
2628
return function () {

0 commit comments

Comments
 (0)