@flareapp/js 2.0.0-rc.1 → 2.0.0

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/src/Flare.ts DELETED
@@ -1,208 +0,0 @@
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 DELETED
@@ -1,27 +0,0 @@
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 DELETED
@@ -1 +0,0 @@
1
- export * from './Api';
@@ -1,36 +0,0 @@
1
- export function catchWindowErrors() {
2
- if (typeof window === 'undefined') {
3
- return;
4
- }
5
-
6
- // @ts-ignore
7
- const flare = window.flare;
8
-
9
- if (!window || !flare) {
10
- return;
11
- }
12
-
13
- const originalOnerrorHandler = window.onerror;
14
- const originalOnunhandledrejectionHandler = window.onunhandledrejection;
15
-
16
- window.onerror = (_1, _2, _3, _4, error) => {
17
- if (error) {
18
- flare.report(error);
19
- }
20
-
21
- if (typeof originalOnerrorHandler === 'function') {
22
- originalOnerrorHandler(_1, _2, _3, _4, error);
23
- }
24
- };
25
-
26
- window.onunhandledrejection = (error: PromiseRejectionEvent) => {
27
- if (error.reason instanceof Error) {
28
- flare.report(error.reason);
29
- }
30
-
31
- if (typeof originalOnunhandledrejectionHandler === 'function') {
32
- // @ts-ignore
33
- originalOnunhandledrejectionHandler(error);
34
- }
35
- };
36
- }
@@ -1 +0,0 @@
1
- export * from './catchWindowErrors';
@@ -1,18 +0,0 @@
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
- }
@@ -1,17 +0,0 @@
1
- export default function cookie() {
2
- if (!window.document.cookie) {
3
- return {};
4
- }
5
-
6
- return {
7
- cookies: window.document.cookie.split('; ').reduce(
8
- (cookies, cookie) => {
9
- const [cookieName, cookieValue] = cookie.split(/=/);
10
- cookies[cookieName] = cookieValue;
11
-
12
- return cookies;
13
- },
14
- {} as { [key: string]: string }
15
- ),
16
- };
17
- }
@@ -1 +0,0 @@
1
- export * from './collectContext';
@@ -1,10 +0,0 @@
1
- export default function request() {
2
- return {
3
- request: {
4
- url: window.document.location.href,
5
- useragent: window.navigator.userAgent,
6
- referrer: window.document.referrer,
7
- readyState: window.document.readyState,
8
- },
9
- };
10
- }
@@ -1,13 +0,0 @@
1
- export default function requestData() {
2
- if (!window.location.search) {
3
- return {};
4
- }
5
-
6
- const queryString: { [key: string]: string } = {};
7
-
8
- new URLSearchParams(window.location.search).forEach((value, key) => {
9
- queryString[key] = value;
10
- });
11
-
12
- return { request_data: { queryString } };
13
- }
package/src/env/index.ts DELETED
@@ -1,12 +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.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 DELETED
@@ -1,14 +0,0 @@
1
- import { Flare } from './Flare';
2
- import { catchWindowErrors } from './browser';
3
-
4
- // Expose package singleton
5
- export const flare = new Flare();
6
-
7
- if (typeof window !== 'undefined' && window) {
8
- // @ts-expect-error
9
- window.flare = flare;
10
-
11
- catchWindowErrors();
12
- }
13
-
14
- export { Flare } from './Flare';
@@ -1,35 +0,0 @@
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
- }
@@ -1 +0,0 @@
1
- export * from './getSolutions';
@@ -1,59 +0,0 @@
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
- }
@@ -1,96 +0,0 @@
1
- const cachedFiles: { [key: string]: string } = {};
2
-
3
- type CodeSnippet = {
4
- [key: number]: string;
5
- };
6
-
7
- type ReaderResponse = {
8
- codeSnippet: CodeSnippet;
9
- trimmedColumnNumber: number | null;
10
- };
11
-
12
- export function getCodeSnippet(url?: string, lineNumber?: number, columnNumber?: number): Promise<ReaderResponse> {
13
- return new Promise((resolve) => {
14
- if (!url || !lineNumber) {
15
- return resolve({
16
- codeSnippet: {
17
- 0: `Could not read from file: missing file URL or line number. URL: ${url} lineNumber: ${lineNumber}`,
18
- },
19
- trimmedColumnNumber: null,
20
- });
21
- }
22
-
23
- readFile(url).then((fileText) => {
24
- if (!fileText) {
25
- return resolve({
26
- codeSnippet: {
27
- 0: `Could not read from file: Error while opening file at URL ${url}`,
28
- },
29
- trimmedColumnNumber: null,
30
- });
31
- }
32
-
33
- return resolve(readLinesFromFile(fileText, lineNumber, columnNumber));
34
- });
35
- });
36
- }
37
-
38
- function readFile(url: string): Promise<string | null> {
39
- if (cachedFiles[url]) {
40
- return Promise.resolve(cachedFiles[url]);
41
- }
42
-
43
- return fetch(url)
44
- .then((response) => {
45
- if (response.status !== 200) {
46
- return null;
47
- }
48
-
49
- return response.text();
50
- })
51
- .catch(() => null);
52
- }
53
-
54
- export function readLinesFromFile(
55
- fileText: string,
56
- lineNumber: number,
57
- columnNumber?: number,
58
- maxSnippetLineLength = 1000,
59
- maxSnippetLines = 40
60
- ): ReaderResponse {
61
- const codeSnippet: CodeSnippet = {};
62
- let trimmedColumnNumber = null;
63
-
64
- const lines = fileText.split('\n');
65
-
66
- for (let i = -maxSnippetLines / 2; i <= maxSnippetLines / 2; i++) {
67
- const currentLineIndex = lineNumber + i;
68
-
69
- if (currentLineIndex >= 0 && lines[currentLineIndex]) {
70
- const displayLine = currentLineIndex + 1; // the linenumber in a stacktrace is not zero-based like an array
71
-
72
- if (lines[currentLineIndex].length > maxSnippetLineLength) {
73
- if (columnNumber && columnNumber + maxSnippetLineLength / 2 > maxSnippetLineLength) {
74
- codeSnippet[displayLine] = lines[currentLineIndex].substr(
75
- columnNumber - Math.round(maxSnippetLineLength / 2),
76
- maxSnippetLineLength
77
- );
78
-
79
- if (displayLine === lineNumber) {
80
- trimmedColumnNumber = Math.round(maxSnippetLineLength / 2);
81
- }
82
-
83
- continue;
84
- }
85
-
86
- codeSnippet[displayLine] = lines[currentLineIndex].substr(0, maxSnippetLineLength) + '…';
87
-
88
- continue;
89
- }
90
-
91
- codeSnippet[displayLine] = lines[currentLineIndex];
92
- }
93
- }
94
-
95
- return { codeSnippet, trimmedColumnNumber };
96
- }
@@ -1 +0,0 @@
1
- export * from './createStackTrace';
package/src/types.ts DELETED
@@ -1,76 +0,0 @@
1
- export type Config = {
2
- key: string;
3
- version: string;
4
- sourcemapVersion: string;
5
- stage: string;
6
- maxGlowsPerReport: number;
7
- reportBrowserExtensionErrors: boolean;
8
- reportingUrl: string;
9
- debug: boolean;
10
- beforeEvaluate: (error: Error) => Error | false | null | Promise<Error | false | null>;
11
- beforeSubmit: (report: Report) => Report | false | null | Promise<Report | false | null>;
12
- };
13
-
14
- export type Report = {
15
- notifier: string;
16
- exception_class: string;
17
- seen_at: number;
18
- message: string;
19
- language: 'javascript';
20
- glows: Glow[];
21
- context: Context;
22
- stacktrace: StackFrame[];
23
- sourcemap_version_id: string;
24
- solutions: Solution[];
25
- stage?: string;
26
- };
27
-
28
- export interface SolutionProviderExtraParameters {}
29
-
30
- export type SolutionProvider = {
31
- canSolve: (error: Error, extraParameters?: SolutionProviderExtraParameters) => boolean | Promise<boolean>;
32
- getSolutions: (error: Error, extraParameters?: SolutionProviderExtraParameters) => Solution[] | Promise<Solution[]>;
33
- };
34
-
35
- export type Solution = {
36
- class: string;
37
- title: string;
38
- description: string;
39
- links: { [label: string]: string };
40
- action_description?: string;
41
- is_runnable?: boolean;
42
- };
43
-
44
- export type Context = {
45
- request?: {
46
- url?: String;
47
- useragent?: String;
48
- referrer?: String; // TODO: Flare doesn't catch this yet
49
- readyState?: String; // TODO: Flare doesn't catch this yet
50
- };
51
- request_data?: {
52
- queryString: { [key: string]: string };
53
- };
54
- cookies?: { [key: string]: string };
55
- [key: string]: any;
56
- };
57
-
58
- export type StackFrame = {
59
- line_number: number;
60
- column_number: number; // TODO: Flare doesn't catch this yet
61
- method: string;
62
- file: string;
63
- code_snippet: { [key: number]: string };
64
- trimmed_column_number: number | null;
65
- class: string;
66
- };
67
-
68
- export type Glow = {
69
- time: number;
70
- microtime: number;
71
- name: String;
72
- message_level: MessageLevel;
73
- meta_data: object | object[];
74
- };
75
-
76
- export type MessageLevel = 'info' | 'debug' | 'warning' | 'error' | 'critical';
@@ -1,9 +0,0 @@
1
- import { CLIENT_VERSION } from '../env';
2
-
3
- export function assert(value: any, message: string, debug: boolean) {
4
- if (debug && !value) {
5
- console.error(`Flare JavaScript client v${CLIENT_VERSION}: ${message}`);
6
- }
7
-
8
- return !!value;
9
- }
@@ -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,12 +0,0 @@
1
- import { assert } from './assert';
2
-
3
- export function assertSolutionProvider(solutionProvider: object, debug: boolean): boolean {
4
- return (
5
- assert('canSolve' in solutionProvider, 'A solution provider without a [canSolve] property was added.', debug) &&
6
- assert(
7
- 'getSolutions' in solutionProvider,
8
- 'A solution provider without a [getSolutions] property was added.',
9
- debug
10
- )
11
- );
12
- }