Skip to content

Commit

Permalink
Merge pull request #111 from codex-team/nuxt-support
Browse files Browse the repository at this point in the history
feat: nuxt, captureError method, perevent multiple sending
  • Loading branch information
neSpecc authored Oct 22, 2024
2 parents 0d2714a + 8ebc66c commit 5aecc52
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 26 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package.json
5 changes: 3 additions & 2 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,10 @@ <h2>Test Vue integration: &lt;test-component&gt;</h2>
})
</script>
</section>
<script src="assets/hawk.js"></script>
<script src="sample-errors.js"></script>
<script>
<script type="module">
import HawkCatcher from '../src';

window.hawk = new HawkCatcher({
token: 'eyJpbnRlZ3JhdGlvbklkIjoiNWU5OTE1MzItZTdiYy00ZjA0LTliY2UtYmIzZmE5ZTUwMTg3Iiwic2VjcmV0IjoiMTBlMTA4MjQtZTcyNC00YWFkLTkwMDQtMzExYTU1OWMzZTIxIn0=',
// collectorEndpoint: 'ws://localhost:3000/ws',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hawk.so/javascript",
"version": "3.0.10",
"version": "3.1.0",
"description": "JavaScript errors tracking for Hawk.so",
"files": [
"dist"
Expand Down
34 changes: 29 additions & 5 deletions src/catcher.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Socket from './modules/socket';
import Sanitizer from './modules/sanitizer';
import log from './modules/logger';
import log from './utils/log';
import StackParser from './modules/stackParser';
import type { CatcherMessage, HawkInitialSettings } from '@/types';
import { VueIntegration } from './integrations/vue';
import { generateRandomId } from './utils';
import { id } from './utils/id';
import type {
AffectedUser,
EventContext,
Expand All @@ -15,6 +15,7 @@ import type {
import type { JavaScriptCatcherIntegrations } from './types/integrations';
import { EventRejectedError } from './errors';
import type { HawkJavaScriptEvent } from './types';
import { isErrorProcessed, markErrorAsProcessed } from './utils/event';

/**
* Allow to use global VERSION, that will be overwritten by Webpack
Expand Down Expand Up @@ -153,7 +154,7 @@ export default class Catcher {
if (storedId) {
userId = storedId;
} else {
userId = generateRandomId();
userId = id();
localStorage.setItem(LOCAL_STORAGE_KEY, userId);
}

Expand Down Expand Up @@ -182,6 +183,18 @@ export default class Catcher {
void this.formatAndSend(message, undefined, context);
}

/**
* Method for Frameworks SDK using own error handlers.
* Allows to send errors to Hawk with additional Frameworks data (addons)
*
* @param error - error to send
* @param [addons] - framework-specific data, can be undefined
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public captureError(error: Error | string, addons?: JavaScriptCatcherIntegrations): void {
void this.formatAndSend(error, addons);
}

/**
* Add error handing to the passed Vue app
*
Expand Down Expand Up @@ -245,13 +258,24 @@ export default class Catcher {
context?: EventContext
): Promise<void> {
try {
const isAlreadySentError = isErrorProcessed(error);

if (isAlreadySentError) {
/**
* @todo add debug build and log this case
*/
return;
} else {
markErrorAsProcessed(error);
}

const errorFormatted = await this.prepareErrorFormatted(error, context);

/**
* If this event caught by integration (Vue or other), it can pass extra addons
*/
if (integrationAddons) {
this.appendIntegrationAddons(errorFormatted, integrationAddons);
this.appendIntegrationAddons(errorFormatted, Sanitizer.sanitize(integrationAddons));
}

this.sendErrorFormatted(errorFormatted);
Expand All @@ -263,7 +287,7 @@ export default class Catcher {
return;
}

log('Internal error ლ(´ڡ`ლ)', 'error', e);
log('Unable to send error. Seems like it is Hawk internal bug. Please, report it here: https://github.com/codex-team/hawk.javascript/issues/new', 'warn', e);
}
}

Expand Down
9 changes: 4 additions & 5 deletions src/integrations/vue.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Sanitizer from './../modules/sanitizer';
import type { VueIntegrationAddons } from '@hawk.so/types';
import type { JsonNode, VueIntegrationAddons } from '@hawk.so/types';

interface VueIntegrationOptions {
/**
Expand Down Expand Up @@ -102,7 +101,7 @@ export class VueIntegration {
* Fill props
*/
if (vm.$options && vm.$options.propsData) {
addons.props = Sanitizer.sanitize(vm.$options.propsData);
addons.props = vm.$options.propsData;
}

/**
Expand All @@ -112,7 +111,7 @@ export class VueIntegration {
addons.data = {};

Object.entries(vm._data).forEach(([key, value]) => {
addons.data![key] = Sanitizer.sanitize(value);
addons.data![key] = value as JsonNode;
});
}

Expand All @@ -124,7 +123,7 @@ export class VueIntegration {

Object.entries(vm._computedWatchers).forEach(([key, watcher]) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
addons.computed![key] = Sanitizer.sanitize((watcher as {[key: string]: any}).value);
addons.computed![key] = (watcher as {[key: string]: any}).value;
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/modules/fetchTimer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import log from './logger';
import log from '../utils/log';

/**
* Sends AJAX request and wait for some time.
Expand Down
2 changes: 1 addition & 1 deletion src/modules/socket.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import log from './logger';
import log from '../utils/log';
import type { CatcherMessage } from '@/types';

/**
Expand Down
2 changes: 1 addition & 1 deletion src/modules/stackParser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { StackFrame } from 'error-stack-parser';
import ErrorStackParser from 'error-stack-parser';
import type { BacktraceFrame, SourceCodeLine } from '@hawk.so/types';
import log from './logger';
import log from '../utils/log';
import fetchTimer from './fetchTimer';

/**
Expand Down
7 changes: 6 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import type { CatcherMessage } from './catcher-message';
import type { HawkInitialSettings } from './hawk-initial-settings';
import type { HawkJavaScriptEvent } from './event';
import type { VueIntegrationData, NuxtIntegrationData, NuxtIntegrationAddons, JavaScriptCatcherIntegrations } from './integrations';

export type {
CatcherMessage,
HawkInitialSettings,
HawkJavaScriptEvent
HawkJavaScriptEvent,
VueIntegrationData,
NuxtIntegrationData,
NuxtIntegrationAddons,
JavaScriptCatcherIntegrations
};
27 changes: 27 additions & 0 deletions src/types/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,36 @@ export type VueIntegrationData = {
vue: VueIntegrationAddons;
};

/**
* Useful info extracted from Nuxt app
*/
export type NuxtIntegrationAddons = {
'Component': string | null;
'Route': {
path: string;
fullPath: string;
name?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
redirectedFrom?: string | Record<string, any>;
},
'Props': {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
},
'Source': string;
};

/**
* The Nuxt integration will append this data to the addons
*/
export type NuxtIntegrationData = {
nuxt: NuxtIntegrationAddons;
};

/**
* Union Type for available integrations
*/
export type JavaScriptCatcherIntegrations =
| VueIntegrationData
| NuxtIntegrationData
;
46 changes: 46 additions & 0 deletions src/utils/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import log from './log';

/**
* Symbol to mark error as processed by Hawk
*/
const errorSentShadowProperty = Symbol('__hawk_processed__');

/**
* Check if the error has alrady been sent to Hawk.
*
* Motivation:
* Some integrations may catch errors on their own side and then normally re-throw them down.
* In this case, Hawk will catch the error again.
* We need to prevent this from happening.
*
* @param error - error object
*/
export function isErrorProcessed(error: unknown): boolean {
if (typeof error !== 'object' || error === null) {
return false;
}

return error[errorSentShadowProperty] === true;
}

/**
* Add non-enumerable property to the error object to mark it as processed.
*
* @param error - error object
*/
export function markErrorAsProcessed(error: unknown): void {
try {
if (typeof error !== 'object' || error === null) {
return;
}

Object.defineProperty(error, errorSentShadowProperty, {
enumerable: false, // Prevent from beight collected by Hawk
value: true,
writable: true,
configurable: true,
});
} catch (e) {
log('Failed to mark error as processed', 'error', e);
}
}
2 changes: 1 addition & 1 deletion src/utils.ts → src/utils/id.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Returns random string
*/
export function generateRandomId(): string {
export function id(): string {
const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

let array = new Uint8Array(40);
Expand Down
14 changes: 6 additions & 8 deletions src/modules/logger.ts → src/utils/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,15 @@ export default function log(msg: string, type = 'log', args?: any, style = 'colo
return;
}

const editorLabelText = `Hawk JavaScript.js ${VERSION}`;
const editorLabelText = `Hawk (${VERSION})`;
const editorLabelStyle = `line-height: 1em;
color: #006FEA;
color: #fff;
display: inline-block;
font-size: 11px;
line-height: 1em;
background-color: #fff;
padding: 4px 9px;
border-radius: 30px;
border: 1px solid rgba(56, 138, 229, 0.16);
margin: 4px 5px 4px 0;`;
background-color: rgba(0,0,0,.7);
padding: 3px 5px;
border-radius: 3px;
margin-right: 2px`;

try {
if (['time', 'timeEnd'].includes(type)) {
Expand Down

0 comments on commit 5aecc52

Please sign in to comment.