• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ringcentral / ringcentral-js / 8789319645

22 Apr 2024 06:48PM UTC coverage: 93.418%. Remained the same
8789319645

push

github

SushilMallRC
Add comments for code

574 of 702 branches covered (81.77%)

Branch coverage included in aggregate %.

1924 of 1972 relevant lines covered (97.57%)

42.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.5
/sdk/src/http/Client.ts
1
import {EventEmitter} from 'events';
4✔
2
import isPlainObject from 'is-plain-object';
4✔
3

4
import Externals from '../core/Externals';
5
import {objectToUrlParams} from './utils';
4✔
6

7
function findHeaderName(name, headers) {
8
    name = name.toLowerCase();
213✔
9
    return Object.keys(headers).reduce((res, key) => {
213✔
10
        if (res) {return res;}
219✔
11
        if (name === key.toLowerCase()) {return key;}
115✔
12
        return res;
1✔
13
    }, null);
14
}
15

16
export interface ApiError extends Error {
17
    originalMessage?: string;
18
    response?: Response;
19
    request?: Request;
20
}
21

22
export interface ClientOptions {
23
    externals: Externals;
24
    defaultRequestInit: CreateRequestOptions;
25
}
26

27
export enum events {
4✔
28
    beforeRequest = 'beforeRequest',
4✔
29
    requestSuccess = 'requestSuccess',
4✔
30
    requestError = 'requestError',
4✔
31
}
32

