Skip to content

Commit 3c7825f

Browse files
committed
Flow completed up to invite list
1 parent 5bfea6f commit 3c7825f

4 files changed

Lines changed: 185 additions & 15 deletions

File tree

src/appTemplates/Collector.js

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const { HDSLibError } = require('../errors');
2+
const CollectorInvite = require('./CollectorInvite');
23

34
const COLLECTOR_STREAMID_SUFFIXES = {
5+
archive: 'archive',
46
internal: 'internal',
57
public: 'public',
68
pending: 'pending',
@@ -38,7 +40,11 @@ class Collector {
3840
this.name = streamData.name;
3941
this.appManaging = appManaging;
4042
this.#streamData = streamData;
41-
this.#cache = {};
43+
this.#cache = {
44+
invites: {},
45+
invitesInitialized: false,
46+
invitesInitializing: false
47+
};
4248
}
4349

4450
/**
@@ -143,6 +149,76 @@ class Collector {
143149
return await this.#setStatus(Collector.STATUSES.active);
144150
}
145151

152+
#addOrUpdateInvite (eventData) {
153+
const key = CollectorInvite.getKeyForEvent(eventData);
154+
if (this.#cache.invites[key]) {
155+
this.#cache.invites[key].setEventData(eventData);
156+
} else {
157+
this.#cache.invites[key] = new CollectorInvite(this, eventData);
158+
}
159+
return this.#cache.invites[key];
160+
}
161+
162+
async getInvites (forceRefresh = false) {
163+
while (this.#cache.invitesInitializing) (await new Promise((resolve) => { setTimeout(resolve, 100); }));
164+
this.#cache.invitesInitializing = true;
165+
if (!forceRefresh && this.#cache.invitesInitialized) return Object.values(this.#cache.invites);
166+
const queryParams = { types: ['invite/collector-v1'], streams: [this.streamId], fromTime: 0, toTime: 8640000000000000, limit: 10000 };
167+
try {
168+
await this.appManaging.connection.getEventsStreamed(queryParams, (eventData) => {
169+
this.#addOrUpdateInvite(eventData);
170+
});
171+
} catch (e) {
172+
this.#cache.invitesInitialized = true;
173+
this.#cache.invitesInitializing = false;
174+
throw e;
175+
}
176+
this.#cache.invitesInitialized = true;
177+
this.#cache.invitesInitializing = false;
178+
return Object.values(this.#cache.invites);
179+
}
180+
181+
async checkInbox () {
182+
const tempr = [];
183+
184+
const params = { types: ['credentials/collector-v1'], limit: 1, streams: [this.streamIdFor(Collector.STREAMID_SUFFIXES.inbox)] };
185+
const incomingCredentials = await this.appManaging.connection.apiOne('events.get', params, 'events');
186+
for (const incomingCredential of incomingCredentials) {
187+
// fetch corresponding invite
188+
const inviteEvent = await this.appManaging.connection.apiOne('events.getOne', { id: incomingCredential.content.eventId }, 'event');
189+
if (inviteEvent == null) throw new HDSLibError(`Cannot find invite event matching id: ${incomingCredential.content.eventId}`, incomingCredential);
190+
// update inviteEvent and archive inbox message
191+
const apiCalls = [
192+
{
193+
method: 'events.update',
194+
params: {
195+
id: inviteEvent.id,
196+
update: {
197+
streamIds: [this.streamIdFor(Collector.STREAMID_SUFFIXES.active)],
198+
content: Object.assign(inviteEvent.content, { apiEndpoint: incomingCredential.content.apiEndpoint })
199+
}
200+
}
201+
},
202+
{
203+
method: 'events.update',
204+
params: {
205+
id: incomingCredential.id,
206+
update: {
207+
streamIds: [this.streamIdFor(Collector.STREAMID_SUFFIXES.archive)]
208+
}
209+
}
210+
}
211+
];
212+
const results = await this.appManaging.connection.api(apiCalls);
213+
const errors = results.filter((r) => (!r.event));
214+
if (errors.length > 0) throw new HDSLibError('Error activating incoming request', errors);
215+
const eventUpdated = results[0].event;
216+
const inviteUpdated = this.#addOrUpdateInvite(eventUpdated);
217+
tempr.push(inviteUpdated);
218+
}
219+
return tempr;
220+
}
221+
146222
/**
147223
* Create a "pending" invite to be sent to an app using AppSharingAccount
148224
* @param {string} name a default display name for this request
@@ -160,11 +236,8 @@ class Collector {
160236
}
161237
};
162238
const newInvite = await this.appManaging.connection.apiOne('events.create', eventParams, 'event');
163-
const result = {
164-
apiEndpoint: await this.sharingApiEndpoint(),
165-
eventId: newInvite.id
166-
};
167-
return result;
239+
const invite = this.#addOrUpdateInvite(newInvite);
240+
return invite;
168241
}
169242

170243
/**
@@ -260,6 +333,22 @@ class Collector {
260333
streamIdFor (suffix) {
261334
return this.streamId + '-' + suffix;
262335
}
336+
337+
/**
338+
* Invite Status for streamId
339+
* reverse of streamIdFor
340+
*/
341+
inviteStatusForStreamId (streamId) {
342+
if (!this.#cache.inviteStatusForStreamId) {
343+
this.#cache.inviteStatusForStreamId = {};
344+
for (const status of [COLLECTOR_STREAMID_SUFFIXES.pending, COLLECTOR_STREAMID_SUFFIXES.active, COLLECTOR_STREAMID_SUFFIXES.error]) {
345+
this.#cache.inviteStatusForStreamId[this.streamIdFor(status)] = status;
346+
}
347+
}
348+
const status = this.#cache.inviteStatusForStreamId[streamId];
349+
if (status == null) throw new HDSLibError(`Cannot find status for streamId: ${streamId}`);
350+
return status;
351+
}
263352
}
264353

