particle-api-js 10.3.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.
- package/.eslintrc.js +23 -21
- package/CHANGELOG.md +8 -0
- package/EventStream-e2e-browser.html +7 -7
- package/EventStream-e2e-node.js +14 -14
- package/dist/particle.min.js +1 -1
- package/dist/particle.min.js.map +1 -1
- package/docs/api.md +3448 -875
- package/karma.conf.js +59 -59
- package/package.json +1 -1
- package/src/Agent.js +387 -363
- package/src/Client.js +162 -162
- package/src/Defaults.js +5 -5
- package/src/EventStream.js +254 -253
- package/src/Library.js +21 -21
- package/src/Particle.js +2685 -2649
- package/test/.eslintrc +5 -5
- package/test/Agent.integration.js +14 -14
- package/test/Agent.spec.js +495 -495
- package/test/Client.spec.js +203 -203
- package/test/Defaults.spec.js +20 -20
- package/test/EventStream.spec.js +231 -231
- package/test/FakeAgent.js +18 -18
- package/test/Library.spec.js +29 -29
- package/test/Particle.integration.js +29 -29
- package/test/Particle.spec.js +3127 -3127
- package/test/fixtures/index.js +7 -7
- package/test/support/FixtureHttpServer.js +16 -16
- package/test/test-setup.js +3 -3
- package/tsconfig.json +3 -1
- package/webpack.config.js +39 -39
package/test/EventStream.spec.js
CHANGED
|
@@ -5,255 +5,255 @@ const { EventEmitter } = require('events');
|
|
|
5
5
|
const EventStream = require('../src/EventStream');
|
|
6
6
|
|
|
7
7
|
describe('EventStream', () => {
|
|
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
|
-
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
sinon.restore();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function makeRequest() {
|
|
13
|
+
const fakeRequest = new EventEmitter();
|
|
14
|
+
fakeRequest.end = sinon.spy();
|
|
15
|
+
fakeRequest.setTimeout = sinon.spy();
|
|
16
|
+
return fakeRequest;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function makeResponse(statusCode) {
|
|
20
|
+
const fakeResponse = new EventEmitter();
|
|
21
|
+
fakeResponse.statusCode = statusCode;
|
|
22
|
+
return fakeResponse;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('constructor', () => {
|
|
26
|
+
it('creates an EventStream objects', () => {
|
|
27
|
+
const eventStream = new EventStream('uri', 'token');
|
|
28
|
+
|
|
29
|
+
expect(eventStream).to.own.include({ uri: 'uri', token: 'token' });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('connect', () => {
|
|
34
|
+
it('successfully connects to http', () => {
|
|
35
|
+
sinon.useFakeTimers({ shouldAdvanceTime: true });
|
|
36
|
+
const fakeRequest = makeRequest();
|
|
37
|
+
sinon.stub(http, 'request').callsFake(() => {
|
|
38
|
+
setImmediate(() => {
|
|
39
|
+
const fakeResponse = makeResponse(200);
|
|
40
|
+
fakeRequest.emit('response', fakeResponse);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return fakeRequest;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const eventStream = new EventStream('http://hostname:8080/path', 'token');
|
|
47
|
+
|
|
48
|
+
return eventStream.connect().then(() => {
|
|
49
|
+
expect(http.request).to.have.been.calledWith({
|
|
50
|
+
hostname: 'hostname',
|
|
51
|
+
protocol: 'http:',
|
|
52
|
+
path: '/path?access_token=token',
|
|
53
|
+
method: 'get',
|
|
54
|
+
port: 8080,
|
|
55
|
+
mode: 'prefer-streaming'
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns http errors on connect', () => {
|
|
61
|
+
sinon.useFakeTimers({ shouldAdvanceTime: true });
|
|
62
|
+
const fakeRequest = makeRequest();
|
|
63
|
+
sinon.stub(http, 'request').callsFake(() => {
|
|
64
|
+
setImmediate(() => {
|
|
65
|
+
const fakeResponse = makeResponse(500);
|
|
66
|
+
fakeRequest.emit('response', fakeResponse);
|
|
67
|
+
setImmediate(() => {
|
|
68
|
+
fakeResponse.emit('data', '{"error":"unknown"}');
|
|
69
|
+
fakeResponse.emit('end');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return fakeRequest;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const eventStream = new EventStream('http://hostname:8080/path', 'token');
|
|
77
|
+
|
|
78
|
+
return eventStream.connect().then(() => {
|
|
79
|
+
throw new Error('expected to throw error');
|
|
80
|
+
}, (reason) => {
|
|
81
|
+
expect(reason).to.eql({
|
|
82
|
+
statusCode: 500,
|
|
83
|
+
errorDescription: 'HTTP error 500 from http://hostname:8080/path',
|
|
84
|
+
body: {
|
|
85
|
+
error: 'unknown'
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('parse', () => {
|
|
93
|
+
let eventStream;
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
eventStream = new EventStream();
|
|
96
|
+
sinon.stub(eventStream, 'parseEventStreamLine');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('accumulates date into the buffer before parsing line', () => {
|
|
100
|
+
eventStream.parse('wo');
|
|
101
|
+
eventStream.parse('rd');
|
|
102
|
+
|
|
103
|
+
expect(eventStream.buf).to.eql('word');
|
|
104
|
+
expect(eventStream.parseEventStreamLine).not.to.have.been.called;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('parses a line ending with \\n', () => {
|
|
108
|
+
const line = 'field: value\n';
|
|
109
|
+
|
|
110
|
+
eventStream.parse(line);
|
|
111
|
+
|
|
112
|
+
expect(eventStream.parseEventStreamLine).to.have.been.calledWith(0, line.indexOf(':'), line.indexOf('\n'));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('parses 2 lines ending with \\n', () => {
|
|
116
|
+
const line1 = 'field: value\n';
|
|
117
|
+
const line2 = 'field2: value2\n';
|
|
118
|
+
|
|
119
|
+
eventStream.parse(line1 + line2);
|
|
120
|
+
|
|
121
|
+
expect(eventStream.parseEventStreamLine).to.have.been.calledWith(0, line1.indexOf(':'), line1.indexOf('\n'));
|
|
122
|
+
expect(eventStream.parseEventStreamLine).to.have.been.calledWith(line1.length, line2.indexOf(':'), line2.indexOf('\n'));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
it('parses a line ending with \\r\\n', () => {
|
|
127
|
+
const line = 'field: value\r\n';
|
|
128
|
+
|
|
129
|
+
eventStream.parse(line);
|
|
130
|
+
|
|
131
|
+
expect(eventStream.parseEventStreamLine).to.have.been.calledWith(0, line.indexOf(':'), line.indexOf('\r'));
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('parses 2 lines ending with \\r\\n', () => {
|
|
135
|
+
const line1 = 'field: value\r\n';
|
|
136
|
+
const line2 = 'field2: value2\r\n';
|
|
137
|
+
|
|
138
|
+
eventStream.parse(line1 + line2);
|
|
139
|
+
|
|
140
|
+
expect(eventStream.parseEventStreamLine).to.have.been.calledWith(0, line1.indexOf(':'), line1.indexOf('\r'));
|
|
141
|
+
expect(eventStream.parseEventStreamLine).to.have.been.calledWith(line1.length, line2.indexOf(':'), line2.indexOf('\r'));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('clears buffer after parsing full lines', () => {
|
|
145
|
+
eventStream.parse('field: value\n');
|
|
146
|
+
|
|
147
|
+
expect(eventStream.buf).to.eql('');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('keeps partial lines in buffer after parsing full lines', () => {
|
|
151
|
+
eventStream.parse('field: value\nfield2');
|
|
152
|
+
|
|
153
|
+
expect(eventStream.buf).to.eql('field2');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('parseEventStreamLine', () => {
|
|
158
|
+
let eventStream;
|
|
159
|
+
beforeEach(() => {
|
|
160
|
+
eventStream = new EventStream();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('ignores comments', () => {
|
|
164
|
+
// comments starts with : at column 0
|
|
165
|
+
const line = ':ok\n';
|
|
166
|
+
eventStream.buf = line;
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
eventStream.parseEventStreamLine(0, line.indexOf(':'), line.indexOf('\n'));
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
expect(eventStream.event).not.to.be.ok;
|
|
171
|
+
expect(eventStream.data).to.be.eql('');
|
|
172
|
+
});
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
it('saves event name', () => {
|
|
175
|
+
const line = 'event: testevent\n';
|
|
176
|
+
eventStream.buf = line;
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
eventStream.parseEventStreamLine(0, line.indexOf(':'), line.indexOf('\n'));
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
expect(eventStream.event).to.be.true;
|
|
181
|
+
expect(eventStream.eventName).to.eql('testevent');
|
|
182
|
+
});
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
it('saves event data', () => {
|
|
185
|
+
const line = 'data: {"data":"test"}\n';
|
|
186
|
+
eventStream.buf = line;
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
eventStream.parseEventStreamLine(0, line.indexOf(':'), line.indexOf('\n'));
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
expect(eventStream.data).to.eql('{"data":"test"}\n');
|
|
191
|
+
});
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
it('saves event name and data on separate lines', () => {
|
|
194
|
+
const lines = ['event: testevent\n', 'data: {"data":"test"}\n'];
|
|
195
|
+
for (let line of lines) {
|
|
196
|
+
eventStream.buf = line;
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
eventStream.parseEventStreamLine(0, line.indexOf(':'), line.indexOf('\n'));
|
|
199
|
+
}
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
expect(eventStream.event).to.be.true;
|
|
202
|
+
expect(eventStream.eventName).to.eql('testevent');
|
|
203
|
+
expect(eventStream.data).to.eql('{"data":"test"}\n');
|
|
204
|
+
});
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
206
|
+
it('emits event on blank line after saving event name and data', () => {
|
|
207
|
+
const handler = sinon.spy();
|
|
208
|
+
eventStream.event = true;
|
|
209
|
+
eventStream.eventName = 'testevent';
|
|
210
|
+
eventStream.data = '{"data":"test"}\n';
|
|
211
|
+
eventStream.on('event', handler);
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
eventStream.parseEventStreamLine(0, -1, 0);
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
215
|
+
expect(handler).to.have.been.calledWith({
|
|
216
|
+
name: 'testevent',
|
|
217
|
+
data: 'test'
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
221
|
+
it('emits error if the event handler crashes', () => {
|
|
222
|
+
const errorHandler = sinon.spy();
|
|
223
|
+
eventStream.event = true;
|
|
224
|
+
eventStream.eventName = 'testevent';
|
|
225
|
+
eventStream.data = '{"data":"test"}\n';
|
|
226
|
+
eventStream.on('error', errorHandler);
|
|
227
|
+
eventStream.on('event', () => {
|
|
228
|
+
throw new Error('failed!');
|
|
229
|
+
});
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
eventStream.parseEventStreamLine(0, -1, 0);
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
expect(errorHandler).to.have.been.called;
|
|
234
|
+
});
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
236
|
+
it('clears the event after emitting it', () => {
|
|
237
|
+
eventStream.event = true;
|
|
238
|
+
eventStream.eventName = 'testevent';
|
|
239
|
+
eventStream.data = '{"data":"test"}\n';
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
eventStream.parseEventStreamLine(0, -1, 0);
|
|
242
242
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
243
|
+
expect(eventStream.event).to.be.false;
|
|
244
|
+
expect(eventStream.eventName).to.be.undefined;
|
|
245
|
+
expect(eventStream.data).to.eql('');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('ignores multiple blank lines in succession', () => {
|
|
249
|
+
const handler = sinon.spy();
|
|
250
|
+
eventStream.on('event', handler);
|
|
251
|
+
|
|
252
|
+
eventStream.parseEventStreamLine(0, -1, 0);
|
|
253
|
+
eventStream.parseEventStreamLine(0, -1, 0);
|
|
254
|
+
eventStream.parseEventStreamLine(0, -1, 0);
|
|
255
|
+
|
|
256
|
+
expect(handler).not.to.have.been.called;
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
259
|
});
|
package/test/FakeAgent.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
class FakeAgent {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
get({ uri, auth, headers, query, context }){
|
|
3
|
+
return this.request({ uri, method: 'get', auth, headers, query, context });
|
|
4
|
+
}
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
head({ uri, auth, headers, query, context }){
|
|
7
|
+
return this.request({ uri, method: 'head', auth, headers, query, context });
|
|
8
|
+
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
post({ uri, headers, data, auth, context }){
|
|
11
|
+
return this.request({ uri, method: 'post', auth, headers, data, context });
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
put({ uri, auth, headers, data, context }){
|
|
15
|
+
return this.request({ uri, method: 'put', auth, headers, data, context });
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
delete({ uri, auth, headers, data, context }){
|
|
19
|
+
return this.request({ uri, method: 'delete', auth, headers, data, context });
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
request(opts){
|
|
23
|
+
return new Promise((resolve) => resolve(opts));
|
|
24
|
+
}
|
|
25
25
|
}
|
|
26
26
|
module.exports = FakeAgent;
|
package/test/Library.spec.js
CHANGED
|
@@ -5,35 +5,35 @@ let client = {};
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
describe('Library', () => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('sets attributes', () => {
|
|
10
|
+
const library = new Library(client, {
|
|
11
|
+
attributes: {
|
|
12
|
+
name: 'testlib',
|
|
13
|
+
version: '1.0.0'
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
expect(library.name).to.equal('testlib');
|
|
17
|
+
expect(library.version).to.equal('1.0.0');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
describe('download', () => {
|
|
22
|
+
it('return the file contents', () => {
|
|
23
|
+
client.downloadFile = (url) => {
|
|
24
|
+
return Promise.resolve(`${url}-content`);
|
|
25
|
+
};
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
const library = new Library(client, {
|
|
28
|
+
attributes: {
|
|
29
|
+
name: 'testlib',
|
|
30
|
+
version: '1.0.0'
|
|
31
|
+
},
|
|
32
|
+
links: {
|
|
33
|
+
download: 'url'
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
expect(library.download()).to.eventually.equal('url-content');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
39
|
});
|