Skip to content

Commit 75f3d78

Browse files
Merge pull request #3 from improbable-eng/feature/more-browsers
Added IE9, Opera and additional platforms
2 parents 0dfec4f + 2107ee8 commit 75f3d78

File tree

5 files changed

+138
-17
lines changed

5 files changed

+138
-17
lines changed

README.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ $ npm install browser-headers
1717
```
1818

1919
## Browser Support
20-
This library is tested against IE 10, Safari, Firefox and Chrome. It relies on browser support for [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) and [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) Browser APIs; for legacy environments such as Safari and IE10, you will need to supply one or more of the polyfills listed below:
20+
This library is tested against Chrome, Safari, Firefox, Edge, IE 10 and IE 8.
2121

2222
## API
2323

@@ -33,7 +33,7 @@ headers.forEach((key, values) => {
3333
console.log(key, values);
3434
});
3535

36-
// Outputs:
36+
// Output:
3737
// "content-type", ["application/json"]
3838
// "my-header", ["value-one","value-two"]
3939
```
@@ -45,6 +45,12 @@ The `BrowserHeaders` class can be constructed from one of:
4545
* An object consisting of `string->(string|string[])` (e.g. `{"key-a":["one","two"],"key-b":"three"}`)
4646
* A `Map<string, string|string[]>`
4747

48+
The constructor takes an additional optional `options` parameter of `{ splitValues: boolean = false }`, where
49+
`splitValues` defines whether the header values should be split by comma (`,`) into separate strings - this is useful
50+
to unify the `.append` functionality of `Headers` implementations (see the warning at the end of this README).
51+
`splitValues` should be used with caution and defaults to `false` because it might split what is actually a single
52+
logical value that contained a `,`.
53+
4854
The `BrowserHeaders` class has the following methods:
4955

5056
#### .get(key: string): string[]
@@ -72,4 +78,30 @@ Otherwise:
7278
Returns true if the `key` has at least one value.
7379

7480
#### .appendFromString(str: string): void
75-
Appends the headers defined in the provided CLRF-delimited string (e.g. `key-a: one\r\nkey-b: two`)
81+
Appends the headers defined in the provided CLRF-delimited string (e.g. `key-a: one\r\nkey-b: two`)
82+
83+
## Warning about .append in native `Headers`
84+
The `.append` function of the `Headers` class differs significantly between browsers.
85+
86+
Some browsers concatenate the values with `, ` or just `,` and others actually maintain the individual values such that
87+
they can return later return an array. There is a constructor option
88+
```js
89+
const headers = new Headers();
90+
headers.append("key-A", "one");
91+
headers.append("key-A", "two");
92+
const keyA = headers.get("key-A"); // or .getAll depending on the browser
93+
console.log(typeof keyA);
94+
console.log(keyA);
95+
96+
// Output in Edge 14:
97+
// string
98+
// one, two
99+
100+
// Output in Safari 10:
101+
// string
102+
// one,two
103+
104+
// Output in Chrome 56:
105+
// object
106+
// ["one", "two"]
107+
```

