Skip to content

Commit 8173212

Browse files
authored
make URL path concatenation work right whether base URL has a trailing slash or not (#61)
* make URL path concatenation work right whether base URL has a trailing slash or not * lint
1 parent ac38b52 commit 8173212

File tree

6 files changed

+22
-6
lines changed

6 files changed

+22
-6
lines changed

src/EventProcessor.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default function EventProcessor(
1515
) {
1616
const processor = {};
1717
const eventSender = sender || EventSender(platform, environmentId, options);
18-
const mainEventsUrl = options.eventsUrl + '/events/bulk/' + environmentId;
18+
const mainEventsUrl = utils.appendUrlPath(options.eventsUrl, '/events/bulk/' + environmentId);
1919
const summarizer = EventSummarizer();
2020
const userFilter = UserFilter(options);
2121
const inlineUsers = options.inlineUsersInEvents;

src/Requestor.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export default function Requestor(platform, options, environment) {
7575
// Performs a GET request to an arbitrary path under baseUrl. Returns a Promise which will resolve
7676
// with the parsed JSON response, or will be rejected if the request failed.
7777
requestor.fetchJSON = function(path) {
78-
return fetchJSON(baseUrl + path, null);
78+
return fetchJSON(utils.appendUrlPath(baseUrl, path), null);
7979
};
8080

8181
// Requests the current state of all flags for the given user from LaunchDarkly. Returns a Promise

src/Stream.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as messages from './messages';
2-
import { base64URLEncode, getLDHeaders, transformHeaders, objectHasOwnProperty } from './utils';
2+
import { appendUrlPath, base64URLEncode, getLDHeaders, transformHeaders, objectHasOwnProperty } from './utils';
33

44
// The underlying event source implementation is abstracted via the platform object, which should
55
// have these three properties:
@@ -20,7 +20,7 @@ export default function Stream(platform, config, environment, diagnosticsAccumul
2020
const baseUrl = config.streamUrl;
2121
const logger = config.logger;
2222
const stream = {};
23-
const evalUrlPrefix = baseUrl + '/eval/' + environment;
23+
const evalUrlPrefix = appendUrlPath(baseUrl, '/eval/' + environment);
2424
const useReport = config.useReport;
2525
const withReasons = config.evaluationReasons;
2626
const streamReconnectDelay = config.streamReconnectDelay;
@@ -98,7 +98,7 @@ export default function Stream(platform, config, environment, diagnosticsAccumul
9898
options.body = JSON.stringify(user);
9999
} else {
100100
// if we can't do REPORT, fall back to the old ping-based stream
101-
url = baseUrl + '/ping/' + environment;
101+
url = appendUrlPath(baseUrl, '/ping/' + environment);
102102
query = '';
103103
}
104104
} else {

src/__tests__/utils-test.js

+8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
appendUrlPath,
23
base64URLEncode,
34
getLDHeaders,
45
transformHeaders,
@@ -10,6 +11,13 @@ import {
1011
import * as stubPlatform from './stubPlatform';
1112

1213
describe('utils', () => {
14+
it('appendUrlPath', () => {
15+
expect(appendUrlPath('http://base', '/path')).toEqual('http://base/path');
16+
expect(appendUrlPath('http://base', 'path')).toEqual('http://base/path');
17+
expect(appendUrlPath('http://base/', '/path')).toEqual('http://base/path');
18+
expect(appendUrlPath('http://base/', '/path')).toEqual('http://base/path');
19+
});
20+
1321
describe('wrapPromiseCallback', () => {
1422
it('should resolve to the value', done => {
1523
const promise = wrapPromiseCallback(Promise.resolve('woohoo'));

src/diagnosticEvents.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const uuidv1 = require('uuid/v1');
55

66
const { baseOptionDefs } = require('./configuration');
77
const messages = require('./messages');
8+
const { appendUrlPath } = require('./utils');
89

910
function DiagnosticId(sdkKey) {
1011
const ret = {
@@ -80,7 +81,7 @@ function DiagnosticsManager(
8081
) {
8182
const combinedMode = !!platform.diagnosticUseCombinedEvent;
8283
const localStorageKey = 'ld:' + environmentId + ':$diagnostics';
83-
const diagnosticEventsUrl = config.eventsUrl + '/events/diagnostic/' + environmentId;
84+
const diagnosticEventsUrl = appendUrlPath(config.eventsUrl, '/events/diagnostic/' + environmentId);
8485
const periodicInterval = config.diagnosticRecordingInterval;
8586
const acc = accumulator;
8687
const initialEventSamplingInterval = 4; // used only in combined mode - see start()

src/utils.js

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import fastDeepEqual from 'fast-deep-equal';
33

44
const userAttrsToStringify = ['key', 'secondary', 'ip', 'country', 'email', 'firstName', 'lastName', 'avatar', 'name'];
55

6+
export function appendUrlPath(baseUrl, path) {
7+
// Ensure that URL concatenation is done correctly regardless of whether the
8+
// base URL has a trailing slash or not.
9+
const trimBaseUrl = baseUrl.endsWith('/') ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl;
10+
return trimBaseUrl + (path.startsWith('/') ? '' : '/') + path;
11+
}
12+
613
// See http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
714
export function btoa(s) {
815
const escaped = unescape(encodeURIComponent(s));

0 commit comments

Comments
 (0)