particle-api-js 10.2.0 → 10.3.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.
@@ -5,259 +5,260 @@ const url = require('url');
5
5
  const { EventEmitter } = require('events');
6
6
 
7
7
  class EventStream extends EventEmitter {
8
- constructor(uri, token) {
9
- super();
10
- this.uri = uri;
11
- this.token = token;
12
- this.reconnectInterval = 2000;
13
- this.timeout = 13000; // keep alive can be sent up to 12 seconds after last event
14
- this.data = '';
15
- this.buf = '';
16
-
17
- this.parse = this.parse.bind(this);
18
- this.end = this.end.bind(this);
19
- this.idleTimeoutExpired = this.idleTimeoutExpired.bind(this);
20
- }
21
-
22
- connect() {
23
- return new Promise((resolve, reject) => {
24
- const { hostname, protocol, port, path } = url.parse(this.uri);
25
- this.origin = `${protocol}//${hostname}${port ? (':' + port) : ''}`;
26
-
27
- const isSecure = protocol === 'https:';
28
- const requestor = isSecure ? https : http;
29
- const req = requestor.request({
30
- hostname,
31
- protocol,
32
- path: `${path}?access_token=${this.token}`,
33
- method: 'get',
34
- port: parseInt(port, 10) || (isSecure ? 443 : 80),
35
- // @ts-ignore
36
- mode: 'prefer-streaming'
37
- });
38
-
39
- this.req = req;
40
-
41
- let connected = false;
42
- let connectionTimeout = setTimeout(() => {
43
- if (this.req) {
44
- this.req.abort();
45
- }
46
- reject({ error: new Error('Timeout'), errorDescription: `Timeout connecting to ${this.uri}` });
47
- }, this.timeout);
48
-
49
- req.on('error', e => {
50
- clearTimeout(connectionTimeout);
51
-
52
- if (connected) {
53
- this.end();
54
- } else {
55
- reject({ error: e, errorDescription: `Network error from ${this.uri}` });
56
- }
57
- });
58
-
59
- req.on('response', res => {
60
- clearTimeout(connectionTimeout);
61
-
62
- const statusCode = res.statusCode;
63
- if (statusCode !== 200) {
64
- let body = '';
65
- res.on('data', chunk => body += chunk);
66
- res.on('end', () => {
67
- try {
68
- body = JSON.parse(body);
69
- } catch (e) {
70
- // don't bother doing anything special if the JSON.parse fails
71
- // since we are already about to reject the promise anyway
72
- } finally {
73
- let errorDescription = `HTTP error ${statusCode} from ${this.uri}`;
74
- // @ts-ignore
75
- if (body && body.error_description) {
76
- // @ts-ignore
77
- errorDescription += ' - ' + body.error_description;
78
- }
79
- reject({ statusCode, errorDescription, body });
80
- this.req = undefined;
81
- }
82
- });
83
- return;
84
- }
85
-
86
- this.data = '';
87
- this.buf = '';
88
-
89
- connected = true;
90
- res.on('data', this.parse);
91
- res.once('end', this.end);
92
- this.startIdleTimeout();
93
- resolve(this);
94
- });
95
- req.end();
96
- });
97
- }
98
-
99
- abort() {
100
- if (this.req) {
101
- this.req.abort();
102
- this.req = undefined;
103
- }
104
- this.removeAllListeners();
105
- }
106
-
107
- /* Private methods */
108
-
109
- emitSafe(event, param) {
110
- try {
111
- this.emit(event, param);
112
- } catch (error) {
113
- if (event !== 'error') {
114
- this.emitSafe('error', error);
115
- }
116
- }
117
- }
118
-
119
- end() {
120
- this.stopIdleTimeout();
121
-
122
- if (!this.req) {
123
- // request was ended intentionally by abort
124
- // do not auto reconnect.
125
- return;
126
- }
127
-
128
- this.req = undefined;
129
- this.emitSafe('disconnect');
130
- this.reconnect();
131
- }
132
-
133
- reconnect() {
134
- setTimeout(() => {
135
- if (this.isOffline()) {
136
- this.reconnect();
137
- return;
138
- }
139
-
140
- this.emitSafe('reconnect');
141
- this.connect().then(() => {
142
- this.emitSafe('reconnect-success');
143
- }).catch(err => {
144
- this.emitSafe('reconnect-error', err);
145
- this.reconnect();
146
- });
147
- }, this.reconnectInterval);
148
- }
149
-
150
- isOffline() {
151
- if (typeof navigator === 'undefined' || navigator.hasOwnProperty('onLine')) {
152
- return false;
153
- }
154
- return !navigator.onLine;
155
- }
156
-
157
- startIdleTimeout() {
158
- this.stopIdleTimeout();
159
- this.idleTimeout = setTimeout(this.idleTimeoutExpired, this.timeout);
160
- }
161
-
162
- stopIdleTimeout() {
163
- if (this.idleTimeout) {
164
- clearTimeout(this.idleTimeout);
165
- this.idleTimeout = null;
166
- }
167
- }
168
-
169
- idleTimeoutExpired() {
170
- if (this.req) {
171
- this.req.abort();
172
- this.end();
173
- }
174
- }
175
-
176
- parse(chunk) {
177
- this.startIdleTimeout();
178
-
179
- this.buf += chunk;
180
- let pos = 0;
181
- let length = this.buf.length;
182
- let discardTrailingNewline = false;
183
-
184
- while (pos < length) {
185
- if (discardTrailingNewline) {
186
- if (this.buf[pos] === '\n') {
187
- ++pos;
188
- }
189
- discardTrailingNewline = false;
190
- }
191
-
192
- let lineLength = -1;
193
- let fieldLength = -1;
194
-
195
- for (let i = pos; lineLength < 0 && i < length; ++i) {
196
- const c = this.buf[i];
197
- if (c === ':') {
198
- if (fieldLength < 0) {
199
- fieldLength = i - pos;
200
- }
201
- } else if (c === '\r') {
202
- discardTrailingNewline = true;
203
- lineLength = i - pos;
204
- } else if (c === '\n') {
205
- lineLength = i - pos;
206
- }
207
- }
208
-
209
- if (lineLength < 0) {
210
- break;
211
- }
212
-
213
- this.parseEventStreamLine(pos, fieldLength, lineLength);
214
-
215
- pos += lineLength + 1;
216
- }
217
-
218
- if (pos === length) {
219
- this.buf = '';
220
- } else if (pos > 0) {
221
- this.buf = this.buf.slice(pos);
222
- }
223
- }
224
-
225
- parseEventStreamLine(pos, fieldLength, lineLength) {
226
- if (lineLength === 0) {
227
- try {
228
- if (this.data.length > 0 && this.event) {
229
- const event = JSON.parse(this.data);
230
- event.name = this.eventName || '';
231
- this.emitSafe('event', event);
232
- }
233
- } catch (e) {
234
- // do nothing if JSON.parse fails
235
- } finally {
236
- this.data = '';
237
- this.eventName = undefined;
238
- this.event = false;
239
- }
240
- } else if (fieldLength > 0) {
241
- const field = this.buf.slice(pos, pos + fieldLength);
242
- let step = 0;
243
-
244
- if (this.buf[pos + fieldLength + 1] !== ' ') {
245
- step = fieldLength + 1;
246
- } else {
247
- step = fieldLength + 2;
248
- }
249
- pos += step;
250
- const valueLength = lineLength - step;
251
- const value = this.buf.slice(pos, pos + valueLength);
252
-
253
- if (field === 'data') {
254
- this.data += value + '\n';
255
- } else if (field === 'event') {
256
- this.eventName = value;
257
- this.event = true;
258
- }
259
- }
260
- }
8
+ constructor(uri, token) {
9
+ super();
10
+ this.uri = uri;
11
+ this.token = token;
12
+ this.reconnectInterval = 2000;
13
+ this.timeout = 13000; // keep alive can be sent up to 12 seconds after last event
14
+ this.data = '';
15
+ this.buf = '';
16
+
17
+ this.parse = this.parse.bind(this);
18
+ this.end = this.end.bind(this);
19
+ this.idleTimeoutExpired = this.idleTimeoutExpired.bind(this);
20
+ }
21
+
22
+ connect() {
23
+ return new Promise((resolve, reject) => {
24
+ const { hostname, protocol, port, path } = url.parse(this.uri);
25
+ this.origin = `${protocol}//${hostname}${port ? (':' + port) : ''}`;
26
+
27
+ const isSecure = protocol === 'https:';
28
+ const requestor = isSecure ? https : http;
29
+ const req = requestor.request({
30
+ hostname,
31
+ protocol,
32
+ path: `${path}?access_token=${this.token}`,
33
+ method: 'get',
34
+ // @ts-ignore
35
+ port: parseInt(port, 10) || (isSecure ? 443 : 80),
36
+ // @ts-ignore
37
+ mode: 'prefer-streaming'
38
+ });
39
+
40
+ this.req = req;
41
+
42
+ let connected = false;
43
+ let connectionTimeout = setTimeout(() => {
44
+ if (this.req) {
45
+ this.req.abort();
46
+ }
47
+ reject({ error: new Error('Timeout'), errorDescription: `Timeout connecting to ${this.uri}` });
48
+ }, this.timeout);
49
+
50
+ req.on('error', e => {
51
+ clearTimeout(connectionTimeout);
52
+
53
+ if (connected) {
54
+ this.end();
55
+ } else {
56
+ reject({ error: e, errorDescription: `Network error from ${this.uri}` });
57
+ }
58
+ });
59
+
60
+ req.on('response', res => {
61
+ clearTimeout(connectionTimeout);
62
+
63
+ const statusCode = res.statusCode;
64
+ if (statusCode !== 200) {
65
+ let body = '';
66
+ res.on('data', chunk => body += chunk);
67
+ res.on('end', () => {
68
+ try {
69
+ body = JSON.parse(body);
70
+ } catch (e) {
71
+ // don't bother doing anything special if the JSON.parse fails
72
+ // since we are already about to reject the promise anyway
73
+ } finally {
74
+ let errorDescription = `HTTP error ${statusCode} from ${this.uri}`;
75
+ // @ts-ignore
76
+ if (body && body.error_description) {
77
+ // @ts-ignore
78
+ errorDescription += ' - ' + body.error_description;
79
+ }
80
+ reject({ statusCode, errorDescription, body });
81
+ this.req = undefined;
82
+ }
83
+ });
84
+ return;
85
+ }
86
+
87
+ this.data = '';
88
+ this.buf = '';
89
+
90
+ connected = true;
91
+ res.on('data', this.parse);
92
+ res.once('end', this.end);
93
+ this.startIdleTimeout();
94
+ resolve(this);
95
+ });
96
+ req.end();
97
+ });
98
+ }
99
+
100
+ abort() {
101
+ if (this.req) {
102
+ this.req.abort();
103
+ this.req = undefined;
104
+ }
105
+ this.removeAllListeners();
106
+ }
107
+
108
+ /* Private methods */
109
+
110
+ emitSafe(event, param) {
111
+ try {
112
+ this.emit(event, param);
113
+ } catch (error) {
114
+ if (event !== 'error') {
115
+ this.emitSafe('error', error);
116
+ }
117
+ }
118
+ }
119
+
120
+ end() {
121
+ this.stopIdleTimeout();
122
+
123
+ if (!this.req) {
124
+ // request was ended intentionally by abort
125
+ // do not auto reconnect.
126
+ return;
127
+ }
128
+
129
+ this.req = undefined;
130
+ this.emitSafe('disconnect');
131
+ this.reconnect();
132
+ }
133
+
134
+ reconnect() {
135
+ setTimeout(() => {
136
+ if (this.isOffline()) {
137
+ this.reconnect();
138
+ return;
139
+ }
140
+
141
+ this.emitSafe('reconnect');
142
+ this.connect().then(() => {
143
+ this.emitSafe('reconnect-success');
144
+ }).catch(err => {
145
+ this.emitSafe('reconnect-error', err);
146
+ this.reconnect();
147
+ });
148
+ }, this.reconnectInterval);
149
+ }
150
+
151
+ isOffline() {
152
+ if (typeof navigator === 'undefined' || navigator.hasOwnProperty('onLine')) {
153
+ return false;
154
+ }
155
+ return !navigator.onLine;
156
+ }
157
+
158
+ startIdleTimeout() {
159
+ this.stopIdleTimeout();
160
+ this.idleTimeout = setTimeout(this.idleTimeoutExpired, this.timeout);
161
+ }
162
+
163
+ stopIdleTimeout() {
164
+ if (this.idleTimeout) {
165
+ clearTimeout(this.idleTimeout);
166
+ this.idleTimeout = null;
167
+ }
168
+ }
169
+
170
+ idleTimeoutExpired() {
171
+ if (this.req) {
172
+ this.req.abort();
173
+ this.end();
174
+ }
175
+ }
176
+
177
+ parse(chunk) {
178
+ this.startIdleTimeout();
179
+
180
+ this.buf += chunk;
181
+ let pos = 0;
182
+ let length = this.buf.length;
183
+ let discardTrailingNewline = false;
184
+
185
+ while (pos < length) {
186
+ if (discardTrailingNewline) {
187
+ if (this.buf[pos] === '\n') {
188
+ ++pos;
189
+ }
190
+ discardTrailingNewline = false;
191
+ }
192
+
193
+ let lineLength = -1;
194
+ let fieldLength = -1;
195
+
196
+ for (let i = pos; lineLength < 0 && i < length; ++i) {
197
+ const c = this.buf[i];
198
+ if (c === ':') {
199
+ if (fieldLength < 0) {
200
+ fieldLength = i - pos;
201
+ }
202
+ } else if (c === '\r') {
203
+ discardTrailingNewline = true;
204
+ lineLength = i - pos;
205
+ } else if (c === '\n') {
206
+ lineLength = i - pos;
207
+ }
208
+ }
209
+
210
+ if (lineLength < 0) {
211
+ break;
212
+ }
213
+
214
+ this.parseEventStreamLine(pos, fieldLength, lineLength);
215
+
216
+ pos += lineLength + 1;
217
+ }
218
+
219
+ if (pos === length) {
220
+ this.buf = '';
221
+ } else if (pos > 0) {
222
+ this.buf = this.buf.slice(pos);
223
+ }
224
+ }
225
+
226
+ parseEventStreamLine(pos, fieldLength, lineLength) {
227
+ if (lineLength === 0) {
228
+ try {
229
+ if (this.data.length > 0 && this.event) {
230
+ const event = JSON.parse(this.data);
231
+ event.name = this.eventName || '';
232
+ this.emitSafe('event', event);
233
+ }
234
+ } catch (e) {
235
+ // do nothing if JSON.parse fails
236
+ } finally {
237
+ this.data = '';
238
+ this.eventName = undefined;
239
+ this.event = false;
240
+ }
241
+ } else if (fieldLength > 0) {
242
+ const field = this.buf.slice(pos, pos + fieldLength);
243
+ let step = 0;
244
+
245
+ if (this.buf[pos + fieldLength + 1] !== ' ') {
246
+ step = fieldLength + 1;
247
+ } else {
248
+ step = fieldLength + 2;
249
+ }
250
+ pos += step;
251
+ const valueLength = lineLength - step;
252
+ const value = this.buf.slice(pos, pos + valueLength);
253
+
254
+ if (field === 'data') {
255
+ this.data += value + '\n';
256
+ } else if (field === 'event') {
257
+ this.eventName = value;
258
+ this.event = true;
259
+ }
260
+ }
261
+ }
261
262
  }