265354
module.exports = Collector;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
const pryv = require('pryv');
2+
const { HDSLibError } = require('../errors');
3+
4+
/**
5+
* Collector Invite
6+
* There is one Collector Invite per Collector => Enduser connection
7+
*/
8+
9+
class CollectorInvite {
10+
/**
11+
* get the key that will be assigne to this event;
12+
* @param {Event} eventData
13+
* @returns {string}
14+
*/
15+
static getKeyForEvent (eventData) {
16+
return eventData.id;
17+
}
18+
19+
/** @type {Collector} */
20+
collector;
21+
/** @type {Event} */
22+
eventData;
23+
/** @type {pryv.Connection} */
24+
#connection;
25+
26+
get key () {
27+
return CollectorInvite.getKeyForEvent(this.eventData);
28+
}
29+
30+
get status () {
31+
return this.collector.inviteStatusForStreamId(this.eventData.streamIds[0]);
32+
}
33+
34+
get apiEndpoint () {
35+
return this.eventData.content.apiEndpoint;
36+
}
37+
38+
get connection () {
39+
if (this.#connection == null) {
40+
this.#connection = new pryv.Connection(this.apiEndpoint);
41+
}
42+
return this.#connection;
43+
}
44+
45+
get displayName () {
46+
return this.eventData.content.name;
47+
}
48+
49+
constructor (collector, eventData) {
50+
if (eventData.type !== 'invite/collector-v1') throw new HDSLibError('Wrong type of event', eventData);
51+
this.collector = collector;
52+
this.eventData = eventData;
53+
}
54+
55+
setEventData (eventData) {
56+
if (eventData.id !== this.eventData.id) throw new HDSLibError('CollectInvite event id does not match new Event');
57+
this.eventData = eventData;
58+
}
59+
60+
async getSharingData () {
61+
if (this.status !== 'pending') throw new HDSLibError('Only pendings can be shared');
62+
return {
63+
apiEndpoint: await this.collector.sharingApiEndpoint(),
64+
eventId: this.eventData.id
65+
};
66+
}
67+
}
68+
69+
module.exports = CollectorInvite;

