@openreplay/tracker 14.0.7 → 14.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/bun.lockb +0 -0
  3. package/cjs/app/index.js +5 -1
  4. package/cjs/index.js +1 -1
  5. package/cjs/modules/network.js +4 -2
  6. package/lib/app/index.js +5 -1
  7. package/lib/common/tsconfig.tsbuildinfo +1 -1
  8. package/lib/index.js +1 -1
  9. package/lib/modules/network.js +4 -2
  10. package/package.json +2 -1
  11. package/tsconfig-base.json +2 -2
  12. package/cjs/modules/Network/beaconProxy.d.ts +0 -16
  13. package/cjs/modules/Network/beaconProxy.js +0 -87
  14. package/cjs/modules/Network/fetchProxy.d.ts +0 -34
  15. package/cjs/modules/Network/fetchProxy.js +0 -309
  16. package/cjs/modules/Network/index.d.ts +0 -3
  17. package/cjs/modules/Network/index.js +0 -28
  18. package/cjs/modules/Network/networkMessage.d.ts +0 -49
  19. package/cjs/modules/Network/networkMessage.js +0 -87
  20. package/cjs/modules/Network/types.d.ts +0 -13
  21. package/cjs/modules/Network/types.js +0 -3
  22. package/cjs/modules/Network/utils.d.ts +0 -11
  23. package/cjs/modules/Network/utils.js +0 -216
  24. package/cjs/modules/Network/xhrProxy.d.ts +0 -39
  25. package/cjs/modules/Network/xhrProxy.js +0 -249
  26. package/lib/modules/Network/beaconProxy.d.ts +0 -16
  27. package/lib/modules/Network/beaconProxy.js +0 -79
  28. package/lib/modules/Network/fetchProxy.d.ts +0 -34
  29. package/lib/modules/Network/fetchProxy.js +0 -280
  30. package/lib/modules/Network/index.d.ts +0 -3
  31. package/lib/modules/Network/index.js +0 -22
  32. package/lib/modules/Network/networkMessage.d.ts +0 -49
  33. package/lib/modules/Network/networkMessage.js +0 -83
  34. package/lib/modules/Network/types.d.ts +0 -13
  35. package/lib/modules/Network/types.js +0 -2
  36. package/lib/modules/Network/utils.d.ts +0 -11
  37. package/lib/modules/Network/utils.js +0 -204
  38. package/lib/modules/Network/xhrProxy.d.ts +0 -39
  39. package/lib/modules/Network/xhrProxy.js +0 -221
