particle-api-js 9.4.1 → 10.1.0

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 (51) hide show
  1. package/.circleci/config.yml +7 -5
  2. package/CHANGELOG.md +11 -0
  3. package/{test/EventStream-e2e-browser.html → EventStream-e2e-browser.html} +0 -1
  4. package/{test/EventStream-e2e-node.js → EventStream-e2e-node.js} +2 -3
  5. package/README.md +2 -2
  6. package/RELEASE.md +1 -1
  7. package/dist/particle.min.js +1 -399
  8. package/dist/particle.min.js.map +1 -1
  9. package/docs/api.md +5223 -115
  10. package/fs.js +2 -0
  11. package/karma.conf.js +18 -6
  12. package/package.json +23 -26
  13. package/src/Agent.js +407 -0
  14. package/src/Client.js +170 -0
  15. package/src/Defaults.js +7 -0
  16. package/src/EventStream.js +263 -0
  17. package/src/Library.js +33 -0
  18. package/src/Particle.js +2644 -0
  19. package/test/Agent.integration.js +5 -4
  20. package/test/Agent.spec.js +174 -291
  21. package/test/Client.spec.js +7 -7
  22. package/test/Defaults.spec.js +2 -2
  23. package/test/EventStream.spec.js +6 -4
  24. package/test/FakeAgent.js +2 -2
  25. package/test/Library.spec.js +2 -2
  26. package/test/Particle.integration.js +7 -7
  27. package/test/Particle.spec.js +332 -18
  28. package/test/fixtures/index.js +4 -18
  29. package/test/support/FixtureHttpServer.js +5 -3
  30. package/test/test-setup.js +5 -5
  31. package/tsconfig.json +14 -0
  32. package/webpack.config.js +45 -0
  33. package/.babelrc +0 -4
  34. package/lib/Agent.js +0 -516
  35. package/lib/Agent.js.map +0 -1
  36. package/lib/Client.js +0 -312
  37. package/lib/Client.js.map +0 -1
  38. package/lib/Defaults.js +0 -14
  39. package/lib/Defaults.js.map +0 -1
  40. package/lib/EventStream.js +0 -335
  41. package/lib/EventStream.js.map +0 -1
  42. package/lib/Library.js +0 -67
  43. package/lib/Library.js.map +0 -1
  44. package/lib/Particle.js +0 -3248
  45. package/lib/Particle.js.map +0 -1
  46. package/lib/superagent-binary-parser.js +0 -20
  47. package/lib/superagent-binary-parser.js.map +0 -1
  48. package/test/Client.integration.js +0 -69
  49. package/test/fixtures/tarball.tar.gz +0 -0
  50. package/test/fixtures/test-library-publish-0.0.1.tar.gz +0 -0
  51. package/test/fixtures/test-library-publish-0.0.2.tar.gz +0 -0