262
263
 
263
264
  module.exports = EventStream;
package/src/Library.js CHANGED
@@ -3,31 +3,31 @@
3
3
  */
4
4
 
5
5
  class Library {
6
- constructor(client, data) {
7
- // Make client non-enumerable so it doesn't show up in Object.keys, JSON.stringify, etc
8
- Object.defineProperty(this, 'client', { value: client });
9
- this._assignAttributes(data);
10
- this.downloadUrl = data.links && data.links.download;
11
- }
6
+ constructor(client, data) {
7
+ // Make client non-enumerable so it doesn't show up in Object.keys, JSON.stringify, etc
8
+ Object.defineProperty(this, 'client', { value: client });
9
+ this._assignAttributes(data);
10
+ this.downloadUrl = data.links && data.links.download;
11
+ }
12
12
 
13
- _assignAttributes(data) {
14
- Object.assign(this, data.attributes);
15
- }
13
+ _assignAttributes(data) {
14
+ Object.assign(this, data.attributes);
15
+ }
16
16
 
17
17
 
18
- /**
19
- * Download the compressed file containing the source code for this library version.
20
- * @return {Promise} Resolves to the .tar.gz compressed source code
21
- */
22
- download() {
23
- if (!this.downloadUrl) {
24
- return Promise.reject(new Error('No download URL for this library'));
25
- }
26
- // @ts-ignore
27
- return this.client.downloadFile(this.downloadUrl);
28
- }
18
+ /**
19
+ * Download the compressed file containing the source code for this library version.
20
+ * @return {Promise} Resolves to the .tar.gz compressed source code
21
+ */
22
+ download() {
23
+ if (!this.downloadUrl) {
24
+ return Promise.reject(new Error('No download URL for this library'));
25
+ }
26
+ // @ts-ignore
27
+ return this.client.downloadFile(this.downloadUrl);
28
+ }
29
29
 
30
- /* TODO: add a versions() method to fetch an array of library objects */
30
+ /* TODO: add a versions() method to fetch an array of library objects */
31
31
  }
32
32
 
33
33
  module.exports = Library;