forked from pryv/lib-js
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathService.js
More file actions
221 lines (201 loc) · 7.71 KB
/
Service.js
File metadata and controls
221 lines (201 loc) · 7.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/**
* @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 { body } = await utils.fetchGet(this._serviceInfoUrl);
baseServiceInfo = body;
}
Object.assign(baseServiceInfo, this._pryvServiceCustomizations);
// @ts-ignore - baseServiceInfo is populated from body or customizations
this.setServiceInfo(baseServiceInfo);
}
return this._serviceInfo;
}
/**
* Check if a service supports High Frequency Data Sets
* @returns {Promise<boolean>} Promise resolving to true if HF is supported
*/
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.
* @returns {Promise<boolean>} Promise resolving to 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|null>} 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 - The username
* @param {string} [token] - Optional authorization token
* @returns {Promise<APIEndpoint>} Promise resolving to the API endpoint URL
*/
async apiEndpointFor (username, token) {
const serviceInfo = await this.info();
return Service.buildAPIEndpoint(serviceInfo, username, token);
}
/**
* Return an API endpoint from a username, token and ServiceInfo.
* This method is rarely used. See **apiEndpointFor** as an alternative.
* @param {ServiceInfo} serviceInfo - The service info object containing API URL template
* @param {string} username - The username
* @param {string} [token] - Optional authorization token
* @returns {APIEndpoint} The constructed API endpoint URL
*/
static buildAPIEndpoint (serviceInfo, username, token) {
const endpoint = serviceInfo.api.replace('{username}', username);
return utils.buildAPIEndpoint({ endpoint, 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);
const headers = {};
originHeader = originHeader || (await this.info()).register;
if (!utils.isBrowser()) {
headers.Origin = originHeader;
}
const { response, body } = await utils.fetchPost(
apiEndpoint + 'auth/login',
{ username, password, appId },
headers
);
if (!response.ok) {
if (body?.error?.message) {
throw new Error(body.error.message);
}
throw new Error('Login failed: ' + JSON.stringify(body));
}
if (!body.token) {
throw new Error('Invalid login response: ' + JSON.stringify(body));
}
return new Connection(
Service.buildAPIEndpoint(await this.info(), username, body.token),
this // Pre load Connection with service
);
}
}
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
* @property {Object} [features] Platform feature flags
* @property {boolean} [features.noHF] True if HF data is not supported
*/