package/src/Client.js ADDED
@@ -0,0 +1,170 @@
1
+ const Library = require('./Library');
2
+ let Particle;
3
+
4
+ class Client {
5
+ constructor({ auth, api = new Particle() }){
6
+ this.auth = auth;
7
+ this.api = api;
8
+ }
9
+
10
+ ready(){
11
+ return Boolean(this.auth);
12
+ }
13
+
14
+ /**
15
+ * Get firmware library objects
16
+ * @param {Object} query The query parameters for libraries. See Particle.listLibraries
17
+ * @returns {Promise} A promise
18
+ */
19
+ libraries(query = {}){
20
+ return this.api.listLibraries(Object.assign({}, query, { auth: this.auth }))
21
+ .then(payload => {
22
+ const libraries = payload.body.data || [];
23
+ return libraries.map(l => new Library(this, l));
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Get one firmware library object
29
+ * @param {String} name Name of the library to fetch
30
+ * @param {Object} query The query parameters for libraries. See Particle.getLibrary
31
+ * @returns {Promise} A promise
32
+ */
33
+ library(name, query = {}){
34
+ return this.api.getLibrary(Object.assign({}, query, { name, auth: this.auth }))
35
+ .then(payload => {
36
+ const library = payload.body.data || {};
37
+ return new Library(this, library);
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Get list of library versions
43
+ * @param {String} name Name of the library to fetch
44
+ * @param {Object} query The query parameters for versions. See Particle.getLibraryVersions
45
+ * @returns {Promise} A promise
46
+ */
47
+ libraryVersions(name, query = {}){
48
+ return this.api.getLibraryVersions(Object.assign({}, query, { name, auth: this.auth }))
49
+ .then(payload => {
50
+ const libraries = payload.body.data || [];
51
+ return libraries.map(l => new Library(this, l));
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Contribute a new library version
57
+ * @param {Buffer} archive The compressed archive with the library source
58
+ * @returns {Promise} A promise
59
+ */
60
+ contributeLibrary(archive){
61
+ return this.api.contributeLibrary({ archive, auth: this.auth })
62
+ .then(payload => {
63
+ const library = payload.body.data || {};
64
+ return new Library(this, library);
65
+ }, error => {
66
+ this._throwError(error);
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Make the the most recent private library version public
72
+ * @param {string} name The name of the library to publish
73
+ * @return {Promise} To publish the library
74
+ */
75
+ publishLibrary(name){
76
+ return this.api.publishLibrary({ name, auth: this.auth })
77
+ .then(payload => {
78
+ const library = payload.body.data || {};
79
+ return new Library(this, library);
80
+ }, error => {
81
+ this._throwError(error);
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Delete an entire published library
87
+ * @param {object} params Specific params of the library to delete
88
+ * @param {string} params.name Name of the library to delete
89
+ * @param {string} params.force Key to force deleting a public library
90
+ * @returns {Promise} A promise
91
+ */
92
+ deleteLibrary({ name, force }){
93
+ return this.api.deleteLibrary({ name, force, auth: this.auth })
94
+ .then(() => true, error => this._throwError(error));
95
+ }
96
+
97
+ _throwError(error){
98
+ if (error.body && error.body.errors){
99
+ const errorMessages = error.body.errors.map((e) => e.message).join('\n');
100
+ throw new Error(errorMessages);
101
+ }
102
+ throw error;
103
+ }
104
+
105
+ downloadFile(uri){
106
+ return this.api.downloadFile({ uri });
107
+ }
108
+
109
+ /**
110
+ * @param {Object} files Object containing files to be compiled
111
+ * @param {Number} platformId Platform id number of the device you are compiling for
112
+ * @param {String} targetVersion System firmware version to compile against
113
+ * @returns {Promise} A promise
114
+ * @deprecated Will be removed in 6.5
115
+ */
116
+ compileCode(files, platformId, targetVersion){
117
+ return this.api.compileCode({ files, platformId, targetVersion, auth: this.auth });
118
+ }
119
+
120
+ /**
121
+ * @param {object} params
122
+ * @param {string} params.deviceId Device ID or Name
123
+ * @param {boolean} params.signal Signal on or off
124
+ * @returns {Promise} A promise
125
+ * @deprecated Will be removed in 6.5
126
+ */
127
+ signalDevice({ signal, deviceId }){
128
+ return this.api.signalDevice({ signal, deviceId, auth: this.auth });
129
+ }
130
+
131
+ /**
132
+ * @returns {Promise} A promise
133
+ * @deprecated Will be removed in 6.5
134
+ */
135
+ listDevices(){
136
+ return this.api.listDevices({ auth: this.auth });
137
+ }
138
+
139
+ /**
140
+ * @returns {Promise} A promise
141
+ * @deprecated Will be removed in 6.5
142
+ */
143
+ listBuildTargets(){
144
+ return this.api.listBuildTargets({ onlyFeatured: true, auth: this.auth })
145
+ .then(payload => {
146
+ let targets = [];
147
+ for (let target of payload.body.targets){
148
+ for (let platform of target.platforms){
149
+ targets.push({
150
+ version: target.version,
151
+ platform: platform,
152
+ prerelease: target.prereleases.indexOf(platform) > -1,
153
+ firmware_vendor: target.firmware_vendor
154
+ });
155
+ }
156
+ }
157
+ return targets;
158
+ }, () => {});
159
+ }
160
+
161
+ trackingIdentity({ full = false, context = undefined }={}){
162
+ return this.api.trackingIdentity({ full, context, auth: this.auth })
163
+ .then(payload => {
164
+ return payload.body;
165
+ });
166
+ }
167
+ }
168
+
169
+ module.exports = Client;
170
+ Particle = require('./Particle'); // Move it to after the export to avoid issue with circular reference
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ baseUrl: 'https://api.particle.io',
3
+ clientSecret: 'particle-api',
4
+ clientId: 'particle-api',
5
+ tokenDuration: 7776000, // 90 days
6
+ auth: undefined
7
+ };
@@ -0,0 +1,263 @@
1
+ /* eslint max-depth: 0 */
2
+ const http = require('http');
3
+ const https = require('https');
4
+ const url = require('url');
5
+ const { EventEmitter } = require('events');
6
+
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
+ }
261
+ }
262
+
263
+ module.exports = EventStream;
package/src/Library.js ADDED
@@ -0,0 +1,33 @@
1
+ /* Library
2
+ * Represents a version of a library contributed in the cloud.
3
+ */
4
+
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
+ }
12
+
13
+ _assignAttributes(data) {
14
+ Object.assign(this, data.attributes);
15
+ }
16
+
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
+ }
29
+
30
+ /* TODO: add a versions() method to fetch an array of library objects */
31
+ }
32
+
33
+ module.exports = Library;