33
export default class Client extends EventEmitter {
4✔
34
    public static _contentType = 'Content-Type';
4✔
35

36
    public static _jsonContentType = 'application/json';
4✔
37

38
    public static _multipartContentType = 'multipart/mixed';
4✔
39

40
    public static _urlencodedContentType = 'application/x-www-form-urlencoded';
4✔
41

42
    public static _headerSeparator = ':';
4✔
43

44
    public static _bodySeparator = '\n\n';
4✔
45

46
    public static _boundarySeparator = '--';
4✔
47

48
    public static _unauthorizedStatus = 401;
4✔
49

50
    public static _rateLimitStatus = 429;
4✔
51

52
    public static _allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
4✔
53

54
    public static _defaultRequestInit: CreateRequestOptions = {
4✔
55
        credentials: 'include',
56
        mode: 'cors',
57
    };
58

59
    public events = events;
97✔
60

61
    private _externals: Externals;
62

63
    private _defaultRequestInit: CreateRequestOptions = {};
97✔
64

65
    public constructor({externals, defaultRequestInit = {}}: ClientOptions) {
97!
66
        super();
97✔
67
        this._defaultRequestInit = defaultRequestInit;
97✔
68
        this._externals = externals;
97✔
69
    }
70

71
    public async sendRequest(request: Request): Promise<Response> {
4✔
72
        let response;
73
        try {
74
            //TODO Stop request if listeners return false
75
            this.emit(this.events.beforeRequest, request);
210✔
76

77
            response = await this._loadResponse(request);
210✔
78

79
            if (!response.ok) {throw new Error('Response has unsuccessful status');}
210✔
80

81
            this.emit(this.events.requestSuccess, response, request);
182✔
82

83
            return response;
182✔
84
        } catch (e) {
85
            const error = !e.response ? await this.makeError(e, response, request) : e;
56!
86

87
            this.emit(this.events.requestError, error);
28✔
88

89
            throw error;
28✔
90
        }
91
    }
92

93
    public async _loadResponse(request: Request): Promise<Response> {
4✔
94
        return this._externals.fetch.call(null, request); // fixed illegal invocation in Chrome
210✔
95
    }
96

97
    /**
98
     * Wraps the JS Error object with transaction information
99
     */
100
    public async makeError(e: any, response: Response = null, request: Request = null): Promise<ApiError> {
30!
101
        // Wrap only if regular error
102
        if (!e.response && !e.originalMessage) {
29!
103
            e.response = response;
29✔
104
            e.request = request;
29✔
105
            e.originalMessage = e.message;
29✔
106
            e.message = (response && (await this.error(response, true))) || e.originalMessage;
29!
107
        }
108

109
        return e;
29✔
110
    }
111

112
    public createRequest(init: CreateRequestOptions = Client._defaultRequestInit): Request {
214!
113
        init = {...this._defaultRequestInit, ...init};
214✔
114
        init.headers = init.headers || {};
214✔
115

116
        // Sanity checks
117
        if (!init.url) {throw new Error('Url is not defined');}
214!
118
        if (!init.method) {init.method = 'GET';}
214✔
119
        init.method = init.method.toUpperCase();
214✔
120
        if (init.method && Client._allowedMethods.indexOf(init.method) < 0) {
214✔
121
            throw new Error(`Method has wrong value: ${init.method}`);
1✔
122
        }
123

124
        // Defaults
125
        init.credentials = init.credentials || 'include';
213✔
126
        init.mode = init.mode || 'cors';
213✔
127

128
        // Append Query String
129
        if (init.query) {
213✔
130
            init.url = init.url + (init.url.includes('?') ? '&' : '?') + objectToUrlParams(init.query);
27!
131
        }
132
        // Serialize body
133
        if (isPlainObject(init.body) || !init.body) {
213✔
134
            let contentTypeHeaderName = findHeaderName(Client._contentType, init.headers);
213✔
135

136
            if (!contentTypeHeaderName) {
213✔
137
                contentTypeHeaderName = Client._contentType;
99✔
138
                init.headers[contentTypeHeaderName] = Client._jsonContentType;
99✔
139
            }
140

141
            const contentType = init.headers[contentTypeHeaderName];
213✔
142

143
            // Assign a new encoded body
144
            if (contentType.includes(Client._jsonContentType)) {
213✔
145
                if ((init.method === 'GET' || init.method === 'HEAD') && !!init.body) {
99!
146
                    // oddly setting body to null still result in TypeError in phantomjs
147
                    init.body = undefined;
×
148
                } else {
149
                    init.body = JSON.stringify(init.body);
99✔
150
                }
151
            } else if (contentType.includes(Client._urlencodedContentType)) {
114✔
152
                init.body = objectToUrlParams(init.body);
114✔
153
            }
154
        }
155

156
        // Create a request with encoded body
157
        const req = new this._externals.Request(init.url, init);
213✔
158

159
        // Keep the original body accessible directly (for mocks)
160
        req.originalBody = init.body;
213✔
161

162
        return req;
213✔
163
    }
164

165
    public _isContentType(contentType, response) {
4✔
166
        return this.getContentType(response).includes(contentType);
9✔
167
    }
168

169
    public getContentType(response) {
4✔
170
        return response.headers.get(Client._contentType) || '';
13!
171
    }
172

173
    public isMultipart(response) {
4✔
174
        return this._isContentType(Client._multipartContentType, response);
8✔
175
    }
176

177
    public isJson(response) {
4✔
178
        return this._isContentType(Client._jsonContentType, response);
1✔
179
    }
180

181
    public async toMultipart(response: Response): Promise<Response[]> {
4✔
182
        return this.isMultipart(response) ? this.multipart(response) : [response];
1!
183
    }
184

185
    public async multipart(response: Response): Promise<Response[]> {
4✔
186
        if (!this.isMultipart(response)) {throw new Error('Response is not multipart');}
6✔
187

188
        // Step 1. Split multipart response
189

190
        const text = await response.text();
5✔
191

192
        if (!text) {throw new Error('No response body');}
5✔
193

194
        let boundary;
195

196
        try {
4✔
197
            boundary = this.getContentType(response).match(/boundary=([^;]+)/i)[1]; //eslint-disable-line
4✔
198
        } catch (e) {
199
            throw new Error('Cannot find boundary');
1✔
200
        }
201

202
        if (!boundary) {throw new Error('Cannot find boundary');}
3!
203

204
        const parts = text.toString().split(Client._boundarySeparator + boundary);
3✔
205

206
        if (parts[0].trim() === '') {parts.shift();}
3✔
207
        if (parts[parts.length - 1].trim() === Client._boundarySeparator) {parts.pop();}
3✔
208

209
        if (parts.length < 1) {throw new Error('No parts in body');}
3!
210

211
        // Step 2. Parse status info
212

213
        const statusInfo = await this._create(parts.shift(), response.status, response.statusText).json();
3✔
214

215
        // Step 3. Parse all other parts
216

217
        return parts.map((part, i) => this._create(part, statusInfo.response[i].status));
5✔
218
    }
219

220
    /**
221
     * Method is used to create Response object from string parts of multipart/mixed response
222
     */
223
    private _create(text = '', status = 200, statusText = 'OK'): Response {
13!
224
        text = text.replace(/\r/g, '');
8✔
225

226
        const headers = new this._externals.Headers();
8✔
227
        const headersAndBody = text.split(Client._bodySeparator);
8✔
228
        const headersText = headersAndBody.length > 1 ? headersAndBody.shift() : '';
8!
229

230
        text = headersAndBody.length > 0 ? headersAndBody.join(Client._bodySeparator) : null;
8!
231

232
        (headersText || '').split('\n').forEach(header => {
8!
233
            const split = header.trim().split(Client._headerSeparator);
16✔
234
            const key = split.shift().trim();
16✔
235
            const value = split.join(Client._headerSeparator).trim();
16✔
236

237
            if (key) {headers.append(key, value);}
16✔
238
        });
239

240
        return new this._externals.Response(text, {
8✔
241
            headers,
242
            status,
243
            statusText,
244
        });
245
    }
246

247
    public async error(response: Response, skipOKCheck = false): Promise<string> {
31✔
248
        if (response.ok && !skipOKCheck) {return null;}
30!
249

250
        let msg = (response.status ? `${response.status} ` : '') + (response.statusText ? response.statusText : '');
30!
251

252
        try {
253
            const {message, error_description, description} = await response.clone().json();
30✔
254

255
            if (message) {msg = message;}
24✔
256
            if (error_description) {msg = error_description;}
24✔
257
            if (description) {msg = description;}
24✔
258
        } catch (e) {} //eslint-disable-line
30✔
259

260
        return msg;
30✔
261
    }
262

263
    public on(event: events.beforeRequest, listener: (request: Request) => void);
264
    public on(event: events.requestSuccess, listener: (response: Response, request: Request) => void);
265
    public on(event: events.requestError, listener: (error: ApiError) => void);
266
    public on(event: string, listener: (...args) => void) {
4✔
267
        return super.on(event, listener);
40✔
268
    }
269
}
4✔
270

271
export interface CreateRequestOptions extends RequestInit {
272
    url?: string;
273
    body?: any;
274
    query?: any;
275
    headers?: any;
276
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc