pryv 2.4.7 → 3.0.1
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/package.json +4 -3
- 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 +172 -119
- package/src/Service.js +44 -42
- package/src/ServiceAssets.js +17 -11
- package/src/globals.d.ts +76 -0
- package/src/index.d.ts +167 -39
- package/src/index.js +3 -1
- package/src/lib/PryvError.js +30 -0
- package/src/lib/buildSearchParams.js +26 -0
- package/src/lib/{browser-getEventStreamed.js → getEventStreamed.js} +4 -7
- package/src/lib/json-parser.js +1 -2
- package/src/utils.js +69 -7
- package/test/Browser.AuthController.test.js +14 -14
- package/test/Browser.test.js +13 -13
- package/test/Connection.test.js +63 -71
- package/test/Service.test.js +16 -18
- package/test/ServiceAssets.test.js +15 -16
- package/test/utils.test.js +6 -7
package/src/Connection.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
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');
|
|
9
|
+
const buildSearchParams = require('./lib/buildSearchParams');
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* @class Connection
|
|
@@ -51,19 +53,26 @@ class Connection {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/**
|
|
54
|
-
*
|
|
55
|
-
* It's async as
|
|
56
|
-
* @
|
|
57
|
-
* @param {*} progress
|
|
56
|
+
* Get username for this connection.
|
|
57
|
+
* It's async as it's constructed from access info.
|
|
58
|
+
* @returns {Promise<string>} Promise resolving to the username
|
|
58
59
|
*/
|
|
59
60
|
async username () {
|
|
60
61
|
const accessInfo = await this.accessInfo();
|
|
62
|
+
if (accessInfo.error) {
|
|
63
|
+
throw new PryvError(
|
|
64
|
+
'Failed fetching accessinfo: ' + accessInfo.error.message,
|
|
65
|
+
accessInfo.error
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
// @ts-ignore - username is always a string
|
|
61
69
|
return accessInfo.user.username;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
/**
|
|
65
|
-
*
|
|
66
|
-
* It's async as it is
|
|
73
|
+
* Get access info for this connection.
|
|
74
|
+
* It's async as it is fetched from the API.
|
|
75
|
+
* @returns {Promise<AccessInfo>} Promise resolving to the access info
|
|
67
76
|
*/
|
|
68
77
|
async accessInfo () {
|
|
69
78
|
return this.get('access-info', null);
|
|
@@ -89,11 +98,12 @@ class Connection {
|
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
/**
|
|
92
|
-
* Make one
|
|
93
|
-
* @param {string} method -
|
|
94
|
-
* @param {Object|Array} [params] -
|
|
95
|
-
* @param {string} [
|
|
96
|
-
* @
|
|
101
|
+
* Make one API call
|
|
102
|
+
* @param {string} method - Method ID (e.g., 'events.get', 'streams.create')
|
|
103
|
+
* @param {Object|Array} [params={}] - The params associated with this method
|
|
104
|
+
* @param {string} [expectedKey] - If given, returns the value of this key or throws an error if not present
|
|
105
|
+
* @returns {Promise<Object>} Promise resolving to the API result or the value of expectedKey
|
|
106
|
+
* @throws {Error} If .error is present in the response or expectedKey is missing
|
|
97
107
|
*/
|
|
98
108
|
async apiOne (method, params = {}, expectedKey) {
|
|
99
109
|
const result = await this.api([{ method, params }]);
|
|
@@ -103,13 +113,12 @@ class Connection {
|
|
|
103
113
|
(expectedKey != null && result[0][expectedKey] == null)
|
|
104
114
|
) {
|
|
105
115
|
const innerObject = result[0]?.error || result;
|
|
106
|
-
|
|
116
|
+
throw new PryvError(
|
|
107
117
|
`Error for api method: "${method}" with params: ${JSON.stringify(
|
|
108
118
|
params
|
|
109
|
-
)} >> Result: ${JSON.stringify(innerObject)}"
|
|
119
|
+
)} >> Result: ${JSON.stringify(innerObject)}"`,
|
|
120
|
+
innerObject
|
|
110
121
|
);
|
|
111
|
-
error.innerObject = innerObject;
|
|
112
|
-
throw error;
|
|
113
122
|
}
|
|
114
123
|
if (expectedKey != null) return result[0][expectedKey];
|
|
115
124
|
return result[0];
|
|
@@ -117,9 +126,10 @@ class Connection {
|
|
|
117
126
|
|
|
118
127
|
/**
|
|
119
128
|
* 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
|
|
129
|
+
* - Do not throw error if access is already revoked, just return null;
|
|
130
|
+
* @param {boolean} [throwOnFail=true] - if set to false do not throw Error on failure
|
|
131
|
+
* @param {Connection} [usingConnection] - specify which connection issues the revoke, might be necessary when selfRevoke
|
|
132
|
+
* @returns {Promise<Object|null>} Promise resolving to deletion result or null if already revoked/failed
|
|
123
133
|
*/
|
|
124
134
|
async revoke (throwOnFail = true, usingConnection) {
|
|
125
135
|
usingConnection = usingConnection || this;
|
|
@@ -174,7 +184,6 @@ class Connection {
|
|
|
174
184
|
});
|
|
175
185
|
}
|
|
176
186
|
const resRequest = await callHandler(thisBatch);
|
|
177
|
-
|
|
178
187
|
// result checks
|
|
179
188
|
if (!resRequest || !Array.isArray(resRequest.results)) {
|
|
180
189
|
throw new Error(
|
|
@@ -208,76 +217,107 @@ class Connection {
|
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
/**
|
|
211
|
-
* Post to API return results
|
|
212
|
-
* @param {
|
|
213
|
-
* @param {Object}
|
|
214
|
-
* @
|
|
215
|
-
* @returns {Promise<Array|Object>} Promise to result.body
|
|
220
|
+
* Post to API and return results
|
|
221
|
+
* @param {string} path - API path
|
|
222
|
+
* @param {(Array | Object)} data - Data to post
|
|
223
|
+
* @returns {Promise<Object|Object[]>} Promise to result.body
|
|
216
224
|
*/
|
|
217
|
-
async post (path, data
|
|
225
|
+
async post (path, data) {
|
|
218
226
|
const now = getTimestamp();
|
|
219
|
-
const res = await this.
|
|
227
|
+
const res = await this._postFetch(path, data);
|
|
220
228
|
this._handleMeta(res.body, now);
|
|
221
229
|
return res.body;
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* @param {
|
|
228
|
-
* @param {
|
|
229
|
-
* @returns {
|
|
233
|
+
* @private
|
|
234
|
+
* Post object as JSON to API
|
|
235
|
+
* @param {string} path - API path
|
|
236
|
+
* @param {Array | Object} data - Data to post as JSON
|
|
237
|
+
* @returns {Promise<{response: Response, body: Object|Object[]}>} Promise to response and body
|
|
230
238
|
*/
|
|
231
|
-
async
|
|
232
|
-
return this.
|
|
239
|
+
async _postFetch (path, data) {
|
|
240
|
+
return this._postFetchRaw(path, JSON.stringify(data), 'application/json');
|
|
233
241
|
}
|
|
234
242
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
243
|
+
/**
|
|
244
|
+
* @private
|
|
245
|
+
* Raw Post to API
|
|
246
|
+
* @param {string} path - API path
|
|
247
|
+
* @param {any} data - Raw data to post
|
|
248
|
+
* @param {string} [contentType] - Content-Type header (optional, allows fetch to set it for FormData)
|
|
249
|
+
* @returns {Promise<{response: Response, body: Object|Object[]}>} Promise to response and body
|
|
250
|
+
*/
|
|
251
|
+
async _postFetchRaw (path, data, contentType) {
|
|
252
|
+
const headers = {
|
|
253
|
+
Authorization: this.token,
|
|
254
|
+
Accept: 'application/json'
|
|
255
|
+
};
|
|
256
|
+
// optional for form-data llowing fetch to
|
|
257
|
+
// automatically set multipart/form-data with the correct boundary
|
|
258
|
+
if (contentType) {
|
|
259
|
+
headers['Content-Type'] = contentType;
|
|
260
|
+
}
|
|
261
|
+
const response = await fetch(this.endpoint + path, {
|
|
262
|
+
method: 'POST',
|
|
263
|
+
headers,
|
|
264
|
+
body: data
|
|
265
|
+
});
|
|
266
|
+
const body = await response.json();
|
|
267
|
+
return { response, body };
|
|
240
268
|
}
|
|
241
269
|
|
|
242
270
|
/**
|
|
243
|
-
*
|
|
244
|
-
* @param {
|
|
245
|
-
* @param {
|
|
246
|
-
* @returns {Promise<
|
|
271
|
+
* GET from API and return results
|
|
272
|
+
* @param {string} path - API path
|
|
273
|
+
* @param {Object} [queryParams] - Query parameters
|
|
274
|
+
* @returns {Promise<Object|Object[]>} Promise to result.body
|
|
247
275
|
*/
|
|
248
276
|
async get (path, queryParams) {
|
|
249
277
|
const now = getTimestamp();
|
|
250
|
-
const res = await this.
|
|
278
|
+
const res = await this._getFetchRaw(path, queryParams);
|
|
251
279
|
this._handleMeta(res.body, now);
|
|
252
280
|
return res.body;
|
|
253
281
|
}
|
|
254
282
|
|
|
255
283
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* @param {string} path
|
|
259
|
-
* @
|
|
284
|
+
* @private
|
|
285
|
+
* Raw GET from API
|
|
286
|
+
* @param {string} path - API path
|
|
287
|
+
* @param {Object} [queryParams={}] - Query parameters
|
|
288
|
+
* @returns {Promise<{response: Response, body: Object|Object[]}>} Promise to response and body
|
|
260
289
|
*/
|
|
261
|
-
|
|
290
|
+
async _getFetchRaw (path, queryParams = {}) {
|
|
262
291
|
path = path || '';
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
292
|
+
let queryStr = '';
|
|
293
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
294
|
+
queryStr = '?' + buildSearchParams(queryParams);
|
|
295
|
+
}
|
|
296
|
+
const response = await fetch(this.endpoint + path + queryStr, {
|
|
297
|
+
headers: {
|
|
298
|
+
Authorization: this.token,
|
|
299
|
+
Accept: 'application/json'
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
const body = await response.json();
|
|
303
|
+
return { response, body };
|
|
268
304
|
}
|
|
269
305
|
|
|
270
306
|
/**
|
|
271
|
-
*
|
|
272
|
-
*
|
|
307
|
+
* Add data points to an HF (High Frequency) series event (flatJSON format)
|
|
308
|
+
* @param {string} eventId - The HF event ID
|
|
309
|
+
* @param {string[]} fields - Array of field names for the series
|
|
310
|
+
* @param {Array<Array<number|string>>} points - Array of data points, each point is an array of values
|
|
311
|
+
* @returns {Promise<HFSeriesAddResult>} Promise resolving to status response
|
|
312
|
+
* @see https://api.pryv.com/reference/#add-hf-series-data-points
|
|
273
313
|
*/
|
|
274
314
|
async addPointsToHFEvent (eventId, fields, points) {
|
|
275
315
|
const res = await this.post('events/' + eventId + '/series', {
|
|
276
316
|
format: 'flatJSON',
|
|
277
|
-
fields
|
|
278
|
-
points
|
|
317
|
+
fields,
|
|
318
|
+
points
|
|
279
319
|
});
|
|
280
|
-
if (
|
|
320
|
+
if (res.status !== 'ok') {
|
|
281
321
|
throw new Error('Failed loading serie: ' + JSON.stringify(res.status));
|
|
282
322
|
}
|
|
283
323
|
return res;
|
|
@@ -285,7 +325,6 @@ class Connection {
|
|
|
285
325
|
|
|
286
326
|
/**
|
|
287
327
|
* Streamed get Event.
|
|
288
|
-
* Fallbacks to not streamed, for browsers that does not support `fetch()` API
|
|
289
328
|
* @see https://api.pryv.com/reference/#get-events
|
|
290
329
|
* @param {Object} queryParams See `events.get` parameters
|
|
291
330
|
* @param {Function} forEachEvent Function taking one event as parameter. Will be called for each event
|
|
@@ -293,38 +332,7 @@ class Connection {
|
|
|
293
332
|
*/
|
|
294
333
|
async getEventsStreamed (queryParams, forEachEvent) {
|
|
295
334
|
const myParser = jsonParser(forEachEvent, queryParams.includeDeletions);
|
|
296
|
-
|
|
297
|
-
if (typeof window === 'undefined') {
|
|
298
|
-
// node
|
|
299
|
-
res = await this.getRaw('events', queryParams)
|
|
300
|
-
.buffer(false)
|
|
301
|
-
.parse(myParser);
|
|
302
|
-
} else if (
|
|
303
|
-
typeof fetch !== 'undefined' &&
|
|
304
|
-
!(typeof navigator !== 'undefined' && navigator.product === 'ReactNative')
|
|
305
|
-
) {
|
|
306
|
-
// browser supports fetch and it is not react native
|
|
307
|
-
res = await browserGetEventStreamed(this, queryParams, myParser);
|
|
308
|
-
} else {
|
|
309
|
-
// browser no fetch supports
|
|
310
|
-
console.log(
|
|
311
|
-
'WARNING: Browser does not support fetch() required by pryv.Connection.getEventsStreamed()'
|
|
312
|
-
);
|
|
313
|
-
res = await this.getRaw('events', queryParams);
|
|
314
|
-
res.body.eventsCount = 0;
|
|
315
|
-
if (res.body.events) {
|
|
316
|
-
res.body.events.forEach(forEachEvent);
|
|
317
|
-
res.body.eventsCount += res.body.events.length;
|
|
318
|
-
delete res.body.events;
|
|
319
|
-
}
|
|
320
|
-
if (res.body.eventDeletions) {
|
|
321
|
-
// deletions are in a seprated Array
|
|
322
|
-
res.body.eventDeletions.forEach(forEachEvent);
|
|
323
|
-
res.body.eventsCount += res.body.eventDeletions.length;
|
|
324
|
-
delete res.body.eventDeletions;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
335
|
+
const res = await libGetEventStreamed(this, queryParams, myParser);
|
|
328
336
|
const now = getTimestamp();
|
|
329
337
|
this._handleMeta(res.body, now);
|
|
330
338
|
return res.body;
|
|
@@ -337,38 +345,44 @@ class Connection {
|
|
|
337
345
|
* @param {string} filePath
|
|
338
346
|
*/
|
|
339
347
|
async createEventWithFile (event, filePath) {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
348
|
+
const fs = require('fs');
|
|
349
|
+
const path = require('path');
|
|
350
|
+
|
|
351
|
+
if (!fs || !path) {
|
|
352
|
+
throw new Error('createEventWithFile is only available in Node.js. Use createEventWithFormData in browser.');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const fileName = path.basename(filePath);
|
|
356
|
+
const mimeType = getMimeType(path.extname(filePath));
|
|
357
|
+
const fileBlob = await fs.openAsBlob(filePath, { type: mimeType });
|
|
358
|
+
|
|
359
|
+
const formData = new FormData();
|
|
360
|
+
formData.append('event', JSON.stringify(event));
|
|
361
|
+
formData.append('file', fileBlob, fileName);
|
|
343
362
|
|
|
344
363
|
const now = getTimestamp();
|
|
345
|
-
this.
|
|
346
|
-
|
|
364
|
+
const { body } = await this._postFetchRaw('events', formData);
|
|
365
|
+
this._handleMeta(body, now);
|
|
366
|
+
return body;
|
|
347
367
|
}
|
|
348
368
|
|
|
349
369
|
/**
|
|
350
370
|
* Create an event from a Buffer
|
|
351
|
-
* @param {
|
|
371
|
+
* @param {Object} event
|
|
352
372
|
* @param {Buffer|Blob} bufferData - Buffer for node, Blob for browser
|
|
353
|
-
* @param {string}
|
|
373
|
+
* @param {string} filename
|
|
354
374
|
*/
|
|
355
375
|
async createEventWithFileFromBuffer (event, bufferData, filename) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
/* global FormData */
|
|
367
|
-
const formData = new FormData();
|
|
368
|
-
formData.append('file', bufferData, filename);
|
|
369
|
-
const body = await this.createEventWithFormData(event, formData);
|
|
370
|
-
return body;
|
|
371
|
-
}
|
|
376
|
+
const mimeType = getMimeType(getExtname(filename));
|
|
377
|
+
const fileBlob = bufferData instanceof Blob
|
|
378
|
+
? bufferData
|
|
379
|
+
// @ts-ignore - Buffer is valid for Blob in Node.js
|
|
380
|
+
: new Blob([bufferData], { type: mimeType });
|
|
381
|
+
|
|
382
|
+
const formData = new FormData();
|
|
383
|
+
formData.append('file', fileBlob, filename);
|
|
384
|
+
const body = await this.createEventWithFormData(event, formData);
|
|
385
|
+
return body;
|
|
372
386
|
}
|
|
373
387
|
|
|
374
388
|
/**
|
|
@@ -379,8 +393,8 @@ class Connection {
|
|
|
379
393
|
*/
|
|
380
394
|
async createEventWithFormData (event, formData) {
|
|
381
395
|
formData.append('event', JSON.stringify(event));
|
|
382
|
-
const
|
|
383
|
-
return
|
|
396
|
+
const { body } = await this._postFetchRaw('events', formData);
|
|
397
|
+
return body;
|
|
384
398
|
}
|
|
385
399
|
|
|
386
400
|
/**
|
|
@@ -394,9 +408,9 @@ class Connection {
|
|
|
394
408
|
}
|
|
395
409
|
|
|
396
410
|
/**
|
|
397
|
-
* API endpoint of this connection
|
|
411
|
+
* API endpoint of this connection (includes token if present)
|
|
398
412
|
* @readonly
|
|
399
|
-
* @property {APIEndpoint}
|
|
413
|
+
* @property {APIEndpoint} apiEndpoint
|
|
400
414
|
*/
|
|
401
415
|
get apiEndpoint () {
|
|
402
416
|
return utils.buildAPIEndpoint(this);
|
|
@@ -422,6 +436,45 @@ function getTimestamp () {
|
|
|
422
436
|
return Date.now() / 1000;
|
|
423
437
|
}
|
|
424
438
|
|
|
439
|
+
const MIME_TYPES = {
|
|
440
|
+
'.png': 'image/png',
|
|
441
|
+
'.jpg': 'image/jpeg',
|
|
442
|
+
'.jpeg': 'image/jpeg',
|
|
443
|
+
'.gif': 'image/gif',
|
|
444
|
+
'.webp': 'image/webp',
|
|
445
|
+
'.svg': 'image/svg+xml',
|
|
446
|
+
'.bmp': 'image/bmp',
|
|
447
|
+
'.ico': 'image/x-icon',
|
|
448
|
+
'.pdf': 'application/pdf',
|
|
449
|
+
'.json': 'application/json',
|
|
450
|
+
'.xml': 'application/xml',
|
|
451
|
+
'.zip': 'application/zip',
|
|
452
|
+
'.gz': 'application/gzip',
|
|
453
|
+
'.tar': 'application/x-tar',
|
|
454
|
+
'.txt': 'text/plain',
|
|
455
|
+
'.html': 'text/html',
|
|
456
|
+
'.htm': 'text/html',
|
|
457
|
+
'.css': 'text/css',
|
|
458
|
+
'.js': 'application/javascript',
|
|
459
|
+
'.mjs': 'application/javascript',
|
|
460
|
+
'.mp3': 'audio/mpeg',
|
|
461
|
+
'.wav': 'audio/wav',
|
|
462
|
+
'.ogg': 'audio/ogg',
|
|
463
|
+
'.mp4': 'video/mp4',
|
|
464
|
+
'.webm': 'video/webm',
|
|
465
|
+
'.avi': 'video/x-msvideo',
|
|
466
|
+
'.mov': 'video/quicktime'
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
function getMimeType (ext) {
|
|
470
|
+
return MIME_TYPES[ext.toLowerCase()] || 'application/octet-stream';
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function getExtname (filename) {
|
|
474
|
+
const lastDot = filename.lastIndexOf('.');
|
|
475
|
+
return lastDot >= 0 ? filename.slice(lastDot) : '';
|
|
476
|
+
}
|
|
477
|
+
|
|
425
478
|
// service is require "after" to allow circular require
|
|
426
479
|
const Service = require('./Service');
|
|
427
480
|
|
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
|
}
|