Skip to content

Commit c869c49

Browse files
committed
Applicatio Client Account
1 parent d5b6aee commit c869c49

7 files changed

Lines changed: 123 additions & 24 deletions

File tree

src/appTemplates/AppClientAccount.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,66 @@
1+
const { HDSLibError } = require('../errors');
2+
const pryv = require('../patchedPryv');
13
const Application = require('./Application');
4+
const CollectorClient = require('./CollectorClient');
5+
26
/**
37
* - applications
48
* - [baseStreamId] "Root" stream from this app
59
*/
10+
11+
const MAX_COLLECTORS = 1000;
612
class AppClientAccount extends Application {
13+
constructor (baseStreamId, connection, appName) {
14+
super(...arguments);
15+
this.cache.collectorClientsMap = {};
16+
}
17+
718
get appSettings () {
819
return {
920
canBePersonnal: true,
1021
mustBeMaster: true
1122
};
1223
}
1324

25+
/**
26+
* When the app receives a new request for data sharing
27+
* @param {string} apiEndpoint
28+
* @param {string} [incomingEventId] - Information for the recipient
29+
*/
30+
async handleIncomingRequest (apiEndpoint, incomingEventId) {
31+
const requesterConnection = new pryv.Connection(apiEndpoint);
32+
const accessInfos = await requesterConnection.accessInfo();
33+
// check if request is known
34+
if (this.cache.collectorClientsMap[CollectorClient.keyFromInfo(accessInfos)]) {
35+
const collectorClient = this.collectoClientsMap[accessInfos.name];
36+
if (incomingEventId && collectorClient.requesterEventId !== incomingEventId) {
37+
throw new HDSLibError('Found existing collectorClient with a different eventId', { actual: collectorClient.requesterEventId, incoming: incomingEventId });
38+
}
39+
return collectorClient;
40+
}
41+
// check if comming form hdsCollector
42+
if (!accessInfos?.clientData?.hdsCollector) {
43+
throw new HDSLibError('Invalid collector request, cannot find clientData.hdsCollector', { clientData: accessInfos?.clientData });
44+
}
45+
// else create it
46+
47+
const collectorClient = await CollectorClient.create(this, apiEndpoint, incomingEventId, accessInfos);
48+
this.cache.collectorClientsMap[collectorClient.key] = collectorClient;
49+
return collectorClient;
50+
}
51+
52+
async getCollectorClients () {
53+
const apiCalls = [{
54+
method: 'accesses.get',
55+
params: { includeDeletions: true }
56+
}, {
57+
method: 'events.get',
58+
params: { types: ['request/collector-client-v1'], streamIds: [this.baseStreamId], limit: MAX_COLLECTORS }
59+
}
60+
];
61+
const [accessesRes, eventRes] = await this.connection.api(apiCalls);
62+
}
63+
1464
/**
1565
* - Check connection validity
1666
* - Make sure stream structure exists

src/appTemplates/Application.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const pryv = require('../patchedPryv');
2+
/**
3+
* Common code for AppClientAccount and AppManagingAccount
4+
*/
25
class Application {
36
/** @type {Pryv.Connection} */
47
connection;
@@ -114,7 +117,8 @@ async function createAppStreams (app) {
114117
isPersonalOrMaster = true;
115118
if (app.appSettings.mustBemaster && !masterFound) {
116119
throw new Error('Application with "app" type of access requires "master" token (streamId = "*", level = "manage")');
117-
} else { // check that app has "manage" level on baseStreamId
120+
}
121+
if (!masterFound) { // check that app has "manage" level on baseStreamId
118122
const baseStreamFound = infos.permissions.find(p => (p.streamId === app.baseStreamId && p.level === 'manage'));
119123
if (!baseStreamFound) throw new Error(`Application with "app" type of access requires (streamId = '${app.baseStreamId}', level = "manage") or master access`);
120124
}

src/appTemplates/Collector.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,10 @@ class Collector {
153153
const clientData = {
154154
hdsCollector: {
155155
public: {
156-
streamId: this.streamIdFor(Collector.public)
156+
streamId: this.streamIdFor(Collector.STREAMID_SUFFIXES.public)
157157
},
158158
inbox: {
159-
streamId: this.streamIdFor(Collector.inbox)
159+
streamId: this.streamIdFor(Collector.STREAMID_SUFFIXES.inbox)
160160
}
161161
}
162162
};

src/appTemplates/CollectorClient.js

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,47 @@
55
*/
66

77
class CollectorClient {
8-
apiEndpoint;
9-
eventId;
10-
constructor (apiEndpoint, eventId) {
11-
this.apiEndpoint = apiEndpoint;
12-
this.eventId = eventId;
8+
/** @type {AppClientAccount} */
9+
app;
10+
/** @type {PryvEvent} */
11+
eventData;
12+
13+
get key () {
14+
return CollectorClient.keyFromInfo(this.eventData.content.accessInfo);
15+
}
16+
17+
get requesterEventId () {
18+
return this.eventData.content.requesterEventId;
19+
}
20+
21+
constructor (app, eventData) {
22+
this.app = app;
23+
this.eventData = eventData;
24+
}
25+
26+
/**
27+
* Create a new Event
28+
*/
29+
static async create (app, apiEndpoint, requesterEventId, accessInfo) {
30+
const eventData = {
31+
type: 'request/collector-client-v1',
32+
streamIds: [app.baseStreamId],
33+
content: {
34+
apiEndpoint,
35+
requesterEventId,
36+
accessInfo
37+
}
38+
};
39+
const event = await app.connection.apiOne('events.create', eventData, 'event');
40+
return new CollectorClient(app, event);
41+
}
42+
43+
/**
44+
* return the key to discriminate collectorClients
45+
* @param {PryvAccessInfo} accessInfo
46+
*/
47+
static keyFromInfo (info) {
48+
return info.user.username + ':' + info.name;
1349
}
1450
}
1551

src/patchedPryv.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,25 @@
44
*/
55
const pryv = require('pryv');
66

7+
// patch Pryv only if needed.
8+
if (!pryv.Connection.prototype.apiOne) {
79
/**
810
* Make one api Api call
911
* @param {string} method - methodId
1012
* @param {object} [params] - the method associated with this result
1113
* @param {string} [resultKey] - if given, returns the value or throws an error if not present
1214
* @throws {Error} if .error is present the response
1315
*/
14-
pryv.Connection.prototype.apiOne = async function (method, params = {}, expectedKey) {
15-
const result = await this.api([{ method, params }]);
16-
if (result[0] == null || result[0].error || (expectedKey != null && result[0][expectedKey] == null)) {
17-
const innerObject = result[0]?.error || result;
18-
const error = new Error(`Error for api method: "${method}" with params: ${JSON.stringify(params)} >> Result: ${JSON.stringify(innerObject)}"`);
19-
error.innerObject = innerObject;
20-
throw error;
21-
}
22-
if (expectedKey != null) return result[0][expectedKey];
23-
return result[0];
24-
};
25-
16+
pryv.Connection.prototype.apiOne = async function (method, params = {}, expectedKey) {
17+
const result = await this.api([{ method, params }]);
18+
if (result[0] == null || result[0].error || (expectedKey != null && result[0][expectedKey] == null)) {
19+
const innerObject = result[0]?.error || result;
20+
const error = new Error(`Error for api method: "${method}" with params: ${JSON.stringify(params)} >> Result: ${JSON.stringify(innerObject)}"`);
21+
error.innerObject = innerObject;
22+
throw error;
23+
}
24+
if (expectedKey != null) return result[0][expectedKey];
25+
return result[0];
26+
};
27+
}
2628
module.exports = pryv;

tests/apptemplates.test.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('[APTX] appTemplates', function () {
1919
managingUser = await createUserAndPermissions(null, permissionsManager, initialStreams, appName);
2020
const connection = new pryv.Connection(managingUser.appApiEndpoint);
2121
appManaging = await AppManagingAccount.newFromConnection(baseStreamIdManager, connection);
22-
// -- user
22+
// -- receiving user
2323
user = await createUser();
2424
});
2525

@@ -125,12 +125,20 @@ describe('[APTX] appTemplates', function () {
125125
assert.deepEqual(inviteEvent.content, { name: 'Invite One', customData: options.customData });
126126

127127
// Invitee receives the invite
128+
const permissionsClient = [{ streamId: '*', level: 'manage' }];
129+
const clientUser = await createUserPermissions(user, permissionsClient, [], appClientName);
130+
const appClient = await AppClientAccount.newFromApiEndpoint(baseStreamIdClient, clientUser.appApiEndpoint, appClientName);
131+
const collectorClient = await appClient.handleIncomingRequest(invite.apiEndpoint, invite.eventId);
132+
assert.equal(collectorClient.eventData.streamIds[0], appClient.baseStreamId);
133+
assert.equal(collectorClient.eventData.content.apiEndpoint, invite.apiEndpoint);
134+
assert.equal(collectorClient.eventData.content.requesterEventId, invite.eventId);
135+
// check collectorClient.eventData.accessInfo
128136
});
129137

130138
describe('[APCX] app Templates Client', function () {
131139
it('[APCE] Should throw error if not initialized with a personal or master token', async () => {
132-
const permissionsManager = [{ streamId: 'dummy', level: 'manage' }];
133-
const clientUserNonMaster = await createUserPermissions(user, permissionsManager, [], appName);
140+
const permissionsDummy = [{ streamId: 'dummy', level: 'manage' }];
141+
const clientUserNonMaster = await createUserPermissions(user, permissionsDummy, [], appName);
134142
// non master app
135143
try {
136144
await AppClientAccount.newFromApiEndpoint(baseStreamIdClient, clientUserNonMaster.appApiEndpoint, appClientName);

tests/test-utils/pryvService.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ async function createUserPermissions (user, permissions, initialStreams = [], ap
130130
const res = await personalConnection.api(apiCalls);
131131
const accessRequestResult = res.pop();
132132
const appApiEndpoint = accessRequestResult.access?.apiEndpoint;
133-
134133
const result = {
135134
username: user.username,
136135
personalApiEndpoint: user.apiEndpoint,

0 commit comments

Comments
 (0)