Skip to content

Commit d1ecc80

Browse files
Merge pull request #344 from splitio/enhanced_sdk_headers
Update enhanced SDK headers
2 parents 1e90013 + 379bfd4 commit d1ecc80

File tree

2 files changed

+123
-83
lines changed

2 files changed

+123
-83
lines changed

src/sync/streaming/SSEClient/__tests__/index.spec.ts

Lines changed: 111 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import { url } from '../../../../utils/settingsValidation/url';
66

77
import { SSEClient } from '../index';
88

9+
const EXPECTED_HEADERS = {
10+
SplitSDKClientKey: '1234',
11+
SplitSDKVersion: settings.version
12+
};
13+
914
const EXPECTED_URL = url(settings, '/sse') +
1015
'?channels=' + channelsQueryParamSample +
1116
'&accessToken=' + authDataSample.token +
1217
'&v=1.1&heartbeats=true';
1318

14-
const EXPECTED_HEADERS = {
15-
SplitSDKClientKey: '1234',
16-
SplitSDKVersion: settings.version
17-
};
19+
const EXPECTED_BROWSER_URL = EXPECTED_URL +
20+
`&SplitSDKVersion=${settings.version}&SplitSDKClientKey=${EXPECTED_HEADERS.SplitSDKClientKey}`;
1821

