@flareapp/js 1.0.1 → 1.1.0-rc.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.d.mts +97 -102
- package/dist/index.d.ts +97 -102
- package/dist/index.js +184 -212
- package/dist/index.mjs +182 -210
- package/package.json +1 -1
- package/src/Flare.ts +208 -0
- package/src/api/Api.ts +27 -0
- package/src/api/index.ts +1 -0
- package/src/{browserClient/index.ts → browser/catchWindowErrors.ts} +2 -3
- package/src/browser/index.ts +1 -0
- package/src/context/collectContext.ts +18 -0
- package/src/context/cookie.ts +1 -1
- package/src/context/index.ts +1 -17
- package/src/env/index.ts +12 -0
- package/src/index.ts +8 -5
- package/src/solutions/getSolutions.ts +35 -0
- package/src/solutions/index.ts +1 -44
- package/src/stacktrace/createStackTrace.ts +59 -0
- package/src/stacktrace/fileReader.ts +7 -21
- package/src/stacktrace/index.ts +1 -71
- package/src/types.ts +66 -82
- package/src/util/assert.ts +9 -0
- package/src/util/assertKey.ts +11 -0
- package/src/util/assertSolutionProvider.ts +12 -0
- package/src/util/flatJsonStringify.ts +22 -0
- package/src/util/flattenOnce.ts +5 -0
- package/src/util/index.ts +6 -44
- package/src/util/now.ts +3 -0
- package/tsconfig.json +1 -1
- package/src/FlareClient.ts +0 -285
- package/src/build.ts +0 -16
- package/src/module.d.ts +0 -3
package/src/Flare.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { Api } from './api';
|
|
2
|
+
import { collectContext } from './context';
|
|
3
|
+
import { CLIENT_VERSION, KEY, SOURCEMAP_VERSION } from './env';
|
|
4
|
+
import { getSolutions } from './solutions';
|
|
5
|
+
import { createStackTrace } from './stacktrace';
|
|
6
|
+
import {
|
|
7
|
+
Config,
|
|
8
|
+
Context,
|
|
9
|
+
Glow,
|
|
10
|
+
MessageLevel,
|
|
11
|
+
Report,
|
|
12
|
+
SolutionProvider,
|
|
13
|
+
SolutionProviderExtraParameters,
|
|
14
|
+
} from './types';
|
|
15
|
+
import { assert, assertKey, assertSolutionProvider, now } from './util';
|
|
16
|
+
|
|
17
|
+
export class Flare {
|
|
18
|
+
config: Config = {
|
|
19
|
+
key: KEY,
|
|
20
|
+
version: CLIENT_VERSION,
|
|
21
|
+
sourcemapVersion: SOURCEMAP_VERSION,
|
|
22
|
+
stage: '',
|
|
23
|
+
maxGlowsPerReport: 30,
|
|
24
|
+
reportingUrl: 'https://reporting.flareapp.io/api/reports',
|
|
25
|
+
reportBrowserExtensionErrors: false,
|
|
26
|
+
debug: false,
|
|
27
|
+
beforeEvaluate: (error) => error,
|
|
28
|
+
beforeSubmit: (report) => report,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
glows: Glow[] = [];
|
|
32
|
+
context: Context = { context: {} };
|
|
33
|
+
solutionProviders: SolutionProvider[] = [];
|
|
34
|
+
|
|
35
|
+
constructor(public http: Api = new Api()) {}
|
|
36
|
+
|
|
37
|
+
light(key: string = KEY, debug: boolean = false): Flare {
|
|
38
|
+
this.config.key = key;
|
|
39
|
+
this.config.debug = debug;
|
|
40
|
+
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
configure(config: Partial<Config>): Flare {
|
|
45
|
+
this.config = { ...this.config, ...config };
|
|
46
|
+
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
test(): Promise<void> {
|
|
51
|
+
return this.report(new Error('The Flare client is set up correctly!'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
glow(name: string, level: MessageLevel = 'info', data: object | object[] = []): Flare {
|
|
55
|
+
const time = now();
|
|
56
|
+
|
|
57
|
+
this.glows.push({
|
|
58
|
+
name,
|
|
59
|
+
message_level: level,
|
|
60
|
+
meta_data: data,
|
|
61
|
+
time,
|
|
62
|
+
microtime: time,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (this.glows.length > this.config.maxGlowsPerReport) {
|
|
66
|
+
this.glows = this.glows.slice(this.glows.length - this.config.maxGlowsPerReport);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
clearGlows(): Flare {
|
|
73
|
+
this.glows = [];
|
|
74
|
+
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
addContext(name: string, value: any): Flare {
|
|
79
|
+
this.context.context[name] = value;
|
|
80
|
+
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
addContextGroup(groupName: string, value: object): Flare {
|
|
85
|
+
this.context[groupName] = value;
|
|
86
|
+
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
registerSolutionProvider(solutionProvider: SolutionProvider): Flare {
|
|
91
|
+
if (!assertSolutionProvider(solutionProvider, this.config.debug)) {
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.solutionProviders.push(solutionProvider);
|
|
96
|
+
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async report(
|
|
101
|
+
error: Error,
|
|
102
|
+
context: Context = {},
|
|
103
|
+
extraSolutionParameters: SolutionProviderExtraParameters = {}
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
const errorToReport = await this.config.beforeEvaluate(error);
|
|
106
|
+
|
|
107
|
+
if (!errorToReport) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const report = await this.createReportFromError(error, context, extraSolutionParameters);
|
|
112
|
+
|
|
113
|
+
if (!report) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return this.sendReport(report);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async reportMessage(message: string, context: Context = {}, exceptionClass: string = 'Log'): Promise<void> {
|
|
121
|
+
const stackTrace = await createStackTrace(Error(), this.config.debug);
|
|
122
|
+
|
|
123
|
+
// The first item in the stacktrace is from this file, and irrelevant
|
|
124
|
+
stackTrace.shift();
|
|
125
|
+
|
|
126
|
+
this.sendReport({
|
|
127
|
+
notifier: `Flare JavaScript client v${CLIENT_VERSION}`,
|
|
128
|
+
exception_class: exceptionClass,
|
|
129
|
+
seen_at: now(),
|
|
130
|
+
message: message,
|
|
131
|
+
language: 'javascript',
|
|
132
|
+
glows: this.glows,
|
|
133
|
+
context: collectContext({ ...context, ...this.context }),
|
|
134
|
+
stacktrace: stackTrace,
|
|
135
|
+
sourcemap_version_id: this.config.sourcemapVersion,
|
|
136
|
+
solutions: [],
|
|
137
|
+
stage: this.config.stage,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
createReportFromError(
|
|
142
|
+
error: Error,
|
|
143
|
+
context: Context = {},
|
|
144
|
+
extraSolutionParameters: SolutionProviderExtraParameters = {}
|
|
145
|
+
): Promise<Report | false> {
|
|
146
|
+
if (!assert(error, 'No error provided.', this.config.debug)) {
|
|
147
|
+
return Promise.resolve(false);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const seenAt = now();
|
|
151
|
+
|
|
152
|
+
return Promise.all([
|
|
153
|
+
getSolutions(this.solutionProviders, error, extraSolutionParameters),
|
|
154
|
+
createStackTrace(error, this.config.debug),
|
|
155
|
+
]).then((result) => {
|
|
156
|
+
const [solutions, stacktrace] = result;
|
|
157
|
+
|
|
158
|
+
assert(stacktrace.length, "Couldn't generate stacktrace of this error: " + error, this.config.debug);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
notifier: `Flare JavaScript client v${CLIENT_VERSION}`,
|
|
162
|
+
exception_class: error.constructor && error.constructor.name ? error.constructor.name : 'undefined',
|
|
163
|
+
seen_at: seenAt,
|
|
164
|
+
message: error.message,
|
|
165
|
+
language: 'javascript',
|
|
166
|
+
glows: this.glows,
|
|
167
|
+
context: collectContext({ ...context, ...this.context }),
|
|
168
|
+
stacktrace,
|
|
169
|
+
sourcemap_version_id: this.config.sourcemapVersion,
|
|
170
|
+
solutions,
|
|
171
|
+
stage: this.config.stage,
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async sendReport(report: Report): Promise<void> {
|
|
177
|
+
if (!assertKey(this.config.key, this.config.debug)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const reportToSubmit = await this.config.beforeSubmit(report);
|
|
182
|
+
|
|
183
|
+
if (!reportToSubmit) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return this.http.report(
|
|
188
|
+
reportToSubmit,
|
|
189
|
+
this.config.reportingUrl,
|
|
190
|
+
this.config.key,
|
|
191
|
+
this.config.reportBrowserExtensionErrors
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Deprecated, the following methods exist for backwards compatibility.
|
|
196
|
+
|
|
197
|
+
set beforeEvaluate(beforeEvaluate: Config['beforeEvaluate']) {
|
|
198
|
+
this.config.beforeEvaluate = beforeEvaluate ?? '';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
set beforeSubmit(beforeSubmit: Config['beforeSubmit']) {
|
|
202
|
+
this.config.beforeSubmit = beforeSubmit ?? '';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
set stage(stage: string | undefined) {
|
|
206
|
+
this.config.stage = stage ?? '';
|
|
207
|
+
}
|
|
208
|
+
}
|
package/src/api/Api.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Report } from '../types';
|
|
2
|
+
import { flatJsonStringify } from '../util';
|
|
3
|
+
|
|
4
|
+
export class Api {
|
|
5
|
+
report(report: Report, url: string, key: string, reportBrowserExtensionErrors: boolean): Promise<void> {
|
|
6
|
+
return fetch(url, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
headers: {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
'X-Api-Token': key,
|
|
11
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
12
|
+
'X-Report-Browser-Extension-Errors': JSON.stringify(reportBrowserExtensionErrors),
|
|
13
|
+
},
|
|
14
|
+
body: flatJsonStringify({
|
|
15
|
+
...report,
|
|
16
|
+
key: key,
|
|
17
|
+
}),
|
|
18
|
+
}).then(
|
|
19
|
+
(response) => {
|
|
20
|
+
if (response.status !== 204) {
|
|
21
|
+
console.error(`Received response with status ${response.status} from Flare`);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
(error) => console.error(error)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Api';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export
|
|
1
|
+
export function catchWindowErrors() {
|
|
2
2
|
if (typeof window === 'undefined') {
|
|
3
3
|
return;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
// @ts-ignore
|
|
6
7
|
const flare = window.flare;
|
|
7
8
|
|
|
8
9
|
if (!window || !flare) {
|
|
@@ -27,8 +28,6 @@ export default function catchWindowErrors() {
|
|
|
27
28
|
flare.report(error.reason);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
// TODO: Maybe also send errors without stacktrace for unhandled rejections without an Error as reason? (could be a string, …)
|
|
31
|
-
|
|
32
31
|
if (typeof originalOnunhandledrejectionHandler === 'function') {
|
|
33
32
|
// @ts-ignore
|
|
34
33
|
originalOnunhandledrejectionHandler(error);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './catchWindowErrors';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Context } from '../types';
|
|
2
|
+
|
|
3
|
+
import cookie from './cookie';
|
|
4
|
+
import request from './request';
|
|
5
|
+
import requestData from './requestData';
|
|
6
|
+
|
|
7
|
+
export function collectContext(additionalContext: object): Context {
|
|
8
|
+
if (typeof window === 'undefined') {
|
|
9
|
+
return additionalContext;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
...cookie(),
|
|
14
|
+
...request(),
|
|
15
|
+
...requestData(),
|
|
16
|
+
...additionalContext,
|
|
17
|
+
};
|
|
18
|
+
}
|
package/src/context/cookie.ts
CHANGED
package/src/context/index.ts
CHANGED
|
@@ -1,17 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import request from './request';
|
|
3
|
-
import requestData from './requestData';
|
|
4
|
-
import { Flare } from '../types';
|
|
5
|
-
|
|
6
|
-
export function collectContext(additionalContext: object): Flare.Context {
|
|
7
|
-
if (typeof window === 'undefined') {
|
|
8
|
-
return additionalContext;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return {
|
|
12
|
-
...cookie(),
|
|
13
|
-
...request(),
|
|
14
|
-
...requestData(),
|
|
15
|
-
...additionalContext,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
1
|
+
export * from './collectContext';
|
package/src/env/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
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.env.FLARE_JS_CLIENT_VERSION === 'undefined' ? '?' : process.env.FLARE_JS_CLIENT_VERSION;
|
|
7
|
+
|
|
8
|
+
// Injected by flare-vite-plugin-sourcemap-uploader (optional)
|
|
9
|
+
export const KEY = typeof FLARE_JS_KEY === 'undefined' ? '' : FLARE_JS_KEY;
|
|
10
|
+
|
|
11
|
+
// Injected by flare-vite-plugin-sourcemap-uploader (optional)
|
|
12
|
+
export const SOURCEMAP_VERSION = typeof FLARE_SOURCEMAP_VERSION === 'undefined' ? '' : FLARE_SOURCEMAP_VERSION;
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import catchWindowErrors from './
|
|
3
|
-
export { readLinesFromFile } from './stacktrace/fileReader';
|
|
1
|
+
import { Flare } from './Flare';
|
|
2
|
+
import { catchWindowErrors } from './browser';
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
// Expose package singleton
|
|
5
|
+
export const flare = new Flare();
|
|
6
6
|
|
|
7
7
|
if (typeof window !== 'undefined' && window) {
|
|
8
|
+
// @ts-expect-error
|
|
8
9
|
window.flare = flare;
|
|
10
|
+
|
|
11
|
+
catchWindowErrors();
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
export { Flare } from './Flare';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Solution, SolutionProvider, SolutionProviderExtraParameters } from '../types';
|
|
2
|
+
import { flattenOnce } from '../util';
|
|
3
|
+
|
|
4
|
+
export function getSolutions(
|
|
5
|
+
solutionProviders: Array<SolutionProvider>,
|
|
6
|
+
error: Error,
|
|
7
|
+
extraSolutionParameters: SolutionProviderExtraParameters = {}
|
|
8
|
+
): Promise<Array<Solution>> {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const canSolves = solutionProviders.reduce(
|
|
11
|
+
(canSolves, provider) => {
|
|
12
|
+
canSolves.push(Promise.resolve(provider.canSolve(error, extraSolutionParameters)));
|
|
13
|
+
|
|
14
|
+
return canSolves;
|
|
15
|
+
},
|
|
16
|
+
[] as Array<Promise<boolean>>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
Promise.all(canSolves).then((resolvedCanSolves) => {
|
|
20
|
+
const solutionPromises: Array<Promise<Array<Solution>>> = [];
|
|
21
|
+
|
|
22
|
+
resolvedCanSolves.forEach((canSolve, i) => {
|
|
23
|
+
if (canSolve) {
|
|
24
|
+
solutionPromises.push(
|
|
25
|
+
Promise.resolve(solutionProviders[i].getSolutions(error, extraSolutionParameters))
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
Promise.all(solutionPromises).then((solutions) => {
|
|
31
|
+
resolve(flattenOnce(solutions));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
package/src/solutions/index.ts
CHANGED
|
@@ -1,44 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Flare } from '../types';
|
|
3
|
-
|
|
4
|
-
export default function getSolutions(
|
|
5
|
-
solutionProviders: Array<Flare.SolutionProvider>,
|
|
6
|
-
error: Error,
|
|
7
|
-
extraSolutionParameters: Flare.SolutionProviderExtraParameters = {},
|
|
8
|
-
): Promise<Array<Flare.Solution>> {
|
|
9
|
-
return new Promise((resolve) => {
|
|
10
|
-
const canSolves = solutionProviders.reduce(
|
|
11
|
-
(canSolves, provider) => {
|
|
12
|
-
canSolves.push(
|
|
13
|
-
Promise.resolve(
|
|
14
|
-
provider.canSolve(error, extraSolutionParameters),
|
|
15
|
-
),
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
return canSolves;
|
|
19
|
-
},
|
|
20
|
-
[] as Array<Promise<boolean>>,
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
Promise.all(canSolves).then((resolvedCanSolves) => {
|
|
24
|
-
const solutionPromises: Array<Promise<Array<Flare.Solution>>> = [];
|
|
25
|
-
|
|
26
|
-
resolvedCanSolves.forEach((canSolve, i) => {
|
|
27
|
-
if (canSolve) {
|
|
28
|
-
solutionPromises.push(
|
|
29
|
-
Promise.resolve(
|
|
30
|
-
solutionProviders[i].getSolutions(
|
|
31
|
-
error,
|
|
32
|
-
extraSolutionParameters,
|
|
33
|
-
),
|
|
34
|
-
),
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
Promise.all(solutionPromises).then((solutions) => {
|
|
40
|
-
resolve(flattenOnce(solutions));
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
}
|
|
1
|
+
export * from './getSolutions';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import ErrorStackParser from 'error-stack-parser';
|
|
2
|
+
|
|
3
|
+
import { StackFrame } from '../types';
|
|
4
|
+
import { assert } from '../util';
|
|
5
|
+
|
|
6
|
+
import { getCodeSnippet } from './fileReader';
|
|
7
|
+
|
|
8
|
+
export function createStackTrace(error: Error, debug: boolean): Promise<Array<StackFrame>> {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
if (!hasStack(error)) {
|
|
11
|
+
assert(false, "Couldn't generate stacktrace of below error:", debug);
|
|
12
|
+
|
|
13
|
+
if (debug) {
|
|
14
|
+
console.error(error);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return resolve([
|
|
18
|
+
{
|
|
19
|
+
line_number: 0,
|
|
20
|
+
column_number: 0,
|
|
21
|
+
method: 'unknown',
|
|
22
|
+
file: 'unknown',
|
|
23
|
+
code_snippet: {
|
|
24
|
+
0: 'Could not read from file: stacktrace missing',
|
|
25
|
+
},
|
|
26
|
+
trimmed_column_number: null,
|
|
27
|
+
class: 'unknown',
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Promise.all(
|
|
33
|
+
ErrorStackParser.parse(error).map((frame) => {
|
|
34
|
+
return new Promise<StackFrame>((resolve) => {
|
|
35
|
+
getCodeSnippet(frame.fileName, frame.lineNumber, frame.columnNumber).then((snippet) => {
|
|
36
|
+
resolve({
|
|
37
|
+
line_number: frame.lineNumber || 1,
|
|
38
|
+
column_number: frame.columnNumber || 1,
|
|
39
|
+
method: frame.functionName || 'Anonymous or unknown function',
|
|
40
|
+
file: frame.fileName || 'Unknown file',
|
|
41
|
+
code_snippet: snippet.codeSnippet,
|
|
42
|
+
trimmed_column_number: snippet.trimmedColumnNumber,
|
|
43
|
+
class: '',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
})
|
|
48
|
+
).then(resolve);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hasStack(err: any): boolean {
|
|
53
|
+
return (
|
|
54
|
+
!!err &&
|
|
55
|
+
(!!err.stack || !!err.stacktrace || !!err['opera#sourceloc']) &&
|
|
56
|
+
typeof (err.stack || err.stacktrace || err['opera#sourceloc']) === 'string' &&
|
|
57
|
+
err.stack !== `${err.name}: ${err.message}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -9,11 +9,7 @@ type ReaderResponse = {
|
|
|
9
9
|
trimmedColumnNumber: number | null;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export function getCodeSnippet(
|
|
13
|
-
url?: string,
|
|
14
|
-
lineNumber?: number,
|
|
15
|
-
columnNumber?: number,
|
|
16
|
-
): Promise<ReaderResponse> {
|
|
12
|
+
export function getCodeSnippet(url?: string, lineNumber?: number, columnNumber?: number): Promise<ReaderResponse> {
|
|
17
13
|
return new Promise((resolve) => {
|
|
18
14
|
if (!url || !lineNumber) {
|
|
19
15
|
return resolve({
|
|
@@ -34,9 +30,7 @@ export function getCodeSnippet(
|
|
|
34
30
|
});
|
|
35
31
|
}
|
|
36
32
|
|
|
37
|
-
return resolve(
|
|
38
|
-
readLinesFromFile(fileText, lineNumber, columnNumber),
|
|
39
|
-
);
|
|
33
|
+
return resolve(readLinesFromFile(fileText, lineNumber, columnNumber));
|
|
40
34
|
});
|
|
41
35
|
});
|
|
42
36
|
}
|
|
@@ -62,7 +56,7 @@ export function readLinesFromFile(
|
|
|
62
56
|
lineNumber: number,
|
|
63
57
|
columnNumber?: number,
|
|
64
58
|
maxSnippetLineLength = 1000,
|
|
65
|
-
maxSnippetLines = 40
|
|
59
|
+
maxSnippetLines = 40
|
|
66
60
|
): ReaderResponse {
|
|
67
61
|
const codeSnippet: CodeSnippet = {};
|
|
68
62
|
let trimmedColumnNumber = null;
|
|
@@ -76,28 +70,20 @@ export function readLinesFromFile(
|
|
|
76
70
|
const displayLine = currentLineIndex + 1; // the linenumber in a stacktrace is not zero-based like an array
|
|
77
71
|
|
|
78
72
|
if (lines[currentLineIndex].length > maxSnippetLineLength) {
|
|
79
|
-
if (
|
|
80
|
-
columnNumber &&
|
|
81
|
-
columnNumber + maxSnippetLineLength / 2 >
|
|
82
|
-
maxSnippetLineLength
|
|
83
|
-
) {
|
|
73
|
+
if (columnNumber && columnNumber + maxSnippetLineLength / 2 > maxSnippetLineLength) {
|
|
84
74
|
codeSnippet[displayLine] = lines[currentLineIndex].substr(
|
|
85
75
|
columnNumber - Math.round(maxSnippetLineLength / 2),
|
|
86
|
-
maxSnippetLineLength
|
|
76
|
+
maxSnippetLineLength
|
|
87
77
|
);
|
|
88
78
|
|
|
89
79
|
if (displayLine === lineNumber) {
|
|
90
|
-
trimmedColumnNumber = Math.round(
|
|
91
|
-
maxSnippetLineLength / 2,
|
|
92
|
-
);
|
|
80
|
+
trimmedColumnNumber = Math.round(maxSnippetLineLength / 2);
|
|
93
81
|
}
|
|
94
82
|
|
|
95
83
|
continue;
|
|
96
84
|
}
|
|
97
85
|
|
|
98
|
-
codeSnippet[displayLine] =
|
|
99
|
-
lines[currentLineIndex].substr(0, maxSnippetLineLength) +
|
|
100
|
-
'…';
|
|
86
|
+
codeSnippet[displayLine] = lines[currentLineIndex].substr(0, maxSnippetLineLength) + '…';
|
|
101
87
|
|
|
102
88
|
continue;
|
|
103
89
|
}
|
package/src/stacktrace/index.ts
CHANGED
|
@@ -1,71 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { getCodeSnippet } from './fileReader';
|
|
3
|
-
import { Flare } from '../types';
|
|
4
|
-
import { flare } from '..';
|
|
5
|
-
import { assert } from '../util';
|
|
6
|
-
|
|
7
|
-
export function createStackTrace(
|
|
8
|
-
error: Error,
|
|
9
|
-
): Promise<Array<Flare.StackFrame>> {
|
|
10
|
-
return new Promise((resolve) => {
|
|
11
|
-
if (!hasStack(error)) {
|
|
12
|
-
assert(
|
|
13
|
-
false,
|
|
14
|
-
"Couldn't generate stacktrace of below error:",
|
|
15
|
-
flare.debug,
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
if (flare.debug) {
|
|
19
|
-
console.error(error);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return resolve([
|
|
23
|
-
{
|
|
24
|
-
line_number: 0,
|
|
25
|
-
column_number: 0,
|
|
26
|
-
method: 'unknown',
|
|
27
|
-
file: 'unknown',
|
|
28
|
-
code_snippet: {
|
|
29
|
-
0: 'Could not read from file: stacktrace missing',
|
|
30
|
-
},
|
|
31
|
-
trimmed_column_number: null,
|
|
32
|
-
class: 'unknown',
|
|
33
|
-
},
|
|
34
|
-
]);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
Promise.all(
|
|
38
|
-
ErrorStackParser.parse(error).map((frame) => {
|
|
39
|
-
return new Promise<Flare.StackFrame>((resolve) => {
|
|
40
|
-
getCodeSnippet(
|
|
41
|
-
frame.fileName,
|
|
42
|
-
frame.lineNumber,
|
|
43
|
-
frame.columnNumber,
|
|
44
|
-
).then((snippet) => {
|
|
45
|
-
resolve({
|
|
46
|
-
line_number: frame.lineNumber || 1,
|
|
47
|
-
column_number: frame.columnNumber || 1,
|
|
48
|
-
method:
|
|
49
|
-
frame.functionName ||
|
|
50
|
-
'Anonymous or unknown function',
|
|
51
|
-
file: frame.fileName || 'Unknown file',
|
|
52
|
-
code_snippet: snippet.codeSnippet,
|
|
53
|
-
trimmed_column_number: snippet.trimmedColumnNumber,
|
|
54
|
-
class: '',
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}),
|
|
59
|
-
).then(resolve);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function hasStack(err: any): boolean {
|
|
64
|
-
return (
|
|
65
|
-
!!err &&
|
|
66
|
-
(!!err.stack || !!err.stacktrace || !!err['opera#sourceloc']) &&
|
|
67
|
-
typeof (err.stack || err.stacktrace || err['opera#sourceloc']) ===
|
|
68
|
-
'string' &&
|
|
69
|
-
err.stack !== `${err.name}: ${err.message}`
|
|
70
|
-
);
|
|
71
|
-
}
|
|
1
|
+
export * from './createStackTrace';
|