pryv 2.1.8

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.
Files changed (43) hide show
  1. package/.jsdoc-conf.json +29 -0
  2. package/.mocharc.js +13 -0
  3. package/LICENSE.md +27 -0
  4. package/README.md +723 -0
  5. package/package.json +57 -0
  6. package/scripts/setup-environment-dev.sh +28 -0
  7. package/scripts/upload.sh +15 -0
  8. package/src/Auth/AuthController.js +276 -0
  9. package/src/Auth/AuthStates.js +20 -0
  10. package/src/Auth/LoginMessages.js +29 -0
  11. package/src/Auth/index.js +43 -0
  12. package/src/Browser/CookieUtils.js +51 -0
  13. package/src/Browser/LoginButton.js +199 -0
  14. package/src/Browser/index.js +55 -0
  15. package/src/Connection.js +331 -0
  16. package/src/Pryv.js +19 -0
  17. package/src/Service.js +197 -0
  18. package/src/ServiceAssets.js +162 -0
  19. package/src/index-socket.io-monitor.js +4 -0
  20. package/src/index.html +17 -0
  21. package/src/index.js +3 -0
  22. package/src/lib/browser-getEventStreamed.js +80 -0
  23. package/src/lib/json-parser.js +156 -0
  24. package/src/utils.js +136 -0
  25. package/test/Browser.AuthController.test.js +97 -0
  26. package/test/Browser.test.js +79 -0
  27. package/test/Connection.test.js +455 -0
  28. package/test/Service.test.js +89 -0
  29. package/test/ServiceAssets.test.js +79 -0
  30. package/test/Y.png +0 -0
  31. package/test/browser-index.js +11 -0
  32. package/test/browser-tests.html +31 -0
  33. package/test/helpers.js +8 -0
  34. package/test/load-test-account.js +108 -0
  35. package/test/test-data.js +92 -0
  36. package/test/utils.test.js +68 -0
  37. package/web-demos/auth-with-redirection.html +72 -0
  38. package/web-demos/auth.html +77 -0
  39. package/web-demos/custom-login-button.html +158 -0
  40. package/web-demos/index.html +186 -0
  41. package/web-demos/service-info.json +13 -0
  42. package/web-demos/stream-examples.html +80 -0
  43. package/webpack.config.js +71 -0