src/errors.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class HDSLibError extends Error {
22
constructor (message, innerObject = { }) {
3-
const msg = (innerObject.message !== null) ? message + ' >> ' + innerObject.message : message;
3+
const msg = (innerObject.message != null) ? message + ' >> ' + innerObject.message : message;
44
super(msg);
55
this.innerObject = innerObject;
66
}

tests/apptemplates.test.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('[APTX] appTemplates', function () {
5757

5858
// check StreamStructure
5959
const resultCheckStructure = await newCollector.checkStreamStructure();
60-
assert.equal(resultCheckStructure.created.length, 6, 'Should create 5 streams');
60+
assert.equal(resultCheckStructure.created.length, 7, 'Should create 7 streams');
6161
for (const created of resultCheckStructure.created) {
6262
assert.equal(created.parentId, newCollector.streamId, 'Should have collector stream as parentid');
6363
}
@@ -172,11 +172,14 @@ describe('[APTX] appTemplates', function () {
172172
// create invite
173173
const options = { customData: { hello: 'bob' } };
174174
const invite = await newCollector.createInvite('Invite One', options);
175-
assert.equal(invite.apiEndpoint, await newCollector.sharingApiEndpoint());
176-
assert.ok(invite.eventId.length > 5);
175+
assert.equal(invite.status, 'pending');
176+
const inviteSharingData = await invite.getSharingData();
177+
assert.equal(inviteSharingData.apiEndpoint, await newCollector.sharingApiEndpoint());
178+
assert.ok(invite.key.length > 5);
179+
assert.ok(inviteSharingData.eventId.length > 5);
177180

178181
// check invite can be found in "pendings"
179-
const inviteEvent = await appManaging.connection.apiOne('events.getOne', { id: invite.eventId }, 'event');
182+
const inviteEvent = await appManaging.connection.apiOne('events.getOne', { id: inviteSharingData.eventId }, 'event');
180183
assert.equal(inviteEvent.type, 'invite/collector-v1');
181184
assert.ok(inviteEvent.streamIds[0].endsWith('-pending'));
182185
assert.deepEqual(inviteEvent.content, { name: 'Invite One', customData: options.customData });
@@ -185,10 +188,10 @@ describe('[APTX] appTemplates', function () {
185188
const permissionsClient = [{ streamId: '*', level: 'manage' }];
186189
const clientUser = await createUserPermissions(user, permissionsClient, [], appClientName);
187190
const appClient = await AppClientAccount.newFromApiEndpoint(baseStreamIdClient, clientUser.appApiEndpoint, appClientName);
188-
const collectorClient = await appClient.handleIncomingRequest(invite.apiEndpoint, invite.eventId);
191+
const collectorClient = await appClient.handleIncomingRequest(inviteSharingData.apiEndpoint, inviteSharingData.eventId);
189192
assert.equal(collectorClient.eventData.streamIds[0], appClient.baseStreamId);
190-
assert.equal(collectorClient.eventData.content.apiEndpoint, invite.apiEndpoint);
191-
assert.equal(collectorClient.eventData.content.requesterEventId, invite.eventId);
193+
assert.equal(collectorClient.eventData.content.apiEndpoint, inviteSharingData.apiEndpoint);
194+
assert.equal(collectorClient.eventData.content.requesterEventId, inviteSharingData.eventId);
192195

193196
// TODO check collectorClient.eventData.accessInfo
194197

@@ -203,13 +206,22 @@ describe('[APTX] appTemplates', function () {
203206

204207
// accept
205208
const acceptResult = await collectorClient.accept();
206-
assert.equal(acceptResult.requesterEvent.content.eventId, invite.eventId);
209+
assert.equal(acceptResult.requesterEvent.content.eventId, inviteSharingData.eventId);
207210
assert.ok(!!acceptResult.requesterEvent.content.apiEndpoint);
208211

209212
// force refresh and check online
210213
const collectorClients2 = await appClient.getCollectorClients(true);
211214
assert.equal(collectorClients2.length, 1);
212215
assert.deepEqual(collectorClients2[0].accessData, acceptResult.accessData);
216+
217+
// Continue on Collector side
218+
const invitesFromInbox = await newCollector.checkInbox();
219+
assert.equal(invitesFromInbox[0].eventData.type, 'invite/collector-v1');
220+
assert.equal(invitesFromInbox[0].status, 'active');
221+
222+
// check current invites
223+
const invites = await newCollector.getInvites(true);
224+
assert.equal(invites[0], invitesFromInbox[0]);
213225
});
214226

215227
describe('[APCX] app Templates Client', function () {

0 commit comments

Comments
 (0)