Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: nuxt error handlers, collection nuxt addons #5

Merged
merged 2 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hawk.so/nuxt",
"version": "0.0.7",
"version": "1.0.0",
"description": "Hawk error tracker integration to Nuxt app",
"repository": {
"type": "git",
Expand All @@ -25,6 +25,7 @@
"build": "nuxt-module-build prepare && nuxt-module-build build",
"dev": "nuxi dev playground",
"dev:build": "nuxi build playground",
"dev:preview": "nuxi preview playground",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
"release": "yarn run lint && yarn run test && yarn run prepack && changelogen --release && yarn publish && git push --follow-tags",
"lint": "eslint .",
Expand All @@ -34,7 +35,7 @@
"preinstall": "npx only-allow yarn"
},
"dependencies": {
"@hawk.so/javascript": "^3.0.8",
"@hawk.so/javascript": "^3.1.0",
"@hawk.so/vite-plugin": "^1.0.4",
"@nuxt/kit": "^3.13.2"
},
Expand Down
11 changes: 11 additions & 0 deletions playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
Trigger exception
</button>

<TheButton
text="Trigger exception from component"
@activate="componentEventHandler"
/>

<button @click="manuallyFromComposable">
Manually send from useSomething() composable
</button>
Expand All @@ -15,6 +20,8 @@
</template>

<script setup lang="ts">
import TheButton from '~/components/UiButton.vue'

const something = useSomething()

function triggerException() {
Expand All @@ -24,6 +31,10 @@ function triggerException() {
function manuallyFromComposable() {
something.testManualSendingFromComposable(new Error('Error sent manually from composable'))
}

function componentEventHandler() {
return erererer.bb
}
</script>

<style module>
Expand Down
22 changes: 22 additions & 0 deletions playground/components/UiButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<button
@click="emit('activate')"
>
{{ text }} {{ myComputedProperty }}
</button>
</template>

<script setup lang="ts">
import { defineEmits } from 'vue'

const emit = defineEmits<{
activate: []
stop: []
}>()

const props = defineProps<{
text: string
}>()

const myComputedProperty = computed(() => props.text.length)
</script>
134 changes: 131 additions & 3 deletions src/runtime/plugin.client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import HawkCatcher from '@hawk.so/javascript'
// import HawkCatcher from '@hawk.so/javascript'
import HawkCatcher, { type NuxtIntegrationData, type NuxtIntegrationAddons } from '../../../hawk.javascript'
import type { HawkModuleConfig } from '../types'
import beforeSend from '#build/hawk-before-send.mjs'
import { defineNuxtPlugin, useRuntimeConfig } from '#app'
Expand Down Expand Up @@ -35,20 +36,147 @@ export default defineNuxtPlugin((nuxtApp) => {
token: hawkConfig.token,
vue: nuxtApp.vueApp,
release: getReleaseId(),
disableVueErrorHandler: true,
}, {
beforeSend,
}))

/**
* @todo use NuxtApp to extract useful information:
* - current route
* - is SSR
* - Vue Component
* - SSR Request headers ?
*/

nuxtApp.hook('vue:error', (error: unknown, instance, info) => {
try {
const addons = spoilAddons(nuxtApp, info, instance)

hawkInstance.captureError(error as Error, addons)
}
catch (error) {
console.warn('Hawk nuxt unable to capture error. Report this error to maintainers: https://github.com/codex-team/hawk.nuxt/issues/new', error)
}
})

nuxtApp.hook('app:error', (error) => {
try {
const addons = spoilAddons(nuxtApp, 'App startup error')

hawkInstance.captureError(error as Error, addons)
}
catch (error) {
console.warn('Hawk nuxt unable to capture error. Report this error to maintainers: https://github.com/codex-team/hawk.nuxt/issues/new', error)
}
})

/**
* Add Hawk instance to the global context
*/
nuxtApp.$hawk = hawkInstance
})

/**
* Extracts useful information from the Vue component instance and Nuxt app
* @param nuxtApp - Nuxt app instance
* @param info - Error source info (3rd argument of the vue:error hook)
* @param componentInstance - Vue component public instance
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function spoilAddons(nuxtApp: any, info: string, componentInstance?: any): NuxtIntegrationData {
const addons = {} as NuxtIntegrationAddons

/**
* Extract the component name
*/
if (componentInstance) {
if (componentInstance.$options !== undefined) {
addons['Component'] = `<${componentInstance.$options.__name || componentInstance.$options.name || componentInstance.$options._componentTag || 'Anonymous'}>`
}

/**
* Fill props
*/
if (Object.keys(componentInstance.$props).length) {
addons['Props'] = componentInstance.$props
}
}

/**
* Extract the current route
*/
if (nuxtApp.$route) {
addons['Route'] = {
path: nuxtApp.$route.path,
fullPath: nuxtApp.$route.fullPath,
}

if (nuxtApp.$route.name) {
addons['Route'].name = nuxtApp.$route.name
}

if (nuxtApp.$route.redirectedFrom) {
addons['Route'].redirectedFrom = nuxtApp.$route.redirectedFrom
}
}

/**
* Error source type
*/
addons['Source'] = getRuntimeErrorSourceByCode(info)

return {
nuxt: addons,
}
}

/**
* In production, the error code is a link with reference to doc.
* This method returns the error message by the code extracted from the link
*
* @param code - Error source info (3rd argument of the vue:error hook)
* https://vuejs.org/api/composition-api-lifecycle.html#onerrorcaptured
*/
function getRuntimeErrorSourceByCode(code: string): string {
if (!code.includes('https://vuejs.org/error-reference/#runtime-')) {
return code
}

const codeParts = code.split('https://vuejs.org/error-reference/#runtime-')
const errorCode = codeParts[codeParts.length - 1]

const errorCodeMap = new Map([
['0', 'setup function'],
['1', 'render function'],
['2', 'watcher getter'],
['3', 'watcher callback'],
['4', 'watcher cleanup function'],
['5', 'native event handler'],
['6', 'component event handler'],
['7', 'vnode hook'],
['8', 'directive hook'],
['9', 'transition hook'],
['10', 'app errorHandler'],
['11', 'app warnHandler'],
['12', 'ref function'],
['13', 'async component loader'],
['14', 'scheduler flush'],
['15', 'component update'],
['16', 'app unmount cleanup function'],
['sp', 'serverPrefetch hook'],
['bc', 'beforeCreate hook'],
['c', 'created hook'],
['bm', 'beforeMount hook'],
['m', 'mounted hook'],
['bu', 'beforeUpdate hook'],
['u', 'updated'],
['bum', 'beforeUnmount hook'],
['um', 'unmounted hook'],
['a', 'activated hook'],
['da', 'deactivated hook'],
['ec', 'errorCaptured hook'],
['rtc', 'renderTracked hook'],
['rtg', 'renderTriggered hook'],
])

return errorCodeMap.get(errorCode) || code
}
Loading