diff --git a/README.md b/README.md index 6503ee39..39cfc32e 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The LaunchDarkly SDK can be used in all major browsers. However, web browsers va ### Logging -By default, the SDK uses the `winston` package. There are four logging levels: `debug`, `info`, `warn`, and `error`; by default, `debug` and `info` messages are hidden. See [`LDOptions.logger`](https://launchdarkly.github.io/js-client-sdk/interfaces/_ldclient_js_.ldoptions.html#logger) and [`createConsoleLogger`](https://launchdarkly.github.io/js-client-sdk/index.html#createconsolelogger)` for more details. +By default, the SDK uses the `winston` package. There are four logging levels: `debug`, `info`, `warn`, and `error`; by default, `debug` and `info` messages are hidden. See [`LDOptions.logger`](https://launchdarkly.github.io/js-client-sdk/interfaces/_launchdarkly_js_client_sdk_.ldoptions.html#logger) and [`createConsoleLogger`](https://launchdarkly.github.io/js-client-sdk/index.html#createconsolelogger) for more details. ## Learn more diff --git a/packages/launchdarkly-js-sdk-common/src/EventProcessor.js b/packages/launchdarkly-js-sdk-common/src/EventProcessor.js index 08d95c92..43e28b7a 100644 --- a/packages/launchdarkly-js-sdk-common/src/EventProcessor.js +++ b/packages/launchdarkly-js-sdk-common/src/EventProcessor.js @@ -5,7 +5,7 @@ import * as errors from './errors'; import * as messages from './messages'; import * as utils from './utils'; -export default function EventProcessor(platform, options, environmentId, logger, emitter = null, sender = null) { +export default function EventProcessor(platform, options, environmentId, emitter = null, sender = null) { const processor = {}; const eventSender = sender || EventSender(platform, options.eventsUrl, environmentId); const summarizer = EventSummarizer(); @@ -13,6 +13,7 @@ export default function EventProcessor(platform, options, environmentId, logger, const inlineUsers = options.inlineUsersInEvents; const samplingInterval = options.samplingInterval; const flushInterval = options.flushInterval; + const logger = options.logger; let queue = []; let lastKnownPastTime = 0; let disabled = false; diff --git a/packages/launchdarkly-js-sdk-common/src/Requestor.js b/packages/launchdarkly-js-sdk-common/src/Requestor.js index 159f846d..16462425 100644 --- a/packages/launchdarkly-js-sdk-common/src/Requestor.js +++ b/packages/launchdarkly-js-sdk-common/src/Requestor.js @@ -13,11 +13,12 @@ function getResponseError(result) { } } -export default function Requestor(platform, options, environment, logger) { +export default function Requestor(platform, options, environment) { const baseUrl = options.baseUrl; const useReport = options.useReport; const withReasons = options.evaluationReasons; const sendLDHeaders = options.sendLDHeaders; + const logger = options.logger; const requestor = {}; diff --git a/packages/launchdarkly-js-sdk-common/src/Stream.js b/packages/launchdarkly-js-sdk-common/src/Stream.js index 711f9246..7af01040 100644 --- a/packages/launchdarkly-js-sdk-common/src/Stream.js +++ b/packages/launchdarkly-js-sdk-common/src/Stream.js @@ -1,3 +1,4 @@ +import * as messages from './messages'; import { base64URLEncode } from './utils'; // The underlying event source implementation is abstracted via the platform object, which should @@ -11,6 +12,7 @@ import { base64URLEncode } from './utils'; export default function Stream(platform, config, environment, hash) { const baseUrl = config.streamUrl; + const logger = config.logger; const stream = {}; const evalUrlPrefix = baseUrl + '/eval/' + environment; const useReport = config.useReport; @@ -34,10 +36,11 @@ export default function Stream(platform, config, environment, hash) { }; stream.isConnected = function() { - return es && platform.eventSourceIsActive && platform.eventSourceIsActive(es); + return !!(es && platform.eventSourceIsActive && platform.eventSourceIsActive(es)); }; - function reconnect() { + function handleError(err) { + logger.warn(messages.streamError(err)); closeConnection(); tryConnect(streamReconnectDelay); } @@ -53,6 +56,7 @@ export default function Stream(platform, config, environment, hash) { } function openConnection() { + reconnectTimeoutReference = null; let url; let query = ''; const options = {}; @@ -80,6 +84,7 @@ export default function Stream(platform, config, environment, hash) { url = url + (query ? '?' : '') + query; closeConnection(); + logger.info(messages.streamConnecting(url)); es = platform.eventSourceFactory(url, options); for (const key in handlers) { if (handlers.hasOwnProperty(key)) { @@ -87,12 +92,13 @@ export default function Stream(platform, config, environment, hash) { } } - es.onerror = reconnect; + es.onerror = handleError; } } function closeConnection() { if (es) { + logger.info(messages.streamClosing()); es.close(); es = null; } diff --git a/packages/launchdarkly-js-sdk-common/src/__tests__/EventProcessor-test.js b/packages/launchdarkly-js-sdk-common/src/__tests__/EventProcessor-test.js index 70f4924e..f0fa254c 100644 --- a/packages/launchdarkly-js-sdk-common/src/__tests__/EventProcessor-test.js +++ b/packages/launchdarkly-js-sdk-common/src/__tests__/EventProcessor-test.js @@ -10,13 +10,14 @@ describe('EventProcessor', () => { const filteredUser = { key: 'userKey', privateAttrs: ['name'] }; const eventsUrl = '/fake-url'; const envId = 'env'; + const logger = stubPlatform.logger(); const defaultConfig = { eventsUrl: eventsUrl, flushInterval: 2000, samplingInterval: 0, + logger: logger, }; const platform = stubPlatform.defaults(); - const logger = stubPlatform.logger(); mockEventSender.sendEvents = function(events, sync) { mockEventSender.calls.push({ @@ -68,7 +69,7 @@ describe('EventProcessor', () => { } it('should enqueue identify event', async () => { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); const event = { kind: 'identify', creationDate: 1000, key: user.key, user: user }; ep.enqueue(event); await ep.flush(); @@ -79,7 +80,7 @@ describe('EventProcessor', () => { it('filters user in identify event', async () => { const config = Object.assign({}, defaultConfig, { allAttributesPrivate: true }); - const ep = EventProcessor(platform, config, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, config, envId, null, mockEventSender); const event = { kind: 'identify', creationDate: 1000, key: user.key, user: user }; ep.enqueue(event); await ep.flush(); @@ -96,7 +97,7 @@ describe('EventProcessor', () => { }); it('queues individual feature event', async () => { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); const event = { kind: 'feature', creationDate: 1000, @@ -116,7 +117,7 @@ describe('EventProcessor', () => { it('can include inline user in feature event', async () => { const config = Object.assign({}, defaultConfig, { inlineUsersInEvents: true }); - const ep = EventProcessor(platform, config, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, config, envId, null, mockEventSender); const event = { kind: 'feature', creationDate: 1000, @@ -137,7 +138,7 @@ describe('EventProcessor', () => { it('can include reason in feature event', async () => { const config = Object.assign({}, defaultConfig, { inlineUsersInEvents: true }); const reason = { kind: 'FALLTHROUGH' }; - const ep = EventProcessor(platform, config, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, config, envId, null, mockEventSender); const event = { kind: 'feature', creationDate: 1000, @@ -158,7 +159,7 @@ describe('EventProcessor', () => { it('filters user in feature event', async () => { const config = Object.assign({}, defaultConfig, { allAttributesPrivate: true, inlineUsersInEvents: true }); - const ep = EventProcessor(platform, config, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, config, envId, null, mockEventSender); const event = { kind: 'feature', creationDate: 1000, @@ -177,7 +178,7 @@ describe('EventProcessor', () => { }); it('sets event kind to debug if event is temporarily in debug mode', async () => { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); const futureTime = new Date().getTime() + 1000000; const e = { kind: 'feature', @@ -201,7 +202,7 @@ describe('EventProcessor', () => { }); it('can both track and debug an event', async () => { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); const futureTime = new Date().getTime() + 1000000; const e = { kind: 'feature', @@ -226,7 +227,7 @@ describe('EventProcessor', () => { }); it('expires debug mode based on client time if client time is later than server time', async () => { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); // Pick a server time that is somewhat behind the client time const serverTime = new Date().getTime() - 20000; @@ -261,7 +262,7 @@ describe('EventProcessor', () => { }); it('expires debug mode based on server time if server time is later than client time', async () => { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); // Pick a server time that is somewhat ahead of the client time const serverTime = new Date().getTime() + 20000; @@ -296,7 +297,7 @@ describe('EventProcessor', () => { }); it('summarizes nontracked events', async () => { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); function makeEvent(key, date, version, variation, value, defaultVal) { return { kind: 'feature', @@ -336,7 +337,7 @@ describe('EventProcessor', () => { }); it('queues custom event', async () => { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); const e = { kind: 'custom', creationDate: 1000, @@ -355,7 +356,7 @@ describe('EventProcessor', () => { it('can include inline user in custom event', async () => { const config = Object.assign({}, defaultConfig, { inlineUsersInEvents: true }); - const ep = EventProcessor(platform, config, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, config, envId, null, mockEventSender); const e = { kind: 'custom', creationDate: 1000, @@ -374,7 +375,7 @@ describe('EventProcessor', () => { it('filters user in custom event', async () => { const config = Object.assign({}, defaultConfig, { allAttributesPrivate: true, inlineUsersInEvents: true }); - const ep = EventProcessor(platform, config, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, config, envId, null, mockEventSender); const e = { kind: 'custom', creationDate: 1000, @@ -392,13 +393,13 @@ describe('EventProcessor', () => { }); it('sends nothing if there are no events to flush', async () => { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); await ep.flush(); expect(mockEventSender.calls.length).toEqual(0); }); async function verifyUnrecoverableHttpError(status) { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); const e = { kind: 'identify', creationDate: 1000, user: user }; ep.enqueue(e); mockEventSender.status = status; @@ -412,7 +413,7 @@ describe('EventProcessor', () => { } async function verifyRecoverableHttpError(status) { - const ep = EventProcessor(platform, defaultConfig, envId, logger, null, mockEventSender); + const ep = EventProcessor(platform, defaultConfig, envId, null, mockEventSender); const e = { kind: 'identify', creationDate: 1000, user: user }; ep.enqueue(e); mockEventSender.status = status; diff --git a/packages/launchdarkly-js-sdk-common/src/__tests__/Requestor-test.js b/packages/launchdarkly-js-sdk-common/src/__tests__/Requestor-test.js index 918b34e4..60cf8803 100644 --- a/packages/launchdarkly-js-sdk-common/src/__tests__/Requestor-test.js +++ b/packages/launchdarkly-js-sdk-common/src/__tests__/Requestor-test.js @@ -7,12 +7,15 @@ import * as utils from '../utils'; describe('Requestor', () => { const baseUrl = 'http://requestee'; - const defaultConfig = { baseUrl: baseUrl }; const user = { key: 'foo' }; const encodedUser = 'eyJrZXkiOiJmb28ifQ'; const env = 'FAKE_ENV'; const platform = stubPlatform.defaults(); const logger = stubPlatform.logger(); + const defaultConfig = { + baseUrl: baseUrl, + logger: logger, + }; let server; beforeEach(() => { @@ -24,7 +27,7 @@ describe('Requestor', () => { }); it('resolves on success', async () => { - const requestor = Requestor(platform, defaultConfig, 'FAKE_ENV', logger); + const requestor = Requestor(platform, defaultConfig, 'FAKE_ENV'); await requestor.fetchFlagSettings({ key: 'user1' }, 'hash1'); await requestor.fetchFlagSettings({ key: 'user2' }, 'hash2'); @@ -33,7 +36,7 @@ describe('Requestor', () => { it('makes requests with the GET verb if useReport is disabled', async () => { const config = Object.assign({}, defaultConfig, { useReport: false }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, 'hash1'); @@ -43,7 +46,7 @@ describe('Requestor', () => { it('makes requests with the REPORT verb with a payload if useReport is enabled', async () => { const config = Object.assign({}, defaultConfig, { useReport: true }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, 'hash1'); @@ -53,7 +56,7 @@ describe('Requestor', () => { }); it('includes environment and user in GET URL', async () => { - const requestor = Requestor(platform, defaultConfig, env, logger); + const requestor = Requestor(platform, defaultConfig, env); await requestor.fetchFlagSettings(user, null); @@ -62,7 +65,7 @@ describe('Requestor', () => { }); it('includes environment, user, and hash in GET URL', async () => { - const requestor = Requestor(platform, defaultConfig, env, logger); + const requestor = Requestor(platform, defaultConfig, env); await requestor.fetchFlagSettings(user, 'hash1'); @@ -72,7 +75,7 @@ describe('Requestor', () => { it('includes environment, user, and withReasons in GET URL', async () => { const config = Object.assign({}, defaultConfig, { evaluationReasons: true }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, null); @@ -82,7 +85,7 @@ describe('Requestor', () => { it('includes environment, user, hash, and withReasons in GET URL', async () => { const config = Object.assign({}, defaultConfig, { evaluationReasons: true }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, 'hash1'); @@ -92,7 +95,7 @@ describe('Requestor', () => { it('includes environment in REPORT URL', async () => { const config = Object.assign({}, defaultConfig, { useReport: true }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, null); @@ -102,7 +105,7 @@ describe('Requestor', () => { it('includes environment and hash in REPORT URL', async () => { const config = Object.assign({}, defaultConfig, { useReport: true }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, 'hash1'); @@ -112,7 +115,7 @@ describe('Requestor', () => { it('includes environment and withReasons in REPORT URL', async () => { const config = Object.assign({}, defaultConfig, { useReport: true, evaluationReasons: true }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, null); @@ -122,7 +125,7 @@ describe('Requestor', () => { it('includes environment, hash, and withReasons in REPORT URL', async () => { const config = Object.assign({}, defaultConfig, { useReport: true, evaluationReasons: true }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, 'hash1'); @@ -132,7 +135,7 @@ describe('Requestor', () => { it('sends custom user-agent header in GET mode when sendLDHeaders is true', async () => { const config = Object.assign({}, defaultConfig, { sendLDHeaders: true }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, 'hash1'); expect(server.requests.length).toEqual(1); @@ -143,7 +146,7 @@ describe('Requestor', () => { it('sends custom user-agent header in REPORT mode when sendLDHeaders is true', async () => { const config = Object.assign({}, defaultConfig, { useReport: true, sendLDHeaders: true }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, 'hash1'); expect(server.requests.length).toEqual(1); @@ -154,7 +157,7 @@ describe('Requestor', () => { it('does NOT send custom user-agent header when sendLDHeaders is false', async () => { const config = Object.assign({}, defaultConfig, { useReport: true, sendLDHeaders: false }); - const requestor = Requestor(platform, config, env, logger); + const requestor = Requestor(platform, config, env); await requestor.fetchFlagSettings(user, 'hash1'); @@ -163,7 +166,7 @@ describe('Requestor', () => { }); it('returns parsed JSON response on success', async () => { - const requestor = Requestor(platform, defaultConfig, env, logger); + const requestor = Requestor(platform, defaultConfig, env); const data = { foo: 'bar' }; server.respondWith(jsonResponse(data)); @@ -173,7 +176,7 @@ describe('Requestor', () => { }); it('returns error for non-JSON content type', async () => { - const requestor = Requestor(platform, defaultConfig, env, logger); + const requestor = Requestor(platform, defaultConfig, env); server.respondWith([200, { 'Content-Type': 'text/html' }, '']); @@ -182,7 +185,7 @@ describe('Requestor', () => { }); it('returns error for unspecified content type', async () => { - const requestor = Requestor(platform, defaultConfig, env, logger); + const requestor = Requestor(platform, defaultConfig, env); server.respondWith([200, {}, '{}']); @@ -191,7 +194,7 @@ describe('Requestor', () => { }); it('signals specific error for 404 response', async () => { - const requestor = Requestor(platform, defaultConfig, env, logger); + const requestor = Requestor(platform, defaultConfig, env); server.respondWith(errorResponse(404)); @@ -200,7 +203,7 @@ describe('Requestor', () => { }); it('signals general error for non-404 error status', async () => { - const requestor = Requestor(platform, defaultConfig, env, logger); + const requestor = Requestor(platform, defaultConfig, env); server.respondWith(errorResponse(500)); @@ -209,7 +212,7 @@ describe('Requestor', () => { }); it('signals general error for network error', async () => { - const requestor = Requestor(platform, defaultConfig, env, logger); + const requestor = Requestor(platform, defaultConfig, env); server.respondWith(req => req.error()); @@ -218,7 +221,7 @@ describe('Requestor', () => { }); it('coalesces multiple requests so all callers get the latest result', async () => { - const requestor = Requestor(platform, defaultConfig, env, logger); + const requestor = Requestor(platform, defaultConfig, env); let n = 0; server.autoRespond = false; @@ -245,7 +248,7 @@ describe('Requestor', () => { describe('When HTTP requests are not available at all', () => { it('fails on fetchFlagSettings', async () => { - const requestor = Requestor(stubPlatform.withoutHttp(), defaultConfig, env, logger); + const requestor = Requestor(stubPlatform.withoutHttp(), defaultConfig, env); await expect(requestor.fetchFlagSettings(user, null)).rejects.toThrow(messages.httpUnavailable()); }); }); diff --git a/packages/launchdarkly-js-sdk-common/src/__tests__/Stream-test.js b/packages/launchdarkly-js-sdk-common/src/__tests__/Stream-test.js index c57fb7bf..f2cb46c5 100644 --- a/packages/launchdarkly-js-sdk-common/src/__tests__/Stream-test.js +++ b/packages/launchdarkly-js-sdk-common/src/__tests__/Stream-test.js @@ -1,5 +1,6 @@ import EventSource, { sources, resetSources } from './EventSource-mock'; import * as stubPlatform from './stubPlatform'; +import { asyncify, asyncSleep } from './testUtils'; import Stream from '../Stream'; const noop = () => {}; @@ -10,10 +11,13 @@ describe('Stream', () => { const user = { key: 'me' }; const encodedUser = 'eyJrZXkiOiJtZSJ9'; const hash = '012345789abcde'; - const defaultConfig = { streamUrl: baseUrl }; - const platform = stubPlatform.defaults(); + const defaultConfig = { streamUrl: baseUrl, logger: stubPlatform.logger() }; + let platform; - beforeEach(resetSources); + beforeEach(() => { + resetSources(); + platform = stubPlatform.defaults(); + }); function expectStream(url) { if (sources[url]) { @@ -25,6 +29,23 @@ describe('Stream', () => { } } + function expectOneStream() { + const keys = Object.keys(sources); + if (keys.length !== 1) { + throw new Error('Expected only one stream; active streams are: ' + keys.join(', ')); + } + return sources[keys[0]]; + } + + function onNewEventSource(f) { + const factory = platform.eventSourceFactory; + platform.eventSourceFactory = (url, options) => { + const es = factory(url, options); + f(es, url, options); + return es; + }; + } + it('should not throw on EventSource when it does not exist', () => { const platform1 = Object.assign({}, platform); delete platform1['eventSourceFactory']; @@ -82,35 +103,51 @@ describe('Stream', () => { }); it('sets event listeners', () => { - const stream = new Stream(platform, defaultConfig, envName, hash); - const fn1 = () => 0; - const fn2 = () => 1; + const stream = new Stream(platform, defaultConfig, envName); + const fn1 = jest.fn(); + const fn2 = jest.fn(); stream.connect(user, { birthday: fn1, anniversary: fn2, }); - const es = expectStream(`${baseUrl}/eval/${envName}/${encodedUser}?h=${hash}`); - expect(es.__emitter._events.birthday).toEqual(fn1); - expect(es.__emitter._events.anniversary).toEqual(fn2); + const es = expectOneStream(); + + es.mockEmit('birthday'); + expect(fn1).toHaveBeenCalled(); + expect(fn2).not.toHaveBeenCalled(); + + es.mockEmit('anniversary'); + expect(fn2).toHaveBeenCalled(); }); - it('reconnects after encountering an error', () => { + it('reconnects after encountering an error', async () => { const config = Object.assign({}, defaultConfig, { streamReconnectDelay: 0.1, useReport: false }); const stream = new Stream(platform, config, envName); stream.connect(user); - const es = expectStream(baseUrl + '/eval/' + envName + '/' + encodedUser); + + let es = expectOneStream(); expect(es.readyState).toBe(EventSource.CONNECTING); es.mockOpen(); expect(es.readyState).toBe(EventSource.OPEN); - es.mockError('test error'); - expect(es.readyState).toBe(EventSource.CLOSED); - setTimeout(() => { - const es1 = expectStream(baseUrl + '/eval/' + envName + '/' + encodedUser); - expect(es1.readyState).toNotBe(EventSource.CONNECTING); + + const nAttempts = 5; + for (let i = 0; i < nAttempts; i++) { + const newEventSourcePromise = asyncify(onNewEventSource); + + es.mockError('test error'); + const es1 = await newEventSourcePromise; + + expect(es.readyState).toBe(EventSource.CLOSED); + expect(es1.readyState).toBe(EventSource.CONNECTING); + es1.mockOpen(); - expect(es1.readyState).toNotBe(EventSource.OPEN); - }, 1001); + await asyncSleep(0); // make sure the stream logic has a chance to catch up with the new EventSource state + + expect(stream.isConnected()).toBe(true); + + es = es1; + } }); }); diff --git a/packages/launchdarkly-js-sdk-common/src/configuration.js b/packages/launchdarkly-js-sdk-common/src/configuration.js index 1eeeb0c7..ee6f56af 100644 --- a/packages/launchdarkly-js-sdk-common/src/configuration.js +++ b/packages/launchdarkly-js-sdk-common/src/configuration.js @@ -20,7 +20,7 @@ export function validate(options, emitter, extraDefaults, logger) { allAttributesPrivate: false, privateAttributeNames: [], }; - const defaults = utils.extend({}, baseDefaults, extraDefaults); + const defaults = utils.extend({ logger: logger }, baseDefaults, extraDefaults); const deprecatedOptions = { // eslint-disable-next-line camelcase diff --git a/packages/launchdarkly-js-sdk-common/src/index.js b/packages/launchdarkly-js-sdk-common/src/index.js index 2c68cf9b..29da4941 100644 --- a/packages/launchdarkly-js-sdk-common/src/index.js +++ b/packages/launchdarkly-js-sdk-common/src/index.js @@ -34,8 +34,8 @@ export function initialize(env, user, specifiedOptions, platform, extraDefaults) const sendEvents = options.sendEvents; let environment = env; const stream = Stream(platform, options, environment, hash); - const events = options.eventProcessor || EventProcessor(platform, options, environment, logger, emitter); - const requestor = Requestor(platform, options, environment, logger); + const events = options.eventProcessor || EventProcessor(platform, options, environment, emitter); + const requestor = Requestor(platform, options, environment); const seenRequests = {}; let flags = {}; let useLocalStorage; diff --git a/packages/launchdarkly-js-sdk-common/src/messages.js b/packages/launchdarkly-js-sdk-common/src/messages.js index 5a2bb02f..81322677 100644 --- a/packages/launchdarkly-js-sdk-common/src/messages.js +++ b/packages/launchdarkly-js-sdk-common/src/messages.js @@ -1,5 +1,15 @@ import * as errors from './errors'; +function errorString(err) { + if (err && err.message) { + return err.message; + } + if (typeof err === 'string' || err instanceof String) { + return err; + } + return JSON.stringify(err); +} + export const clientInitialized = function() { return 'LaunchDarkly client initialized'; }; @@ -46,7 +56,7 @@ export const environmentNotSpecified = function() { }; export const errorFetchingFlags = function(err) { - return 'Error fetching flag settings: ' + (err.message || err); + return 'Error fetching flag settings: ' + errorString(err); }; export const userNotSpecified = function() { @@ -93,6 +103,18 @@ export const identifyDisabled = function() { return 'identify() has no effect here; it must be called on the main client instance'; }; +export const streamClosing = function() { + return 'Closing stream connection'; +}; + +export const streamConnecting = function(url) { + return 'Opening stream connection to ' + url; +}; + +export const streamError = function(err) { + return 'Error on stream connection: ' + errorString(err); +}; + export const debugPolling = function(url) { return 'polling for feature flags at ' + url; };