Skip to content

Commit 9d71937

Browse files
committed
Now getting request event on CollectorClient
1 parent 13a0378 commit 9d71937

6 files changed

Lines changed: 178 additions & 33 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ An `ItemdDef` is an object representation of the items from the data Model
2121

2222
```javascript
2323
// retrieve an itemDef by it's key
24-
const weight = mode.itemDefs.forKey('body-weight');
24+
const weight = model.itemDefs.forKey('body-weight');
2525
weight.streamId; // => 'body-weight'
2626
weight.eventTypes; // => 'mass/kg', 'mass/lb']
2727
```

src/appTemplates/AppClientAccount.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,51 @@ class AppClientAccount extends Application {
2929
*/
3030
async handleIncomingRequest (apiEndpoint, incomingEventId) {
3131
const requesterConnection = new pryv.Connection(apiEndpoint);
32-
const accessInfos = await requesterConnection.accessInfo();
32+
const accessInfo = await requesterConnection.accessInfo();
3333
// check if request is known
34-
if (this.cache.collectorClientsMap[CollectorClient.keyFromInfo(accessInfos)]) {
35-
const collectorClient = this.collectoClientsMap[accessInfos.name];
34+
if (this.cache.collectorClientsMap[CollectorClient.keyFromInfo(accessInfo)]) {
35+
const collectorClient = this.collectoClientsMap[accessInfo.name];
3636
if (incomingEventId && collectorClient.requesterEventId !== incomingEventId) {
3737
throw new HDSLibError('Found existing collectorClient with a different eventId', { actual: collectorClient.requesterEventId, incoming: incomingEventId });
3838
}
3939
return collectorClient;
4040
}
4141
// check if comming form hdsCollector
42-
if (!accessInfos?.clientData?.hdsCollector) {
43-
throw new HDSLibError('Invalid collector request, cannot find clientData.hdsCollector', { clientData: accessInfos?.clientData });
42+
if (!accessInfo?.clientData?.hdsCollector || accessInfo.clientData?.hdsCollector?.version !== 0) {
43+
throw new HDSLibError('Invalid collector request, cannot find clientData.hdsCollector or wrong version', { clientData: accessInfo?.clientData });
4444
}
4545
// else create it
4646

47-
const collectorClient = await CollectorClient.create(this, apiEndpoint, incomingEventId, accessInfos);
47+
const collectorClient = await CollectorClient.create(this, apiEndpoint, incomingEventId, accessInfo);
4848
this.cache.collectorClientsMap[collectorClient.key] = collectorClient;
4949
return collectorClient;
5050
}
5151

52-
async getCollectorClients () {
52+
async getCollectorClients (forceRefresh = false) {
53+
if (!forceRefresh && this.cache.collectorClientsMapInitialized) return Object.values(this.cache.collectoClientsMap);
5354
const apiCalls = [{
5455
method: 'accesses.get',
5556
params: { includeDeletions: true }
5657
}, {
5758
method: 'events.get',
58-
params: { types: ['request/collector-client-v1'], streamIds: [this.baseStreamId], limit: MAX_COLLECTORS }
59-
}
60-
];
59+
params: { types: ['request/collector-client-v1'], streams: [this.baseStreamId], limit: MAX_COLLECTORS }
60+
}];
6161
const [accessesRes, eventRes] = await this.connection.api(apiCalls);
62+
for (const access of accessesRes.accesses) {
63+
// do something
64+
console.log('##', access);
65+
}
66+
for (const access of accessesRes.accessDeletions) {
67+
// do something
68+
console.log('##', access);
69+
}
70+
for (const event of eventRes.events) {
71+
const collectorClient = new CollectorClient(this, event);
72+
this.cache.collectorClientsMap[collectorClient.key] = collectorClient;
73+
}
74+
75+
this.cache.collectorClientsMapInitialized = true;
76+
return Object.values(this.cache.collectorClientsMap);
6277
}
6378

6479
/**

src/appTemplates/Collector.js

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
/**
2-
* Collector is used by AppManagingAccount
3-
* A "Collector" can be seen as a "Request" and set of "Responses"
4-
* - Responses are authorization tokens from individuals
5-
*/
6-
71
const { HDSLibError } = require('../errors');
82

93
const COLLECTOR_STREAMID_SUFFIXES = {
@@ -15,6 +9,12 @@ const COLLECTOR_STREAMID_SUFFIXES = {
159
error: 'error'
1610
};
1711
Object.freeze(COLLECTOR_STREAMID_SUFFIXES);
12+
13+
/**
14+
* Collector is used by AppManagingAccount
15+
* A "Collector" can be seen as a "Request" and set of "Responses"
16+
* - Responses are authorization tokens from individuals
17+
*/
1818
class Collector {
1919
static STREAMID_SUFFIXES = COLLECTOR_STREAMID_SUFFIXES;
2020
static STATUSES = Object.freeze({
@@ -49,12 +49,35 @@ class Collector {
4949
return this.#cache.status.content.status;
5050
}
5151

52+
/**
53+
* @typedef {RequestContent}
54+
* @property {number} version
55+
* @property {Localizable} description
56+
* @property {Localizable} consent
57+
* @property {Array<Permission>} permissions - Like Pryv permission request
58+
* @property {Object} app
59+
* @property {String} app.id
60+
* @property {String} app.url
61+
* @property {Object} app.data - to be finalized
62+
*/
63+
64+
/**
65+
* @typedef {StatusData}
66+
* @property {RequestContent} requestContent
67+
*/
68+
69+
/** @type {StatusData} */
70+
get statusData () {
71+
if (this.#cache.status == null) throw new Error('Init Collector first');
72+
return this.#cache.status.content.data;
73+
}
74+
5275
/**
5376
* Fetch online data
5477
*/
5578
async init () {
5679
await this.checkStreamStructure();
57-
await this.getStatus();
80+
await this.#getStatus();
5881
}
5982

6083
/**
@@ -69,12 +92,12 @@ class Collector {
6992
* @param {boolean} forceRefresh - if true, forces fetching the status from the server
7093
* @returns {StatusEvent}
7194
*/
72-
async getStatus (forceRefresh = false) {
95+
async #getStatus (forceRefresh = false) {
7396
if (!forceRefresh && this.#cache.status) return this.#cache.status;
7497
const params = { types: ['status/collector-v1'], limit: 1, streams: [this.streamIdFor(Collector.STREAMID_SUFFIXES.internal)] };
7598
const statusEvents = await this.appManaging.connection.apiOne('events.get', params, 'events');
7699
if (statusEvents.length === 0) { // non exsitent set "draft" status
77-
return this.setStatus(Collector.STATUSES.draft, {});
100+
return this.#setStatus(Collector.STATUSES.draft);
78101
}
79102
this.#cache.status = statusEvents[0];
80103
return this.#cache.status;
@@ -83,11 +106,15 @@ class Collector {
83106
/**
84107
* Change the status
85108
* @param {string} status one of of 'draft', 'active', 'deactivated'
86-
* @param {object} data - custom data
109+
* @param {object} [data] - if not set reuuse current data or { requestContent: {} }
87110
* @returns {StatusEvent}
88111
*/
89-
async setStatus (status, data) {
112+
async #setStatus (status, data) {
90113
if (!Collector.STATUSES[status]) throw new HDSLibError('Unkown status key', { status, data });
114+
if (!data) {
115+
data = (!this.#cache.status) ? { requestContent: {} } : this.statusData;
116+
}
117+
91118
const event = {
92119
type: 'status/collector-v1',
93120
streamIds: [this.streamIdFor(Collector.STREAMID_SUFFIXES.internal)],
@@ -101,13 +128,29 @@ class Collector {
101128
return this.#cache.status;
102129
}
103130

131+
async save () {
132+
if (this.statusCode !== Collector.STATUSES.draft) throw new Error(`Cannot save when status = "${this.statusCode}".`);
133+
return await this.#setStatus(Collector.STATUSES.draft);
134+
}
135+
136+
async publish () {
137+
const publicEventData = {
138+
type: 'request/collector-v1',
139+
streamIds: [this.streamIdFor(Collector.STREAMID_SUFFIXES.public)],
140+
content: this.statusData.requestContent
141+
};
142+
await this.appManaging.connection.apiOne('events.create', publicEventData, 'event');
143+
return await this.#setStatus(Collector.STATUSES.active);
144+
}
145+
104146
/**
105147
* Create a "pending" invite to be sent to an app using AppSharingAccount
106148
* @param {string} name a default display name for this request
107149
* @param {Object} [options]
108150
* @param {Object} [options.customData] any data to be used by the client app
109151
*/
110152
async createInvite (name, options = {}) {
153+
if (this.statusCode !== Collector.STATUSES.active) throw new Error(`Collector must be in "active" state error to create invite, current: ${this.statusCode}`);
111154
const eventParams = {
112155
type: 'invite/collector-v1',
113156
streamIds: [this.streamIdFor(Collector.STREAMID_SUFFIXES.pending)],
@@ -128,6 +171,7 @@ class Collector {
128171
* Get sharing api endpoint
129172
*/
130173
async sharingApiEndpoint () {
174+
if (this.statusCode !== Collector.STATUSES.active) throw new Error(`Collector must be in "active" state error to get sharing link, current: ${this.statusCode}`);
131175
if (this.#cache.sharingApiEndpoint) return this.#cache.sharingApiEndpoint;
132176
// check if sharing present
133177
const sharedAccessId = 'a-' + this.streamId;
@@ -152,6 +196,7 @@ class Collector {
152196
];
153197
const clientData = {
154198
hdsCollector: {
199+
version: 0,
155200
public: {
156201
streamId: this.streamIdFor(Collector.STREAMID_SUFFIXES.public)
157202
},

src/appTemplates/CollectorClient.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1+
const pryv = require('pryv');
2+
const { HDSLibError } = require('../errors');
3+
14
/**
25
* Client App in relation to an AppManagingAccount/Collector
3-
* A "Collector" can be seen as a "Request" and set of "Responses"
4-
*
56
*/
6-
77
class CollectorClient {
8+
static STATUSES = Object.freeze({
9+
incoming: 'Incoming',
10+
active: 'Active',
11+
deactivated: 'Deactivated',
12+
refused: 'Refused'
13+
});
14+
815
/** @type {AppClientAccount} */
916
app;
1017
/** @type {PryvEvent} */
1118
eventData;
19+
/** @type {Object} - when active or deactivated - there is a link with accessData */
20+
accessData;
1221

1322
get key () {
1423
return CollectorClient.keyFromInfo(this.eventData.content.accessInfo);
@@ -18,6 +27,13 @@ class CollectorClient {
1827
return this.eventData.content.requesterEventId;
1928
}
2029

30+
get status () {
31+
if (!this.accessData && this.eventData.status === CollectorClient.STATUSES.refused) {
32+
return CollectorClient.STATUSES.refused;
33+
}
34+
return CollectorClient.STATUSES.incoming;
35+
}
36+
2137
constructor (app, eventData) {
2238
this.app = app;
2339
this.eventData = eventData;
@@ -27,12 +43,20 @@ class CollectorClient {
2743
* Create a new Event
2844
*/
2945
static async create (app, apiEndpoint, requesterEventId, accessInfo) {
46+
// check content of accessInfo
47+
const publicStreamId = accessInfo.clientData.hdsCollector.public.streamId;
48+
// get request event cont
49+
const requesterConnection = new pryv.Connection(apiEndpoint);
50+
const requesterEvents = await requesterConnection.apiOne('events.get', { streams: [publicStreamId], limit: 1 }, 'events');
51+
if (!requesterEvents[0]) throw new HDSLibError('Cannot find requester event in public stream', requesterEvents);
52+
3053
const eventData = {
3154
type: 'request/collector-client-v1',
3255
streamIds: [app.baseStreamId],
3356
content: {
3457
apiEndpoint,
3558
requesterEventId,
59+
requesterEventData: requesterEvents[0],
3660
accessInfo
3761
}
3862
};

src/index-webpack.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import HDSModel from './HDSModel/HDSModel';
2-
import appTemplates from './appTemplates';
2+
import appTemplates from './appTemplates/appTemplates';
33
/**
44
* Export for webpack build
55
*/
6-
export const HDSLib = {
6+
export {
77
HDSModel,
88
appTemplates
99
};

tests/apptemplates.test.js

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const assert = require('node:assert/strict');
33
const { createUserAndPermissions, pryv, createUser, createUserPermissions } = require('./test-utils/pryvService');
44
const AppManagingAccount = require('../src/appTemplates/AppManagingAccount');
55
const AppClientAccount = require('../src/appTemplates/AppClientAccount');
6+
const Collector = require('../src/appTemplates/Collector');
67

78
describe('[APTX] appTemplates', function () {
89
this.timeout(10000);
@@ -74,14 +75,31 @@ describe('[APTX] appTemplates', function () {
7475
assert.equal(e.message, 'Init Collector first');
7576
}
7677

78+
await newCollector.init();
79+
7780
// Get status
78-
const currentStatus = await newCollector.getStatus();
79-
assert.equal(currentStatus.content.status, 'draft');
8081
assert.equal(newCollector.statusCode, 'draft');
8182

82-
// Get status 2nd should retrun the same
83-
const currentStatus2 = await newCollector.getStatus();
84-
assert.equal(currentStatus2, currentStatus);
83+
// trying to get a sharing token in draft should throw an error
84+
try {
85+
// eslint-disable-next-line no-unused-expressions
86+
await newCollector.sharingApiEndpoint();
87+
throw new Error('Should throw error');
88+
} catch (e) {
89+
assert.equal(e.message, 'Collector must be in "active" state error to get sharing link, current: draft');
90+
}
91+
92+
// trying to create an invite in draft should throw an error
93+
try {
94+
// eslint-disable-next-line no-unused-expressions
95+
await newCollector.createInvite({});
96+
throw new Error('Should throw error');
97+
} catch (e) {
98+
assert.equal(e.message, 'Collector must be in "active" state error to create invite, current: draft');
99+
}
100+
101+
// Publish
102+
await newCollector.publish();
85103

86104
// Sharing token creation
87105
const sharingApiEndpoint = await newCollector.sharingApiEndpoint();
@@ -104,6 +122,7 @@ describe('[APTX] appTemplates', function () {
104122
const resultCheckStructure3 = await collector2.checkStreamStructure();
105123
assert.equal(resultCheckStructure3.created.length, 0, 'Should create 0 streams');
106124
// should return the same access access point
125+
await collector2.init();
107126
const sharingApiEndpoint3 = await collector2.sharingApiEndpoint();
108127
assert.equal(sharingApiEndpoint3, sharingApiEndpoint);
109128
});
@@ -112,6 +131,41 @@ describe('[APTX] appTemplates', function () {
112131
const newCollector = await appManaging.createCollector('Invite test 1');
113132
assert(newCollector.statusCode, 'draft');
114133

134+
// set request content
135+
const requestContent = {
136+
version: 0,
137+
description: {
138+
en: 'Short Description'
139+
},
140+
consent: {
141+
en: 'This is a consent message'
142+
},
143+
permissions: [
144+
{ streamId: 'profile-name', defaultName: 'Name', level: 'read' },
145+
{
146+
streamId: 'profile-date-of-birth',
147+
defaultName: 'Date of Birth',
148+
level: 'read'
149+
}
150+
],
151+
app: { // may have "url" in the future
152+
id: 'test-app',
153+
url: 'https://xxx.yyy',
154+
data: { // settings for the app
155+
dummy: 'dummy'
156+
}
157+
}
158+
};
159+
newCollector.statusData.requestContent = requestContent;
160+
161+
// save
162+
await newCollector.save();
163+
assert.deepEqual(newCollector.statusData.requestContent, requestContent);
164+
assert.ok(newCollector.statusData.requestContent !== requestContent, 'Should be the same content but different objects');
165+
// publish
166+
await newCollector.publish();
167+
assert.equal(newCollector.statusCode, Collector.STATUSES.active);
168+
115169
// create invite
116170
const options = { customData: { hello: 'bob' } };
117171
const invite = await newCollector.createInvite('Invite One', options);
@@ -132,7 +186,14 @@ describe('[APTX] appTemplates', function () {
132186
assert.equal(collectorClient.eventData.streamIds[0], appClient.baseStreamId);
133187
assert.equal(collectorClient.eventData.content.apiEndpoint, invite.apiEndpoint);
134188
assert.equal(collectorClient.eventData.content.requesterEventId, invite.eventId);
135-
// check collectorClient.eventData.accessInfo
189+
190+
// TODO check collectorClient.eventData.accessInfo
191+
192+
// check collectorClients
193+
const collectorClientsCached = await appClient.getCollectorClients();
194+
assert.equal(collectorClientsCached.length, 1);
195+
const collectorClients = await appClient.getCollectorClients(true);
196+
assert.equal(collectorClients.length, 1);
136197
});
137198

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

0 commit comments

Comments
 (0)