Skip to content

Commit 8ea71ba

Browse files
committed
refactor(browser-utils): Move getBodyString
- Also moved `NetworkMetaWarning` type. Signed-off-by: Kaung Zin Hein <[email protected]>
1 parent ff49b03 commit 8ea71ba

File tree

15 files changed

+153
-97
lines changed

15 files changed

+153
-97
lines changed

packages/browser-utils/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@
5656
"clean": "rimraf build coverage sentry-internal-browser-utils-*.tgz",
5757
"fix": "eslint . --format stylish --fix",
5858
"lint": "eslint . --format stylish",
59-
"test:unit": "jest",
60-
"test": "jest",
61-
"test:watch": "jest --watch",
59+
"test:unit": "vitest run",
60+
"test": "vitest run",
61+
"test:watch": "vitest --watch",
6262
"yalc:publish": "yalc publish --push --sig"
6363
},
6464
"volta": {

packages/browser-utils/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ export { addHistoryInstrumentationHandler } from './instrument/history';
2424
export { fetch, setTimeout, clearCachedImplementation, getNativeImplementation } from './getNativeImplementation';
2525

2626
export { addXhrInstrumentationHandler, SENTRY_XHR_DATA_KEY } from './instrument/xhr';
27+
28+
export type { NetworkMetaWarning } from './types';
29+
30+
export { getBodyString } from './networkUtils';
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { ConsoleLevel, Logger } from '@sentry/core';
2+
import { DEBUG_BUILD } from './debug-build';
3+
import type { NetworkMetaWarning } from './types';
4+
5+
type ReplayConsoleLevels = Extract<ConsoleLevel, 'info' | 'warn' | 'error' | 'log'>;
6+
type LoggerMethod = (...args: unknown[]) => void;
7+
type LoggerConsoleMethods = Record<ReplayConsoleLevels, LoggerMethod>;
8+
9+
interface LoggerConfig {
10+
captureExceptions: boolean;
11+
traceInternals: boolean;
12+
}
13+
14+
// Duplicate from replay-internal
15+
interface ReplayLogger extends LoggerConsoleMethods {
16+
/**
17+
* Calls `logger.info` but saves breadcrumb in the next tick due to race
18+
* conditions before replay is initialized.
19+
*/
20+
infoTick: LoggerMethod;
21+
/**
22+
* Captures exceptions (`Error`) if "capture internal exceptions" is enabled
23+
*/
24+
exception: LoggerMethod;
25+
/**
26+
* Configures the logger with additional debugging behavior
27+
*/
28+
setConfig(config: Partial<LoggerConfig>): void;
29+
}
30+
31+
function _serializeFormData(formData: FormData): string {
32+
// This is a bit simplified, but gives us a decent estimate
33+
// This converts e.g. { name: 'Anne Smith', age: 13 } to 'name=Anne+Smith&age=13'
34+
// @ts-expect-error passing FormData to URLSearchParams actually works
35+
return new URLSearchParams(formData).toString();
36+
}
37+
38+
/** Get the string representation of a body. */
39+
export function getBodyString(
40+
body: unknown,
41+
logger?: Logger | ReplayLogger,
42+
): [string | undefined, NetworkMetaWarning?] {
43+
try {
44+
if (typeof body === 'string') {
45+
return [body];
46+
}
47+
48+
if (body instanceof URLSearchParams) {
49+
return [body.toString()];
50+
}
51+
52+
if (body instanceof FormData) {
53+
return [_serializeFormData(body)];
54+
}
55+
56+
if (!body) {
57+
return [undefined];
58+
}
59+
} catch (error) {
60+
// RelayLogger
61+
if (DEBUG_BUILD && logger && 'exception' in logger) {
62+
logger.exception(error, 'Failed to serialize body', body);
63+
} else if (DEBUG_BUILD && logger) {
64+
logger.error(error, 'Failed to serialize body', body);
65+
}
66+
return [undefined, 'BODY_PARSE_ERROR'];
67+
}
68+
69+
DEBUG_BUILD && logger?.info('Skipping network body because of body type', body);
70+
71+
return [undefined, 'UNPARSEABLE_BODY_TYPE'];
72+
}

packages/browser-utils/src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@ export const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ &
44
// document is not available in all browser environments (webworkers). We make it optional so you have to explicitly check for it
55
Omit<Window, 'document'> &
66
Partial<Pick<Window, 'document'>>;
7+
8+
export type NetworkMetaWarning =
9+
| 'MAYBE_JSON_TRUNCATED'
10+
| 'TEXT_TRUNCATED'
11+
| 'URL_SKIPPED'
12+
| 'BODY_PARSE_ERROR'
13+
| 'BODY_PARSE_TIMEOUT'
14+
| 'UNPARSEABLE_BODY_TYPE';
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
5+
import { describe, expect, it } from 'vitest';
6+
import { getBodyString } from '../src/networkUtils';
7+
8+
describe('getBodyString', () => {
9+
it('works with a string', () => {
10+
const actual = getBodyString('abc');
11+
expect(actual).toEqual(['abc']);
12+
});
13+
14+
it('works with URLSearchParams', () => {
15+
const body = new URLSearchParams();
16+
body.append('name', 'Anne');
17+
body.append('age', '32');
18+
const actual = getBodyString(body);
19+
expect(actual).toEqual(['name=Anne&age=32']);
20+
});
21+
22+
it('works with FormData', () => {
23+
const body = new FormData();
24+
body.append('name', 'Anne');
25+
body.append('age', '32');
26+
const actual = getBodyString(body);
27+
expect(actual).toEqual(['name=Anne&age=32']);
28+
});
29+
30+
it('works with empty data', () => {
31+
const body = undefined;
32+
const actual = getBodyString(body);
33+
expect(actual).toEqual([undefined]);
34+
});
35+
36+
it('works with other type of data', () => {
37+
const body = {};
38+
const actual = getBodyString(body);
39+
expect(actual).toEqual([undefined, 'UNPARSEABLE_BODY_TYPE']);
40+
});
41+
});

packages/browser-utils/tsconfig.test.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"extends": "./tsconfig.json",
33

4-
"include": ["test/**/*"],
4+
"include": ["test/**/*", "vite.config.ts"],
55

66
"compilerOptions": {
77
// should include all types from `./tsconfig.json` plus types for all test frameworks used
8-
"types": ["node", "jest"]
8+
"types": ["node", "jest", "vitest"]
99

1010
// other package-specific, test-specific options
1111
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineConfig } from 'vitest/config';
2+
3+
import baseConfig from '../../vite/vite.config';
4+
5+
export default defineConfig({
6+
...baseConfig,
7+
test: {
8+
...baseConfig.test,
9+
},
10+
});

packages/browser/src/integrations/graphqlClient.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { SENTRY_XHR_DATA_KEY } from '@sentry-internal/browser-utils';
2-
import { getBodyString } from '@sentry-internal/replay';
1+
import { SENTRY_XHR_DATA_KEY, getBodyString } from '@sentry-internal/browser-utils';
32
import type { FetchHint, XhrHint } from '@sentry-internal/replay';
43
import {
54
SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD,

packages/core/src/utils-hoist/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export {
3838
} from './is';
3939
export { isBrowser } from './isBrowser';
4040
export { CONSOLE_LEVELS, consoleSandbox, logger, originalConsoleMethods } from './logger';
41+
export type { Logger } from './logger';
42+
4143
export {
4244
addContextToFrame,
4345
addExceptionMechanism,

packages/replay-internal/src/coreHandlers/util/fetchUtils.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { setTimeout } from '@sentry-internal/browser-utils';
1+
import { getBodyString, setTimeout } from '@sentry-internal/browser-utils';
2+
import type { NetworkMetaWarning } from '@sentry-internal/browser-utils';
23
import type { Breadcrumb, FetchBreadcrumbData } from '@sentry/core';
34

45
import { DEBUG_BUILD } from '../../debug-build';
56
import type {
67
FetchHint,
7-
NetworkMetaWarning,
88
ReplayContainer,
99
ReplayNetworkOptions,
1010
ReplayNetworkRequestData,
@@ -17,7 +17,6 @@ import {
1717
buildSkippedNetworkRequestOrResponse,
1818
getAllowedHeaders,
1919
getBodySize,
20-
getBodyString,
2120
makeNetworkReplayBreadcrumb,
2221
mergeWarning,
2322
parseContentLengthHeader,
@@ -118,7 +117,7 @@ function _getRequestInfo(
118117

119118
// We only want to transmit string or string-like bodies
120119
const requestBody = _getFetchRequestArgBody(input);
121-
const [bodyStr, warning] = getBodyString(requestBody);
120+
const [bodyStr, warning] = getBodyString(requestBody, logger);
122121
const data = buildNetworkRequestOrResponse(headers, requestBodySize, bodyStr);
123122

124123
if (warning) {

0 commit comments

Comments
 (0)