|
| 1 | +const { HDSLibError } = require('../errors'); |
| 2 | +const ShortUniqueId = require('short-unique-id'); |
| 3 | +const collectorIdGenerator = new ShortUniqueId({ dictionary: 'alphanum_lower', length: 7 }); |
| 4 | + |
| 5 | +/** |
| 6 | + * App which manages Collectors |
| 7 | + * A "Collector" can be see as a "Request" and set of "Responses" |
| 8 | + * - Responses are authorization tokens from individuals |
| 9 | + * |
| 10 | + * The App can create multiple "collectors e.g. Questionnaries" |
| 11 | + * |
| 12 | + * Stream structure |
| 13 | + * - [baseStreamId] "Root" stream for this app |
| 14 | + * - [baseStreamId]-[collectorsId] Each "questionnary" or "request for a set of data" has it's own stream |
| 15 | + * - [baseStreamId]-[collectorsId]-settings Contains events with the current settings of this app (this stream will be shared in "read" with the request) |
| 16 | + * - [baseStreamId]-[collectorsId]-pending Contains events with "pending" requests |
| 17 | + * - [baseStreamId]-[collectorsId]-active Contains events with "active" users |
| 18 | + * - [baseStreamId]-[scollectorsId]-errors Contains events with "revoked" or "erroneous" users |
| 19 | + */ |
| 20 | +class AppManagingAccount { |
| 21 | + /** @type {Pryv.Connection} */ |
| 22 | + connection; |
| 23 | + /** @type {string} */ |
| 24 | + baseStreamId; |
| 25 | + /** @type {string} */ |
| 26 | + appName; |
| 27 | + |
| 28 | + #cache; |
| 29 | + |
| 30 | + /** |
| 31 | + * use AppManagingAccount.newFromConnection() to create new AppManagingAccount |
| 32 | + * @param {string} appName |
| 33 | + * @param {string} baseStreamId |
| 34 | + * @param {Pryv.Connection} connection |
| 35 | + */ |
| 36 | + constructor (appName, baseStreamId, connection) { |
| 37 | + this.baseStreamId = baseStreamId; |
| 38 | + this.appName = appName; |
| 39 | + this.connection = connection; |
| 40 | + this.#cache = { }; |
| 41 | + } |
| 42 | + |
| 43 | + /** |
| 44 | + * Return an initialized AppManagingAccount instance |
| 45 | + * @param {Pryv.Connection} connection |
| 46 | + * @returns {AppManagingAccount} |
| 47 | + */ |
| 48 | + static async newFromConnection (connection, baseStreamId) { |
| 49 | + const appManagingAccount = await newAppManagingAccountFromConnection(connection, baseStreamId); |
| 50 | + await appManagingAccount.init(); |
| 51 | + return appManagingAccount; |
| 52 | + } |
| 53 | + |
| 54 | + async init () { |
| 55 | + // -- check if stream structure exists |
| 56 | + await this.getCollectors(); |
| 57 | + return this; |
| 58 | + } |
| 59 | + |
| 60 | + async getCollectors (forceRefresh) { |
| 61 | + if (!forceRefresh && this.#cache.collectorsMap) return Object.values(this.#cache.collectorsMap); |
| 62 | + // Collectors are materialized by streams |
| 63 | + const apiCalls = [{ |
| 64 | + method: 'streams.get', |
| 65 | + params: { |
| 66 | + parentId: this.baseStreamId |
| 67 | + } |
| 68 | + }]; |
| 69 | + const result = (await this.connection.api(apiCalls))[0]; |
| 70 | + if (result.error) throw new HDSLibError('Failed getting collectors', result.error); |
| 71 | + if (!result.streams || !Array.isArray(result.streams)) throw new HDSLibError('Failed getting collectors, invalid result', result); |
| 72 | + const collectorsMap = {}; |
| 73 | + for (const stream of result.streams) { |
| 74 | + const collector = new Collector(this, stream); |
| 75 | + collectorsMap[collector.id] = collector; |
| 76 | + } |
| 77 | + this.#cache.collectorsMap = collectorsMap; |
| 78 | + return Object.values(this.#cache.collectorsMap); |
| 79 | + } |
| 80 | + |
| 81 | + async createCollector (name) { |
| 82 | + const streamId = this.baseStreamId + '-' + collectorIdGenerator.rnd(); |
| 83 | + const apiCalls = [{ |
| 84 | + method: 'streams.create', |
| 85 | + params: { |
| 86 | + id: streamId, |
| 87 | + name, |
| 88 | + parentId: this.baseStreamId |
| 89 | + } |
| 90 | + }]; |
| 91 | + const result = (await this.connection.api(apiCalls))[0]; |
| 92 | + if (result.error) throw new HDSLibError('Failed creating collector', result.error); |
| 93 | + if (!result.stream?.name) throw new HDSLibError('Failed creating collector, invalid result', result); |
| 94 | + const collector = new Collector(this, result.stream); |
| 95 | + this.#cache.collectorsMap[collector.id] = collector; |
| 96 | + return collector; |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +class Collector { |
| 101 | + appManaging; |
| 102 | + id; |
| 103 | + name; |
| 104 | + /** |
| 105 | + * @param {AppManagingAccount} appManaging |
| 106 | + * @param {Pryv.Stream} stream |
| 107 | + */ |
| 108 | + constructor (appManaging, stream) { |
| 109 | + this.id = stream.id; |
| 110 | + this.name = stream.name; |
| 111 | + this.appManaging = appManaging; |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +module.exports = AppManagingAccount; |
| 116 | + |
| 117 | +async function newAppManagingAccountFromConnection (connection, baseStreamId) { |
| 118 | + const accessInfo = (await connection.api([{ method: 'getAccessInfo', params: {} }]))[0]; |
| 119 | + if (!accessInfo.type === 'app') throw new Error('Failed createing new AppManagingAccount, invalid coonection: ' + JSON.stringify(accessInfo)); |
| 120 | + // check if baseStreamId is in pemission set |
| 121 | + const found = accessInfo.permissions.find(p => (p.streamId === baseStreamId || p.streamId === '*')); |
| 122 | + if (found && found.level !== 'manage') { // check if level 'manage' |
| 123 | + throw new Error(`Failed creating new AppManagingAccount, Not sufficient permissions on stream: ${baseStreamId}`); |
| 124 | + } |
| 125 | + if (!found) { |
| 126 | + // here we may check if we can create or manage baseStreamId as it might be covered by permissions |
| 127 | + throw new Error(`Failed creating new AppManagingAccount, cannot find "${baseStreamId}" in permission list`); |
| 128 | + } |
| 129 | + const appManagingAccount = new AppManagingAccount(accessInfo.name, baseStreamId, connection); |
| 130 | + return appManagingAccount; |
| 131 | +} |
0 commit comments