1922
test('SSClient / instance creation throws error if EventSource is not provided', () => {
2023
expect(() => { new SSEClient(settings); }).toThrow(Error);
@@ -80,86 +83,118 @@ test('SSClient / setEventHandler, open and close methods', () => {
8083

8184
});
8285

83-
test('SSClient / open method on client-side: metadata as query params', () => {
84-
85-
const instance = new SSEClient(settings, { getEventSource: () => EventSourceMock });
86-
instance.open(authDataSample);
87-
88-
const EXPECTED_BROWSER_URL = EXPECTED_URL + `&SplitSDKVersion=${settings.version}&SplitSDKClientKey=${EXPECTED_HEADERS.SplitSDKClientKey}`;
89-
90-
expect(instance.connection.url).toBe(EXPECTED_BROWSER_URL); // URL is properly set for streaming connection
91-
expect(instance.connection.__eventSourceInitDict).toEqual({}); // No headers are passed for streaming connection
92-
});
93-
94-
test('SSClient / open method on server-side: metadata as headers', () => {
95-
96-
const instance = new SSEClient(settingsServerSide, { getEventSource: () => EventSourceMock });
97-
instance.open(authDataSample);
86+
describe('SSClient / open method on client-side', () => {
87+
88+
test('metadata as query params', () => {
89+
90+
const instance = new SSEClient(settings, { getEventSource: () => EventSourceMock });
91+
instance.open(authDataSample);
92+
93+
expect(instance.connection.url).toBe(EXPECTED_BROWSER_URL);
94+
expect(instance.connection.__eventSourceInitDict).toEqual({}); // No headers are passed for streaming connection
95+
});
96+
97+
test('custom headers', () => {
98+
const settingsWithGetHeaderOverrides = {
99+
...settings,
100+
sync: {
101+
requestOptions: {
102+
getHeaderOverrides: (context) => {
103+
expect(context).toEqual({ headers: {} });
104+
context.headers['otherheader'] = 'customvalue';
105+
return {
106+
SplitSDKClientKey: '4321', // will not be overridden
107+
CustomHeader: 'custom-value'
108+
};
109+
}
110+
}
111+
},
112+
};
113+
const instance = new SSEClient(settingsWithGetHeaderOverrides, { getEventSource: () => EventSourceMock });
114+
instance.open(authDataSample);
115+
116+
expect(instance.connection.url).toBe(EXPECTED_BROWSER_URL);
117+
expect(instance.connection.__eventSourceInitDict).toEqual({
118+
headers: {
119+
CustomHeader: 'custom-value'
120+
}
121+
}); // Only custom headers are passed for streaming connection
122+
});
98123

99-
expect(instance.connection.url).toBe(EXPECTED_URL); // URL is properly set for streaming connection
100-
expect(instance.connection.__eventSourceInitDict).toEqual({ headers: EXPECTED_HEADERS }); // Headers are properly set for streaming connection
101124
});
102125

103-
test('SSClient / open method on server-side: metadata with IP and Hostname as headers', () => {
104-
105-
const settingsWithRuntime = {
106-
...settingsServerSide,
107-
runtime: {
108-
ip: 'some ip',
109-
hostname: 'some hostname'
110-
}
111-
};
112-
const instance = new SSEClient(settingsWithRuntime, { getEventSource: () => EventSourceMock });
113-
instance.open(authDataSample);
114-
115-
expect(instance.connection.url).toBe(EXPECTED_URL); // URL is properly set for streaming connection
116-
expect(instance.connection.__eventSourceInitDict).toEqual({
117-
headers: {
118-
...EXPECTED_HEADERS,
119-
SplitSDKMachineIP: settingsWithRuntime.runtime.ip,
120-
SplitSDKMachineName: settingsWithRuntime.runtime.hostname
121-
}
122-
}); // Headers are properly set for streaming connection
123-
});
126+
describe('SSClient / open method on server-side', () => {
124127

125-
test('SSClient / open method on server-side: metadata as headers and options', () => {
126-
const platform = { getEventSource: jest.fn(() => EventSourceMock), getOptions: jest.fn(() => ({ withCredentials: true })) };
128+
test('metadata as headers', () => {
127129

128-
const instance = new SSEClient(settingsServerSide, platform);
129-
instance.open(authDataSample);
130+
const instance = new SSEClient(settingsServerSide, { getEventSource: () => EventSourceMock });
131+
instance.open(authDataSample);
130132

131-
expect(instance.connection.url).toBe(EXPECTED_URL); // URL is properly set for streaming connection
132-
expect(instance.connection.__eventSourceInitDict).toEqual({ headers: EXPECTED_HEADERS, withCredentials: true }); // Headers and options are properly set for streaming connection
133+
expect(instance.connection.url).toBe(EXPECTED_URL);
134+
expect(instance.connection.__eventSourceInitDict).toEqual({ headers: EXPECTED_HEADERS });
135+
});
133136

134-
// Assert that getEventSource and getOptions were called once with settings
135-
expect(platform.getEventSource.mock.calls).toEqual([[settingsServerSide]]);
136-
expect(platform.getOptions.mock.calls).toEqual([[settingsServerSide]]);
137-
});
137+
test('metadata with IP and Hostname as headers', () => {
138138

139-
test('SSClient / open method with getHeaderOverrides: custom headers', () => {
140-
const settingsWithGetHeaderOverrides = {
141-
...settings,
142-
sync: {
143-
requestOptions: {
144-
getHeaderOverrides: (context) => {
145-
expect(context).toEqual({ headers: EXPECTED_HEADERS });
146-
context.headers['otherheader'] = 'customvalue';
147-
return {
148-
SplitSDKClientKey: '4321', // will not be overridden
149-
CustomHeader: 'custom-value'
150-
};
139+
const settingsWithRuntime = {
140+
...settingsServerSide,
141+
runtime: {
142+
ip: 'some ip',
143+
hostname: 'some hostname'
144+
}
145+
};
146+
const instance = new SSEClient(settingsWithRuntime, { getEventSource: () => EventSourceMock });
147+
instance.open(authDataSample);
148+
149+
expect(instance.connection.url).toBe(EXPECTED_URL);
150+
expect(instance.connection.__eventSourceInitDict).toEqual({
151+
headers: {
152+
...EXPECTED_HEADERS,
153+
SplitSDKMachineIP: settingsWithRuntime.runtime.ip,
154+
SplitSDKMachineName: settingsWithRuntime.runtime.hostname
155+
}
156+
});
157+
});
158+
159+
test('metadata as headers and custom options', () => {
160+
const platform = { getEventSource: jest.fn(() => EventSourceMock), getOptions: jest.fn(() => ({ withCredentials: true })) };
161+
162+
const instance = new SSEClient(settingsServerSide, platform);
163+
instance.open(authDataSample);
164+
165+
expect(instance.connection.url).toBe(EXPECTED_URL);
166+
expect(instance.connection.__eventSourceInitDict).toEqual({ headers: EXPECTED_HEADERS, withCredentials: true }); // Headers and options are properly set for streaming connection
167+
168+
// Assert that getEventSource and getOptions were called once with settings
169+
expect(platform.getEventSource.mock.calls).toEqual([[settingsServerSide]]);
170+
expect(platform.getOptions.mock.calls).toEqual([[settingsServerSide]]);
171+
});
172+
173+
test('custom headers', () => {
174+
const settingsWithGetHeaderOverrides = {
175+
...settingsServerSide,
176+
sync: {
177+
requestOptions: {
178+
getHeaderOverrides: (context) => {
179+
expect(context).toEqual({ headers: EXPECTED_HEADERS });
180+
context.headers['otherheader'] = 'customvalue';
181+
return {
182+
SplitSDKClientKey: '4321', // will not be overridden
183+
CustomHeader: 'custom-value'
184+
};
185+
}
151186
}
187+
},
188+
};
189+
const instance = new SSEClient(settingsWithGetHeaderOverrides, { getEventSource: () => EventSourceMock });
190+
instance.open(authDataSample);
191+
192+
expect(instance.connection.url).toBe(EXPECTED_URL);
193+
expect(instance.connection.__eventSourceInitDict).toEqual({
194+
headers: {
195+
...EXPECTED_HEADERS,
196+
CustomHeader: 'custom-value'
152197
}
153-
}
154-
};
155-
const instance = new SSEClient(settingsWithGetHeaderOverrides, { getEventSource: () => EventSourceMock });
156-
instance.open(authDataSample);
157-
158-
expect(instance.connection.url).toBe(EXPECTED_URL); // URL is properly set for streaming connection
159-
expect(instance.connection.__eventSourceInitDict).toEqual({
160-
headers: {
161-
...EXPECTED_HEADERS,
162-
CustomHeader: 'custom-value'
163-
}
164-
}); // Headers are properly set for streaming connection
198+
}); // SDK headers and custom headers are passed for streaming connection
199+
});
165200
});

src/sync/streaming/SSEClient/index.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,20 @@ export class SSEClient implements ISSEClient {
7878
}
7979
).join(',');
8080
const url = `${this.settings.urls.streaming}/sse?channels=${channelsQueryParam}&accessToken=${authToken.token}&v=${ABLY_API_VERSION}&heartbeats=true`; // same results using `&heartbeats=false`
81-
// use headers in server-side or if getHeaderOverrides is defined
82-
const useHeaders = !this.settings.core.key || this.settings.sync.requestOptions?.getHeaderOverrides;
81+
const isServerSide = !this.settings.core.key;
8382

8483
this.connection = new this.eventSource!(
85-
// For client-side SDKs, SplitSDKClientKey and SplitSDKClientKey metadata is passed as query params,
86-
// because native EventSource implementations for browser doesn't support headers.
87-
useHeaders ? url : url + `&SplitSDKVersion=${this.headers.SplitSDKVersion}&SplitSDKClientKey=${this.headers.SplitSDKClientKey}`,
88-
// For server-side SDKs, metadata is passed via headers. EventSource must support headers, like 'eventsource' package for Node.
89-
objectAssign(useHeaders ? { headers: decorateHeaders(this.settings, this.headers) } : {}, this.options)
84+
// For client-side SDKs, metadata is passed as query param to avoid CORS issues and because native EventSource implementations in browsers do not support headers
85+
isServerSide ? url : url + `&SplitSDKVersion=${this.headers.SplitSDKVersion}&SplitSDKClientKey=${this.headers.SplitSDKClientKey}`,
86+
// For server-side SDKs, metadata is passed via headers
87+
objectAssign(
88+
isServerSide ?
89+
{ headers: decorateHeaders(this.settings, this.headers) } :
90+
this.settings.sync.requestOptions?.getHeaderOverrides ?
91+
{ headers: decorateHeaders(this.settings, {}) } : // User must provide a window.EventSource polyfill that supports headers
92+
{},
93+
this.options
94+
)
9095
);
9196

9297
if (this.handler) { // no need to check if SSEClient is used only by PushManager

0 commit comments

Comments
 (0)