@flareapp/js 1.2.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,203 +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: null,
20
- version: CLIENT_VERSION,
21
- sourcemapVersion: SOURCEMAP_VERSION,
22
- stage: '',
23
- maxGlowsPerReport: 30,
24
- reportingUrl: 'https://ingress.flareapp.io/v1/errors',
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 api: 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.api.report(reportToSubmit, this.config);
188
- }
189
-
190
- // Deprecated, the following methods exist for backwards compatibility.
191
-
192
- set beforeEvaluate(beforeEvaluate: Config['beforeEvaluate']) {
193
- this.config.beforeEvaluate = beforeEvaluate ?? '';
194
- }
195
-
196
- set beforeSubmit(beforeSubmit: Config['beforeSubmit']) {
197
- this.config.beforeSubmit = beforeSubmit ?? '';
198
- }
199
-
200
- set stage(stage: string | undefined) {
201
- this.config.stage = stage ?? '';
202
- }
203
- }
package/src/api/Api.ts DELETED
@@ -1,27 +0,0 @@
1
- import { Config, Report } from '../types';
2
- import { flatJsonStringify } from '../util';
3
-
4
- import { mapToV2Wire } from './mapToV2Wire';
5
-
6
- export class Api {
7
- report(report: Report, config: Config): Promise<void> {
8
- return fetch(config.reportingUrl, {
9
- method: 'POST',
10
- headers: {
11
- 'Accept': 'application/json',
12
- 'Content-Type': 'application/json',
13
- 'X-Api-Token': config.key ?? '',
14
- 'X-Report-Browser-Extension-Errors': JSON.stringify(config.reportBrowserExtensionErrors),
15
- 'X-Flare-Client-Version': '1',
16
- },
17
- body: flatJsonStringify(mapToV2Wire(report, config)),
18
- }).then(
19
- (response) => {
20
- if (response.status !== 200 && response.status !== 201 && 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,2 +0,0 @@
1
- export * from './Api';
2
- export * from './mapToV2Wire';
@@ -1,120 +0,0 @@
1
- import { CLIENT_VERSION } from '../env';
2
- import { Config, Context, Report, StackFrame } from '../types';
3
- import { glowsToEvents } from '../util/glowsToEvents';
4
-
5
- import { V2AttributeValue, V2Attributes, V2StackFrame, V2WirePayload } from './v2WireTypes';
6
-
7
- const KNOWN_CONTEXT_BUCKETS = new Set(['request', 'request_data', 'cookies', 'context']);
8
-
9
- const NON_APPLICATION_FRAME_PATTERN = /node_modules|vendor|chunk-/;
10
-
11
- export function mapToV2Wire(report: Report, config: Config): V2WirePayload {
12
- const wire: V2WirePayload = {
13
- seenAtUnixNano: Math.round(report.seen_at * 1_000_000_000),
14
- stacktrace: report.stacktrace.map(mapStackFrame),
15
- events: glowsToEvents(report.glows),
16
- attributes: buildAttributes(report, config),
17
- };
18
-
19
- if (report.exception_class) {
20
- wire.exceptionClass = report.exception_class;
21
- }
22
- if (report.message != null) {
23
- wire.message = report.message;
24
- }
25
- if (report.sourcemap_version_id) {
26
- wire.sourcemapVersionId = report.sourcemap_version_id;
27
- }
28
-
29
- return wire;
30
- }
31
-
32
- function mapStackFrame(frame: StackFrame): V2StackFrame {
33
- const out: V2StackFrame = {
34
- file: frame.file,
35
- lineNumber: frame.line_number,
36
- isApplicationFrame: !NON_APPLICATION_FRAME_PATTERN.test(frame.file),
37
- };
38
-
39
- if (frame.column_number != null) {
40
- out.columnNumber = frame.column_number;
41
- }
42
- if (frame.method) {
43
- out.method = frame.method;
44
- }
45
- if (frame.class) {
46
- out.class = frame.class;
47
- }
48
- if (frame.code_snippet) {
49
- out.codeSnippet = frame.code_snippet;
50
- }
51
-
52
- return out;
53
- }
54
-
55
- function buildAttributes(report: Report, config: Config): V2Attributes {
56
- const attrs: V2Attributes = {
57
- 'telemetry.sdk.language': 'javascript',
58
- 'telemetry.sdk.name': '@flareapp/js',
59
- 'telemetry.sdk.version': CLIENT_VERSION,
60
- 'flare.language.name': 'javascript',
61
- 'flare.entry_point.type': 'web',
62
- };
63
-
64
- if (typeof window !== 'undefined' && window.location && window.location.href) {
65
- attrs['flare.entry_point.value'] = window.location.href;
66
- }
67
-
68
- if (config.stage) {
69
- attrs['service.stage'] = config.stage;
70
- }
71
- if (config.version) {
72
- attrs['service.version'] = config.version;
73
- }
74
-
75
- const context: Context = report.context ?? {};
76
-
77
- if (context.request?.url) attrs['url.full'] = String(context.request.url);
78
- if (context.request?.useragent) attrs['user_agent.original'] = String(context.request.useragent);
79
- if (context.request?.referrer) attrs['http.request.referrer'] = String(context.request.referrer);
80
- if (context.request?.readyState) attrs['document.ready_state'] = String(context.request.readyState);
81
-
82
- if (context.request_data?.queryString) {
83
- attrs['url.query'] = context.request_data.queryString as V2AttributeValue;
84
- }
85
- if (context.cookies) {
86
- attrs['http.request.cookies'] = context.cookies as V2AttributeValue;
87
- }
88
-
89
- const custom = buildCustomContext(context);
90
- if (Object.keys(custom).length > 0) {
91
- attrs['context.custom'] = custom;
92
- }
93
-
94
- return attrs;
95
- }
96
-
97
- function buildCustomContext(context: Context): { [k: string]: V2AttributeValue } {
98
- const custom: { [k: string]: V2AttributeValue } = {};
99
-
100
- // Passed-context: any top-level key not in known buckets.
101
- for (const key of Object.keys(context)) {
102
- if (KNOWN_CONTEXT_BUCKETS.has(key)) continue;
103
- custom[key] = context[key] as V2AttributeValue;
104
- }
105
-
106
- // addContext('foo', value) populates context.context.foo. Flatten its
107
- // children into siblings under context.custom. We process this AFTER the
108
- // passed-context loop above so that on a collision (e.g. user calls both
109
- // addContext('vue', X) and flare.report(err, {vue: Y})), the addContext
110
- // value wins — same outcome as the {...passed, ...this.context} merge in
111
- // Flare.report's createReportFromError.
112
- const added = (context as any).context;
113
- if (added && typeof added === 'object') {
114
- for (const key of Object.keys(added)) {
115
- custom[key] = added[key] as V2AttributeValue;
116
- }
117
- }
118
-
119
- return custom;
120
- }
@@ -1,39 +0,0 @@
1
- // Internal types — NOT re-exported from packages/js/src/index.ts.
2
- // These describe the v2 wire shape posted to https://ingress.flareapp.io/v1/errors.
3
-
4
- export type V2AttributeValue =
5
- | string
6
- | number
7
- | boolean
8
- | null
9
- | V2AttributeValue[]
10
- | { [key: string]: V2AttributeValue };
11
-
12
- export type V2Attributes = Record<string, V2AttributeValue>;
13
-
14
- export type V2StackFrame = {
15
- file: string;
16
- lineNumber: number;
17
- columnNumber?: number;
18
- method?: string;
19
- class?: string;
20
- codeSnippet?: { [line: number]: string };
21
- isApplicationFrame?: boolean;
22
- };
23
-
24
- export type V2SpanEvent = {
25
- type: string;
26
- startTimeUnixNano: number;
27
- endTimeUnixNano: number | null;
28
- attributes: V2Attributes;
29
- };
30
-
31
- export type V2WirePayload = {
32
- exceptionClass?: string;
33
- message?: string;
34
- seenAtUnixNano: number;
35
- sourcemapVersionId?: string;
36
- stacktrace: V2StackFrame[];
37
- events: V2SpanEvent[];
38
- attributes: V2Attributes;
39
- };
@@ -1,72 +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
- window.addEventListener('error', (event: ErrorEvent) => {
14
- if (event.error) {
15
- flare.report(event.error);
16
- }
17
- });
18
-
19
- window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
20
- const reason = event.reason;
21
-
22
- if (reason instanceof Error) {
23
- flare.report(reason);
24
- return;
25
- }
26
-
27
- if (hasStack(reason)) {
28
- const error = new Error(rejectionReasonToMessage(reason));
29
- error.stack = (reason as { stack: string }).stack;
30
- flare.report(error);
31
- return;
32
- }
33
-
34
- flare.reportMessage(rejectionReasonToMessage(reason), {}, 'UnhandledPromiseRejection');
35
- });
36
- }
37
-
38
- function rejectionReasonToMessage(reason: unknown): string {
39
- if (typeof reason === 'string') {
40
- return reason;
41
- }
42
-
43
- if (reason == null) {
44
- return `Unhandled promise rejection (${reason})`;
45
- }
46
-
47
- if (typeof reason === 'object') {
48
- if ('message' in reason && typeof (reason as Record<string, unknown>).message === 'string') {
49
- return (reason as Record<string, unknown>).message as string;
50
- }
51
-
52
- try {
53
- const json = JSON.stringify(reason);
54
- if (json && json !== '{}') {
55
- return `Unhandled promise rejection: ${json}`;
56
- }
57
- } catch {}
58
-
59
- return 'Unhandled promise rejection with non-serializable object';
60
- }
61
-
62
- return `Unhandled promise rejection: ${String(reason)}`;
63
- }
64
-
65
- function hasStack(value: unknown): boolean {
66
- return (
67
- typeof value === 'object' &&
68
- value !== null &&
69
- 'stack' in value &&
70
- typeof (value as Record<string, unknown>).stack === 'string'
71
- );
72
- }
@@ -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,24 +0,0 @@
1
- export default function cookie() {
2
- if (!window.document.cookie) {
3
- return {};
4
- }
5
-
6
- const cookies: { [key: string]: string } = {};
7
-
8
- for (const raw of window.document.cookie.split('; ')) {
9
- const eq = raw.indexOf('=');
10
- if (eq === -1) {
11
- continue;
12
- }
13
-
14
- const name = raw.slice(0, eq);
15
- const value = raw.slice(eq + 1);
16
- cookies[name] = value;
17
- }
18
-
19
- if (Object.keys(cookies).length === 0) {
20
- return {};
21
- }
22
-
23
- return { cookies };
24
- }
@@ -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,69 +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([fallbackFrame('stacktrace missing')]);
18
- }
19
-
20
- let parsed: ReturnType<typeof ErrorStackParser.parse>;
21
- try {
22
- parsed = ErrorStackParser.parse(error);
23
- } catch (parseError) {
24
- if (debug) {
25
- console.error('Flare: failed to parse stacktrace', parseError);
26
- }
27
- return resolve([fallbackFrame('Could not parse stacktrace')]);
28
- }
29
-
30
- Promise.all(
31
- parsed.map((frame) => {
32
- return new Promise<StackFrame>((resolve) => {
33
- getCodeSnippet(frame.fileName, frame.lineNumber, frame.columnNumber).then((snippet) => {
34
- resolve({
35
- line_number: frame.lineNumber || 1,
36
- column_number: frame.columnNumber || 1,
37
- method: frame.functionName || 'Anonymous or unknown function',
38
- file: frame.fileName || 'Unknown file',
39
- code_snippet: snippet.codeSnippet,
40
- trimmed_column_number: snippet.trimmedColumnNumber,
41
- class: '',
42
- });
43
- });
44
- });
45
- })
46
- ).then(resolve);
47
- });
48
- }
49
-
50
- function fallbackFrame(message: string): StackFrame {
51
- return {
52
- line_number: 0,
53
- column_number: 0,
54
- method: 'unknown',
55
- file: 'unknown',
56
- code_snippet: { 0: message },
57
- trimmed_column_number: null,
58
- class: 'unknown',
59
- };
60
- }
61
-
62
- function hasStack(err: any): boolean {
63
- return (
64
- !!err &&
65
- (!!err.stack || !!err.stacktrace || !!err['opera#sourceloc']) &&
66
- typeof (err.stack || err.stacktrace || err['opera#sourceloc']) === 'string' &&
67
- err.stack !== `${err.name}: ${err.message}`
68
- );
69
- }