karma.conf.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,55 @@ module.exports = function(config) {
66
// Browsers to run on Sauce Labs
77
// Check out https://saucelabs.com/platforms for all browser/OS combos
88
var customLaunchers = {
9-
'SL_Safari': {
9+
'SL_Safari_Latest': {
1010
base: 'SauceLabs',
1111
browserName: 'safari',
1212
platform: 'OS X 10.11'
1313
},
14-
'SL_Chrome': {
14+
'SL_Safari_8': {
15+
base: 'SauceLabs',
16+
browserName: 'safari',
17+
platform: 'OS X 10.10',
18+
version: '8',
19+
},
20+
'SL_Chrome_Latest': {
1521
base: 'SauceLabs',
1622
browserName: 'chrome',
1723
platform: 'linux'
1824
},
19-
'SL_Firefox': {
25+
'SL_Chrome_48': {
26+
base: 'SauceLabs',
27+
browserName: 'chrome',
28+
platform: 'OS X 10.10',
29+
version: '48',
30+
},
31+
'SL_Firefox_Latest': {
2032
base: 'SauceLabs',
2133
browserName: 'firefox',
2234
platform: 'linux'
2335
},
24-
'SL_IE10': {
36+
'SL_Opera_12': {
37+
base: 'SauceLabs',
38+
browserName: 'opera',
39+
platform: 'Windows 7',
40+
version: '12'
41+
},
42+
'SL_Edge': {
43+
base: 'SauceLabs',
44+
browserName: 'microsoftedge',
45+
platform: 'Windows 10'
46+
},
47+
'SL_IE_10': {
2548
base: 'SauceLabs',
2649
browserName: 'internet explorer',
2750
platform: 'Windows 7',
2851
version: '10'
52+
},
53+
'SL_IE_9': {
54+
base: 'SauceLabs',
55+
browserName: 'internet explorer',
56+
platform: 'Windows 7',
57+
version: '9'
2958
}
3059
};
3160

@@ -38,7 +67,7 @@ module.exports = function(config) {
3867
reporters.push('saucelabs');
3968
Array.prototype.push.apply(browsers, Object.keys(customLaunchers));
4069
singlerun = true;
41-
concurrency = 1;
70+
concurrency = 4;
4271
}
4372

4473
config.set({

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.0",
44
"main": "lib/index.js",
55
"repository": "https://github.com/improbable-eng/js-browser-headers",
6-
"license": "MIT",
6+
"license": "Apache-2.0",
77
"keywords": [
88
"headers",
99
"fetch"
@@ -28,7 +28,6 @@
2828
"jasmine": "^2.4.1",
2929
"jasmine-core": "^2.4.1",
3030
"karma": "^1.2.0",
31-
"karma-chrome-launcher": "^1.0.1",
3231
"karma-jasmine": "^1.0.2",
3332
"karma-sauce-launcher": "^1.0.0",
3433
"tslint": "^4.4.2",

src/BrowserHeaders.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export declare interface WindowHeaders {
88
delete(key: string): void;
99
keys(): any;
1010
entries(): any;
11+
forEach(callback: (value: string, key: string) => void): any;
1112
append(key: string, value: string): void;
1213
set(key: string, value: string): void;
1314
[Symbol.iterator]: () => Iterator<string>,
@@ -24,7 +25,12 @@ function getHeaderValues(headers: WindowHeaders, key: string): string[] {
2425
}
2526

2627
// There is no getAll() function so get *should* return an array
27-
return headers.get(key);
28+
const getValue = headers.get(key);
29+
if (getValue && typeof getValue === "string") {
30+
// some .get() implementations return a string even though they don't have a .getAll() - notably Microsoft Edge
31+
return [getValue];
32+
}
33+
return getValue;
2834
}
2935

3036
// getHeaderKeys returns an array of keys in a headers instance
@@ -40,8 +46,16 @@ function getHeaderKeys(headers: WindowHeaders): string[] {
4046
keys.push(key);
4147
}
4248
}
49+
} else if (headers.forEach) {
50+
headers.forEach((_, key) => {
51+
if (!asMap[key]) {
52+
// Only add the key if it hasn't been added already
53+
asMap[key] = true;
54+
keys.push(key);
55+
}
56+
});
4357
} else {
44-
// If keys() isn't available then fallback to iterating through headers
58+
// If keys() and forEach() aren't available then fallback to iterating through headers
4559
for (let entry of headers) {
4660
const key = entry[0];
4761
if (!asMap[key]) {
@@ -54,14 +68,25 @@ function getHeaderKeys(headers: WindowHeaders): string[] {
5468
return keys;
5569
}
5670

71+
function splitHeaderValue(str: string) {
72+
const values = [];
73+
const commaSpaceValues = value.split(", ");
74+
commaSpaceValues.forEach(commaSpaceValue => {
75+
commaSpaceValue.split(",").forEach(commaValue => {
76+
values.push(commaValue);
77+
});
78+
});
79+
return values;
80+
}
81+
5782
export type HeaderObject = {[key: string]: string|string[]};
5883
export type HeaderMap = Map<string,string|string[]>;
5984

6085
// BrowserHeaders is a wrapper class for Headers
6186
export default class BrowserHeaders {
6287
keyValueMap: {[key: string]: string[]};
6388

64-
constructor(init?: HeaderObject | HeaderMap | BrowserHeaders | WindowHeaders | string) {
89+
constructor(init: HeaderObject | HeaderMap | BrowserHeaders | WindowHeaders | string = "", options: {splitValues: boolean} = { splitValues: false } ) {
6590
this.keyValueMap = {};
6691

6792
if (init) {
@@ -70,7 +95,11 @@ export default class BrowserHeaders {
7095
keys.forEach(key => {
7196
const values = getHeaderValues(init as WindowHeaders, key);
7297
values.forEach(value => {
73-
this.append(key, value)
98+
if (options.splitValues) {
99+
this.append(key, splitHeaderValue(value));
100+
} else {
101+
this.append(key, value);
102+
}
74103
});
75104
});
76105
} else if (init instanceof BrowserHeaders) {

test/BrowserHeaders.spec.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,45 @@ describe("browser-headers", () => {
196196
// Can only test the Headers compatibility if there is a Headers class in this browser
197197
if (typeof Headers !== "undefined") {
198198
describe("Headers-compatibility", () => {
199-
it("should construct a BrowserHeaders from a Headers instance", () => {
199+
it("should construct a BrowserHeaders from a Headers instance and not split the values by default", () => {
200200
const headers = new Headers();
201-
headers.append("keyA", "one");
202-
headers.append("keyA", "Two");
201+
headers.append("keyA", "one, Two");
203202
headers.append("keyB", "three");
204203

205204
const browserHeaders = new BrowserHeaders(headers);
205+
deepEqual(browserHeaders.get("keyA"), ["one, Two"]);
206+
deepEqual(browserHeaders.get("keyB"), ["three"]);
207+
});
208+
209+
it("should construct a BrowserHeaders from a Headers instance and split the values if specified (comma)", () => {
210+
const headers = new Headers();
211+
headers.append("keyA", "one,Two");
212+
headers.append("keyB", "three");
213+
214+
const browserHeaders = new BrowserHeaders(headers, {splitValues: true});
215+
deepEqual(browserHeaders.get("keyA"), ["one", "Two"]);
216+
deepEqual(browserHeaders.get("keyB"), ["three"]);
217+
});
218+
219+
it("should construct a BrowserHeaders from a Headers instance and split the values if specified (comma + space)", () => {
220+
const headers = new Headers();
221+
headers.append("keyA", "one, Two");
222+
headers.append("keyB", "three");
223+
224+
const browserHeaders = new BrowserHeaders(headers, {splitValues: true});
225+
deepEqual(browserHeaders.get("keyA"), ["one", "Two"]);
226+
deepEqual(browserHeaders.get("keyB"), ["three"]);
227+
});
228+
229+
/* This test uses separate append calls for `keyA`, which in some browsers joins the strings with a comma. By
230+
setting splitValues to true, this test checks that this browser-specific detail can be abstracted. */
231+
it("should construct a BrowserHeaders from a Headers instance and split the values if specified - separate appends", () => {
232+
const headers = new Headers();
233+
headers.append("keyA", "one");
234+
headers.append("keya", "Two");
235+
headers.append("keyB", "three");
236+
237+
const browserHeaders = new BrowserHeaders(headers, {splitValues: true});
206238
deepEqual(browserHeaders.get("keyA"), ["one", "Two"]);
207239
deepEqual(browserHeaders.get("keyB"), ["three"]);
208240
});

0 commit comments

Comments
 (0)