pryv 2.4.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +5 -4
- package/src/Auth/AuthController.js +46 -29
- package/src/Auth/index.js +3 -3
- package/src/Browser/CookieUtils.js +8 -5
- package/src/Browser/LoginButton.js +7 -4
- package/src/Connection.js +173 -120
- package/src/Service.js +44 -42
- package/src/ServiceAssets.js +17 -11
- package/src/globals.d.ts +76 -0
- package/src/index.d.ts +168 -40
- package/src/index.js +3 -1
- package/src/lib/PryvError.js +30 -0
- package/src/lib/{browser-getEventStreamed.js → getEventStreamed.js} +2 -7
- package/src/lib/json-parser.js +1 -2
- package/src/utils.js +68 -7
- package/test/Browser.AuthController.test.js +10 -10
- package/test/Browser.test.js +10 -10
- package/test/Connection.test.js +9 -34
- package/test/Service.test.js +6 -8
- package/test/ServiceAssets.test.js +8 -9
- package/test/utils.test.js +0 -1
package/src/Connection.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const utils = require('./utils.js');
|
|
6
6
|
const jsonParser = require('./lib/json-parser');
|
|
7
|
-
const
|
|
7
|
+
const libGetEventStreamed = require('./lib/getEventStreamed');
|
|
8
|
+
const PryvError = require('./lib/PryvError');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @class Connection
|
|
@@ -51,19 +52,26 @@ class Connection {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
|
-
*
|
|
55
|
-
* It's async as
|
|
56
|
-
* @
|
|
57
|
-
* @param {*} progress
|
|
55
|
+
* Get username for this connection.
|
|
56
|
+
* It's async as it's constructed from access info.
|
|
57
|
+
* @returns {Promise<string>} Promise resolving to the username
|
|
58
58
|
*/
|
|
59
59
|
async username () {
|
|
60
60
|
const accessInfo = await this.accessInfo();
|
|
61
|
+
if (accessInfo.error) {
|
|
62
|
+
throw new PryvError(
|
|
63
|
+
'Failed fetching accessinfo: ' + accessInfo.error.message,
|
|
64
|
+
accessInfo.error
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
// @ts-ignore - username is always a string
|
|
61
68
|
return accessInfo.user.username;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
/**
|
|
65
|
-
*
|
|
66
|
-
* It's async as it is
|
|
72
|
+
* Get access info for this connection.
|
|
73
|
+
* It's async as it is fetched from the API.
|
|
74
|
+
* @returns {Promise<AccessInfo>} Promise resolving to the access info
|
|
67
75
|
*/
|
|
68
76
|
async accessInfo () {
|
|
69
77
|
return this.get('access-info', null);
|
|
@@ -89,11 +97,12 @@ class Connection {
|
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
/**
|
|
92
|
-
* Make one
|
|
93
|
-
* @param {string} method -
|
|
94
|
-
* @param {Object|Array} [params] -
|
|
95
|
-
* @param {string} [
|
|
96
|
-
* @
|
|
100
|
+
* Make one API call
|
|
101
|
+
* @param {string} method - Method ID (e.g., 'events.get', 'streams.create')
|
|
102
|
+
* @param {Object|Array} [params={}] - The params associated with this method
|
|
103
|
+
* @param {string} [expectedKey] - If given, returns the value of this key or throws an error if not present
|
|
104
|
+
* @returns {Promise<Object>} Promise resolving to the API result or the value of expectedKey
|
|
105
|
+
* @throws {Error} If .error is present in the response or expectedKey is missing
|
|
97
106
|
*/
|
|
98
107
|
async apiOne (method, params = {}, expectedKey) {
|
|
99
108
|
const result = await this.api([{ method, params }]);
|
|
@@ -103,13 +112,12 @@ class Connection {
|
|
|
103
112
|
(expectedKey != null && result[0][expectedKey] == null)
|
|
104
113
|
) {
|
|
105
114
|
const innerObject = result[0]?.error || result;
|
|
106
|
-
|
|
115
|
+
throw new PryvError(
|
|
107
116
|
`Error for api method: "${method}" with params: ${JSON.stringify(
|
|
108
117
|
params
|
|
109
|
-
)} >> Result: ${JSON.stringify(innerObject)}"
|
|
118
|
+
)} >> Result: ${JSON.stringify(innerObject)}"`,
|
|
119
|
+
innerObject
|
|
110
120
|
);
|
|
111
|
-
error.innerObject = innerObject;
|
|
112
|
-
throw error;
|
|
113
121
|
}
|
|
114
122
|
if (expectedKey != null) return result[0][expectedKey];
|
|
115
123
|
return result[0];
|
|
@@ -117,9 +125,10 @@ class Connection {
|
|
|
117
125
|
|
|
118
126
|
/**
|
|
119
127
|
* Revoke : Delete the accessId
|
|
120
|
-
* - Do not
|
|
121
|
-
* @param {boolean} [throwOnFail
|
|
122
|
-
* @param {Connection} [usingConnection] - specify which connection issues the revoke, might be necessary when
|
|
128
|
+
* - Do not throw error if access is already revoked, just return null;
|
|
129
|
+
* @param {boolean} [throwOnFail=true] - if set to false do not throw Error on failure
|
|
130
|
+
* @param {Connection} [usingConnection] - specify which connection issues the revoke, might be necessary when selfRevoke
|
|
131
|
+
* @returns {Promise<Object|null>} Promise resolving to deletion result or null if already revoked/failed
|
|
123
132
|
*/
|
|
124
133
|
async revoke (throwOnFail = true, usingConnection) {
|
|
125
134
|
usingConnection = usingConnection || this;
|
|
@@ -174,7 +183,6 @@ class Connection {
|
|
|
174
183
|
});
|
|
175
184
|
}
|
|
176
185
|
const resRequest = await callHandler(thisBatch);
|
|
177
|
-
|
|
178
186
|
// result checks
|
|
179
187
|
if (!resRequest || !Array.isArray(resRequest.results)) {
|
|
180
188
|
throw new Error(
|
|
@@ -193,7 +201,8 @@ class Connection {
|
|
|
193
201
|
if (arrayOfAPICalls[i + cursor].handleResult) {
|
|
194
202
|
await arrayOfAPICalls[i + cursor].handleResult.call(
|
|
195
203
|
null,
|
|
196
|
-
resRequest.results[i]
|
|
204
|
+
resRequest.results[i],
|
|
205
|
+
thisBatch[i] // request
|
|
197
206
|
);
|
|
198
207
|
}
|
|
199
208
|
}
|
|
@@ -207,76 +216,107 @@ class Connection {
|
|
|
207
216
|
}
|
|
208
217
|
|
|
209
218
|
/**
|
|
210
|
-
* Post to API return results
|
|
211
|
-
* @param {
|
|
212
|
-
* @param {Object}
|
|
213
|
-
* @
|
|
214
|
-
* @returns {Promise<Array|Object>} Promise to result.body
|
|
219
|
+
* Post to API and return results
|
|
220
|
+
* @param {string} path - API path
|
|
221
|
+
* @param {(Array | Object)} data - Data to post
|
|
222
|
+
* @returns {Promise<Object|Object[]>} Promise to result.body
|
|
215
223
|
*/
|
|
216
|
-
async post (path, data
|
|
224
|
+
async post (path, data) {
|
|
217
225
|
const now = getTimestamp();
|
|
218
|
-
const res = await this.
|
|
226
|
+
const res = await this._postFetch(path, data);
|
|
219
227
|
this._handleMeta(res.body, now);
|
|
220
228
|
return res.body;
|
|
221
229
|
}
|
|
222
230
|
|
|
223
231
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* @param {
|
|
227
|
-
* @param {
|
|
228
|
-
* @returns {
|
|
232
|
+
* @private
|
|
233
|
+
* Post object as JSON to API
|
|
234
|
+
* @param {string} path - API path
|
|
235
|
+
* @param {Array | Object} data - Data to post as JSON
|
|
236
|
+
* @returns {Promise<{response: Response, body: Object|Object[]}>} Promise to response and body
|
|
229
237
|
*/
|
|
230
|
-
async
|
|
231
|
-
return this.
|
|
238
|
+
async _postFetch (path, data) {
|
|
239
|
+
return this._postFetchRaw(path, JSON.stringify(data), 'application/json');
|
|
232
240
|
}
|
|
233
241
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
242
|
+
/**
|
|
243
|
+
* @private
|
|
244
|
+
* Raw Post to API
|
|
245
|
+
* @param {string} path - API path
|
|
246
|
+
* @param {any} data - Raw data to post
|
|
247
|
+
* @param {string} [contentType] - Content-Type header (optional, allows fetch to set it for FormData)
|
|
248
|
+
* @returns {Promise<{response: Response, body: Object|Object[]}>} Promise to response and body
|
|
249
|
+
*/
|
|
250
|
+
async _postFetchRaw (path, data, contentType) {
|
|
251
|
+
const headers = {
|
|
252
|
+
Authorization: this.token,
|
|
253
|
+
Accept: 'application/json'
|
|
254
|
+
};
|
|
255
|
+
// optional for form-data llowing fetch to
|
|
256
|
+
// automatically set multipart/form-data with the correct boundary
|
|
257
|
+
if (contentType) {
|
|
258
|
+
headers['Content-Type'] = contentType;
|
|
259
|
+
}
|
|
260
|
+
const response = await fetch(this.endpoint + path, {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
headers,
|
|
263
|
+
body: data
|
|
264
|
+
});
|
|
265
|
+
const body = await response.json();
|
|
266
|
+
return { response, body };
|
|
239
267
|
}
|
|
240
268
|
|
|
241
269
|
/**
|
|
242
|
-
*
|
|
243
|
-
* @param {
|
|
244
|
-
* @param {
|
|
245
|
-
* @returns {Promise<
|
|
270
|
+
* GET from API and return results
|
|
271
|
+
* @param {string} path - API path
|
|
272
|
+
* @param {Object} [queryParams] - Query parameters
|
|
273
|
+
* @returns {Promise<Object|Object[]>} Promise to result.body
|
|
246
274
|
*/
|
|
247
275
|
async get (path, queryParams) {
|
|
248
276
|
const now = getTimestamp();
|
|
249
|
-
const res = await this.
|
|
277
|
+
const res = await this._getFetchRaw(path, queryParams);
|
|
250
278
|
this._handleMeta(res.body, now);
|
|
251
279
|
return res.body;
|
|
252
280
|
}
|
|
253
281
|
|
|
254
282
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* @param {string} path
|
|
258
|
-
* @
|
|
283
|
+
* @private
|
|
284
|
+
* Raw GET from API
|
|
285
|
+
* @param {string} path - API path
|
|
286
|
+
* @param {Object} [queryParams={}] - Query parameters
|
|
287
|
+
* @returns {Promise<{response: Response, body: Object|Object[]}>} Promise to response and body
|
|
259
288
|
*/
|
|
260
|
-
|
|
289
|
+
async _getFetchRaw (path, queryParams = {}) {
|
|
261
290
|
path = path || '';
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
291
|
+
let queryStr = '';
|
|
292
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
293
|
+
queryStr = '?' + new URLSearchParams(queryParams).toString();
|
|
294
|
+
}
|
|
295
|
+
const response = await fetch(this.endpoint + path + queryStr, {
|
|
296
|
+
headers: {
|
|
297
|
+
Authorization: this.token,
|
|
298
|
+
Accept: 'application/json'
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
const body = await response.json();
|
|
302
|
+
return { response, body };
|
|
267
303
|
}
|
|
268
304
|
|
|
269
305
|
/**
|
|
270
|
-
*
|
|
271
|
-
*
|
|
306
|
+
* Add data points to an HF (High Frequency) series event (flatJSON format)
|
|
307
|
+
* @param {string} eventId - The HF event ID
|
|
308
|
+
* @param {string[]} fields - Array of field names for the series
|
|
309
|
+
* @param {Array<Array<number|string>>} points - Array of data points, each point is an array of values
|
|
310
|
+
* @returns {Promise<HFSeriesAddResult>} Promise resolving to status response
|
|
311
|
+
* @see https://api.pryv.com/reference/#add-hf-series-data-points
|
|
272
312
|
*/
|
|
273
313
|
async addPointsToHFEvent (eventId, fields, points) {
|
|
274
314
|
const res = await this.post('events/' + eventId + '/series', {
|
|
275
315
|
format: 'flatJSON',
|
|
276
|
-
fields
|
|
277
|
-
points
|
|
316
|
+
fields,
|
|
317
|
+
points
|
|
278
318
|
});
|
|
279
|
-
if (
|
|
319
|
+
if (res.status !== 'ok') {
|
|
280
320
|
throw new Error('Failed loading serie: ' + JSON.stringify(res.status));
|
|
281
321
|
}
|
|
282
322
|
return res;
|
|
@@ -284,7 +324,6 @@ class Connection {
|
|
|
284
324
|
|
|
285
325
|
/**
|
|
286
326
|
* Streamed get Event.
|
|
287
|
-
* Fallbacks to not streamed, for browsers that does not support `fetch()` API
|
|
288
327
|
* @see https://api.pryv.com/reference/#get-events
|
|
289
328
|
* @param {Object} queryParams See `events.get` parameters
|
|
290
329
|
* @param {Function} forEachEvent Function taking one event as parameter. Will be called for each event
|
|
@@ -292,38 +331,7 @@ class Connection {
|
|
|
292
331
|
*/
|
|
293
332
|
async getEventsStreamed (queryParams, forEachEvent) {
|
|
294
333
|
const myParser = jsonParser(forEachEvent, queryParams.includeDeletions);
|
|
295
|
-
|
|
296
|
-
if (typeof window === 'undefined') {
|
|
297
|
-
// node
|
|
298
|
-
res = await this.getRaw('events', queryParams)
|
|
299
|
-
.buffer(false)
|
|
300
|
-
.parse(myParser);
|
|
301
|
-
} else if (
|
|
302
|
-
typeof fetch !== 'undefined' &&
|
|
303
|
-
!(typeof navigator !== 'undefined' && navigator.product === 'ReactNative')
|
|
304
|
-
) {
|
|
305
|
-
// browser supports fetch and it is not react native
|
|
306
|
-
res = await browserGetEventStreamed(this, queryParams, myParser);
|
|
307
|
-
} else {
|
|
308
|
-
// browser no fetch supports
|
|
309
|
-
console.log(
|
|
310
|
-
'WARNING: Browser does not support fetch() required by pryv.Connection.getEventsStreamed()'
|
|
311
|
-
);
|
|
312
|
-
res = await this.getRaw('events', queryParams);
|
|
313
|
-
res.body.eventsCount = 0;
|
|
314
|
-
if (res.body.events) {
|
|
315
|
-
res.body.events.forEach(forEachEvent);
|
|
316
|
-
res.body.eventsCount += res.body.events.length;
|
|
317
|
-
delete res.body.events;
|
|
318
|
-
}
|
|
319
|
-
if (res.body.eventDeletions) {
|
|
320
|
-
// deletions are in a seprated Array
|
|
321
|
-
res.body.eventDeletions.forEach(forEachEvent);
|
|
322
|
-
res.body.eventsCount += res.body.eventDeletions.length;
|
|
323
|
-
delete res.body.eventDeletions;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
334
|
+
const res = await libGetEventStreamed(this, queryParams, myParser);
|
|
327
335
|
const now = getTimestamp();
|
|
328
336
|
this._handleMeta(res.body, now);
|
|
329
337
|
return res.body;
|
|
@@ -336,38 +344,44 @@ class Connection {
|
|
|
336
344
|
* @param {string} filePath
|
|
337
345
|
*/
|
|
338
346
|
async createEventWithFile (event, filePath) {
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
347
|
+
const fs = require('fs');
|
|
348
|
+
const path = require('path');
|
|
349
|
+
|
|
350
|
+
if (!fs || !path) {
|
|
351
|
+
throw new Error('createEventWithFile is only available in Node.js. Use createEventWithFormData in browser.');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const fileName = path.basename(filePath);
|
|
355
|
+
const mimeType = getMimeType(path.extname(filePath));
|
|
356
|
+
const fileBlob = await fs.openAsBlob(filePath, { type: mimeType });
|
|
357
|
+
|
|
358
|
+
const formData = new FormData();
|
|
359
|
+
formData.append('event', JSON.stringify(event));
|
|
360
|
+
formData.append('file', fileBlob, fileName);
|
|
342
361
|
|
|
343
362
|
const now = getTimestamp();
|
|
344
|
-
this.
|
|
345
|
-
|
|
363
|
+
const { body } = await this._postFetchRaw('events', formData);
|
|
364
|
+
this._handleMeta(body, now);
|
|
365
|
+
return body;
|
|
346
366
|
}
|
|
347
367
|
|
|
348
368
|
/**
|
|
349
369
|
* Create an event from a Buffer
|
|
350
|
-
* @param {
|
|
370
|
+
* @param {Object} event
|
|
351
371
|
* @param {Buffer|Blob} bufferData - Buffer for node, Blob for browser
|
|
352
|
-
* @param {string}
|
|
372
|
+
* @param {string} filename
|
|
353
373
|
*/
|
|
354
374
|
async createEventWithFileFromBuffer (event, bufferData, filename) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
/* global FormData */
|
|
366
|
-
const formData = new FormData();
|
|
367
|
-
formData.append('file', bufferData, filename);
|
|
368
|
-
const body = await this.createEventWithFormData(event, formData);
|
|
369
|
-
return body;
|
|
370
|
-
}
|
|
375
|
+
const mimeType = getMimeType(getExtname(filename));
|
|
376
|
+
const fileBlob = bufferData instanceof Blob
|
|
377
|
+
? bufferData
|
|
378
|
+
// @ts-ignore - Buffer is valid for Blob in Node.js
|
|
379
|
+
: new Blob([bufferData], { type: mimeType });
|
|
380
|
+
|
|
381
|
+
const formData = new FormData();
|
|
382
|
+
formData.append('file', fileBlob, filename);
|
|
383
|
+
const body = await this.createEventWithFormData(event, formData);
|
|
384
|
+
return body;
|
|
371
385
|
}
|
|
372
386
|
|
|
373
387
|
/**
|
|
@@ -378,8 +392,8 @@ class Connection {
|
|
|
378
392
|
*/
|
|
379
393
|
async createEventWithFormData (event, formData) {
|
|
380
394
|
formData.append('event', JSON.stringify(event));
|
|
381
|
-
const
|
|
382
|
-
return
|
|
395
|
+
const { body } = await this._postFetchRaw('events', formData);
|
|
396
|
+
return body;
|
|
383
397
|
}
|
|
384
398
|
|
|
385
399
|
/**
|
|
@@ -393,9 +407,9 @@ class Connection {
|
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
/**
|
|
396
|
-
* API endpoint of this connection
|
|
410
|
+
* API endpoint of this connection (includes token if present)
|
|
397
411
|
* @readonly
|
|
398
|
-
* @property {APIEndpoint}
|
|
412
|
+
* @property {APIEndpoint} apiEndpoint
|
|
399
413
|
*/
|
|
400
414
|
get apiEndpoint () {
|
|
401
415
|
return utils.buildAPIEndpoint(this);
|
|
@@ -421,6 +435,45 @@ function getTimestamp () {
|
|
|
421
435
|
return Date.now() / 1000;
|
|
422
436
|
}
|
|
423
437
|
|
|
438
|
+
const MIME_TYPES = {
|
|
439
|
+
'.png': 'image/png',
|
|
440
|
+
'.jpg': 'image/jpeg',
|
|
441
|
+
'.jpeg': 'image/jpeg',
|
|
442
|
+
'.gif': 'image/gif',
|
|
443
|
+
'.webp': 'image/webp',
|
|
444
|
+
'.svg': 'image/svg+xml',
|
|
445
|
+
'.bmp': 'image/bmp',
|
|
446
|
+
'.ico': 'image/x-icon',
|
|
447
|
+
'.pdf': 'application/pdf',
|
|
448
|
+
'.json': 'application/json',
|
|
449
|
+
'.xml': 'application/xml',
|
|
450
|
+
'.zip': 'application/zip',
|
|
451
|
+
'.gz': 'application/gzip',
|
|
452
|
+
'.tar': 'application/x-tar',
|
|
453
|
+
'.txt': 'text/plain',
|
|
454
|
+
'.html': 'text/html',
|
|
455
|
+
'.htm': 'text/html',
|
|
456
|
+
'.css': 'text/css',
|
|
457
|
+
'.js': 'application/javascript',
|
|
458
|
+
'.mjs': 'application/javascript',
|
|
459
|
+
'.mp3': 'audio/mpeg',
|
|
460
|
+
'.wav': 'audio/wav',
|
|
461
|
+
'.ogg': 'audio/ogg',
|
|
462
|
+
'.mp4': 'video/mp4',
|
|
463
|
+
'.webm': 'video/webm',
|
|
464
|
+
'.avi': 'video/x-msvideo',
|
|
465
|
+
'.mov': 'video/quicktime'
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
function getMimeType (ext) {
|
|
469
|
+
return MIME_TYPES[ext.toLowerCase()] || 'application/octet-stream';
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function getExtname (filename) {
|
|
473
|
+
const lastDot = filename.lastIndexOf('.');
|
|
474
|
+
return lastDot >= 0 ? filename.slice(lastDot) : '';
|
|
475
|
+
}
|
|
476
|
+
|
|
424
477
|
// service is require "after" to allow circular require
|
|
425
478
|
const Service = require('./Service');
|
|
426
479
|
|
package/src/Service.js
CHANGED
|
@@ -52,17 +52,18 @@ class Service {
|
|
|
52
52
|
* - name of a platform
|
|
53
53
|
* `const serviceName = await service.info().name`
|
|
54
54
|
* @see ServiceInfo For details on available properties.
|
|
55
|
-
* @param {boolean
|
|
55
|
+
* @param {boolean} [forceFetch] If true, will force fetching service info.
|
|
56
56
|
* @returns {Promise<ServiceInfo>} Promise to Service info Object
|
|
57
57
|
*/
|
|
58
58
|
async info (forceFetch) {
|
|
59
59
|
if (forceFetch || !this._serviceInfo) {
|
|
60
60
|
let baseServiceInfo = {};
|
|
61
61
|
if (this._serviceInfoUrl) {
|
|
62
|
-
const
|
|
63
|
-
baseServiceInfo =
|
|
62
|
+
const { body } = await utils.fetchGet(this._serviceInfoUrl);
|
|
63
|
+
baseServiceInfo = body;
|
|
64
64
|
}
|
|
65
65
|
Object.assign(baseServiceInfo, this._pryvServiceCustomizations);
|
|
66
|
+
// @ts-ignore - baseServiceInfo is populated from body or customizations
|
|
66
67
|
this.setServiceInfo(baseServiceInfo);
|
|
67
68
|
}
|
|
68
69
|
return this._serviceInfo;
|
|
@@ -70,7 +71,7 @@ class Service {
|
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
73
|
* Check if a service supports High Frequency Data Sets
|
|
73
|
-
* @
|
|
74
|
+
* @returns {Promise<boolean>} Promise resolving to true if HF is supported
|
|
74
75
|
*/
|
|
75
76
|
async supportsHF () {
|
|
76
77
|
const infos = await this.info();
|
|
@@ -78,8 +79,8 @@ class Service {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
|
-
* Check if a service has username in the hostname or in the path of the
|
|
82
|
-
* @
|
|
82
|
+
* Check if a service has username in the hostname or in the path of the API.
|
|
83
|
+
* @returns {Promise<boolean>} Promise resolving to true if the service does not rely on DNS to find a host related to a username
|
|
83
84
|
*/
|
|
84
85
|
async isDnsLess () {
|
|
85
86
|
const infos = await this.info();
|
|
@@ -107,8 +108,8 @@ class Service {
|
|
|
107
108
|
|
|
108
109
|
/**
|
|
109
110
|
* Return assets property content
|
|
110
|
-
* @param {boolean
|
|
111
|
-
* @returns {Promise<ServiceAssets>} Promise to ServiceAssets
|
|
111
|
+
* @param {boolean} [forceFetch] If true, will force fetching service info.
|
|
112
|
+
* @returns {Promise<ServiceAssets|null>} Promise to ServiceAssets
|
|
112
113
|
*/
|
|
113
114
|
async assets (forceFetch) {
|
|
114
115
|
if (!forceFetch && this._assets) {
|
|
@@ -134,9 +135,9 @@ class Service {
|
|
|
134
135
|
|
|
135
136
|
/**
|
|
136
137
|
* Return an API endpoint from a username and token
|
|
137
|
-
* @param {string} username
|
|
138
|
-
* @param {string} [token]
|
|
139
|
-
* @
|
|
138
|
+
* @param {string} username - The username
|
|
139
|
+
* @param {string} [token] - Optional authorization token
|
|
140
|
+
* @returns {Promise<APIEndpoint>} Promise resolving to the API endpoint URL
|
|
140
141
|
*/
|
|
141
142
|
async apiEndpointFor (username, token) {
|
|
142
143
|
const serviceInfo = await this.info();
|
|
@@ -144,16 +145,16 @@ class Service {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
/**
|
|
147
|
-
* Return an API endpoint from a username
|
|
148
|
-
* This
|
|
149
|
-
* @param {ServiceInfo} serviceInfo
|
|
150
|
-
* @param {string} username
|
|
151
|
-
* @param {string} [token]
|
|
152
|
-
* @
|
|
148
|
+
* Return an API endpoint from a username, token and ServiceInfo.
|
|
149
|
+
* This method is rarely used. See **apiEndpointFor** as an alternative.
|
|
150
|
+
* @param {ServiceInfo} serviceInfo - The service info object containing API URL template
|
|
151
|
+
* @param {string} username - The username
|
|
152
|
+
* @param {string} [token] - Optional authorization token
|
|
153
|
+
* @returns {APIEndpoint} The constructed API endpoint URL
|
|
153
154
|
*/
|
|
154
155
|
static buildAPIEndpoint (serviceInfo, username, token) {
|
|
155
156
|
const endpoint = serviceInfo.api.replace('{username}', username);
|
|
156
|
-
return utils.buildAPIEndpoint({ endpoint
|
|
157
|
+
return utils.buildAPIEndpoint({ endpoint, token });
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
/**
|
|
@@ -169,32 +170,31 @@ class Service {
|
|
|
169
170
|
async login (username, password, appId, originHeader) {
|
|
170
171
|
const apiEndpoint = await this.apiEndpointFor(username);
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
const headers = {};
|
|
174
|
+
originHeader = originHeader || (await this.info()).register;
|
|
175
|
+
if (!utils.isBrowser()) {
|
|
176
|
+
headers.Origin = originHeader;
|
|
177
|
+
}
|
|
178
|
+
const { response, body } = await utils.fetchPost(
|
|
179
|
+
apiEndpoint + 'auth/login',
|
|
180
|
+
{ username, password, appId },
|
|
181
|
+
headers
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
if (body?.error?.message) {
|
|
186
|
+
throw new Error(body.error.message);
|
|
177
187
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
.send({ username: username, password: password, appId: appId });
|
|
188
|
+
throw new Error('Login failed: ' + JSON.stringify(body));
|
|
189
|
+
}
|
|
181
190
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
return new Connection(
|
|
186
|
-
Service.buildAPIEndpoint(await this.info(), username, res.body.token),
|
|
187
|
-
this // Pre load Connection with service
|
|
188
|
-
);
|
|
189
|
-
} catch (e) {
|
|
190
|
-
if (e.response &&
|
|
191
|
-
e.response.body &&
|
|
192
|
-
e.response.body.error &&
|
|
193
|
-
e.response.body.error.message) {
|
|
194
|
-
throw new Error(e.response.body.error.message);
|
|
195
|
-
}
|
|
196
|
-
throw (e);
|
|
191
|
+
if (!body.token) {
|
|
192
|
+
throw new Error('Invalid login response: ' + JSON.stringify(body));
|
|
197
193
|
}
|
|
194
|
+
return new Connection(
|
|
195
|
+
Service.buildAPIEndpoint(await this.info(), username, body.token),
|
|
196
|
+
this // Pre load Connection with service
|
|
197
|
+
);
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -215,5 +215,7 @@ const Connection = require('./Connection');
|
|
|
215
215
|
* @property {string} terms The terms and conditions, in plain text or the URL displaying them.
|
|
216
216
|
* @property {string} eventTypes The URL of the list of validated event types.
|
|
217
217
|
* @property {Object} [assets] Holder for service specific Assets (icons, css, ...)
|
|
218
|
-
* @property {
|
|
218
|
+
* @property {string} [assets.definitions] URL to json object with assets definitions
|
|
219
|
+
* @property {Object} [features] Platform feature flags
|
|
220
|
+
* @property {boolean} [features.noHF] True if HF data is not supported
|
|
219
221
|
*/
|
package/src/ServiceAssets.js
CHANGED
|
@@ -16,8 +16,8 @@ const utils = require('./utils.js');
|
|
|
16
16
|
class ServiceAssets {
|
|
17
17
|
/**
|
|
18
18
|
* Private => use ServiceAssets.setup()
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {
|
|
19
|
+
* @param {Object} assets The content of service/info.assets properties.
|
|
20
|
+
* @param {string} assetsURL Url point to assets of the service of a Pryv platform
|
|
21
21
|
*/
|
|
22
22
|
constructor (assets, assetsURL) {
|
|
23
23
|
this._assets = assets;
|
|
@@ -25,13 +25,13 @@ class ServiceAssets {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Load Assets definition
|
|
29
|
-
* @param {string} pryvServiceAssetsSourceUrl
|
|
30
|
-
* @returns {ServiceAssets}
|
|
28
|
+
* Load Assets definition from URL
|
|
29
|
+
* @param {string} pryvServiceAssetsSourceUrl - URL to the assets definition JSON
|
|
30
|
+
* @returns {Promise<ServiceAssets>} Promise resolving to ServiceAssets instance
|
|
31
31
|
*/
|
|
32
32
|
static async setup (pryvServiceAssetsSourceUrl) {
|
|
33
|
-
const
|
|
34
|
-
return new ServiceAssets(
|
|
33
|
+
const { body } = await utils.fetchGet(pryvServiceAssetsSourceUrl);
|
|
34
|
+
return new ServiceAssets(body, pryvServiceAssetsSourceUrl);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -85,6 +85,8 @@ class ServiceAssets {
|
|
|
85
85
|
* Set service Favicon to Web Page
|
|
86
86
|
*/
|
|
87
87
|
setFavicon () {
|
|
88
|
+
/** @type {HTMLLinkElement} */
|
|
89
|
+
// @ts-ignore - querySelector returns Element but we know it's HTMLLinkElement
|
|
88
90
|
const link = document.querySelector("link[rel*='icon']") || document.createElement('link');
|
|
89
91
|
link.type = 'image/x-icon';
|
|
90
92
|
link.rel = 'shortcut icon';
|
|
@@ -110,18 +112,20 @@ class ServiceAssets {
|
|
|
110
112
|
|
|
111
113
|
/**
|
|
112
114
|
* Get HTML for Login Button
|
|
115
|
+
* @returns {Promise<string>} Promise resolving to HTML string
|
|
113
116
|
*/
|
|
114
117
|
async loginButtonGetHTML () {
|
|
115
|
-
const
|
|
116
|
-
return
|
|
118
|
+
const { text } = await utils.fetchGetText(this.relativeURL(this._assets['lib-js'].buttonSignIn.html));
|
|
119
|
+
return text;
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
/**
|
|
120
123
|
* Get Messages strings for Login Button
|
|
124
|
+
* @returns {Promise<Object.<string, string>>} Promise resolving to messages object
|
|
121
125
|
*/
|
|
122
126
|
async loginButtonGetMessages () {
|
|
123
|
-
const
|
|
124
|
-
return
|
|
127
|
+
const { body } = await utils.fetchGet(this.relativeURL(this._assets['lib-js'].buttonSignIn.messages));
|
|
128
|
+
return body;
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
@@ -153,8 +157,10 @@ function loadCSS (url) {
|
|
|
153
157
|
\*/
|
|
154
158
|
|
|
155
159
|
function relPathToAbs (baseUrlString, sRelPath) {
|
|
160
|
+
/** @type {Location|HTMLAnchorElement} */
|
|
156
161
|
var baseLocation = location;
|
|
157
162
|
if (baseUrlString) {
|
|
163
|
+
// @ts-ignore - HTMLAnchorElement has compatible URL properties
|
|
158
164
|
baseLocation = document.createElement('a');
|
|
159
165
|
baseLocation.href = baseUrlString;
|
|
160
166
|
}
|