@flareapp/js 1.0.0-beta.1 → 1.0.0-beta.3
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 +114 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +532 -0
- package/dist/index.mjs +494 -0
- package/package.json +1 -1
- package/tests/index.test.ts +0 -50
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
declare namespace Flare {
|
|
2
|
+
type BeforeEvaluate = (error: Error) => Error | false | Promise<Error | false>;
|
|
3
|
+
type BeforeSubmit = (report: ErrorReport) => ErrorReport | false | Promise<ErrorReport | false>;
|
|
4
|
+
type Config = {
|
|
5
|
+
key: string;
|
|
6
|
+
reportingUrl: string;
|
|
7
|
+
maxGlowsPerReport: number;
|
|
8
|
+
maxReportsPerMinute: number;
|
|
9
|
+
stage?: string;
|
|
10
|
+
};
|
|
11
|
+
type ErrorReport = {
|
|
12
|
+
notifier: string;
|
|
13
|
+
exception_class: string;
|
|
14
|
+
seen_at: number;
|
|
15
|
+
message: string;
|
|
16
|
+
language: 'javascript';
|
|
17
|
+
glows: Array<Flare.Glow>;
|
|
18
|
+
context: Flare.Context;
|
|
19
|
+
stacktrace: Array<Flare.StackFrame>;
|
|
20
|
+
sourcemap_version_id: string;
|
|
21
|
+
solutions: Array<Flare.Solution>;
|
|
22
|
+
stage?: string;
|
|
23
|
+
};
|
|
24
|
+
interface SolutionProviderExtraParameters {
|
|
25
|
+
}
|
|
26
|
+
type SolutionProvider = {
|
|
27
|
+
canSolve: (error: Error, extraParameters?: SolutionProviderExtraParameters) => boolean | Promise<boolean>;
|
|
28
|
+
getSolutions: (error: Error, extraParameters?: SolutionProviderExtraParameters) => Array<Flare.Solution> | Promise<Array<Flare.Solution>>;
|
|
29
|
+
};
|
|
30
|
+
type Solution = {
|
|
31
|
+
class: string;
|
|
32
|
+
title: string;
|
|
33
|
+
description: string;
|
|
34
|
+
links: {
|
|
35
|
+
[label: string]: string;
|
|
36
|
+
};
|
|
37
|
+
action_description?: string;
|
|
38
|
+
is_runnable?: boolean;
|
|
39
|
+
};
|
|
40
|
+
type Context = {
|
|
41
|
+
request?: {
|
|
42
|
+
url?: String;
|
|
43
|
+
useragent?: String;
|
|
44
|
+
referrer?: String;
|
|
45
|
+
readyState?: String;
|
|
46
|
+
};
|
|
47
|
+
request_data?: {
|
|
48
|
+
queryString: {
|
|
49
|
+
[key: string]: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
cookies?: {
|
|
53
|
+
[key: string]: string;
|
|
54
|
+
};
|
|
55
|
+
[key: string]: any;
|
|
56
|
+
};
|
|
57
|
+
type StackFrame = {
|
|
58
|
+
line_number: number;
|
|
59
|
+
column_number: number;
|
|
60
|
+
method: string;
|
|
61
|
+
file: string;
|
|
62
|
+
code_snippet: {
|
|
63
|
+
[key: number]: string;
|
|
64
|
+
};
|
|
65
|
+
trimmed_column_number: number | null;
|
|
66
|
+
class: string;
|
|
67
|
+
};
|
|
68
|
+
type Glow = {
|
|
69
|
+
time: number;
|
|
70
|
+
microtime: number;
|
|
71
|
+
name: String;
|
|
72
|
+
message_level: MessageLevel;
|
|
73
|
+
meta_data: Array<Object>;
|
|
74
|
+
};
|
|
75
|
+
type MessageLevel = 'info' | 'debug' | 'warning' | 'error' | 'critical';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
declare class FlareClient {
|
|
79
|
+
version: string;
|
|
80
|
+
config: Flare.Config;
|
|
81
|
+
private glows;
|
|
82
|
+
private context;
|
|
83
|
+
beforeEvaluate: Flare.BeforeEvaluate;
|
|
84
|
+
beforeSubmit: Flare.BeforeSubmit;
|
|
85
|
+
private reportedErrorsTimestamps;
|
|
86
|
+
private solutionProviders;
|
|
87
|
+
private sourcemapVersion;
|
|
88
|
+
debug: boolean;
|
|
89
|
+
stage: string | undefined;
|
|
90
|
+
light(key?: string, debug?: boolean): FlareClient;
|
|
91
|
+
glow(name: string, level?: Flare.MessageLevel, metaData?: Array<object>): FlareClient;
|
|
92
|
+
addContext(name: string, value: any): FlareClient;
|
|
93
|
+
addContextGroup(groupName: string, value: object): FlareClient;
|
|
94
|
+
registerSolutionProvider(provider: Flare.SolutionProvider): FlareClient;
|
|
95
|
+
reportMessage(message: string, context?: Flare.Context, exceptionClass?: string): void;
|
|
96
|
+
report(error: Error, context?: Flare.Context, extraSolutionParameters?: Flare.SolutionProviderExtraParameters): void;
|
|
97
|
+
createReport(error: Error, context?: Flare.Context, extraSolutionParameters?: Flare.SolutionProviderExtraParameters): Promise<Flare.ErrorReport | false>;
|
|
98
|
+
private sendReport;
|
|
99
|
+
private maxReportsPerMinuteReached;
|
|
100
|
+
test(): FlareClient;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type CodeSnippet = {
|
|
104
|
+
[key: number]: string;
|
|
105
|
+
};
|
|
106
|
+
type ReaderResponse = {
|
|
107
|
+
codeSnippet: CodeSnippet;
|
|
108
|
+
trimmedColumnNumber: number | null;
|
|
109
|
+
};
|
|
110
|
+
declare function readLinesFromFile(fileText: string, lineNumber: number, columnNumber?: number, maxSnippetLineLength?: number, maxSnippetLines?: number): ReaderResponse;
|
|
111
|
+
|
|
112
|
+
declare const flare: FlareClient;
|
|
113
|
+
|
|
114
|
+
export { flare, readLinesFromFile };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
declare namespace Flare {
|
|
2
|
+
type BeforeEvaluate = (error: Error) => Error | false | Promise<Error | false>;
|
|
3
|
+
type BeforeSubmit = (report: ErrorReport) => ErrorReport | false | Promise<ErrorReport | false>;
|
|
4
|
+
type Config = {
|
|
5
|
+
key: string;
|
|
6
|
+
reportingUrl: string;
|
|
7
|
+
maxGlowsPerReport: number;
|
|
8
|
+
maxReportsPerMinute: number;
|
|
9
|
+
stage?: string;
|
|
10
|
+
};
|
|
11
|
+
type ErrorReport = {
|
|
12
|
+
notifier: string;
|
|
13
|
+
exception_class: string;
|
|
14
|
+
seen_at: number;
|
|
15
|
+
message: string;
|
|
16
|
+
language: 'javascript';
|
|
17
|
+
glows: Array<Flare.Glow>;
|
|
18
|
+
context: Flare.Context;
|
|
19
|
+
stacktrace: Array<Flare.StackFrame>;
|
|
20
|
+
sourcemap_version_id: string;
|
|
21
|
+
solutions: Array<Flare.Solution>;
|
|
22
|
+
stage?: string;
|
|
23
|
+
};
|
|
24
|
+
interface SolutionProviderExtraParameters {
|
|
25
|
+
}
|
|
26
|
+
type SolutionProvider = {
|
|
27
|
+
canSolve: (error: Error, extraParameters?: SolutionProviderExtraParameters) => boolean | Promise<boolean>;
|
|
28
|
+
getSolutions: (error: Error, extraParameters?: SolutionProviderExtraParameters) => Array<Flare.Solution> | Promise<Array<Flare.Solution>>;
|
|
29
|
+
};
|
|
30
|
+
type Solution = {
|
|
31
|
+
class: string;
|
|
32
|
+
title: string;
|
|
33
|
+
description: string;
|
|
34
|
+
links: {
|
|
35
|
+
[label: string]: string;
|
|
36
|
+
};
|
|
37
|
+
action_description?: string;
|
|
38
|
+
is_runnable?: boolean;
|
|
39
|
+
};
|
|
40
|
+
type Context = {
|
|
41
|
+
request?: {
|
|
42
|
+
url?: String;
|
|
43
|
+
useragent?: String;
|
|
44
|
+
referrer?: String;
|
|
45
|
+
readyState?: String;
|
|
46
|
+
};
|
|
47
|
+
request_data?: {
|
|
48
|
+
queryString: {
|
|
49
|
+
[key: string]: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
cookies?: {
|
|
53
|
+
[key: string]: string;
|
|
54
|
+
};
|
|
55
|
+
[key: string]: any;
|
|
56
|
+
};
|
|
57
|
+
type StackFrame = {
|
|
58
|
+
line_number: number;
|
|
59
|
+
column_number: number;
|
|
60
|
+
method: string;
|
|
61
|
+
file: string;
|
|
62
|
+
code_snippet: {
|
|
63
|
+
[key: number]: string;
|
|
64
|
+
};
|
|
65
|
+
trimmed_column_number: number | null;
|
|
66
|
+
class: string;
|
|
67
|
+
};
|
|
68
|
+
type Glow = {
|
|
69
|
+
time: number;
|
|
70
|
+
microtime: number;
|
|
71
|
+
name: String;
|
|
72
|
+
message_level: MessageLevel;
|
|
73
|
+
meta_data: Array<Object>;
|
|
74
|
+
};
|
|
75
|
+
type MessageLevel = 'info' | 'debug' | 'warning' | 'error' | 'critical';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
declare class FlareClient {
|
|
79
|
+
version: string;
|
|
80
|
+
config: Flare.Config;
|
|
81
|
+
private glows;
|
|
82
|
+
private context;
|
|
83
|
+
beforeEvaluate: Flare.BeforeEvaluate;
|
|
84
|
+
beforeSubmit: Flare.BeforeSubmit;
|
|
85
|
+
private reportedErrorsTimestamps;
|
|
86
|
+
private solutionProviders;
|
|
87
|
+
private sourcemapVersion;
|
|
88
|
+
debug: boolean;
|
|
89
|
+
stage: string | undefined;
|
|
90
|
+
light(key?: string, debug?: boolean): FlareClient;
|
|
91
|
+
glow(name: string, level?: Flare.MessageLevel, metaData?: Array<object>): FlareClient;
|
|
92
|
+
addContext(name: string, value: any): FlareClient;
|
|
93
|
+
addContextGroup(groupName: string, value: object): FlareClient;
|
|
94
|
+
registerSolutionProvider(provider: Flare.SolutionProvider): FlareClient;
|
|
95
|
+
reportMessage(message: string, context?: Flare.Context, exceptionClass?: string): void;
|
|
96
|
+
report(error: Error, context?: Flare.Context, extraSolutionParameters?: Flare.SolutionProviderExtraParameters): void;
|
|
97
|
+
createReport(error: Error, context?: Flare.Context, extraSolutionParameters?: Flare.SolutionProviderExtraParameters): Promise<Flare.ErrorReport | false>;
|
|
98
|
+
private sendReport;
|
|
99
|
+
private maxReportsPerMinuteReached;
|
|
100
|
+
test(): FlareClient;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type CodeSnippet = {
|
|
104
|
+
[key: number]: string;
|
|
105
|
+
};
|
|
106
|
+
type ReaderResponse = {
|
|
107
|
+
codeSnippet: CodeSnippet;
|
|
108
|
+
trimmedColumnNumber: number | null;
|
|
109
|
+
};
|
|
110
|
+
declare function readLinesFromFile(fileText: string, lineNumber: number, columnNumber?: number, maxSnippetLineLength?: number, maxSnippetLines?: number): ReaderResponse;
|
|
111
|
+
|
|
112
|
+
declare const flare: FlareClient;
|
|
113
|
+
|
|
114
|
+
export { flare, readLinesFromFile };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
flare: () => flare,
|
|
34
|
+
readLinesFromFile: () => readLinesFromFile
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(src_exports);
|
|
37
|
+
|
|
38
|
+
// src/build.ts
|
|
39
|
+
var build_default = {
|
|
40
|
+
// Injected during build
|
|
41
|
+
clientVersion: false ? "?" : '"1.0.0-beta.3"',
|
|
42
|
+
// Optionally injected by flare-vite-plugin-sourcemap-uploader
|
|
43
|
+
flareJsKey: typeof FLARE_JS_KEY === "undefined" ? "" : FLARE_JS_KEY,
|
|
44
|
+
sourcemapVersion: typeof FLARE_SOURCEMAP_VERSION === "undefined" ? "" : FLARE_SOURCEMAP_VERSION
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/util/index.ts
|
|
48
|
+
function assert(value, message, debug) {
|
|
49
|
+
if (debug && !value) {
|
|
50
|
+
console.error(
|
|
51
|
+
`Flare JavaScript client v${build_default.clientVersion}: ${message}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return !!value;
|
|
55
|
+
}
|
|
56
|
+
function flatJsonStringify(json) {
|
|
57
|
+
let cache = [];
|
|
58
|
+
const flattenedStringifiedJson = JSON.stringify(json, function(_, value) {
|
|
59
|
+
if (typeof value === "object" && value !== null) {
|
|
60
|
+
if (cache.indexOf(value) !== -1) {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(JSON.stringify(value));
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
cache.push(value);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
});
|
|
71
|
+
cache = null;
|
|
72
|
+
return flattenedStringifiedJson;
|
|
73
|
+
}
|
|
74
|
+
function now() {
|
|
75
|
+
return Math.round(Date.now() / 1e3);
|
|
76
|
+
}
|
|
77
|
+
function flattenOnce(array) {
|
|
78
|
+
return array.reduce((flat, toFlatten) => {
|
|
79
|
+
return flat.concat(toFlatten);
|
|
80
|
+
}, []);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/context/cookie.ts
|
|
84
|
+
function cookie() {
|
|
85
|
+
if (!window.document.cookie) {
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
cookies: window.document.cookie.split("; ").reduce(
|
|
90
|
+
(cookies, cookie2) => {
|
|
91
|
+
const [cookieName, cookieValue] = cookie2.split(/=/);
|
|
92
|
+
cookies[cookieName] = cookieValue;
|
|
93
|
+
return cookies;
|
|
94
|
+
},
|
|
95
|
+
{}
|
|
96
|
+
)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/context/request.ts
|
|
101
|
+
function request() {
|
|
102
|
+
return {
|
|
103
|
+
request: {
|
|
104
|
+
url: window.document.location.href,
|
|
105
|
+
useragent: window.navigator.userAgent,
|
|
106
|
+
referrer: window.document.referrer,
|
|
107
|
+
readyState: window.document.readyState
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/context/requestData.ts
|
|
113
|
+
function requestData() {
|
|
114
|
+
if (!window.location.search) {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
const queryString = {};
|
|
118
|
+
new URLSearchParams(window.location.search).forEach((value, key) => {
|
|
119
|
+
queryString[key] = value;
|
|
120
|
+
});
|
|
121
|
+
return { request_data: { queryString } };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/context/index.ts
|
|
125
|
+
function collectContext(additionalContext) {
|
|
126
|
+
if (typeof window === "undefined") {
|
|
127
|
+
return additionalContext;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
...cookie(),
|
|
131
|
+
...request(),
|
|
132
|
+
...requestData(),
|
|
133
|
+
...additionalContext
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/stacktrace/index.ts
|
|
138
|
+
var import_error_stack_parser = __toESM(require("error-stack-parser"));
|
|
139
|
+
|
|
140
|
+
// src/stacktrace/fileReader.ts
|
|
141
|
+
var cachedFiles = {};
|
|
142
|
+
function getCodeSnippet(url, lineNumber, columnNumber) {
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
if (!url || !lineNumber) {
|
|
145
|
+
return resolve({
|
|
146
|
+
codeSnippet: {
|
|
147
|
+
0: `Could not read from file: missing file URL or line number. URL: ${url} lineNumber: ${lineNumber}`
|
|
148
|
+
},
|
|
149
|
+
trimmedColumnNumber: null
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
readFile(url).then((fileText) => {
|
|
153
|
+
if (!fileText) {
|
|
154
|
+
return resolve({
|
|
155
|
+
codeSnippet: {
|
|
156
|
+
0: `Could not read from file: Error while opening file at URL ${url}`
|
|
157
|
+
},
|
|
158
|
+
trimmedColumnNumber: null
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return resolve(
|
|
162
|
+
readLinesFromFile(fileText, lineNumber, columnNumber)
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function readFile(url) {
|
|
168
|
+
if (cachedFiles[url]) {
|
|
169
|
+
return Promise.resolve(cachedFiles[url]);
|
|
170
|
+
}
|
|
171
|
+
return fetch(url).then((response) => {
|
|
172
|
+
if (response.status !== 200) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return response.text();
|
|
176
|
+
}).catch(() => null);
|
|
177
|
+
}
|
|
178
|
+
function readLinesFromFile(fileText, lineNumber, columnNumber, maxSnippetLineLength = 1e3, maxSnippetLines = 40) {
|
|
179
|
+
const codeSnippet = {};
|
|
180
|
+
let trimmedColumnNumber = null;
|
|
181
|
+
const lines = fileText.split("\n");
|
|
182
|
+
for (let i = -maxSnippetLines / 2; i <= maxSnippetLines / 2; i++) {
|
|
183
|
+
const currentLineIndex = lineNumber + i;
|
|
184
|
+
if (currentLineIndex >= 0 && lines[currentLineIndex]) {
|
|
185
|
+
const displayLine = currentLineIndex + 1;
|
|
186
|
+
if (lines[currentLineIndex].length > maxSnippetLineLength) {
|
|
187
|
+
if (columnNumber && columnNumber + maxSnippetLineLength / 2 > maxSnippetLineLength) {
|
|
188
|
+
codeSnippet[displayLine] = lines[currentLineIndex].substr(
|
|
189
|
+
columnNumber - Math.round(maxSnippetLineLength / 2),
|
|
190
|
+
maxSnippetLineLength
|
|
191
|
+
);
|
|
192
|
+
if (displayLine === lineNumber) {
|
|
193
|
+
trimmedColumnNumber = Math.round(
|
|
194
|
+
maxSnippetLineLength / 2
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
codeSnippet[displayLine] = lines[currentLineIndex].substr(0, maxSnippetLineLength) + "\u2026";
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
codeSnippet[displayLine] = lines[currentLineIndex];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { codeSnippet, trimmedColumnNumber };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/stacktrace/index.ts
|
|
209
|
+
function createStackTrace(error) {
|
|
210
|
+
return new Promise((resolve) => {
|
|
211
|
+
if (!hasStack(error)) {
|
|
212
|
+
assert(
|
|
213
|
+
false,
|
|
214
|
+
"Couldn't generate stacktrace of below error:",
|
|
215
|
+
flare.debug
|
|
216
|
+
);
|
|
217
|
+
if (flare.debug) {
|
|
218
|
+
console.error(error);
|
|
219
|
+
}
|
|
220
|
+
return resolve([
|
|
221
|
+
{
|
|
222
|
+
line_number: 0,
|
|
223
|
+
column_number: 0,
|
|
224
|
+
method: "unknown",
|
|
225
|
+
file: "unknown",
|
|
226
|
+
code_snippet: {
|
|
227
|
+
0: "Could not read from file: stacktrace missing"
|
|
228
|
+
},
|
|
229
|
+
trimmed_column_number: null,
|
|
230
|
+
class: "unknown"
|
|
231
|
+
}
|
|
232
|
+
]);
|
|
233
|
+
}
|
|
234
|
+
Promise.all(
|
|
235
|
+
import_error_stack_parser.default.parse(error).map((frame) => {
|
|
236
|
+
return new Promise((resolve2) => {
|
|
237
|
+
getCodeSnippet(
|
|
238
|
+
frame.fileName,
|
|
239
|
+
frame.lineNumber,
|
|
240
|
+
frame.columnNumber
|
|
241
|
+
).then((snippet) => {
|
|
242
|
+
resolve2({
|
|
243
|
+
line_number: frame.lineNumber || 1,
|
|
244
|
+
column_number: frame.columnNumber || 1,
|
|
245
|
+
method: frame.functionName || "Anonymous or unknown function",
|
|
246
|
+
file: frame.fileName || "Unknown file",
|
|
247
|
+
code_snippet: snippet.codeSnippet,
|
|
248
|
+
trimmed_column_number: snippet.trimmedColumnNumber,
|
|
249
|
+
class: ""
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
})
|
|
254
|
+
).then(resolve);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function hasStack(err) {
|
|
258
|
+
return !!err && (!!err.stack || !!err.stacktrace || !!err["opera#sourceloc"]) && typeof (err.stack || err.stacktrace || err["opera#sourceloc"]) === "string" && err.stack !== `${err.name}: ${err.message}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/solutions/index.ts
|
|
262
|
+
function getSolutions(solutionProviders, error, extraSolutionParameters = {}) {
|
|
263
|
+
return new Promise((resolve) => {
|
|
264
|
+
const canSolves = solutionProviders.reduce(
|
|
265
|
+
(canSolves2, provider) => {
|
|
266
|
+
canSolves2.push(
|
|
267
|
+
Promise.resolve(
|
|
268
|
+
provider.canSolve(error, extraSolutionParameters)
|
|
269
|
+
)
|
|
270
|
+
);
|
|
271
|
+
return canSolves2;
|
|
272
|
+
},
|
|
273
|
+
[]
|
|
274
|
+
);
|
|
275
|
+
Promise.all(canSolves).then((resolvedCanSolves) => {
|
|
276
|
+
const solutionPromises = [];
|
|
277
|
+
resolvedCanSolves.forEach((canSolve, i) => {
|
|
278
|
+
if (canSolve) {
|
|
279
|
+
solutionPromises.push(
|
|
280
|
+
Promise.resolve(
|
|
281
|
+
solutionProviders[i].getSolutions(
|
|
282
|
+
error,
|
|
283
|
+
extraSolutionParameters
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
Promise.all(solutionPromises).then((solutions) => {
|
|
290
|
+
resolve(flattenOnce(solutions));
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/FlareClient.ts
|
|
297
|
+
var FlareClient = class {
|
|
298
|
+
constructor() {
|
|
299
|
+
this.version = build_default.clientVersion;
|
|
300
|
+
this.config = {
|
|
301
|
+
key: "",
|
|
302
|
+
reportingUrl: "https://flareapp.io/api/reports",
|
|
303
|
+
maxGlowsPerReport: 30,
|
|
304
|
+
maxReportsPerMinute: 500
|
|
305
|
+
};
|
|
306
|
+
this.glows = [];
|
|
307
|
+
this.context = { context: {} };
|
|
308
|
+
this.beforeEvaluate = (error) => error;
|
|
309
|
+
this.beforeSubmit = (report) => report;
|
|
310
|
+
this.reportedErrorsTimestamps = [];
|
|
311
|
+
this.solutionProviders = [];
|
|
312
|
+
this.sourcemapVersion = build_default.sourcemapVersion;
|
|
313
|
+
this.debug = false;
|
|
314
|
+
this.stage = void 0;
|
|
315
|
+
}
|
|
316
|
+
light(key = build_default.flareJsKey, debug = false) {
|
|
317
|
+
this.debug = debug;
|
|
318
|
+
if (!assert(
|
|
319
|
+
key && typeof key === "string",
|
|
320
|
+
"An empty or incorrect Flare key was passed, errors will not be reported.",
|
|
321
|
+
this.debug
|
|
322
|
+
) || !assert(
|
|
323
|
+
Promise,
|
|
324
|
+
"ES6 promises are not supported in this environment, errors will not be reported.",
|
|
325
|
+
this.debug
|
|
326
|
+
)) {
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
this.config.key = key;
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
332
|
+
glow(name, level = "info", metaData = []) {
|
|
333
|
+
const time = now();
|
|
334
|
+
this.glows.push({
|
|
335
|
+
name,
|
|
336
|
+
message_level: level,
|
|
337
|
+
meta_data: metaData,
|
|
338
|
+
time,
|
|
339
|
+
microtime: time
|
|
340
|
+
});
|
|
341
|
+
if (this.glows.length > this.config.maxGlowsPerReport) {
|
|
342
|
+
this.glows = this.glows.slice(
|
|
343
|
+
this.glows.length - this.config.maxGlowsPerReport
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return this;
|
|
347
|
+
}
|
|
348
|
+
addContext(name, value) {
|
|
349
|
+
this.context.context[name] = value;
|
|
350
|
+
return this;
|
|
351
|
+
}
|
|
352
|
+
addContextGroup(groupName, value) {
|
|
353
|
+
this.context[groupName] = value;
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
356
|
+
registerSolutionProvider(provider) {
|
|
357
|
+
if (!assert(
|
|
358
|
+
"canSolve" in provider,
|
|
359
|
+
"A solution provider without a [canSolve] property was added.",
|
|
360
|
+
this.debug
|
|
361
|
+
) || !assert(
|
|
362
|
+
"getSolutions" in provider,
|
|
363
|
+
"A solution provider without a [getSolutions] property was added.",
|
|
364
|
+
this.debug
|
|
365
|
+
)) {
|
|
366
|
+
return this;
|
|
367
|
+
}
|
|
368
|
+
this.solutionProviders.push(provider);
|
|
369
|
+
return this;
|
|
370
|
+
}
|
|
371
|
+
reportMessage(message, context = {}, exceptionClass = "Log") {
|
|
372
|
+
const seenAt = now();
|
|
373
|
+
createStackTrace(Error()).then((stacktrace) => {
|
|
374
|
+
stacktrace.shift();
|
|
375
|
+
const report = {
|
|
376
|
+
notifier: `Flare JavaScript client v${build_default.clientVersion}`,
|
|
377
|
+
exception_class: exceptionClass,
|
|
378
|
+
seen_at: seenAt,
|
|
379
|
+
message,
|
|
380
|
+
language: "javascript",
|
|
381
|
+
glows: this.glows,
|
|
382
|
+
context: collectContext({ ...context, ...this.context }),
|
|
383
|
+
stacktrace,
|
|
384
|
+
sourcemap_version_id: this.sourcemapVersion,
|
|
385
|
+
solutions: [],
|
|
386
|
+
stage: this.stage
|
|
387
|
+
};
|
|
388
|
+
this.sendReport(report);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
report(error, context = {}, extraSolutionParameters = {}) {
|
|
392
|
+
Promise.resolve(this.beforeEvaluate(error)).then(
|
|
393
|
+
(reportReadyForEvaluation) => {
|
|
394
|
+
if (!reportReadyForEvaluation) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
this.createReport(error, context, extraSolutionParameters).then(
|
|
398
|
+
(report) => report ? this.sendReport(report) : {}
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
createReport(error, context = {}, extraSolutionParameters = {}) {
|
|
404
|
+
if (!assert(error, "No error provided.", this.debug)) {
|
|
405
|
+
return Promise.resolve(false);
|
|
406
|
+
}
|
|
407
|
+
const seenAt = now();
|
|
408
|
+
return Promise.all([
|
|
409
|
+
getSolutions(
|
|
410
|
+
this.solutionProviders,
|
|
411
|
+
error,
|
|
412
|
+
extraSolutionParameters
|
|
413
|
+
),
|
|
414
|
+
createStackTrace(error)
|
|
415
|
+
]).then((result) => {
|
|
416
|
+
const [solutions, stacktrace] = result;
|
|
417
|
+
assert(
|
|
418
|
+
stacktrace.length,
|
|
419
|
+
"Couldn't generate stacktrace of this error: " + error,
|
|
420
|
+
this.debug
|
|
421
|
+
);
|
|
422
|
+
return {
|
|
423
|
+
notifier: `Flare JavaScript client v${build_default.clientVersion}`,
|
|
424
|
+
exception_class: error.constructor && error.constructor.name ? error.constructor.name : "undefined",
|
|
425
|
+
seen_at: seenAt,
|
|
426
|
+
message: error.message,
|
|
427
|
+
language: "javascript",
|
|
428
|
+
glows: this.glows,
|
|
429
|
+
context: collectContext({ ...context, ...this.context }),
|
|
430
|
+
stacktrace,
|
|
431
|
+
sourcemap_version_id: this.sourcemapVersion,
|
|
432
|
+
solutions,
|
|
433
|
+
stage: this.stage
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
sendReport(report) {
|
|
438
|
+
if (!assert(
|
|
439
|
+
this.config.key,
|
|
440
|
+
"The client was not yet initialised with an API key. Run client.light('<flare-project-key>') when you initialise your app. If you are running in dev mode and didn't run the light command on purpose, you can ignore this error.",
|
|
441
|
+
this.debug
|
|
442
|
+
)) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (this.maxReportsPerMinuteReached()) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
Promise.resolve(this.beforeSubmit(report)).then(
|
|
449
|
+
(reportReadyForSubmit) => {
|
|
450
|
+
if (!reportReadyForSubmit) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
fetch(this.config.reportingUrl, {
|
|
454
|
+
method: "POST",
|
|
455
|
+
headers: {
|
|
456
|
+
"Content-Type": "application/json",
|
|
457
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
458
|
+
"x-api-token": this.config.key
|
|
459
|
+
},
|
|
460
|
+
body: flatJsonStringify({
|
|
461
|
+
...reportReadyForSubmit,
|
|
462
|
+
key: this.config.key
|
|
463
|
+
})
|
|
464
|
+
}).then(
|
|
465
|
+
(response) => {
|
|
466
|
+
if (response.status !== 204) {
|
|
467
|
+
console.error(
|
|
468
|
+
`Received response with status ${response.status} from Flare`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
(error) => console.error(error)
|
|
473
|
+
);
|
|
474
|
+
this.reportedErrorsTimestamps.push(Date.now());
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
maxReportsPerMinuteReached() {
|
|
479
|
+
if (this.reportedErrorsTimestamps.length >= this.config.maxReportsPerMinute) {
|
|
480
|
+
const nErrorsBack = this.reportedErrorsTimestamps[this.reportedErrorsTimestamps.length - this.config.maxReportsPerMinute];
|
|
481
|
+
if (nErrorsBack > Date.now() - 60 * 1e3) {
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
test() {
|
|
488
|
+
this.report(new Error("The Flare client is set up correctly!"));
|
|
489
|
+
return this;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// src/browserClient/index.ts
|
|
494
|
+
function catchWindowErrors() {
|
|
495
|
+
if (typeof window === "undefined") {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const flare2 = window.flare;
|
|
499
|
+
if (!window || !flare2) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const originalOnerrorHandler = window.onerror;
|
|
503
|
+
const originalOnunhandledrejectionHandler = window.onunhandledrejection;
|
|
504
|
+
window.onerror = (_1, _2, _3, _4, error) => {
|
|
505
|
+
if (error) {
|
|
506
|
+
flare2.report(error);
|
|
507
|
+
}
|
|
508
|
+
if (typeof originalOnerrorHandler === "function") {
|
|
509
|
+
originalOnerrorHandler(_1, _2, _3, _4, error);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
window.onunhandledrejection = (error) => {
|
|
513
|
+
if (error.reason instanceof Error) {
|
|
514
|
+
flare2.report(error.reason);
|
|
515
|
+
}
|
|
516
|
+
if (typeof originalOnunhandledrejectionHandler === "function") {
|
|
517
|
+
originalOnunhandledrejectionHandler(error);
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/index.ts
|
|
523
|
+
var flare = new FlareClient();
|
|
524
|
+
if (typeof window !== "undefined" && window) {
|
|
525
|
+
window.flare = flare;
|
|
526
|
+
}
|
|
527
|
+
catchWindowErrors();
|
|
528
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
529
|
+
0 && (module.exports = {
|
|
530
|
+
flare,
|
|
531
|
+
readLinesFromFile
|
|
532
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
// src/build.ts
|
|
2
|
+
var build_default = {
|
|
3
|
+
// Injected during build
|
|
4
|
+
clientVersion: false ? "?" : '"1.0.0-beta.3"',
|
|
5
|
+
// Optionally injected by flare-vite-plugin-sourcemap-uploader
|
|
6
|
+
flareJsKey: typeof FLARE_JS_KEY === "undefined" ? "" : FLARE_JS_KEY,
|
|
7
|
+
sourcemapVersion: typeof FLARE_SOURCEMAP_VERSION === "undefined" ? "" : FLARE_SOURCEMAP_VERSION
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/util/index.ts
|
|
11
|
+
function assert(value, message, debug) {
|
|
12
|
+
if (debug && !value) {
|
|
13
|
+
console.error(
|
|
14
|
+
`Flare JavaScript client v${build_default.clientVersion}: ${message}`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return !!value;
|
|
18
|
+
}
|
|
19
|
+
function flatJsonStringify(json) {
|
|
20
|
+
let cache = [];
|
|
21
|
+
const flattenedStringifiedJson = JSON.stringify(json, function(_, value) {
|
|
22
|
+
if (typeof value === "object" && value !== null) {
|
|
23
|
+
if (cache.indexOf(value) !== -1) {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(JSON.stringify(value));
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
cache.push(value);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
});
|
|
34
|
+
cache = null;
|
|
35
|
+
return flattenedStringifiedJson;
|
|
36
|
+
}
|
|
37
|
+
function now() {
|
|
38
|
+
return Math.round(Date.now() / 1e3);
|
|
39
|
+
}
|
|
40
|
+
function flattenOnce(array) {
|
|
41
|
+
return array.reduce((flat, toFlatten) => {
|
|
42
|
+
return flat.concat(toFlatten);
|
|
43
|
+
}, []);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/context/cookie.ts
|
|
47
|
+
function cookie() {
|
|
48
|
+
if (!window.document.cookie) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
cookies: window.document.cookie.split("; ").reduce(
|
|
53
|
+
(cookies, cookie2) => {
|
|
54
|
+
const [cookieName, cookieValue] = cookie2.split(/=/);
|
|
55
|
+
cookies[cookieName] = cookieValue;
|
|
56
|
+
return cookies;
|
|
57
|
+
},
|
|
58
|
+
{}
|
|
59
|
+
)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/context/request.ts
|
|
64
|
+
function request() {
|
|
65
|
+
return {
|
|
66
|
+
request: {
|
|
67
|
+
url: window.document.location.href,
|
|
68
|
+
useragent: window.navigator.userAgent,
|
|
69
|
+
referrer: window.document.referrer,
|
|
70
|
+
readyState: window.document.readyState
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/context/requestData.ts
|
|
76
|
+
function requestData() {
|
|
77
|
+
if (!window.location.search) {
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
const queryString = {};
|
|
81
|
+
new URLSearchParams(window.location.search).forEach((value, key) => {
|
|
82
|
+
queryString[key] = value;
|
|
83
|
+
});
|
|
84
|
+
return { request_data: { queryString } };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/context/index.ts
|
|
88
|
+
function collectContext(additionalContext) {
|
|
89
|
+
if (typeof window === "undefined") {
|
|
90
|
+
return additionalContext;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
...cookie(),
|
|
94
|
+
...request(),
|
|
95
|
+
...requestData(),
|
|
96
|
+
...additionalContext
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/stacktrace/index.ts
|
|
101
|
+
import ErrorStackParser from "error-stack-parser";
|
|
102
|
+
|
|
103
|
+
// src/stacktrace/fileReader.ts
|
|
104
|
+
var cachedFiles = {};
|
|
105
|
+
function getCodeSnippet(url, lineNumber, columnNumber) {
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
if (!url || !lineNumber) {
|
|
108
|
+
return resolve({
|
|
109
|
+
codeSnippet: {
|
|
110
|
+
0: `Could not read from file: missing file URL or line number. URL: ${url} lineNumber: ${lineNumber}`
|
|
111
|
+
},
|
|
112
|
+
trimmedColumnNumber: null
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
readFile(url).then((fileText) => {
|
|
116
|
+
if (!fileText) {
|
|
117
|
+
return resolve({
|
|
118
|
+
codeSnippet: {
|
|
119
|
+
0: `Could not read from file: Error while opening file at URL ${url}`
|
|
120
|
+
},
|
|
121
|
+
trimmedColumnNumber: null
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return resolve(
|
|
125
|
+
readLinesFromFile(fileText, lineNumber, columnNumber)
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function readFile(url) {
|
|
131
|
+
if (cachedFiles[url]) {
|
|
132
|
+
return Promise.resolve(cachedFiles[url]);
|
|
133
|
+
}
|
|
134
|
+
return fetch(url).then((response) => {
|
|
135
|
+
if (response.status !== 200) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return response.text();
|
|
139
|
+
}).catch(() => null);
|
|
140
|
+
}
|
|
141
|
+
function readLinesFromFile(fileText, lineNumber, columnNumber, maxSnippetLineLength = 1e3, maxSnippetLines = 40) {
|
|
142
|
+
const codeSnippet = {};
|
|
143
|
+
let trimmedColumnNumber = null;
|
|
144
|
+
const lines = fileText.split("\n");
|
|
145
|
+
for (let i = -maxSnippetLines / 2; i <= maxSnippetLines / 2; i++) {
|
|
146
|
+
const currentLineIndex = lineNumber + i;
|
|
147
|
+
if (currentLineIndex >= 0 && lines[currentLineIndex]) {
|
|
148
|
+
const displayLine = currentLineIndex + 1;
|
|
149
|
+
if (lines[currentLineIndex].length > maxSnippetLineLength) {
|
|
150
|
+
if (columnNumber && columnNumber + maxSnippetLineLength / 2 > maxSnippetLineLength) {
|
|
151
|
+
codeSnippet[displayLine] = lines[currentLineIndex].substr(
|
|
152
|
+
columnNumber - Math.round(maxSnippetLineLength / 2),
|
|
153
|
+
maxSnippetLineLength
|
|
154
|
+
);
|
|
155
|
+
if (displayLine === lineNumber) {
|
|
156
|
+
trimmedColumnNumber = Math.round(
|
|
157
|
+
maxSnippetLineLength / 2
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
codeSnippet[displayLine] = lines[currentLineIndex].substr(0, maxSnippetLineLength) + "\u2026";
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
codeSnippet[displayLine] = lines[currentLineIndex];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { codeSnippet, trimmedColumnNumber };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/stacktrace/index.ts
|
|
172
|
+
function createStackTrace(error) {
|
|
173
|
+
return new Promise((resolve) => {
|
|
174
|
+
if (!hasStack(error)) {
|
|
175
|
+
assert(
|
|
176
|
+
false,
|
|
177
|
+
"Couldn't generate stacktrace of below error:",
|
|
178
|
+
flare.debug
|
|
179
|
+
);
|
|
180
|
+
if (flare.debug) {
|
|
181
|
+
console.error(error);
|
|
182
|
+
}
|
|
183
|
+
return resolve([
|
|
184
|
+
{
|
|
185
|
+
line_number: 0,
|
|
186
|
+
column_number: 0,
|
|
187
|
+
method: "unknown",
|
|
188
|
+
file: "unknown",
|
|
189
|
+
code_snippet: {
|
|
190
|
+
0: "Could not read from file: stacktrace missing"
|
|
191
|
+
},
|
|
192
|
+
trimmed_column_number: null,
|
|
193
|
+
class: "unknown"
|
|
194
|
+
}
|
|
195
|
+
]);
|
|
196
|
+
}
|
|
197
|
+
Promise.all(
|
|
198
|
+
ErrorStackParser.parse(error).map((frame) => {
|
|
199
|
+
return new Promise((resolve2) => {
|
|
200
|
+
getCodeSnippet(
|
|
201
|
+
frame.fileName,
|
|
202
|
+
frame.lineNumber,
|
|
203
|
+
frame.columnNumber
|
|
204
|
+
).then((snippet) => {
|
|
205
|
+
resolve2({
|
|
206
|
+
line_number: frame.lineNumber || 1,
|
|
207
|
+
column_number: frame.columnNumber || 1,
|
|
208
|
+
method: frame.functionName || "Anonymous or unknown function",
|
|
209
|
+
file: frame.fileName || "Unknown file",
|
|
210
|
+
code_snippet: snippet.codeSnippet,
|
|
211
|
+
trimmed_column_number: snippet.trimmedColumnNumber,
|
|
212
|
+
class: ""
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
})
|
|
217
|
+
).then(resolve);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function hasStack(err) {
|
|
221
|
+
return !!err && (!!err.stack || !!err.stacktrace || !!err["opera#sourceloc"]) && typeof (err.stack || err.stacktrace || err["opera#sourceloc"]) === "string" && err.stack !== `${err.name}: ${err.message}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/solutions/index.ts
|
|
225
|
+
function getSolutions(solutionProviders, error, extraSolutionParameters = {}) {
|
|
226
|
+
return new Promise((resolve) => {
|
|
227
|
+
const canSolves = solutionProviders.reduce(
|
|
228
|
+
(canSolves2, provider) => {
|
|
229
|
+
canSolves2.push(
|
|
230
|
+
Promise.resolve(
|
|
231
|
+
provider.canSolve(error, extraSolutionParameters)
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
return canSolves2;
|
|
235
|
+
},
|
|
236
|
+
[]
|
|
237
|
+
);
|
|
238
|
+
Promise.all(canSolves).then((resolvedCanSolves) => {
|
|
239
|
+
const solutionPromises = [];
|
|
240
|
+
resolvedCanSolves.forEach((canSolve, i) => {
|
|
241
|
+
if (canSolve) {
|
|
242
|
+
solutionPromises.push(
|
|
243
|
+
Promise.resolve(
|
|
244
|
+
solutionProviders[i].getSolutions(
|
|
245
|
+
error,
|
|
246
|
+
extraSolutionParameters
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
Promise.all(solutionPromises).then((solutions) => {
|
|
253
|
+
resolve(flattenOnce(solutions));
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/FlareClient.ts
|
|
260
|
+
var FlareClient = class {
|
|
261
|
+
constructor() {
|
|
262
|
+
this.version = build_default.clientVersion;
|
|
263
|
+
this.config = {
|
|
264
|
+
key: "",
|
|
265
|
+
reportingUrl: "https://flareapp.io/api/reports",
|
|
266
|
+
maxGlowsPerReport: 30,
|
|
267
|
+
maxReportsPerMinute: 500
|
|
268
|
+
};
|
|
269
|
+
this.glows = [];
|
|
270
|
+
this.context = { context: {} };
|
|
271
|
+
this.beforeEvaluate = (error) => error;
|
|
272
|
+
this.beforeSubmit = (report) => report;
|
|
273
|
+
this.reportedErrorsTimestamps = [];
|
|
274
|
+
this.solutionProviders = [];
|
|
275
|
+
this.sourcemapVersion = build_default.sourcemapVersion;
|
|
276
|
+
this.debug = false;
|
|
277
|
+
this.stage = void 0;
|
|
278
|
+
}
|
|
279
|
+
light(key = build_default.flareJsKey, debug = false) {
|
|
280
|
+
this.debug = debug;
|
|
281
|
+
if (!assert(
|
|
282
|
+
key && typeof key === "string",
|
|
283
|
+
"An empty or incorrect Flare key was passed, errors will not be reported.",
|
|
284
|
+
this.debug
|
|
285
|
+
) || !assert(
|
|
286
|
+
Promise,
|
|
287
|
+
"ES6 promises are not supported in this environment, errors will not be reported.",
|
|
288
|
+
this.debug
|
|
289
|
+
)) {
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
this.config.key = key;
|
|
293
|
+
return this;
|
|
294
|
+
}
|
|
295
|
+
glow(name, level = "info", metaData = []) {
|
|
296
|
+
const time = now();
|
|
297
|
+
this.glows.push({
|
|
298
|
+
name,
|
|
299
|
+
message_level: level,
|
|
300
|
+
meta_data: metaData,
|
|
301
|
+
time,
|
|
302
|
+
microtime: time
|
|
303
|
+
});
|
|
304
|
+
if (this.glows.length > this.config.maxGlowsPerReport) {
|
|
305
|
+
this.glows = this.glows.slice(
|
|
306
|
+
this.glows.length - this.config.maxGlowsPerReport
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
return this;
|
|
310
|
+
}
|
|
311
|
+
addContext(name, value) {
|
|
312
|
+
this.context.context[name] = value;
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
addContextGroup(groupName, value) {
|
|
316
|
+
this.context[groupName] = value;
|
|
317
|
+
return this;
|
|
318
|
+
}
|
|
319
|
+
registerSolutionProvider(provider) {
|
|
320
|
+
if (!assert(
|
|
321
|
+
"canSolve" in provider,
|
|
322
|
+
"A solution provider without a [canSolve] property was added.",
|
|
323
|
+
this.debug
|
|
324
|
+
) || !assert(
|
|
325
|
+
"getSolutions" in provider,
|
|
326
|
+
"A solution provider without a [getSolutions] property was added.",
|
|
327
|
+
this.debug
|
|
328
|
+
)) {
|
|
329
|
+
return this;
|
|
330
|
+
}
|
|
331
|
+
this.solutionProviders.push(provider);
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
reportMessage(message, context = {}, exceptionClass = "Log") {
|
|
335
|
+
const seenAt = now();
|
|
336
|
+
createStackTrace(Error()).then((stacktrace) => {
|
|
337
|
+
stacktrace.shift();
|
|
338
|
+
const report = {
|
|
339
|
+
notifier: `Flare JavaScript client v${build_default.clientVersion}`,
|
|
340
|
+
exception_class: exceptionClass,
|
|
341
|
+
seen_at: seenAt,
|
|
342
|
+
message,
|
|
343
|
+
language: "javascript",
|
|
344
|
+
glows: this.glows,
|
|
345
|
+
context: collectContext({ ...context, ...this.context }),
|
|
346
|
+
stacktrace,
|
|
347
|
+
sourcemap_version_id: this.sourcemapVersion,
|
|
348
|
+
solutions: [],
|
|
349
|
+
stage: this.stage
|
|
350
|
+
};
|
|
351
|
+
this.sendReport(report);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
report(error, context = {}, extraSolutionParameters = {}) {
|
|
355
|
+
Promise.resolve(this.beforeEvaluate(error)).then(
|
|
356
|
+
(reportReadyForEvaluation) => {
|
|
357
|
+
if (!reportReadyForEvaluation) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
this.createReport(error, context, extraSolutionParameters).then(
|
|
361
|
+
(report) => report ? this.sendReport(report) : {}
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
createReport(error, context = {}, extraSolutionParameters = {}) {
|
|
367
|
+
if (!assert(error, "No error provided.", this.debug)) {
|
|
368
|
+
return Promise.resolve(false);
|
|
369
|
+
}
|
|
370
|
+
const seenAt = now();
|
|
371
|
+
return Promise.all([
|
|
372
|
+
getSolutions(
|
|
373
|
+
this.solutionProviders,
|
|
374
|
+
error,
|
|
375
|
+
extraSolutionParameters
|
|
376
|
+
),
|
|
377
|
+
createStackTrace(error)
|
|
378
|
+
]).then((result) => {
|
|
379
|
+
const [solutions, stacktrace] = result;
|
|
380
|
+
assert(
|
|
381
|
+
stacktrace.length,
|
|
382
|
+
"Couldn't generate stacktrace of this error: " + error,
|
|
383
|
+
this.debug
|
|
384
|
+
);
|
|
385
|
+
return {
|
|
386
|
+
notifier: `Flare JavaScript client v${build_default.clientVersion}`,
|
|
387
|
+
exception_class: error.constructor && error.constructor.name ? error.constructor.name : "undefined",
|
|
388
|
+
seen_at: seenAt,
|
|
389
|
+
message: error.message,
|
|
390
|
+
language: "javascript",
|
|
391
|
+
glows: this.glows,
|
|
392
|
+
context: collectContext({ ...context, ...this.context }),
|
|
393
|
+
stacktrace,
|
|
394
|
+
sourcemap_version_id: this.sourcemapVersion,
|
|
395
|
+
solutions,
|
|
396
|
+
stage: this.stage
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
sendReport(report) {
|
|
401
|
+
if (!assert(
|
|
402
|
+
this.config.key,
|
|
403
|
+
"The client was not yet initialised with an API key. Run client.light('<flare-project-key>') when you initialise your app. If you are running in dev mode and didn't run the light command on purpose, you can ignore this error.",
|
|
404
|
+
this.debug
|
|
405
|
+
)) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (this.maxReportsPerMinuteReached()) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
Promise.resolve(this.beforeSubmit(report)).then(
|
|
412
|
+
(reportReadyForSubmit) => {
|
|
413
|
+
if (!reportReadyForSubmit) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
fetch(this.config.reportingUrl, {
|
|
417
|
+
method: "POST",
|
|
418
|
+
headers: {
|
|
419
|
+
"Content-Type": "application/json",
|
|
420
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
421
|
+
"x-api-token": this.config.key
|
|
422
|
+
},
|
|
423
|
+
body: flatJsonStringify({
|
|
424
|
+
...reportReadyForSubmit,
|
|
425
|
+
key: this.config.key
|
|
426
|
+
})
|
|
427
|
+
}).then(
|
|
428
|
+
(response) => {
|
|
429
|
+
if (response.status !== 204) {
|
|
430
|
+
console.error(
|
|
431
|
+
`Received response with status ${response.status} from Flare`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
(error) => console.error(error)
|
|
436
|
+
);
|
|
437
|
+
this.reportedErrorsTimestamps.push(Date.now());
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
maxReportsPerMinuteReached() {
|
|
442
|
+
if (this.reportedErrorsTimestamps.length >= this.config.maxReportsPerMinute) {
|
|
443
|
+
const nErrorsBack = this.reportedErrorsTimestamps[this.reportedErrorsTimestamps.length - this.config.maxReportsPerMinute];
|
|
444
|
+
if (nErrorsBack > Date.now() - 60 * 1e3) {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
test() {
|
|
451
|
+
this.report(new Error("The Flare client is set up correctly!"));
|
|
452
|
+
return this;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// src/browserClient/index.ts
|
|
457
|
+
function catchWindowErrors() {
|
|
458
|
+
if (typeof window === "undefined") {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const flare2 = window.flare;
|
|
462
|
+
if (!window || !flare2) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const originalOnerrorHandler = window.onerror;
|
|
466
|
+
const originalOnunhandledrejectionHandler = window.onunhandledrejection;
|
|
467
|
+
window.onerror = (_1, _2, _3, _4, error) => {
|
|
468
|
+
if (error) {
|
|
469
|
+
flare2.report(error);
|
|
470
|
+
}
|
|
471
|
+
if (typeof originalOnerrorHandler === "function") {
|
|
472
|
+
originalOnerrorHandler(_1, _2, _3, _4, error);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
window.onunhandledrejection = (error) => {
|
|
476
|
+
if (error.reason instanceof Error) {
|
|
477
|
+
flare2.report(error.reason);
|
|
478
|
+
}
|
|
479
|
+
if (typeof originalOnunhandledrejectionHandler === "function") {
|
|
480
|
+
originalOnunhandledrejectionHandler(error);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/index.ts
|
|
486
|
+
var flare = new FlareClient();
|
|
487
|
+
if (typeof window !== "undefined" && window) {
|
|
488
|
+
window.flare = flare;
|
|
489
|
+
}
|
|
490
|
+
catchWindowErrors();
|
|
491
|
+
export {
|
|
492
|
+
flare,
|
|
493
|
+
readLinesFromFile
|
|
494
|
+
};
|
package/package.json
CHANGED
package/tests/index.test.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { flare } from '../src/index';
|
|
3
|
-
import { expect, test } from 'vitest';
|
|
4
|
-
|
|
5
|
-
test('properly lights', (done) => {
|
|
6
|
-
const projectKey = 'aprojectkey';
|
|
7
|
-
|
|
8
|
-
flare.light(projectKey);
|
|
9
|
-
|
|
10
|
-
expect(flare.config.key).toBe(projectKey);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test('can create glows', (done) => {
|
|
14
|
-
flare.glow('glowName', undefined, undefined);
|
|
15
|
-
|
|
16
|
-
expect(flare.glows.length).toBe(1);
|
|
17
|
-
|
|
18
|
-
expect(flare.glows[0]).toMatchObject({
|
|
19
|
-
name: 'glowName',
|
|
20
|
-
message_level: 'info',
|
|
21
|
-
meta_data: [],
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
expect(typeof flare.glows[0].microtime).toBe('number');
|
|
25
|
-
expect(typeof flare.glows[0].time).toBe('number');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test.todo('can register solution providers');
|
|
29
|
-
|
|
30
|
-
test.todo('can use solution providers properly');
|
|
31
|
-
|
|
32
|
-
test.todo('can use async solution providers properly');
|
|
33
|
-
|
|
34
|
-
test.todo('can build an error report from an error');
|
|
35
|
-
|
|
36
|
-
test.todo('can create custom context and context groups');
|
|
37
|
-
|
|
38
|
-
test.todo('can throttle when too many reports are being sent');
|
|
39
|
-
|
|
40
|
-
test.todo('can stop a report from being submitted by using beforeSubmit');
|
|
41
|
-
|
|
42
|
-
test.todo('can use an async beforeSubmit');
|
|
43
|
-
|
|
44
|
-
test.todo('can edit a report using beforeSubmit and still send it');
|
|
45
|
-
|
|
46
|
-
test.todo('can stop an error from being evaluated by using beforeEvaluate');
|
|
47
|
-
|
|
48
|
-
test.todo('can use an async beforeEvaluate');
|
|
49
|
-
|
|
50
|
-
test.todo('can check out an error using beforeEvaluate and still send it');
|