-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathindex.ts
More file actions
104 lines (90 loc) · 4.02 KB
/
Copy pathindex.ts
File metadata and controls
104 lines (90 loc) · 4.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import { IPlatform } from '../../../sdkFactory/types';
import { decorateHeaders } from '../../../services/decorateHeaders';
import { IEventSourceConstructor } from '../../../services/types';
import { ISettings } from '../../../types';
import { checkIfServerSide } from '../../../utils/key';
import { isString } from '../../../utils/lang';
import { objectAssign } from '../../../utils/lang/objectAssign';
import { IAuthTokenPushEnabled } from '../AuthClient/types';
import { ISSEClient, ISseEventHandler } from './types';
const ABLY_API_VERSION = '1.1';
const CONTROL_CHANNEL_REGEX = /^control_/;
/**
* Build metadata headers for SSE connection.
*
* @param settings - Validated settings.
* @returns Headers object
*/
function buildSSEHeaders(settings: ISettings) {
const headers: Record<string, string> = {
SplitSDKClientKey: isString(settings.core.authorizationKey) ? settings.core.authorizationKey.slice(-4) : '',
SplitSDKVersion: settings.version,
};
// ip and hostname are false if IPAddressesEnabled is false
const { ip, hostname } = settings.runtime;
if (ip) headers['SplitSDKMachineIP'] = ip;
if (hostname) headers['SplitSDKMachineName'] = hostname;
return headers;
}
/**
* Handles streaming connections with EventSource API
*/
export class SSEClient implements ISSEClient {
// Instance properties:
eventSource?: IEventSourceConstructor;
connection?: InstanceType<IEventSourceConstructor>;
handler?: ISseEventHandler;
headers: Record<string, string>;
options?: object;
/**
* SSEClient constructor.
*
* @param settings - Validated settings.
* @param platform - object containing environment-specific dependencies
* @throws 'EventSource API is not available.' if EventSource is not available.
*/
constructor(private settings: ISettings, { getEventSource, getOptions }: IPlatform) {
this.eventSource = getEventSource && getEventSource(settings);
// if eventSource is not available, throw an exception
if (!this.eventSource) throw new Error('EventSource API is not available.');
this.headers = buildSSEHeaders(settings);
this.options = getOptions && getOptions(settings);
}
setEventHandler(handler: ISseEventHandler) {
this.handler = handler;
}
/**
* Open the connection with a given authToken
*/
open(authToken: IAuthTokenPushEnabled) {
this.close(); // it closes connection if previously opened
const channelsQueryParam = Object.keys(authToken.channels).map((channel) => {
const params = CONTROL_CHANNEL_REGEX.test(channel) ? '[?occupancy=metrics.publishers]' : '';
return encodeURIComponent(params + channel);
}).join(',');
const url = `${this.settings.urls.streaming}/sse?channels=${channelsQueryParam}&accessToken=${authToken.token}&v=${ABLY_API_VERSION}&heartbeats=true`; // same results using `&heartbeats=false`
const isServerSide = checkIfServerSide(this.settings);
this.connection = new this.eventSource!(
// 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
isServerSide ? url : url + `&SplitSDKVersion=${this.headers.SplitSDKVersion}&SplitSDKClientKey=${this.headers.SplitSDKClientKey}`,
// For server-side SDKs, metadata is passed via headers
objectAssign(
isServerSide ?
{ headers: decorateHeaders(this.settings, this.headers) } :
this.settings.sync.requestOptions?.getHeaderOverrides ?
{ headers: decorateHeaders(this.settings, {}) } : // User must provide a window.EventSource polyfill that supports headers
{},
this.options
)
);
if (this.handler) { // no need to check if SSEClient is used only by PushManager
this.connection.addEventListener('open', this.handler.handleOpen);
this.connection.addEventListener('message', this.handler.handleMessage);
this.connection.addEventListener('error', this.handler.handleError);
}
}
/** Close connection */
close() {
if (this.connection) this.connection.close();
}
}