pryv
Version:
Pryv JavaScript library
220 lines (200 loc) • 7.57 kB
JavaScript
/**
* @license
* [BSD-3-Clause](https://github.com/pryv/lib-js/blob/master/LICENSE)
*/
const utils = require('./utils.js');
// Connection is required at the end of this file to allow circular requires.
const Assets = require('./ServiceAssets.js');
/**
* @class pryv.Service
* A Pryv.io deployment is a unique "Service", as an example **Pryv Lab** is a service, deployed with the domain name **pryv.me**.
*
* `pryv.Service` exposes tools to interact with Pryv.io at a "Platform" level.
*
* ##### Initizalization with a service info URL
```js
const service = new pryv.Service('https://reg.pryv.me/service/info');
```
- With the content of a serviceInfo configuration
Service information properties can be overriden with specific values. This might be usefull to test new designs on production platforms.
```js
const serviceInfoUrl = 'https://reg.pryv.me/service/info';
const serviceCustomizations = {
name: 'Pryv Lab 2',
assets: {
definitions: 'https://pryv.github.io/assets-pryv.me/index.json'
}
}
const service = new pryv.Service(serviceInfoUrl, serviceCustomizations);
```
* @memberof pryv
*
* @constructor
* @param {string} serviceInfoUrl Url point to /service/info of a Pryv platform see: {@link https://api.pryv.com/reference/#service-info}
*/
class Service {
constructor (serviceInfoUrl, serviceCustomizations) {
this._serviceInfo = null;
this._assets = null;
this._polling = false;
this._serviceInfoUrl = serviceInfoUrl;
this._pryvServiceCustomizations = serviceCustomizations;
}
/**
* Return service info parameters info known of fetch it if needed.
* Example
* - name of a platform
* `const serviceName = await service.info().name`
* @see ServiceInfo For details on available properties.
* @param {boolean?} forceFetch If true, will force fetching service info.
* @returns {Promise<ServiceInfo>} Promise to Service info Object
*/
async info (forceFetch) {
if (forceFetch || !this._serviceInfo) {
let baseServiceInfo = {};
if (this._serviceInfoUrl) {
const res = await utils.superagent.get(this._serviceInfoUrl).set('Access-Control-Allow-Origin', '*').set('accept', 'json');
baseServiceInfo = res.body;
}
Object.assign(baseServiceInfo, this._pryvServiceCustomizations);
this.setServiceInfo(baseServiceInfo);
}
return this._serviceInfo;
}
/**
* Check if a service supports High Frequency Data Sets
* @return true if yes
*/
async supportsHF () {
const infos = await this.info();
return (infos.features == null || infos.features.noHF !== true);
}
/**
* Check if a service has username in the hostname or in the path of the api
* @return true if the service does not rely on DNS to find a host related to a username
*/
async isDnsLess () {
const infos = await this.info();
const hostname = infos.api.split('/')[2];
return !hostname.includes('{username}');
}
/**
* @private
* @param {ServiceInfo} serviceInfo
*/
setServiceInfo (serviceInfo) {
if (!serviceInfo.name) {
throw new Error('Invalid data from service/info');
}
// cleanup serviceInfo for eventual url not finishing by "/"
// code will be obsolete with next version of register
['access', 'api', 'register'].forEach((key) => {
if (serviceInfo[key].slice(-1) !== '/') {
serviceInfo[key] += '/';
}
});
this._serviceInfo = serviceInfo;
}
/**
* Return assets property content
* @param {boolean?} forceFetch If true, will force fetching service info.
* @returns {Promise<ServiceAssets>} Promise to ServiceAssets
*/
async assets (forceFetch) {
if (!forceFetch && this._assets) {
return this._assets;
} else {
const serviceInfo = await this.info();
if (!serviceInfo.assets || !serviceInfo.assets.definitions) {
console.log('Warning: no assets for this service');
return null;
}
this._assets = await Assets.setup(serviceInfo.assets.definitions);
return this._assets;
}
}
/**
* Return service info parameters info known or null if not yet loaded
* @returns {ServiceInfo} Service Info definition
*/
infoSync () {
return this._serviceInfo;
}
/**
* Return an API endpoint from a username and token
* @param {string} username
* @param {string} [token]
* @return {APIEndpoint}
*/
async apiEndpointFor (username, token) {
const serviceInfo = await this.info();
return Service.buildAPIEndpoint(serviceInfo, username, token);
}
/**
* Return an API endpoint from a username and token and a ServiceInfo.
* This is method is rarely used. See **apiEndpointFor** as an alternative.
* @param {ServiceInfo} serviceInfo
* @param {string} username
* @param {string} [token]
* @return {APIEndpoint}
*/
static buildAPIEndpoint (serviceInfo, username, token) {
const endpoint = serviceInfo.api.replace('{username}', username);
return utils.buildAPIEndpoint({ endpoint: endpoint, token: token });
}
/**
* Issue a "login call on the Service" return a Connection on success
* **! Warning**: the token of the connection will be a "Personal" token that expires
* @see https://api.pryv.com/reference-full/#login-user
* @param {string} username
* @param {string} password
* @param {string} appId
* @param {string} [originHeader=service-info.register] Only for Node.js. If not set will use the register value of service info. In browsers this will overridden by current page location.
* @throws {Error} on invalid login
*/
async login (username, password, appId, originHeader) {
const apiEndpoint = await this.apiEndpointFor(username);
try {
const headers = { accept: 'json' };
originHeader = originHeader || (await this.info()).register;
if (!utils.isBrowser()) {
headers.Origin = originHeader;
}
const res = await utils.superagent.post(apiEndpoint + 'auth/login')
.set(headers)
.send({ username: username, password: password, appId: appId });
if (!res.body.token) {
throw new Error('Invalid login response: ' + res.body);
}
return new Connection(
Service.buildAPIEndpoint(await this.info(), username, res.body.token),
this // Pre load Connection with service
);
} catch (e) {
if (e.response &&
e.response.body &&
e.response.body.error &&
e.response.body.error.message) {
throw new Error(e.response.body.error.message);
}
throw (e);
}
}
}
module.exports = Service;
// Require is done after exports to allow circular references
const Connection = require('./Connection');
/**
* Object to handle Pryv Service Informations https://api.pryv.com/reference/#service-info
* @typedef {Object} ServiceInfo
* @property {string} register The URL of the register service.
* @property {string} access The URL of the access page.
* @property {string} api The API endpoint format.
* @property {string} name The platform name.
* @property {string} home The URL of the platform's home page.
* @property {string} support The email or URL of the support page.
* @property {string} terms The terms and conditions, in plain text or the URL displaying them.
* @property {string} eventTypes The URL of the list of validated event types.
* @property {Object} [assets] Holder for service specific Assets (icons, css, ...)
* @property {String} [assets.definitions] URL to json object with assets definitions
*/