@flareapp/core 2.2.0 → 2.2.1
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.
- package/dist/index.cjs +806 -0
- package/dist/index.d.cts +411 -0
- package/dist/index.d.mts +411 -0
- package/dist/index.mjs +760 -0
- package/package.json +4 -1
- package/.oxlintrc.json +0 -7
- package/.release-it.json +0 -13
- package/CHANGELOG.md +0 -16
- package/src/Flare.ts +0 -543
- package/src/Scope.ts +0 -96
- package/src/api/Api.ts +0 -35
- package/src/api/index.ts +0 -1
- package/src/env/index.ts +0 -14
- package/src/index.ts +0 -41
- package/src/stacktrace/NullFileReader.ts +0 -28
- package/src/stacktrace/createStackTrace.ts +0 -74
- package/src/stacktrace/fileReader.ts +0 -96
- package/src/stacktrace/index.ts +0 -4
- package/src/types.ts +0 -81
- package/src/util/assert.ts +0 -9
- package/src/util/assertKey.ts +0 -11
- package/src/util/convertToError.ts +0 -22
- package/src/util/extractCode.ts +0 -11
- package/src/util/flatJsonStringify.ts +0 -45
- package/src/util/glowsToEvents.ts +0 -16
- package/src/util/index.ts +0 -8
- package/src/util/now.ts +0 -3
- package/src/util/redactUrl.ts +0 -83
- package/tests/api.test.ts +0 -95
- package/tests/configure.test.ts +0 -16
- package/tests/contextCollector.test.ts +0 -37
- package/tests/convertToError.test.ts +0 -95
- package/tests/createStackTrace.test.ts +0 -54
- package/tests/extractCode.test.ts +0 -30
- package/tests/fileReader.test.ts +0 -51
- package/tests/flatJsonStringify.test.ts +0 -31
- package/tests/flush.test.ts +0 -47
- package/tests/glows.test.ts +0 -47
- package/tests/glowsToEvents.test.ts +0 -41
- package/tests/helpers/FakeApi.ts +0 -20
- package/tests/helpers/index.ts +0 -1
- package/tests/hooks.test.ts +0 -123
- package/tests/light.test.ts +0 -25
- package/tests/nullFileReader.test.ts +0 -11
- package/tests/publicExports.test.ts +0 -17
- package/tests/redactUrl.test.ts +0 -151
- package/tests/report.test.ts +0 -146
- package/tests/sampleRate.test.ts +0 -88
- package/tests/scope.test.ts +0 -64
- package/tests/setEntryPoint.test.ts +0 -79
- package/tests/setFramework.test.ts +0 -48
- package/tests/setSdkInfo.test.ts +0 -62
- package/tsconfig.json +0 -4
- package/vitest.config.ts +0 -17
package/src/api/Api.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Report } from '../types';
|
|
2
|
-
import { flatJsonStringify } from '../util';
|
|
3
|
-
|
|
4
|
-
export class Api {
|
|
5
|
-
report(
|
|
6
|
-
report: Report,
|
|
7
|
-
url: string,
|
|
8
|
-
key: string | null,
|
|
9
|
-
reportBrowserExtensionErrors: boolean,
|
|
10
|
-
debug: boolean = false,
|
|
11
|
-
): Promise<void> {
|
|
12
|
-
return fetch(url, {
|
|
13
|
-
method: 'POST',
|
|
14
|
-
headers: {
|
|
15
|
-
'Accept': 'application/json',
|
|
16
|
-
'Content-Type': 'application/json',
|
|
17
|
-
'X-Api-Token': key ?? '',
|
|
18
|
-
'X-Report-Browser-Extension-Errors': JSON.stringify(reportBrowserExtensionErrors),
|
|
19
|
-
'X-Flare-Client-Version': '2',
|
|
20
|
-
},
|
|
21
|
-
body: flatJsonStringify(report),
|
|
22
|
-
}).then(
|
|
23
|
-
(response) => {
|
|
24
|
-
if (debug && response.status !== 201) {
|
|
25
|
-
console.error(`Received response with status ${response.status} from Flare`);
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
(error) => {
|
|
29
|
-
if (debug) {
|
|
30
|
-
console.error(error);
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
}
|
package/src/api/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Api } from './Api';
|
package/src/env/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
declare const FLARE_JS_KEY: string | undefined;
|
|
2
|
-
declare const FLARE_SOURCEMAP_VERSION: string | undefined;
|
|
3
|
-
|
|
4
|
-
// Injected during build
|
|
5
|
-
export const CLIENT_VERSION =
|
|
6
|
-
typeof process !== 'undefined' && typeof process.env?.FLARE_JS_CLIENT_VERSION !== 'undefined'
|
|
7
|
-
? process.env.FLARE_JS_CLIENT_VERSION
|
|
8
|
-
: '?';
|
|
9
|
-
|
|
10
|
-
// Injected by flare-vite-plugin-sourcemap-uploader (optional)
|
|
11
|
-
export const KEY = typeof FLARE_JS_KEY === 'undefined' ? '' : FLARE_JS_KEY;
|
|
12
|
-
|
|
13
|
-
// Injected by flare-vite-plugin-sourcemap-uploader (optional)
|
|
14
|
-
export const SOURCEMAP_VERSION = typeof FLARE_SOURCEMAP_VERSION === 'undefined' ? '' : FLARE_SOURCEMAP_VERSION;
|
package/src/index.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
AttributeValue,
|
|
3
|
-
Attributes,
|
|
4
|
-
Config,
|
|
5
|
-
EntryPointHandler,
|
|
6
|
-
Framework,
|
|
7
|
-
Glow,
|
|
8
|
-
MessageLevel,
|
|
9
|
-
OverriddenGrouping,
|
|
10
|
-
Report,
|
|
11
|
-
SdkInfo,
|
|
12
|
-
SpanEvent,
|
|
13
|
-
StackFrame,
|
|
14
|
-
} from './types';
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
assert,
|
|
18
|
-
assertKey,
|
|
19
|
-
convertToError,
|
|
20
|
-
DEFAULT_URL_DENYLIST,
|
|
21
|
-
extractCode,
|
|
22
|
-
flatJsonStringify,
|
|
23
|
-
glowsToEvents,
|
|
24
|
-
now,
|
|
25
|
-
redactUrlQuery,
|
|
26
|
-
resolveDenylist,
|
|
27
|
-
} from './util';
|
|
28
|
-
|
|
29
|
-
export { Api } from './api';
|
|
30
|
-
|
|
31
|
-
export { Flare } from './Flare';
|
|
32
|
-
export type { ContextCollector } from './Flare';
|
|
33
|
-
|
|
34
|
-
export { Scope, GlobalScopeProvider } from './Scope';
|
|
35
|
-
export type { ScopeProvider } from './Scope';
|
|
36
|
-
|
|
37
|
-
export { NullFileReader } from './stacktrace/NullFileReader';
|
|
38
|
-
export type { FileReader } from './stacktrace/fileReader';
|
|
39
|
-
|
|
40
|
-
export { createStackTrace } from './stacktrace/createStackTrace';
|
|
41
|
-
export { getCodeSnippet, readLinesFromFile } from './stacktrace/fileReader';
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { FileReader } from './fileReader';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* No-op `FileReader` that returns `null` for every URL it is asked to read.
|
|
5
|
-
*
|
|
6
|
-
* Used as the default for `Flare`'s `fileReader` constructor parameter so the
|
|
7
|
-
* class is usable without picking a side: instantiated bare (`new Flare()`),
|
|
8
|
-
* reports still build, but stack frames omit source-code snippets — which is
|
|
9
|
-
* the correct, safe behavior in an environment we know nothing about.
|
|
10
|
-
*
|
|
11
|
-
* The two real implementations live in the consumer packages and take their
|
|
12
|
-
* place once the right environment is established:
|
|
13
|
-
*
|
|
14
|
-
* - `@flareapp/js` injects `FetchFileReader`, which `fetch()`s source maps
|
|
15
|
-
* and original files over HTTP for browser stack frames.
|
|
16
|
-
* - `@flareapp/node` injects `DiskFileReader`, which reads files from disk
|
|
17
|
-
* via `node:fs/promises` for server stack frames.
|
|
18
|
-
*
|
|
19
|
-
* The interface (`read(url) -> Promise<string | null>`) lets the stack-trace
|
|
20
|
-
* builder treat all three the same way: ask for a URL, render the snippet
|
|
21
|
-
* when text comes back, gracefully skip it when `null` does. No environment
|
|
22
|
-
* checks anywhere in core.
|
|
23
|
-
*/
|
|
24
|
-
export class NullFileReader implements FileReader {
|
|
25
|
-
read(_url: string): Promise<string | null> {
|
|
26
|
-
return Promise.resolve(null);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import ErrorStackParser from 'error-stack-parser';
|
|
2
|
-
|
|
3
|
-
import { StackFrame } from '../types';
|
|
4
|
-
import { assert } from '../util';
|
|
5
|
-
import type { FileReader } from './fileReader';
|
|
6
|
-
import { getCodeSnippet } from './fileReader';
|
|
7
|
-
|
|
8
|
-
export function createStackTrace(error: Error, debug: boolean, fileReader: FileReader): Promise<Array<StackFrame>> {
|
|
9
|
-
return new Promise((resolve) => {
|
|
10
|
-
if (!hasStack(error)) {
|
|
11
|
-
return resolve([fallbackFrame('stacktrace missing')]);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let parsedFrames;
|
|
15
|
-
try {
|
|
16
|
-
parsedFrames = ErrorStackParser.parse(error);
|
|
17
|
-
} catch (parseError) {
|
|
18
|
-
assert(false, "Couldn't parse stacktrace of below error:", debug);
|
|
19
|
-
if (debug) {
|
|
20
|
-
console.error(parseError);
|
|
21
|
-
console.error(error);
|
|
22
|
-
}
|
|
23
|
-
return resolve([fallbackFrame('stacktrace could not be parsed')]);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
Promise.all(
|
|
27
|
-
parsedFrames.map((frame) => {
|
|
28
|
-
return getCodeSnippet(fileReader, frame.fileName, frame.lineNumber, frame.columnNumber).then(
|
|
29
|
-
(snippet) => ({
|
|
30
|
-
lineNumber: frame.lineNumber || 1,
|
|
31
|
-
columnNumber: frame.columnNumber || 1,
|
|
32
|
-
method: frame.functionName || 'Anonymous or unknown function',
|
|
33
|
-
file: frame.fileName || 'Unknown file',
|
|
34
|
-
codeSnippet: snippet.codeSnippet,
|
|
35
|
-
class: '',
|
|
36
|
-
isApplicationFrame: isApplicationFrame(frame.fileName),
|
|
37
|
-
}),
|
|
38
|
-
);
|
|
39
|
-
}),
|
|
40
|
-
).then(resolve);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function fallbackFrame(reason: string): StackFrame {
|
|
45
|
-
return {
|
|
46
|
-
lineNumber: 0,
|
|
47
|
-
columnNumber: 0,
|
|
48
|
-
method: 'unknown',
|
|
49
|
-
file: 'unknown',
|
|
50
|
-
codeSnippet: { 0: `Could not read from file: ${reason}` },
|
|
51
|
-
class: 'unknown',
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Some engines populate `err.stack` with just `"<Name>: <message>"` (no frames) when an Error is
|
|
56
|
-
// constructed but never thrown. Treat that as "no stack" so we fall back instead of parsing garbage.
|
|
57
|
-
// Also accepts the legacy `stacktrace` and Opera `opera#sourceloc` properties.
|
|
58
|
-
function hasStack(err: unknown): boolean {
|
|
59
|
-
if (!err || typeof err !== 'object') return false;
|
|
60
|
-
const e = err as Record<string, unknown>;
|
|
61
|
-
const stack = e.stack ?? e.stacktrace ?? e['opera#sourceloc'];
|
|
62
|
-
return (
|
|
63
|
-
typeof stack === 'string' &&
|
|
64
|
-
stack !== `${(e as { name?: string }).name}: ${(e as { message?: string }).message}`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function isApplicationFrame(fileName: string | undefined): boolean {
|
|
69
|
-
if (!fileName) return true;
|
|
70
|
-
// node_modules and webpack-style vendor chunks should not count as application code
|
|
71
|
-
if (/[/\\]node_modules[/\\]/.test(fileName)) return false;
|
|
72
|
-
if (/(^|[/\\])(vendor|vendors)[.~-][^/\\]*\.js/i.test(fileName)) return false;
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
export interface FileReader {
|
|
2
|
-
read(url: string): Promise<string | null>;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
const cachedFiles: { [key: string]: string } = {};
|
|
6
|
-
|
|
7
|
-
type CodeSnippet = { [key: number]: string };
|
|
8
|
-
|
|
9
|
-
type ReaderResponse = {
|
|
10
|
-
codeSnippet: CodeSnippet;
|
|
11
|
-
trimmedColumnNumber: number | null;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export function getCodeSnippet(
|
|
15
|
-
fileReader: FileReader,
|
|
16
|
-
url?: string,
|
|
17
|
-
lineNumber?: number,
|
|
18
|
-
columnNumber?: number,
|
|
19
|
-
): Promise<ReaderResponse> {
|
|
20
|
-
return new Promise((resolve) => {
|
|
21
|
-
if (!url || !lineNumber) {
|
|
22
|
-
return resolve({
|
|
23
|
-
codeSnippet: {
|
|
24
|
-
0: `Could not read from file: missing file URL or line number. URL: ${url} lineNumber: ${lineNumber}`,
|
|
25
|
-
},
|
|
26
|
-
trimmedColumnNumber: null,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
readFile(fileReader, url).then((fileText) => {
|
|
31
|
-
if (!fileText) {
|
|
32
|
-
return resolve({
|
|
33
|
-
codeSnippet: {
|
|
34
|
-
0: `Could not read from file: Error while opening file at URL ${url}`,
|
|
35
|
-
},
|
|
36
|
-
trimmedColumnNumber: null,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return resolve(readLinesFromFile(fileText, lineNumber, columnNumber));
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function readFile(fileReader: FileReader, url: string): Promise<string | null> {
|
|
45
|
-
if (cachedFiles[url] !== undefined) {
|
|
46
|
-
return Promise.resolve(cachedFiles[url]);
|
|
47
|
-
}
|
|
48
|
-
return fileReader.read(url).then((text) => {
|
|
49
|
-
if (text !== null) {
|
|
50
|
-
cachedFiles[url] = text;
|
|
51
|
-
}
|
|
52
|
-
return text;
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function readLinesFromFile(
|
|
57
|
-
fileText: string,
|
|
58
|
-
lineNumber: number,
|
|
59
|
-
columnNumber?: number,
|
|
60
|
-
maxSnippetLineLength = 1000,
|
|
61
|
-
maxSnippetLines = 40,
|
|
62
|
-
): ReaderResponse {
|
|
63
|
-
const codeSnippet: CodeSnippet = {};
|
|
64
|
-
let trimmedColumnNumber = null;
|
|
65
|
-
|
|
66
|
-
const lines = fileText.split('\n');
|
|
67
|
-
const errorLineIndex = lineNumber - 1;
|
|
68
|
-
const half = Math.floor(maxSnippetLines / 2);
|
|
69
|
-
|
|
70
|
-
for (let i = -half; i <= half; i++) {
|
|
71
|
-
const currentLineIndex = errorLineIndex + i;
|
|
72
|
-
if (currentLineIndex < 0 || !lines[currentLineIndex]) continue;
|
|
73
|
-
const displayLine = currentLineIndex + 1;
|
|
74
|
-
const line = lines[currentLineIndex];
|
|
75
|
-
if (line.length > maxSnippetLineLength) {
|
|
76
|
-
if (columnNumber && columnNumber > maxSnippetLineLength / 2) {
|
|
77
|
-
const start = columnNumber - Math.round(maxSnippetLineLength / 2);
|
|
78
|
-
codeSnippet[displayLine] = line.slice(start, start + maxSnippetLineLength);
|
|
79
|
-
if (displayLine === lineNumber) {
|
|
80
|
-
trimmedColumnNumber = Math.round(maxSnippetLineLength / 2);
|
|
81
|
-
}
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
codeSnippet[displayLine] = line.slice(0, maxSnippetLineLength) + '…';
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
codeSnippet[displayLine] = line;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return { codeSnippet, trimmedColumnNumber };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Clearing the cache is useful in tests.
|
|
94
|
-
export function __clearFileReaderCacheForTests(): void {
|
|
95
|
-
for (const key of Object.keys(cachedFiles)) delete cachedFiles[key];
|
|
96
|
-
}
|
package/src/stacktrace/index.ts
DELETED
package/src/types.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
export type MessageLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency';
|
|
2
|
-
|
|
3
|
-
export type AttributeValue = string | number | boolean | null | AttributeValue[] | { [key: string]: AttributeValue };
|
|
4
|
-
|
|
5
|
-
export type Attributes = Record<string, AttributeValue>;
|
|
6
|
-
|
|
7
|
-
export type Config = {
|
|
8
|
-
key: string | null;
|
|
9
|
-
version: string;
|
|
10
|
-
sourcemapVersionId: string;
|
|
11
|
-
stage: string;
|
|
12
|
-
maxGlowsPerReport: number;
|
|
13
|
-
reportBrowserExtensionErrors: boolean;
|
|
14
|
-
ingestUrl: string;
|
|
15
|
-
debug: boolean;
|
|
16
|
-
urlDenylist: RegExp;
|
|
17
|
-
replaceDefaultUrlDenylist: boolean;
|
|
18
|
-
sampleRate: number;
|
|
19
|
-
beforeEvaluate: (error: Error) => Error | false | null | Promise<Error | false | null>;
|
|
20
|
-
beforeSubmit: (report: Report) => Report | false | null | Promise<Report | false | null>;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export type StackFrame = {
|
|
24
|
-
file: string;
|
|
25
|
-
lineNumber: number;
|
|
26
|
-
columnNumber?: number;
|
|
27
|
-
method?: string;
|
|
28
|
-
class?: string;
|
|
29
|
-
codeSnippet?: { [line: number]: string };
|
|
30
|
-
isApplicationFrame?: boolean;
|
|
31
|
-
arguments?: unknown[];
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export type SpanEvent = {
|
|
35
|
-
type: string;
|
|
36
|
-
startTimeUnixNano: number;
|
|
37
|
-
endTimeUnixNano: number | null;
|
|
38
|
-
attributes: Attributes;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export type OverriddenGrouping =
|
|
42
|
-
| 'exception_class'
|
|
43
|
-
| 'exception_message'
|
|
44
|
-
| 'exception_message_and_class'
|
|
45
|
-
| 'full_stacktrace_and_exception_class_and_code';
|
|
46
|
-
|
|
47
|
-
export type Report = {
|
|
48
|
-
exceptionClass?: string | null;
|
|
49
|
-
message?: string | null;
|
|
50
|
-
code?: string;
|
|
51
|
-
seenAtUnixNano: number;
|
|
52
|
-
isLog?: boolean;
|
|
53
|
-
level?: MessageLevel;
|
|
54
|
-
sourcemapVersionId?: string;
|
|
55
|
-
trackingUuid?: string;
|
|
56
|
-
handled?: boolean;
|
|
57
|
-
openFrameIndex?: number;
|
|
58
|
-
applicationPath?: string;
|
|
59
|
-
overriddenGrouping?: OverriddenGrouping | null;
|
|
60
|
-
stacktrace: StackFrame[];
|
|
61
|
-
events: SpanEvent[];
|
|
62
|
-
attributes: Attributes;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export type Glow = {
|
|
66
|
-
time: number;
|
|
67
|
-
microtime: number;
|
|
68
|
-
name: string;
|
|
69
|
-
messageLevel: MessageLevel;
|
|
70
|
-
metaData: Record<string, unknown> | Record<string, unknown>[];
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export type EntryPointHandler = {
|
|
74
|
-
identifier?: string;
|
|
75
|
-
name?: string;
|
|
76
|
-
type?: string;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export type SdkInfo = { name: string; version: string };
|
|
80
|
-
|
|
81
|
-
export type Framework = { name: string; version?: string };
|
package/src/util/assert.ts
DELETED
package/src/util/assertKey.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { assert } from './assert';
|
|
2
|
-
|
|
3
|
-
export function assertKey(key: unknown, debug: boolean): boolean {
|
|
4
|
-
return assert(
|
|
5
|
-
key,
|
|
6
|
-
'The client was not yet initialised with an API key. ' +
|
|
7
|
-
"Run client.light('<flare-project-key>') when you initialise your app. " +
|
|
8
|
-
"If you are running in dev mode and didn't run the light command on purpose, you can ignore this error.",
|
|
9
|
-
debug,
|
|
10
|
-
);
|
|
11
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export function convertToError(error: unknown): Error {
|
|
2
|
-
if (error instanceof Error) {
|
|
3
|
-
return error;
|
|
4
|
-
}
|
|
5
|
-
if (typeof error === 'string') {
|
|
6
|
-
return new Error(error);
|
|
7
|
-
}
|
|
8
|
-
if (typeof error === 'object' && error !== null) {
|
|
9
|
-
const obj = error as Record<string, unknown>;
|
|
10
|
-
const message = typeof obj.message === 'string' ? obj.message : String(error);
|
|
11
|
-
const converted = new Error(message);
|
|
12
|
-
if (typeof obj.stack === 'string') {
|
|
13
|
-
converted.stack = obj.stack;
|
|
14
|
-
}
|
|
15
|
-
if (typeof obj.name === 'string') {
|
|
16
|
-
converted.name = obj.name;
|
|
17
|
-
}
|
|
18
|
-
return converted;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return new Error(String(error));
|
|
22
|
-
}
|
package/src/util/extractCode.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const MAX_CODE_LENGTH = 64;
|
|
2
|
-
|
|
3
|
-
export function extractCode(error: Error): string | undefined {
|
|
4
|
-
const code = (error as { code?: unknown }).code;
|
|
5
|
-
|
|
6
|
-
if (typeof code !== 'string' || code.length === 0) {
|
|
7
|
-
return undefined;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return code.slice(0, MAX_CODE_LENGTH);
|
|
11
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// JSON.stringify throws on circular references. User-supplied glow data and addContext values can
|
|
2
|
-
// realistically contain cycles (e.g. Vue/React component instances), so we walk the tree first and
|
|
3
|
-
// replace any back-edges with the sentinel "[Circular]" before serialising.
|
|
4
|
-
export function flatJsonStringify(json: object): string {
|
|
5
|
-
return JSON.stringify(decycle(json));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// Restricted to literal object/null prototypes on purpose: class instances may have getters with
|
|
9
|
-
// side effects or non-enumerable internals that we shouldn't traverse. They're left to JSON.stringify
|
|
10
|
-
// to handle (typically by calling toJSON or returning {}).
|
|
11
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
12
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
13
|
-
const proto = Object.getPrototypeOf(value);
|
|
14
|
-
return proto === Object.prototype || proto === null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function decycle(root: unknown): unknown {
|
|
18
|
-
// `inPath` tracks ancestors on the current branch only (added on enter, removed on exit). Using a
|
|
19
|
-
// global "seen" set would mis-flag the same object referenced twice in different branches as
|
|
20
|
-
// circular, even though no cycle exists.
|
|
21
|
-
const inPath = new WeakSet<object>();
|
|
22
|
-
|
|
23
|
-
function clone(node: unknown): unknown {
|
|
24
|
-
if (Array.isArray(node)) {
|
|
25
|
-
if (inPath.has(node)) return '[Circular]';
|
|
26
|
-
inPath.add(node);
|
|
27
|
-
const result = node.map(clone);
|
|
28
|
-
inPath.delete(node);
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
if (isPlainObject(node)) {
|
|
32
|
-
if (inPath.has(node)) return '[Circular]';
|
|
33
|
-
inPath.add(node);
|
|
34
|
-
const result: Record<string, unknown> = {};
|
|
35
|
-
for (const [k, v] of Object.entries(node)) {
|
|
36
|
-
result[k] = clone(v);
|
|
37
|
-
}
|
|
38
|
-
inPath.delete(node);
|
|
39
|
-
return result;
|
|
40
|
-
}
|
|
41
|
-
return node;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return clone(root);
|
|
45
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { AttributeValue, Glow, SpanEvent } from '../types';
|
|
2
|
-
|
|
3
|
-
// glow.microtime is seconds since epoch (see util/now.ts).
|
|
4
|
-
// SpanEvent.startTimeUnixNano is unix nanoseconds.
|
|
5
|
-
export function glowsToEvents(glows: Glow[]): SpanEvent[] {
|
|
6
|
-
return glows.map((glow) => ({
|
|
7
|
-
type: 'php_glow',
|
|
8
|
-
startTimeUnixNano: Math.round(glow.microtime * 1_000_000_000),
|
|
9
|
-
endTimeUnixNano: null,
|
|
10
|
-
attributes: {
|
|
11
|
-
'glow.name': String(glow.name),
|
|
12
|
-
'glow.level': glow.messageLevel,
|
|
13
|
-
'glow.context': (glow.metaData ?? {}) as AttributeValue,
|
|
14
|
-
},
|
|
15
|
-
}));
|
|
16
|
-
}
|
package/src/util/index.ts
DELETED
package/src/util/now.ts
DELETED
package/src/util/redactUrl.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
// Matched against query-string keys (and, in framework SDKs, against prop/route-param keys).
|
|
2
|
-
// Values for matching keys are replaced with [redacted] before the data is sent to Flare so
|
|
3
|
-
// credentials/PII don't leak in error reports.
|
|
4
|
-
export const DEFAULT_URL_DENYLIST =
|
|
5
|
-
/password|passwd|pwd|token|secret|authorization|\bauth\b|bearer|oauth|credentials?|cookie|api[-_]?key|private[-_]?key|session|csrf|xsrf|\bpin\b|\bssn\b|card[-_]?number|\bcvv\b/i;
|
|
6
|
-
|
|
7
|
-
export function resolveDenylist(
|
|
8
|
-
custom?: RegExp,
|
|
9
|
-
replaceDefault: boolean = false,
|
|
10
|
-
defaultDenylist: RegExp = DEFAULT_URL_DENYLIST,
|
|
11
|
-
): RegExp {
|
|
12
|
-
if (!custom) {
|
|
13
|
-
return defaultDenylist;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (replaceDefault) {
|
|
17
|
-
const safeFlags = custom.flags.replace(/[gy]/g, '');
|
|
18
|
-
return new RegExp(custom.source, safeFlags);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const flags = unionFlags(defaultDenylist.flags, custom.flags);
|
|
22
|
-
|
|
23
|
-
return new RegExp(`(?:${defaultDenylist.source})|(?:${custom.source})`, flags);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function unionFlags(a: string, b: string): string {
|
|
27
|
-
const merged = new Set<string>();
|
|
28
|
-
|
|
29
|
-
for (const flag of a + b) {
|
|
30
|
-
if (flag === 'g' || flag === 'y') {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
merged.add(flag);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return [...merged].join('');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function redactUrlQuery(fullPath: string, denylist: RegExp = DEFAULT_URL_DENYLIST): string {
|
|
40
|
-
const queryStart = fullPath.indexOf('?');
|
|
41
|
-
|
|
42
|
-
if (queryStart === -1) {
|
|
43
|
-
return fullPath;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const hashStart = fullPath.indexOf('#', queryStart);
|
|
47
|
-
const queryEnd = hashStart === -1 ? fullPath.length : hashStart;
|
|
48
|
-
|
|
49
|
-
const prefix = fullPath.slice(0, queryStart + 1);
|
|
50
|
-
const queryString = fullPath.slice(queryStart + 1, queryEnd);
|
|
51
|
-
const suffix = fullPath.slice(queryEnd);
|
|
52
|
-
|
|
53
|
-
const redacted = queryString
|
|
54
|
-
.split('&')
|
|
55
|
-
.map((pair) => {
|
|
56
|
-
if (pair === '') {
|
|
57
|
-
return pair;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const eq = pair.indexOf('=');
|
|
61
|
-
const rawKey = eq === -1 ? pair : pair.slice(0, eq);
|
|
62
|
-
const decodedKey = safeDecode(rawKey);
|
|
63
|
-
|
|
64
|
-
if (!denylist.test(decodedKey)) {
|
|
65
|
-
return pair;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return eq === -1 ? rawKey : `${rawKey}=[redacted]`;
|
|
69
|
-
})
|
|
70
|
-
.join('&');
|
|
71
|
-
|
|
72
|
-
return `${prefix}${redacted}${suffix}`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// decodeURIComponent throws on malformed escape sequences (`%E0`, lone `%`, etc). Match against the
|
|
76
|
-
// raw key in that case rather than aborting the whole redaction pass.
|
|
77
|
-
function safeDecode(value: string): string {
|
|
78
|
-
try {
|
|
79
|
-
return decodeURIComponent(value);
|
|
80
|
-
} catch {
|
|
81
|
-
return value;
|
|
82
|
-
}
|
|
83
|
-
}
|