package/src/Service.js ADDED
@@ -0,0 +1,197 @@
1
+
2
+ const utils = require('./utils.js');
3
+ // Connection is required at the end of this file to allow circular requires.
4
+ const Assets = require('./ServiceAssets.js');
5
+
6
+
7
+ /**
8
+ * @class Pryv.Service
9
+ * A Pryv.io deployment is a unique "Service", as an example **Pryv Lab** is a service, deployed with the domain name **pryv.me**.
10
+ *
11
+ * `Pryv.Service` exposes tools to interact with Pryv.io at a "Platform" level.
12
+ *
13
+ * ##### Initizalization with a service info URL
14
+ ```javascript
15
+ const service = new Pryv.Service('https://reg.pryv.me/service/info');
16
+ ```
17
+
18
+ - With the content of a serviceInfo configuration
19
+
20
+ Service information properties can be overriden with specific values. This might be usefull to test new designs on production platforms.
21
+
22
+ ```javascript
23
+ const serviceInfoUrl = 'https://reg.pryv.me/service/info';
24
+ const serviceCustomizations = {
25
+ name: 'Pryv Lab 2',
26
+ assets: {
27
+ definitions: 'https://pryv.github.io/assets-pryv.me/index.json'
28
+ }
29
+ }
30
+ const service = new Pryv.Service(serviceInfoUrl, serviceCustomizations);
31
+ ```
32
+
33
+ * @memberof Pryv
34
+ *
35
+ * @constructor
36
+ * @param {string} serviceInfoUrl Url point to /service/info of a Pryv platform see: {@link https://api.pryv.com/reference/#service-info}
37
+ */
38
+ class Service {
39
+
40
+ constructor (serviceInfoUrl, serviceCustomizations) {
41
+ this._pryvServiceInfo = null;
42
+ this._assets = null;
43
+ this._polling = false;
44
+ this._pryvServiceInfoUrl = serviceInfoUrl;
45
+ this._pryvServiceCustomizations = serviceCustomizations;
46
+ }
47
+
48
+ /**
49
+ * Return service info parameters info known of fetch it if needed.
50
+ * Example
51
+ * - name of a platform
52
+ * `const serviceName = await service.info().name`
53
+ * @see PryvServiceInfo For details on available properties.
54
+ * @param {boolean?} forceFetch If true, will force fetching service info.
55
+ * @returns {Promise<PryvServiceInfo>} Promise to Service info Object
56
+ */
57
+ async info (forceFetch) {
58
+ if (forceFetch || !this._pryvServiceInfo) {
59
+ let baseServiceInfo = {};
60
+ if (this._pryvServiceInfoUrl) {
61
+ const res = await utils.superagent.get(this._pryvServiceInfoUrl).set('Access-Control-Allow-Origin', '*').set('accept', 'json');
62
+ baseServiceInfo = res.body;
63
+ }
64
+ Object.assign(baseServiceInfo, this._pryvServiceCustomizations);
65
+ this.setServiceInfo(baseServiceInfo);
66
+ }
67
+ return this._pryvServiceInfo;
68
+ }
69
+
70
+ /**
71
+ * @private
72
+ * @param {PryvServiceInfo} serviceInfo
73
+ */
74
+ setServiceInfo (serviceInfo) {
75
+ if (!serviceInfo.name) {
76
+ throw new Error('Invalid data from service/info');
77
+ }
78
+ // cleanup serviceInfo for eventual url not finishing by "/"
79
+ // code will be obsolete with next version of register
80
+ ['access', 'api', 'register'].forEach((key) => {
81
+ if (serviceInfo[key].slice(-1) !== '/') {
82
+ serviceInfo[key] += '/';
83
+ }
84
+ });
85
+ this._pryvServiceInfo = serviceInfo;
86
+ }
87
+
88
+ /**
89
+ * Return assets property content
90
+ * @param {boolean?} forceFetch If true, will force fetching service info.
91
+ * @returns {Promise<ServiceAssets>} Promise to ServiceAssets
92
+ */
93
+ async assets (forceFetch) {
94
+ if (!forceFetch && this._assets) {
95
+ return this._assets;
96
+ } else {
97
+ const serviceInfo = await this.info();
98
+ if (!serviceInfo.assets || !serviceInfo.assets.definitions) {
99
+ console.log('Warning: no assets for this service');
100
+ return null;
101
+ }
102
+ this._assets = await Assets.setup(serviceInfo.assets.definitions);
103
+ return this._assets;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Return service info parameters info known or null if not yet loaded
109
+ * @returns {PryvServiceInfo} Service Info definition
110
+ */
111
+ infoSync () {
112
+ return this._pryvServiceInfo;
113
+ }
114
+
115
+ /**
116
+ * Return an API Endpoint from a username and token
117
+ * @param {string} username
118
+ * @param {string} [token]
119
+ * @return {PryvApiEndpoint}
120
+ */
121
+ async apiEndpointFor (username, token) {
122
+ const serviceInfo = await this.info();
123
+ return Service.buildAPIEndpoint(serviceInfo, username, token);
124
+ }
125
+
126
+ /**
127
+ * Return an API Endpoint from a username and token and a PryvServiceInfo.
128
+ * This is method is rarely used. See **apiEndpointFor** as an alternative.
129
+ * @param {PryvServiceInfo} serviceInfo
130
+ * @param {string} username
131
+ * @param {string} [token]
132
+ * @return {PryvApiEndpoint}
133
+ */
134
+ static buildAPIEndpoint (serviceInfo, username, token) {
135
+ const endpoint = serviceInfo.api.replace('{username}', username);
136
+ return utils.buildPryvApiEndpoint({ endpoint: endpoint, token: token });
137
+ }
138
+
139
+ /**
140
+ * Issue a "login call on the Service" return a Connection on success
141
+ * **! Warning**: the token of the connection will be a "Personal" token that expires
142
+ * @see https://api.pryv.com/reference-full/#login-user
143
+ * @param {string} username
144
+ * @param {string} password
145
+ * @param {string} appId
146
+ * @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.
147
+ * @throws {Error} on invalid login
148
+ */
149
+ async login (username, password, appId, originHeader) {
150
+ const apiEndpoint = await this.apiEndpointFor(username);
151
+
152
+ try {
153
+ const headers = { accept: 'json' };
154
+ originHeader = originHeader || (await this.info()).register;
155
+ if (!utils.isBrowser()) {
156
+ headers.Origin = originHeader;
157
+ }
158
+ const res = await utils.superagent.post(apiEndpoint + 'auth/login')
159
+ .set(headers)
160
+ .send({ username: username, password: password, appId: appId });
161
+
162
+ if (!res.body.token) {
163
+ throw new Error('Invalid login response: ' + res.body);
164
+ }
165
+ return new Connection(
166
+ Service.buildAPIEndpoint(await this.info(), username, res.body.token),
167
+ this // Pre load Connection with service
168
+ );
169
+ } catch (e) {
170
+ if (e.response && e.response.body
171
+ && e.response.body.error
172
+ && e.response.body.error.message) {
173
+ throw new Error(e.response.body.error.message)
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ module.exports = Service;
180
+
181
+ // Require is done after exports to allow circular references
182
+ const Connection = require('./Connection');
183
+
184
+ /**
185
+ * Object to handle Pryv Service Informations https://api.pryv.com/reference/#service-info
186
+ * @typedef {Object} PryvServiceInfo
187
+ * @property {string} register The URL of the register service.
188
+ * @property {string} access The URL of the access page.
189
+ * @property {string} api The API endpoint format.
190
+ * @property {string} name The platform name.
191
+ * @property {string} home The URL of the platform's home page.
192
+ * @property {string} support The email or URL of the support page.
193
+ * @property {string} terms The terms and conditions, in plain text or the URL displaying them.
194
+ * @property {string} eventTypes The URL of the list of validated event types.
195
+ * @property {Object} [assets] Holder for service specific Assets (icons, css, ...)
196
+ * @property {String} [assets.definitions] URL to json object with assets definitions
197
+ */
@@ -0,0 +1,162 @@
1
+ const utils = require('./utils.js');
2
+ /**
3
+ * Holds Pryv Service informations.
4
+ *
5
+ * It's returned by `service.assets()`
6
+ *
7
+ * @memberof Pryv
8
+ **/
9
+ class ServiceAssets {
10
+ /**
11
+ * Private => use ServiceAssets.setup()
12
+ * @param { object} assets The content of service/info.assets properties.
13
+ * @param { string } pryvServiceAssetsSourceUrl Url point to assets of the service of a Pryv platform: https://api.pryv.com/reference/#service-info property `assets.src`
14
+ */
15
+ constructor(assets, assetsURL) {
16
+ this._assets = assets;
17
+ this._assetsURL = assetsURL;
18
+ }
19
+
20
+ /**
21
+ * Load Assets definition
22
+ * @param {string} pryvServiceAssetsSourceUrl
23
+ * @returns {ServiceAssets}
24
+ */
25
+ static async setup(pryvServiceAssetsSourceUrl) {
26
+ const res = await utils.superagent.get(pryvServiceAssetsSourceUrl).set('accept', 'json');
27
+ return new ServiceAssets(res.body, pryvServiceAssetsSourceUrl);
28
+ }
29
+
30
+ /**
31
+ * get a value from path separated by `:`
32
+ * exemple of key `lib-js:buttonSignIn`
33
+ * @param {string} [keyPath] if null, will return the all assets
34
+ */
35
+ get(keyPath) {
36
+ let result = Object.assign({}, this._assets);
37
+ if (keyPath) {
38
+ keyPath.split(':').forEach((key) => {
39
+ result = result[key];
40
+ if (typeof result === 'undefined') return result;
41
+ });
42
+ }
43
+ return result;
44
+ }
45
+
46
+ /**
47
+ * get an Url from path separated by `:`
48
+ * identical to doing assets.relativeURL(assets.get(keyPath))
49
+ * exemple of key `lib-js:buttonSignIn`
50
+ * @param {string} [keyPath] if null, will return the all assets
51
+ */
52
+ getUrl(keyPath) {
53
+ const url = this.get(keyPath);
54
+ if (typeof url !== 'string') {
55
+ throw new Error(url + ' returned ' + value);
56
+ }
57
+ return this.relativeURL(url);
58
+ }
59
+
60
+ /**
61
+ * get relativeUrl
62
+ */
63
+ relativeURL(url) {
64
+ return relPathToAbs(this._assets.baseUrl || this._assetsURL, url);
65
+ }
66
+
67
+ //---------------- Default service ressources
68
+
69
+ /**
70
+ * Set all defaults Favicon, CSS
71
+ */
72
+ async setAllDefaults() {
73
+ this.setFavicon();
74
+ await this.loadCSS();
75
+ }
76
+
77
+ /**
78
+ * Set service Favicon to Web Page
79
+ */
80
+ setFavicon() {
81
+ var link = document.querySelector("link[rel*='icon']") || document.createElement('link');
82
+ link.type = 'image/x-icon';
83
+ link.rel = 'shortcut icon';
84
+ link.href = this.relativeURL(this._assets.favicon.default.url);
85
+ document.getElementsByTagName('head')[0].appendChild(link);
86
+ }
87
+
88
+ /**
89
+ * Set default service CSS
90
+ */
91
+ async loadCSS() {
92
+ loadCSS(this.relativeURL(this._assets.css.default.url));
93
+ }
94
+
95
+ // ---- Login
96
+
97
+ /**
98
+ * Load CSS for Login button
99
+ */
100
+ async loginButtonLoadCSS () {
101
+ loadCSS(this.relativeURL(this._assets['lib-js'].buttonSignIn.css));
102
+ }
103
+
104
+ /**
105
+ * Get HTML for Login Button
106
+ */
107
+ async loginButtonGetHTML() {
108
+ const res = await utils.superagent.get(this.relativeURL(this._assets['lib-js'].buttonSignIn.html)).set('accept', 'html');
109
+ return res.text;
110
+ }
111
+
112
+ /**
113
+ * Get Messages strings for Login Button
114
+ */
115
+ async loginButtonGetMessages() {
116
+ const res = await utils.superagent.get(this.relativeURL(this._assets['lib-js'].buttonSignIn.messages)).set('accept', 'json');
117
+ return res.body;
118
+ }
119
+
120
+ }
121
+
122
+ function loadCSS(url) {
123
+ var head = document.getElementsByTagName('head')[0];
124
+ var link = document.createElement('link');
125
+ link.id = url;
126
+ link.rel = 'stylesheet';
127
+ link.type = 'text/css';
128
+ link.href = url;
129
+ link.media = 'all';
130
+ head.appendChild(link);
131
+ }
132
+
133
+ /*\
134
+ |*| Modified version of
135
+ |*| :: translate relative paths to absolute paths ::
136
+ |*|
137
+ |*| https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
138
+ |*|
139
+ |*| The following code is released under the GNU Public License, version 3 or later.
140
+ |*| http://www.gnu.org/licenses/gpl-3.0-standalone.html
141
+ |*|
142
+ \*/
143
+
144
+ function relPathToAbs (baseUrlString, sRelPath) {
145
+ var baseLocation = location;
146
+ if (baseUrlString) {
147
+ baseLocation = document.createElement('a');
148
+ baseLocation.href = baseUrlString;
149
+ }
150
+
151
+ var nUpLn, sDir = "", sPath = baseLocation.pathname.replace(/[^\/]*$/, sRelPath.replace(/(\/|^)(?:\.?\/+)+/g, "$1"));
152
+ for (var nEnd, nStart = 0; nEnd = sPath.indexOf("/../", nStart), nEnd > -1; nStart = nEnd + nUpLn) {
153
+ nUpLn = /^\/(?:\.\.\/)*/.exec(sPath.slice(nEnd))[0].length;
154
+ sDir = (sDir + sPath.substring(nStart, nEnd)).replace(new RegExp("(?:\\\/+[^\\\/]*){0," + ((nUpLn - 1) / 3) + "}$"),
155
+ "/");
156
+ }
157
+ let portStr = baseLocation.port ? ':' + baseLocation.port : '';
158
+ return baseLocation.protocol + '//' + baseLocation.hostname + portStr + sDir + sPath.substr(nStart);
159
+ }
160
+
161
+
162
+ module.exports = ServiceAssets;
@@ -0,0 +1,4 @@
1
+ const Pryv = require('./Pryv');
2
+ require('@pryv/socket.io')(Pryv);
3
+ require('@pryv/monitor')(Pryv);
4
+ module.exports = Pryv;
package/src/index.html ADDED
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Pryv - Light - Javascript Lib</title>
6
+
7
+ <script src="./print.bundle.js"></script>
8
+ </head>
9
+
10
+ <body>
11
+ -
12
+ <script src="./bundle.js"></script>
13
+ +
14
+ <script src="./app.bundle.js"></script>
15
+ </body>
16
+
17
+ </html>
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ const Pryv = require('./Pryv');
2
+
3
+ module.exports = Pryv;
@@ -0,0 +1,80 @@
1
+
2
+ /**
3
+ * @private
4
+ * Replacement for getEventStreamed for Browser
5
+ * To be used as long as superagent does not propose it.
6
+ *
7
+ */
8
+ async function getEventStreamed(conn, queryParam, parser) {
9
+
10
+ /**
11
+ * Holds Parser's settings
12
+ */
13
+ const parserSettings = {
14
+ ondata: null,
15
+ onend: null,
16
+ encoding: 'utf8'
17
+ }
18
+
19
+ /**
20
+ * Mock Response
21
+ */
22
+ const fakeRes = {
23
+ setEncoding : function(encoding) {
24
+ parserSettings.encoding = encoding;
25
+ }, // will receive 'data' and 'end' callbacks
26
+ on: function(key, f) {
27
+ parserSettings['on' + key] = f;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Holds results from the parser
33
+ */
34
+ let errResult;
35
+ let bodyObjectResult;
36
+ /**
37
+ *
38
+ */
39
+ parser(fakeRes, function (err, bodyObject) {
40
+ errResult = err;
41
+ bodyObjectResult = bodyObject;
42
+ });
43
+
44
+
45
+ // ------------ fetch ------------------- //
46
+ let url = new URL(conn.endpoint + 'events');
47
+ url.search = new URLSearchParams(queryParam);
48
+ let fetchParams = {method: 'GET', headers: {Accept: 'application/json'}};
49
+ if (conn.token) fetchParams.headers.Authorization = conn.token;
50
+
51
+ let response = await fetch(url,fetchParams);
52
+ const reader = response.body.getReader();
53
+
54
+ while (true) {
55
+ const { done, value } = await reader.read();
56
+ parserSettings.ondata(new TextDecoder(parserSettings.encoding).decode(value));
57
+ if (done) { parserSettings.onend(); break; }
58
+ }
59
+
60
+ if (errResult) {
61
+ throw new Error(errResult);
62
+ }
63
+
64
+ // We're done!
65
+ const result = {
66
+ text: fakeRes.text, // from the parser
67
+ body: bodyObjectResult, // from the parser
68
+ statusCode: response.status,
69
+ headers: {}
70
+ }
71
+ // add headers to result
72
+ for (var pair of response.headers.entries()) {
73
+ result.headers[pair[0]] = pair[1];
74
+ }
75
+
76
+ return result;
77
+ }
78
+
79
+
80
+ module.exports = getEventStreamed;
@@ -0,0 +1,156 @@
1
+ // there two steps 1 find events, then eventDeletions
2
+ const EVENTMARKERS = ['"events":[', '"eventDeletions":['];
3
+
4
+ /**
5
+ * Customize superagent parser
6
+ * Work on 'node.js' and use by browser-getEventStreamed
7
+ */
8
+ module.exports = function (foreachEvent, includeDeletions) {
9
+ let eventOrEventDeletions = 0; // start with event
10
+ let buffer = ''; // temp data
11
+ let body = null; // to be returned
12
+
13
+ //IN EVENTS VARS
14
+ let depth = 0; // level of depth in brackets
15
+ let inString = false; // cursor is in a String
16
+ let skipNextOne = false; // when a backslash is found
17
+ let cursorPos = 0; // position of Character Cursor
18
+
19
+ // counters
20
+ let eventsCount = 0;
21
+ let eventDeletionsCount = 0;
22
+
23
+ const states = {
24
+ A_BEFORE_EVENTS: 0,
25
+ B_IN_EVENTS: 1,
26
+ D_AFTER_EVENTS: 2
27
+ }
28
+
29
+ let state = states.A_BEFORE_EVENTS;
30
+
31
+ function processBuffer() {
32
+ switch (state) {
33
+ case states.A_BEFORE_EVENTS:
34
+ searchStartEvents();
35
+ break;
36
+ case states.B_IN_EVENTS:
37
+ processEvents();
38
+ break;
39
+ default:
40
+ afterEvents();
41
+ break;
42
+ }
43
+ }
44
+
45
+
46
+ function searchStartEvents() {
47
+ // search for "events": and happend any info before to the body
48
+ var n = buffer.indexOf(EVENTMARKERS[eventOrEventDeletions]);
49
+ if (n > 0) {
50
+ if (eventOrEventDeletions === 0) { // do only once
51
+ body = buffer.substring(0, n);
52
+ }
53
+ buffer = buffer.substr(n + EVENTMARKERS[eventOrEventDeletions].length);
54
+ state = states.B_IN_EVENTS;
55
+ processEvents();
56
+ }
57
+ }
58
+
59
+
60
+ function processEvents() {
61
+ /// ---- in Event
62
+ while (cursorPos < buffer.length && (state === states.B_IN_EVENTS)) {
63
+ if (skipNextOne) { // ignore next character
64
+ skipNextOne = false;
65
+ cursorPos++;
66
+ continue;
67
+ }
68
+ switch (buffer.charCodeAt(cursorPos)) {
69
+ case 93: // ]
70
+ if (depth === 0) { // end of events
71
+ if (cursorPos !== 0) {
72
+ throw new Error('Found trailling ] in mid-course');
73
+ }
74
+ if (eventOrEventDeletions === 0 && includeDeletions) {
75
+ state = states.A_BEFORE_EVENTS;
76
+ eventOrEventDeletions = 1; // now look for eventDeletions
77
+ return;
78
+ } else { // done
79
+ state = states.D_AFTER_EVENTS;
80
+ let eventsOrDeletionMsg = '';
81
+ if (eventOrEventDeletions === 1) {
82
+ eventsOrDeletionMsg = '"eventDeletionsCount":' + eventDeletionsCount + ','
83
+ }
84
+ buffer = eventsOrDeletionMsg + '"eventsCount":' + eventsCount + '' + buffer.substr(1);
85
+ }
86
+ }
87
+ break;
88
+ case 92: // \
89
+ skipNextOne = true;
90
+ break;
91
+ case 123: // {
92
+ if (!inString) depth++;
93
+ break;
94
+ case 34: // "
95
+ inString = !inString;
96
+ break;
97
+ case 125: // }
98
+ if (!inString) depth--;
99
+ if (depth === 0) {
100
+ // ignore possible coma ',' if first char
101
+ const ignoreComa = (buffer.charCodeAt(0) === 44) ? 1 : 0;
102
+ const eventStr = buffer.substring(ignoreComa, cursorPos + 1);
103
+
104
+ if (eventOrEventDeletions === 0) {
105
+ eventsCount++;
106
+ } else {
107
+ eventDeletionsCount++;
108
+ }
109
+ buffer = buffer.substr(cursorPos + 1 );
110
+ addEvent(eventStr);
111
+ cursorPos = -1;
112
+ }
113
+ break;
114
+ }
115
+ cursorPos++;
116
+ }
117
+ }
118
+
119
+ function afterEvents() {
120
+ // just happend the end of message;
121
+ body += buffer;
122
+ buffer = '';
123
+ return;
124
+ }
125
+
126
+ return function (res, fn) {
127
+ res.setEncoding('utf8'); // Already UTF8 in browsers
128
+ res.on('data', chunk => {
129
+ buffer += chunk;
130
+ processBuffer();
131
+ });
132
+ res.on('end', () => {
133
+ let err;
134
+ let bodyObject;
135
+ try {
136
+ res.text = body + buffer;
137
+ bodyObject = res.text && JSON.parse(res.text);
138
+ } catch (err_) {
139
+ err = err_;
140
+ // issue #675: return the raw response if the response parsing fails
141
+ err.rawResponse = res.text || null;
142
+ // issue #876: return the http status code if the response parsing fails
143
+ err.statusCode = res.statusCode;
144
+ } finally {
145
+ fn(err, bodyObject);
146
+ }
147
+ });
148
+ };
149
+
150
+
151
+ /// --- Direct Push
152
+ function addEvent(strEvent) {
153
+ foreachEvent(JSON.parse(strEvent));
154
+ }
155
+
156
+ };