@splitsoftware/splitio 10.26.1-rc.0 → 10.26.1-rc.2

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.
@@ -0,0 +1,519 @@
1
+ /* eslint-disable no-prototype-builtins */
2
+ /* eslint-disable no-restricted-syntax */
3
+ /*
4
+ Modified version of "eventsource" v1.1.2 package (https://www.npmjs.com/package/eventsource/v/1.1.2)
5
+ that accepts a custom agent.
6
+
7
+ The MIT License
8
+
9
+ Copyright (c) EventSource GitHub organisation
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining
12
+ a copy of this software and associated documentation files (the
13
+ "Software"), to deal in the Software without restriction, including
14
+ without limitation the rights to use, copy, modify, merge, publish,
15
+ distribute, sublicense, and/or sell copies of the Software, and to
16
+ permit persons to whom the Software is furnished to do so, subject to
17
+ the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be
20
+ included in all copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
+ */
30
+ var parse = require('url').parse;
31
+ var events = require('events');
32
+ var https = require('https');
33
+ var http = require('http');
34
+ var util = require('util');
35
+
36
+ var httpsOptions = [
37
+ 'pfx', 'key', 'passphrase', 'cert', 'ca', 'ciphers',
38
+ 'rejectUnauthorized', 'secureProtocol', 'servername', 'checkServerIdentity'
39
+ ];
40
+
41
+ var bom = [239, 187, 191];
42
+ var colon = 58;
43
+ var space = 32;
44
+ var lineFeed = 10;
45
+ var carriageReturn = 13;
46
+
47
+ function hasBom(buf) {
48
+ return bom.every(function (charCode, index) {
49
+ return buf[index] === charCode;
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Creates a new EventSource object
55
+ *
56
+ * @param {String} url the URL to which to connect
57
+ * @param {Object} [eventSourceInitDict] extra init params. See README for details.
58
+ * @api public
59
+ **/
60
+ function EventSource(url, eventSourceInitDict) {
61
+ var readyState = EventSource.CONNECTING;
62
+ var headers = eventSourceInitDict && eventSourceInitDict.headers;
63
+ var hasNewOrigin = false;
64
+ Object.defineProperty(this, 'readyState', {
65
+ get: function () {
66
+ return readyState;
67
+ }
68
+ });
69
+
70
+ Object.defineProperty(this, 'url', {
71
+ get: function () {
72
+ return url;
73
+ }
74
+ });
75
+
76
+ var self = this;
77
+ self.reconnectInterval = 1000;
78
+ self.connectionInProgress = false;
79
+
80
+ var reconnectUrl = null;
81
+
82
+ function onConnectionClosed(message) {
83
+ if (readyState === EventSource.CLOSED) return;
84
+ readyState = EventSource.CONNECTING;
85
+ _emit('error', new Event('error', {message: message}));
86
+
87
+ // The url may have been changed by a temporary redirect. If that's the case,
88
+ // revert it now, and flag that we are no longer pointing to a new origin
89
+ if (reconnectUrl) {
90
+ url = reconnectUrl;
91
+ reconnectUrl = null;
92
+ hasNewOrigin = false;
93
+ }
94
+ setTimeout(function () {
95
+ if (readyState !== EventSource.CONNECTING || self.connectionInProgress) {
96
+ return;
97
+ }
98
+ self.connectionInProgress = true;
99
+ connect();
100
+ }, self.reconnectInterval);
101
+ }
102
+
103
+ var req;
104
+ var lastEventId = '';
105
+ if (headers && headers['Last-Event-ID']) {
106
+ lastEventId = headers['Last-Event-ID'];
107
+ delete headers['Last-Event-ID'];
108
+ }
109
+
110
+ var discardTrailingNewline = false;
111
+ var data = '';
112
+ var eventName = '';
113
+
114
+ function connect() {
115
+ var options = parse(url);
116
+ var isSecure = options.protocol === 'https:';
117
+ options.headers = { 'Cache-Control': 'no-cache', 'Accept': 'text/event-stream' };
118
+ if (lastEventId) options.headers['Last-Event-ID'] = lastEventId;
119
+ if (headers) {
120
+ var reqHeaders = hasNewOrigin ? removeUnsafeHeaders(headers) : headers;
121
+ for (var i in reqHeaders) {
122
+ var header = reqHeaders[i];
123
+ if (header) {
124
+ options.headers[i] = header;
125
+ }
126
+ }
127
+ }
128
+
129
+ // Legacy: this should be specified as `eventSourceInitDict.https.rejectUnauthorized`,
130
+ // but for now exists as a backwards-compatibility layer
131
+ options.rejectUnauthorized = !(eventSourceInitDict && !eventSourceInitDict.rejectUnauthorized);
132
+
133
+ if (eventSourceInitDict && eventSourceInitDict.createConnection !== undefined) {
134
+ options.createConnection = eventSourceInitDict.createConnection;
135
+ }
136
+
137
+ // If specify agent, use it.
138
+ if (eventSourceInitDict && eventSourceInitDict.agent !== undefined) {
139
+ options.agent = eventSourceInitDict.agent;
140
+ }
141
+
142
+ // If specify http proxy, make the request to sent to the proxy server,
143
+ // and include the original url in path and Host headers
144
+ var useProxy = eventSourceInitDict && eventSourceInitDict.proxy;
145
+ if (useProxy) {
146
+ var proxy = parse(eventSourceInitDict.proxy);
147
+ isSecure = proxy.protocol === 'https:';
148
+
149
+ options.protocol = isSecure ? 'https:' : 'http:';
150
+ options.path = url;
151
+ options.headers.Host = options.host;
152
+ options.hostname = proxy.hostname;
153
+ options.host = proxy.host;
154
+ options.port = proxy.port;
155
+ }
156
+
157
+ // If https options are specified, merge them into the request options
158
+ if (eventSourceInitDict && eventSourceInitDict.https) {
159
+ for (var optName in eventSourceInitDict.https) {
160
+ if (httpsOptions.indexOf(optName) === -1) {
161
+ continue;
162
+ }
163
+
164
+ var option = eventSourceInitDict.https[optName];
165
+ if (option !== undefined) {
166
+ options[optName] = option;
167
+ }
168
+ }
169
+ }
170
+
171
+ // Pass this on to the XHR
172
+ if (eventSourceInitDict && eventSourceInitDict.withCredentials !== undefined) {
173
+ options.withCredentials = eventSourceInitDict.withCredentials;
174
+ }
175
+
176
+ req = (isSecure ? https : http).request(options, function (res) {
177
+ self.connectionInProgress = false;
178
+ // Handle HTTP errors
179
+ if (res.statusCode === 500 || res.statusCode === 502 || res.statusCode === 503 || res.statusCode === 504) {
180
+ _emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage}));
181
+ onConnectionClosed();
182
+ return;
183
+ }
184
+
185
+ // Handle HTTP redirects
186
+ if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
187
+ var location = res.headers.location;
188
+ if (!location) {
189
+ // Server sent redirect response without Location header.
190
+ _emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage}));
191
+ return;
192
+ }
193
+ var prevOrigin = getOrigin(url);
194
+ var nextOrigin = getOrigin(location);
195
+ hasNewOrigin = prevOrigin !== nextOrigin;
196
+ if (res.statusCode === 307) reconnectUrl = url;
197
+ url = location;
198
+ process.nextTick(connect);
199
+ return;
200
+ }
201
+
202
+ if (res.statusCode !== 200) {
203
+ _emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage}));
204
+ return self.close();
205
+ }
206
+
207
+ readyState = EventSource.OPEN;
208
+ res.on('close', function () {
209
+ res.removeAllListeners('close');
210
+ res.removeAllListeners('end');
211
+ onConnectionClosed();
212
+ });
213
+
214
+ res.on('end', function () {
215
+ res.removeAllListeners('close');
216
+ res.removeAllListeners('end');
217
+ onConnectionClosed();
218
+ });
219
+ _emit('open', new Event('open'));
220
+
221
+ // text/event-stream parser adapted from webkit's
222
+ // Source/WebCore/page/EventSource.cpp
223
+ var isFirst = true;
224
+ var buf;
225
+ var startingPos = 0;
226
+ var startingFieldLength = -1;
227
+ res.on('data', function (chunk) {
228
+ buf = buf ? Buffer.concat([buf, chunk]) : chunk;
229
+ if (isFirst && hasBom(buf)) {
230
+ buf = buf.slice(bom.length);
231
+ }
232
+
233
+ isFirst = false;
234
+ var pos = 0;
235
+ var length = buf.length;
236
+
237
+ while (pos < length) {
238
+ if (discardTrailingNewline) {
239
+ if (buf[pos] === lineFeed) {
240
+ ++pos;
241
+ }
242
+ discardTrailingNewline = false;
243
+ }
244
+
245
+ var lineLength = -1;
246
+ var fieldLength = startingFieldLength;
247
+ var c;
248
+
249
+ for (var i = startingPos; lineLength < 0 && i < length; ++i) {
250
+ c = buf[i];
251
+ if (c === colon) {
252
+ if (fieldLength < 0) {
253
+ fieldLength = i - pos;
254
+ }
255
+ } else if (c === carriageReturn) {
256
+ discardTrailingNewline = true;
257
+ lineLength = i - pos;
258
+ } else if (c === lineFeed) {
259
+ lineLength = i - pos;
260
+ }
261
+ }
262
+
263
+ if (lineLength < 0) {
264
+ startingPos = length - pos;
265
+ startingFieldLength = fieldLength;
266
+ break;
267
+ } else {
268
+ startingPos = 0;
269
+ startingFieldLength = -1;
270
+ }
271
+
272
+ parseEventStreamLine(buf, pos, fieldLength, lineLength);
273
+
274
+ pos += lineLength + 1;
275
+ }
276
+
277
+ if (pos === length) {
278
+ buf = void 0;
279
+ } else if (pos > 0) {
280
+ buf = buf.slice(pos);
281
+ }
282
+ });
283
+ });
284
+
285
+ req.on('error', function (err) {
286
+ self.connectionInProgress = false;
287
+ onConnectionClosed(err.message);
288
+ });
289
+
290
+ if (req.setNoDelay) req.setNoDelay(true);
291
+ req.end();
292
+ }
293
+
294
+ connect();
295
+
296
+ function _emit() {
297
+ if (self.listeners(arguments[0]).length > 0) {
298
+ self.emit.apply(self, arguments);
299
+ }
300
+ }
301
+
302
+ this._close = function () {
303
+ if (readyState === EventSource.CLOSED) return;
304
+ readyState = EventSource.CLOSED;
305
+ if (req.abort) req.abort();
306
+ if (req.xhr && req.xhr.abort) req.xhr.abort();
307
+ };
308
+
309
+ function parseEventStreamLine(buf, pos, fieldLength, lineLength) {
310
+ if (lineLength === 0) {
311
+ if (data.length > 0) {
312
+ var type = eventName || 'message';
313
+ _emit(type, new MessageEvent(type, {
314
+ data: data.slice(0, -1), // remove trailing newline
315
+ lastEventId: lastEventId,
316
+ origin: getOrigin(url)
317
+ }));
318
+ data = '';
319
+ }
320
+ eventName = void 0;
321
+ } else if (fieldLength > 0) {
322
+ var noValue = fieldLength < 0;
323
+ var step = 0;
324
+ var field = buf.slice(pos, pos + (noValue ? lineLength : fieldLength)).toString();
325
+
326
+ if (noValue) {
327
+ step = lineLength;
328
+ } else if (buf[pos + fieldLength + 1] !== space) {
329
+ step = fieldLength + 1;
330
+ } else {
331
+ step = fieldLength + 2;
332
+ }
333
+ pos += step;
334
+
335
+ var valueLength = lineLength - step;
336
+ var value = buf.slice(pos, pos + valueLength).toString();
337
+
338
+ if (field === 'data') {
339
+ data += value + '\n';
340
+ } else if (field === 'event') {
341
+ eventName = value;
342
+ } else if (field === 'id') {
343
+ lastEventId = value;
344
+ } else if (field === 'retry') {
345
+ var retry = parseInt(value, 10);
346
+ if (!Number.isNaN(retry)) {
347
+ self.reconnectInterval = retry;
348
+ }
349
+ }
350
+ }
351
+ }
352
+ }
353
+
354
+ module.exports = EventSource;
355
+
356
+ util.inherits(EventSource, events.EventEmitter);
357
+ EventSource.prototype.constructor = EventSource; // make stacktraces readable
358
+
359
+ ['open', 'error', 'message'].forEach(function (method) {
360
+ Object.defineProperty(EventSource.prototype, 'on' + method, {
361
+ /**
362
+ * Returns the current listener
363
+ *
364
+ * @return {Mixed} the set function or undefined
365
+ * @api private
366
+ */
367
+ get: function get() {
368
+ var listener = this.listeners(method)[0];
369
+ return listener ? (listener._listener ? listener._listener : listener) : undefined;
370
+ },
371
+
372
+ /**
373
+ * Start listening for events
374
+ *
375
+ * @param {Function} listener the listener
376
+ * @return {Mixed} the set function or undefined
377
+ * @api private
378
+ */
379
+ set: function set(listener) {
380
+ this.removeAllListeners(method);
381
+ this.addEventListener(method, listener);
382
+ }
383
+ });
384
+ });
385
+
386
+ /**
387
+ * Ready states
388
+ */
389
+ Object.defineProperty(EventSource, 'CONNECTING', {enumerable: true, value: 0});
390
+ Object.defineProperty(EventSource, 'OPEN', {enumerable: true, value: 1});
391
+ Object.defineProperty(EventSource, 'CLOSED', {enumerable: true, value: 2});
392
+
393
+ EventSource.prototype.CONNECTING = 0;
394
+ EventSource.prototype.OPEN = 1;
395
+ EventSource.prototype.CLOSED = 2;
396
+
397
+ /**
398
+ * Closes the connection, if one is made, and sets the readyState attribute to 2 (closed)
399
+ *
400
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/close
401
+ * @api public
402
+ */
403
+ EventSource.prototype.close = function () {
404
+ this._close();
405
+ };
406
+
407
+ /**
408
+ * Emulates the W3C Browser based WebSocket interface using addEventListener.
409
+ *
410
+ * @param {String} type A string representing the event type to listen out for
411
+ * @param {Function} listener callback
412
+ * @see https://developer.mozilla.org/en/DOM/element.addEventListener
413
+ * @see http://dev.w3.org/html5/websockets/#the-websocket-interface
414
+ * @api public
415
+ */
416
+ EventSource.prototype.addEventListener = function addEventListener(type, listener) {
417
+ if (typeof listener === 'function') {
418
+ // store a reference so we can return the original function again
419
+ listener._listener = listener;
420
+ this.on(type, listener);
421
+ }
422
+ };
423
+
424
+ /**
425
+ * Emulates the W3C Browser based WebSocket interface using dispatchEvent.
426
+ *
427
+ * @param {Event} event An event to be dispatched
428
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
429
+ * @api public
430
+ */
431
+ EventSource.prototype.dispatchEvent = function dispatchEvent(event) {
432
+ if (!event.type) {
433
+ throw new Error('UNSPECIFIED_EVENT_TYPE_ERR');
434
+ }
435
+ // if event is instance of an CustomEvent (or has 'details' property),
436
+ // send the detail object as the payload for the event
437
+ this.emit(event.type, event.detail);
438
+ };
439
+
440
+ /**
441
+ * Emulates the W3C Browser based WebSocket interface using removeEventListener.
442
+ *
443
+ * @param {String} type A string representing the event type to remove
444
+ * @param {Function} listener callback
445
+ * @see https://developer.mozilla.org/en/DOM/element.removeEventListener
446
+ * @see http://dev.w3.org/html5/websockets/#the-websocket-interface
447
+ * @api public
448
+ */
449
+ EventSource.prototype.removeEventListener = function removeEventListener(type, listener) {
450
+ if (typeof listener === 'function') {
451
+ listener._listener = undefined;
452
+ this.removeListener(type, listener);
453
+ }
454
+ };
455
+
456
+ /**
457
+ * W3C Event
458
+ *
459
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/#interface-Event
460
+ * @api private
461
+ */
462
+ function Event(type, optionalProperties) {
463
+ Object.defineProperty(this, 'type', { writable: false, value: type, enumerable: true });
464
+ if (optionalProperties) {
465
+ for (var f in optionalProperties) {
466
+ if (optionalProperties.hasOwnProperty(f)) {
467
+ Object.defineProperty(this, f, { writable: false, value: optionalProperties[f], enumerable: true });
468
+ }
469
+ }
470
+ }
471
+ }
472
+
473
+ /**
474
+ * W3C MessageEvent
475
+ *
476
+ * @see http://www.w3.org/TR/webmessaging/#event-definitions
477
+ * @api private
478
+ */
479
+ function MessageEvent(type, eventInitDict) {
480
+ Object.defineProperty(this, 'type', { writable: false, value: type, enumerable: true });
481
+ for (var f in eventInitDict) {
482
+ if (eventInitDict.hasOwnProperty(f)) {
483
+ Object.defineProperty(this, f, { writable: false, value: eventInitDict[f], enumerable: true });
484
+ }
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Returns a new object of headers that does not include any authorization and cookie headers
490
+ *
491
+ * @param {Object} headers An object of headers ({[headerName]: headerValue})
492
+ * @return {Object} a new object of headers
493
+ * @api private
494
+ */
495
+ function removeUnsafeHeaders(headers) {
496
+ var safe = {};
497
+ for (var key in headers) {
498
+ if (/^(cookie|authorization)$/i.test(key)) {
499
+ continue;
500
+ }
501
+
502
+ safe[key] = headers[key];
503
+ }
504
+
505
+ return safe;
506
+ }
507
+
508
+ /**
509
+ * Transform an URL to a valid origin value.
510
+ *
511
+ * @param {String|Object} url URL to transform to it's origin.
512
+ * @returns {String} The origin.
513
+ * @api private
514
+ */
515
+ function getOrigin(url) {
516
+ if (typeof url === 'string') url = parse(url);
517
+ if (!url.protocol || !url.hostname) return 'null';
518
+ return (url.protocol + '//' + url.host).toLowerCase();
519
+ }
@@ -13,7 +13,7 @@ export function __restore() {
13
13
  export function getEventSource() {
14
14
  // returns EventSource at `eventsource` package. If not available, return global EventSource or undefined
15
15
  try {
16
- return __isCustom ? __eventSource : require('eventsource');
16
+ return __isCustom ? __eventSource : require('./eventsource');
17
17
  } catch (error) {
18
18
  return typeof EventSource === 'function' ? EventSource : undefined;
19
19
  }
@@ -1,14 +1,3 @@
1
- /* eslint-disable compat/compat */
2
- import https from 'https';
3
-
4
- // @TODO
5
- // 1- handle multiple protocols automatically
6
- // 2- destroy it once the sdk is destroyed
7
- const agent = new https.Agent({
8
- keepAlive: true,
9
- keepAliveMsecs: 1500
10
- });
11
-
12
1
  let nodeFetch;
13
2
 
14
3
  try {
@@ -29,12 +18,7 @@ export function __setFetch(fetch) {
29
18
 
30
19
  /**
31
20
  * Retrieves 'node-fetch', a Fetch API polyfill for NodeJS, with fallback to global 'fetch' if available.
32
- * It passes an https agent with keepAlive enabled if URL is https.
33
21
  */
34
22
  export function getFetch() {
35
- if (nodeFetch) {
36
- return (url, options) => {
37
- return nodeFetch(url, Object.assign({ agent: url.startsWith('https:') ? agent : undefined }, options));
38
- };
39
- }
23
+ return nodeFetch;
40
24
  }
@@ -0,0 +1,20 @@
1
+ // @TODO
2
+ // 1- handle multiple protocols automatically
3
+ // 2- destroy it once the sdk is destroyed
4
+ import https from 'https';
5
+
6
+ import { find } from '@splitsoftware/splitio-commons/src/utils/lang';
7
+
8
+ const agent = new https.Agent({
9
+ keepAlive: true,
10
+ keepAliveMsecs: 1500
11
+ });
12
+
13
+ export function getOptions(settings) {
14
+ // If some URL is not HTTPS, we don't use the agent, to let the SDK connect to HTTP endpoints
15
+ if (find(settings.urls, url => !url.startsWith('https:'))) return;
16
+
17
+ return {
18
+ agent
19
+ };
20
+ }
@@ -1,12 +1,14 @@
1
1
  import EventEmitter from 'events';
2
2
  import { getFetch } from '../platform/getFetch/node';
3
3
  import { getEventSource } from '../platform/getEventSource/node';
4
+ import { getOptions } from '../platform/getOptions/node';
4
5
  import { NodeSignalListener } from '@splitsoftware/splitio-commons/src/listeners/node';
5
6
  import { now } from '@splitsoftware/splitio-commons/src/utils/timeTracker/now/node';
6
7
 
7
8
  export const platform = {
8
9
  getFetch,
9
10
  getEventSource,
11
+ getOptions,
10
12
  EventEmitter,
11
13
  now
12
14
  };
@@ -1 +1 @@
1
- export const packageVersion = '10.26.1-rc.0';
1
+ export const packageVersion = '10.26.1-rc.2';