@@ -1,280 +0,0 @@
1
- /**
2
- * I took inspiration in few stack exchange posts
3
- * and Tencent vConsole library (MIT)
4
- * by wrapping the XMLHttpRequest object in a Proxy
5
- * we can intercept the network requests
6
- * in not-so-hacky way
7
- * */
8
- import NetworkMessage, { RequestState } from './networkMessage.js';
9
- import { formatByteSize, genStringBody, getStringResponseByType, getURL } from './utils.js';
10
- export class ResponseProxyHandler {
11
- constructor(resp, item) {
12
- this.resp = resp;
13
- this.item = item;
14
- this.mockReader();
15
- }
16
- set(target, key, value) {
17
- return Reflect.set(target, key, value);
18
- }
19
- get(target, key) {
20
- const value = Reflect.get(target, key);
21
- switch (key) {
22
- case 'arrayBuffer':
23
- case 'blob':
24
- case 'formData':
25
- case 'json':
26
- case 'text':
27
- return () => {
28
- this.item.responseType = key.toLowerCase();
29
- // @ts-ignore
30
- return value.apply(target).then((resp) => {
31
- this.item.response = getStringResponseByType(this.item.responseType, resp);
32
- return resp;
33
- });
34
- };
35
- }
36
- if (typeof value === 'function') {
37
- return value.bind(target);
38
- }
39
- else {
40
- return value;
41
- }
42
- }
43
- mockReader() {
44
- let readerReceivedValue;
45
- if (!this.resp.body) {
46
- // some browsers do not return `body` in some cases, like `OPTIONS` method
47
- return;
48
- }
49
- if (typeof this.resp.body.getReader !== 'function') {
50
- return;
51
- }
52
- const _getReader = this.resp.body.getReader;
53
- // @ts-ignore
54
- this.resp.body.getReader = () => {
55
- const reader = _getReader.apply(this.resp.body);
56
- // when readyState is already 4,
57
- // it's not a chunked stream, or it had already been read.
58
- // so should not update status.
59
- if (this.item.readyState === RequestState.DONE) {
60
- return reader;
61
- }
62
- const _read = reader.read;
63
- const _cancel = reader.cancel;
64
- this.item.responseType = 'arraybuffer';
65
- // @ts-ignore
66
- reader.read = () => {
67
- return _read.apply(reader).then((result) => {
68
- if (!readerReceivedValue) {
69
- // @ts-ignore
70
- readerReceivedValue = new Uint8Array(result.value);
71
- }
72
- else {
73
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
74
- const newValue = new Uint8Array(readerReceivedValue.length + result.value.length);
75
- newValue.set(readerReceivedValue);
76
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
77
- newValue.set(result.value, readerReceivedValue.length);
78
- readerReceivedValue = newValue;
79
- }
80
- this.item.endTime = performance.now();
81
- this.item.duration = this.item.endTime - (this.item.startTime || this.item.endTime);
82
- this.item.readyState = result.done ? 4 : 3;
83
- this.item.statusText = result.done ? String(this.item.status) : 'Loading';
84
- this.item.responseSize = readerReceivedValue.length;
85
- this.item.responseSizeText = formatByteSize(this.item.responseSize);
86
- if (result.done) {
87
- this.item.response = getStringResponseByType(this.item.responseType, readerReceivedValue);
88
- }
89
- return result;
90
- });
91
- };
92
- reader.cancel = (...args) => {
93
- this.item.cancelState = 2;
94
- this.item.statusText = 'Cancel';
95
- this.item.endTime = performance.now();
96
- this.item.duration = this.item.endTime - (this.item.startTime || this.item.endTime);
97
- this.item.response = getStringResponseByType(this.item.responseType, readerReceivedValue);
98
- return _cancel.apply(reader, args);
99
- };
100
- return reader;
101
- };
102
- }
103
- }
104
- export class FetchProxyHandler {
105
- constructor(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl, tokenUrlMatcher) {
106
- this.ignoredHeaders = ignoredHeaders;
107
- this.setSessionTokenHeader = setSessionTokenHeader;
108
- this.sanitize = sanitize;
109
- this.sendMessage = sendMessage;
110
- this.isServiceUrl = isServiceUrl;
111
- this.tokenUrlMatcher = tokenUrlMatcher;
112
- }
113
- apply(target, _, argsList) {
114
- const input = argsList[0];
115
- const init = argsList[1];
116
- if (!input ||
117
- // @ts-ignore
118
- (typeof input !== 'string' && !input?.url)) {
119
- return target.apply(window, argsList);
120
- }
121
- const isORUrl = input instanceof URL || typeof input === 'string'
122
- ? this.isServiceUrl(String(input))
123
- : this.isServiceUrl(String(input.url));
124
- if (isORUrl) {
125
- return target.apply(window, argsList);
126
- }
127
- const item = new NetworkMessage(this.ignoredHeaders, this.setSessionTokenHeader, this.sanitize);
128
- this.beforeFetch(item, input, init);
129
- this.setSessionTokenHeader((name, value) => {
130
- if (this.tokenUrlMatcher !== undefined) {
131
- if (!this.tokenUrlMatcher(item.url)) {
132
- return;
133
- }
134
- }
135
- if (argsList[1] === undefined && argsList[0] instanceof Request) {
136
- return argsList[0].headers.append(name, value);
137
- }
138
- else {
139
- if (!argsList[1])
140
- argsList[1] = {};
141
- if (argsList[1].headers === undefined) {
142
- argsList[1] = { ...argsList[1], headers: {} };
143
- }
144
- if (argsList[1].headers instanceof Headers) {
145
- argsList[1].headers.append(name, value);
146
- }
147
- else if (Array.isArray(argsList[1].headers)) {
148
- argsList[1].headers.push([name, value]);
149
- }
150
- else {
151
- // @ts-ignore
152
- argsList[1].headers[name] = value;
153
- }
154
- }
155
- });
156
- return target.apply(window, argsList)
157
- .then(this.afterFetch(item))
158
- .catch((e) => {
159
- // mock finally
160
- item.endTime = performance.now();
161
- item.duration = item.endTime - (item.startTime || item.endTime);
162
- throw e;
163
- });
164
- }
165
- beforeFetch(item, input, init) {
166
- let url, method = 'GET', requestHeader = {};
167
- // handle `input` content
168
- if (typeof input === 'string') {
169
- // when `input` is a string
170
- method = init?.method || 'GET';
171
- url = getURL(input);
172
- requestHeader = init?.headers || {};
173
- }
174
- else {
175
- // when `input` is a `Request` object
176
- method = input.method || 'GET';
177
- url = getURL(input.url);
178
- requestHeader = input.headers;
179
- }
180
- item.method = method;
181
- item.requestType = 'fetch';
182
- item.requestHeader = requestHeader;
183
- item.url = url.toString();
184
- item.name = (url.pathname.split('/').pop() || '') + url.search;
185
- item.status = 0;
186
- item.statusText = 'Pending';
187
- item.readyState = 1;
188
- if (!item.startTime) {
189
- // UNSENT
190
- item.startTime = performance.now();
191
- }
192
- if (Object.prototype.toString.call(requestHeader) === '[object Headers]') {
193
- item.requestHeader = {};
194
- for (const [key, value] of requestHeader) {
195
- item.requestHeader[key] = value;
196
- }
197
- }
198
- else {
199
- item.requestHeader = requestHeader;
200
- }
201
- // save GET data
202
- if (url.search && url.searchParams) {
203
- item.getData = {};
204
- for (const [key, value] of url.searchParams) {
205
- item.getData[key] = value;
206
- }
207
- }
208
- // save POST data
209
- if (init?.body) {
210
- item.requestData = genStringBody(init.body);
211
- }
212
- }
213
- afterFetch(item) {
214
- return (resp) => {
215
- item.endTime = performance.now();
216
- item.duration = item.endTime - (item.startTime || item.endTime);
217
- item.status = resp.status;
218
- item.statusText = String(resp.status);
219
- let isChunked = false;
220
- item.header = {};
221
- for (const [key, value] of resp.headers) {
222
- item.header[key] = value;
223
- isChunked = value.toLowerCase().indexOf('chunked') > -1 ? true : isChunked;
224
- }
225
- if (isChunked) {
226
- // when `transfer-encoding` is chunked, the response is a stream which is under loading,
227
- // so the `readyState` should be 3 (Loading),
228
- // and the response should NOT be `clone()` which will affect stream reading.
229
- item.readyState = 3;
230
- }
231
- else {
232
- // Otherwise, not chunked, the response is not a stream,
233
- // so it's completed and can be cloned for `text()` calling.
234
- item.readyState = 4;
235
- this.handleResponseBody(resp.clone(), item)
236
- .then((responseValue) => {
237
- item.responseSize =
238
- typeof responseValue === 'string' ? responseValue.length : responseValue.byteLength;
239
- item.responseSizeText = formatByteSize(item.responseSize);
240
- item.response = getStringResponseByType(item.responseType, responseValue);
241
- const msg = item.getMessage();
242
- if (msg) {
243
- this.sendMessage(msg);
244
- }
245
- })
246
- .catch((e) => {
247
- if (e.name !== 'AbortError') {
248
- throw e;
249
- }
250
- else {
251
- // ignore AbortError
252
- }
253
- });
254
- }
255
- return new Proxy(resp, new ResponseProxyHandler(resp, item));
256
- };
257
- }
258
- handleResponseBody(resp, item) {
259
- // parse response body by Content-Type
260
- const contentType = resp.headers.get('content-type');
261
- if (contentType && contentType.includes('application/json')) {
262
- item.responseType = 'json';
263
- return resp.text();
264
- }
265
- else if (contentType &&
266
- (contentType.includes('text/html') || contentType.includes('text/plain'))) {
267
- item.responseType = 'text';
268
- return resp.text();
269
- }
270
- else {
271
- item.responseType = 'arraybuffer';
272
- return resp.arrayBuffer();
273
- }
274
- }
275
- }
276
- export default class FetchProxy {
277
- static create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl, tokenUrlMatcher) {
278
- return new Proxy(fetch, new FetchProxyHandler(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl, tokenUrlMatcher));
279
- }
280
- }
@@ -1,3 +0,0 @@
1
- import { RequestResponseData } from './types.js';
2
- import { NetworkRequest } from '../../common/messages.gen.js';
3
- export default function setProxy(context: typeof globalThis, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean): void;
@@ -1,22 +0,0 @@
1
- import FetchProxy from './fetchProxy.js';
2
- import XHRProxy from './xhrProxy.js';
3
- import BeaconProxy from './beaconProxy.js';
4
- const getWarning = (api) => console.warn(`Openreplay: Can't find ${api} in global context.
5
- If you're using serverside rendering in your app, make sure that tracker is loaded dynamically, otherwise ${api} won't be tracked.`);
6
- export default function setProxy(context, ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl, tokenUrlMatcher) {
7
- if (context.XMLHttpRequest) {
8
- context.XMLHttpRequest = XHRProxy.create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl, tokenUrlMatcher);
9
- }
10
- else {
11
- getWarning('XMLHttpRequest');
12
- }
13
- if (context.fetch) {
14
- context.fetch = FetchProxy.create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl, tokenUrlMatcher);
15
- }
16
- else {
17
- getWarning('fetch');
18
- }
19
- if (context?.navigator?.sendBeacon) {
20
- context.navigator.sendBeacon = BeaconProxy.create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl);
21
- }
22
- }
@@ -1,49 +0,0 @@
1
- import { RequestResponseData } from './types.js';
2
- export type httpMethod = '' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH';
3
- export declare enum RequestState {
4
- UNSENT = 0,
5
- OPENED = 1,
6
- HEADERS_RECEIVED = 2,
7
- LOADING = 3,
8
- DONE = 4
9
- }
10
- /**
11
- * I know we're not using most of the information from this class
12
- * but it can be useful in the future if we will decide to display more stuff in our ui
13
- * */
14
- export default class NetworkMessage {
15
- private readonly ignoredHeaders;
16
- private readonly setSessionTokenHeader;
17
- private readonly sanitize;
18
- id: string;
19
- name?: string;
20
- method: httpMethod;
21
- url: string;
22
- status: number;
23
- statusText?: string;
24
- cancelState?: 0 | 1 | 2 | 3;
25
- readyState?: RequestState;
26
- header: {
27
- [key: string]: string;
28
- };
29
- responseType: XMLHttpRequest['responseType'];
30
- requestType: 'xhr' | 'fetch' | 'ping' | 'custom' | 'beacon';
31
- requestHeader: HeadersInit;
32
- response: any;
33
- responseSize: number;
34
- responseSizeText: string;
35
- startTime: number;
36
- endTime: number;
37
- duration: number;
38
- getData: {
39
- [key: string]: string;
40
- };
41
- requestData: string | null;
42
- constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null);
43
- getMessage(): import("../../common/messages.gen.js").NetworkRequest | undefined;
44
- writeHeaders(): {
45
- reqHs: Record<string, string>;
46
- resHs: Record<string, string>;
47
- };
48
- isHeaderIgnored(key: string): boolean;
49
- }
@@ -1,83 +0,0 @@
1
- import { NetworkRequest } from '../../app/messages.gen.js';
2
- import { getTimeOrigin } from '../../utils.js';
3
- export var RequestState;
4
- (function (RequestState) {
5
- RequestState[RequestState["UNSENT"] = 0] = "UNSENT";
6
- RequestState[RequestState["OPENED"] = 1] = "OPENED";
7
- RequestState[RequestState["HEADERS_RECEIVED"] = 2] = "HEADERS_RECEIVED";
8
- RequestState[RequestState["LOADING"] = 3] = "LOADING";
9
- RequestState[RequestState["DONE"] = 4] = "DONE";
10
- })(RequestState || (RequestState = {}));
11
- /**
12
- * I know we're not using most of the information from this class
13
- * but it can be useful in the future if we will decide to display more stuff in our ui
14
- * */
15
- export default class NetworkMessage {
16
- constructor(ignoredHeaders = [], setSessionTokenHeader, sanitize) {
17
- this.ignoredHeaders = ignoredHeaders;
18
- this.setSessionTokenHeader = setSessionTokenHeader;
19
- this.sanitize = sanitize;
20
- this.id = '';
21
- this.name = '';
22
- this.method = '';
23
- this.url = '';
24
- this.status = 0;
25
- this.statusText = '';
26
- this.cancelState = 0;
27
- this.readyState = 0;
28
- this.header = {};
29
- this.responseType = '';
30
- this.requestHeader = {};
31
- this.responseSize = 0; // bytes
32
- this.responseSizeText = '';
33
- this.startTime = 0;
34
- this.endTime = 0;
35
- this.duration = 0;
36
- this.getData = {};
37
- this.requestData = null;
38
- }
39
- getMessage() {
40
- const { reqHs, resHs } = this.writeHeaders();
41
- const request = {
42
- headers: reqHs,
43
- body: this.method === 'GET' ? JSON.stringify(this.getData) : this.requestData,
44
- };
45
- const response = { headers: resHs, body: this.response };
46
- const messageInfo = this.sanitize({
47
- url: this.url,
48
- method: this.method,
49
- status: this.status,
50
- request,
51
- response,
52
- });
53
- if (!messageInfo)
54
- return;
55
- return NetworkRequest(this.requestType, messageInfo.method, messageInfo.url, JSON.stringify(messageInfo.request), JSON.stringify(messageInfo.response), messageInfo.status, this.startTime + getTimeOrigin(), this.duration, this.responseSize);
56
- }
57
- writeHeaders() {
58
- const reqHs = {};
59
- Object.entries(this.requestHeader).forEach(([key, value]) => {
60
- if (this.isHeaderIgnored(key))
61
- return;
62
- reqHs[key] = value;
63
- });
64
- this.setSessionTokenHeader((name, value) => {
65
- reqHs[name] = value;
66
- });
67
- const resHs = {};
68
- Object.entries(this.header).forEach(([key, value]) => {
69
- if (this.isHeaderIgnored(key))
70
- return;
71
- resHs[key] = value;
72
- });
73
- return { reqHs, resHs };
74
- }
75
- isHeaderIgnored(key) {
76
- if (Array.isArray(this.ignoredHeaders)) {
77
- return this.ignoredHeaders.map((k) => k.toLowerCase()).includes(key.toLowerCase());
78
- }
79
- else {
80
- return this.ignoredHeaders;
81
- }
82
- }
83
- }
@@ -1,13 +0,0 @@
1
- export interface RequestResponseData {
2
- readonly status: number;
3
- readonly method: string;
4
- url: string;
5
- request: {
6
- body: string | null;
7
- headers: Record<string, string>;
8
- };
9
- response: {
10
- body: string | null;
11
- headers: Record<string, string>;
12
- };
13
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- // we only support sanitizing for json/string data because how you're gonna sanitize binary data?
@@ -1,11 +0,0 @@
1
- export declare const genResponseByType: (responseType: XMLHttpRequest['responseType'], response: any) => string | Record<string, any>;
2
- export declare const getStringResponseByType: (responseType: XMLHttpRequest['responseType'], response: any) => string;
3
- export declare const genStringBody: (body?: BodyInit) => string | null;
4
- export declare const genGetDataByUrl: (url: string, getData?: Record<string, any>) => Record<string, any>;
5
- export declare const genFormattedBody: (body?: BodyInit) => string | {
6
- [key: string]: string;
7
- } | null;
8
- export declare function isPureObject(input: any): input is Record<any, any>;
9
- export declare function isIterable(value: any): boolean;
10
- export declare function formatByteSize(bytes: number): string;
11
- export declare const getURL: (urlString: string) => URL;
@@ -1,204 +0,0 @@
1
- export const genResponseByType = (responseType, response) => {
2
- let result = '';
3
- switch (responseType) {
4
- case '':
5
- case 'text':
6
- case 'json':
7
- if (typeof response == 'string') {
8
- try {
9
- result = JSON.parse(response);
10
- }
11
- catch (e) {
12
- // not a JSON string
13
- result = response.slice(0, 10000);
14
- }
15
- }
16
- else if (isPureObject(response) || Array.isArray(response)) {
17
- result = JSON.stringify(response);
18
- }
19
- else if (typeof response !== 'undefined') {
20
- result = Object.prototype.toString.call(response);
21
- }
22
- break;
23
- case 'blob':
24
- case 'document':
25
- case 'arraybuffer':
26
- default:
27
- if (typeof response !== 'undefined') {
28
- result = Object.prototype.toString.call(response);
29
- }
30
- break;
31
- }
32
- return result;
33
- };
34
- export const getStringResponseByType = (responseType, response) => {
35
- let result = '';
36
- switch (responseType) {
37
- case '':
38
- case 'text':
39
- case 'json':
40
- if (typeof response == 'string') {
41
- result = response;
42
- }
43
- else if (isPureObject(response) || Array.isArray(response)) {
44
- result = JSON.stringify(response);
45
- }
46
- else if (typeof response !== 'undefined') {
47
- result = Object.prototype.toString.call(response);
48
- }
49
- break;
50
- case 'blob':
51
- case 'document':
52
- case 'arraybuffer':
53
- default:
54
- if (typeof response !== 'undefined') {
55
- result = Object.prototype.toString.call(response);
56
- }
57
- break;
58
- }
59
- return result;
60
- };
61
- export const genStringBody = (body) => {
62
- if (!body) {
63
- return null;
64
- }
65
- let result;
66
- if (typeof body === 'string') {
67
- if (body[0] === '{' || body[0] === '[') {
68
- result = body;
69
- }
70
- // 'a=1&b=2' => try to parse as query
71
- const arr = body.split('&');
72
- if (arr.length === 1) {
73
- // not a query, parse as original string
74
- result = body;
75
- }
76
- else {
77
- // 'a=1&b=2&c' => parse as query
78
- result = arr.join(',');
79
- }
80
- }
81
- else if (isIterable(body)) {
82
- // FormData or URLSearchParams or Array
83
- const arr = [];
84
- for (const [key, value] of body) {
85
- arr.push(`${key}=${typeof value === 'string' ? value : '[object Object]'}`);
86
- }
87
- result = arr.join(',');
88
- }
89
- else if (body instanceof Blob ||
90
- body instanceof ReadableStream ||
91
- body instanceof ArrayBuffer) {
92
- result = 'byte data';
93
- }
94
- else if (isPureObject(body)) {
95
- // overriding ArrayBufferView which is not convertable to string
96
- result = body;
97
- }
98
- else {
99
- result = `can't parse body ${typeof body}`;
100
- }
101
- return result;
102
- };
103
- export const genGetDataByUrl = (url, getData = {}) => {
104
- if (!isPureObject(getData)) {
105
- getData = {};
106
- }
107
- let query = url ? url.split('?') : []; // a.php?b=c&d=?e => ['a.php', 'b=c&d=', 'e']
108
- query.shift(); // => ['b=c&d=', 'e']
109
- if (query.length > 0) {
110
- query = query.join('?').split('&'); // => 'b=c&d=?e' => ['b=c', 'd=?e']
111
- for (const q of query) {
112
- const kv = q.split('=');
113
- try {
114
- getData[kv[0]] = decodeURIComponent(kv[1]);
115
- }
116
- catch (e) {
117
- // "URIError: URI malformed" will be thrown when `kv[1]` contains "%", so just use raw data
118
- // @issue #470
119
- // @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Malformed_URI
120
- getData[kv[0]] = kv[1];
121
- }
122
- }
123
- }
124
- return getData;
125
- };
126
- export const genFormattedBody = (body) => {
127
- if (!body) {
128
- return null;
129
- }
130
- let result;
131
- if (typeof body === 'string') {
132
- try {
133
- // '{a:1}' =>
134
- result = JSON.parse(body);
135
- }
136
- catch (e) {
137
- // 'a=1&b=2' => try to parse as query
138
- const arr = body.split('&');
139
- result = {};
140
- // eslint-disable-next-line
141
- for (let q of arr) {
142
- const kv = q.split('=');
143
- result[kv[0]] = kv[1] === undefined ? 'undefined' : kv[1];
144
- }
145
- }
146
- }
147
- else if (isIterable(body)) {
148
- // FormData or URLSearchParams or Array
149
- result = {};
150
- for (const [key, value] of body) {
151
- result[key] = typeof value === 'string' ? value : '[object Object]';
152
- }
153
- }
154
- else if (ArrayBuffer.isView(body) ||
155
- body instanceof Blob ||
156
- body instanceof ReadableStream ||
157
- body instanceof ArrayBuffer) {
158
- result = '[byte data]';
159
- }
160
- else if (isPureObject(body)) {
161
- result = body;
162
- }
163
- else {
164
- result = `can't parse body ${typeof body}`;
165
- }
166
- return result;
167
- };
168
- export function isPureObject(input) {
169
- return null !== input && typeof input === 'object';
170
- }
171
- export function isIterable(value) {
172
- if (value === null || value === undefined) {
173
- return false;
174
- }
175
- if (ArrayBuffer.isView(value)) {
176
- return false;
177
- }
178
- return typeof Symbol !== 'undefined' && typeof value[Symbol.iterator] === 'function';
179
- }
180
- export function formatByteSize(bytes) {
181
- if (bytes <= 0) {
182
- // shouldn't happen?
183
- return '';
184
- }
185
- if (bytes >= 1000 * 1000) {
186
- return (bytes / 1000 / 1000).toFixed(1) + ' MB';
187
- }
188
- if (bytes >= 1000) {
189
- return (bytes / 1000).toFixed(1) + ' KB';
190
- }
191
- return `${bytes}B`;
192
- }
193
- export const getURL = (urlString) => {
194
- if (urlString.startsWith('//')) {
195
- const baseUrl = new URL(window.location.href);
196
- urlString = `${baseUrl.protocol}${urlString}`;
197
- }
198
- if (urlString.startsWith('http')) {
199
- return new URL(urlString);
200
- }
201
- else {
202
- return new URL(urlString, window.location.href);
203
- }
204
- };