Files
speckle-server/packages/frontend-2/plugins/020-rum.ts
T
Kristaps Fabians Geikins c54d15fd93 feat: authz frontend foundation + reworked errors (#4275)
* feat: authz frontend foundation + reworked errors

* lint fixes

* test fix

* fixed noCache() util
2025-03-27 16:13:35 +02:00

280 lines
8.2 KiB
TypeScript

import {
useGetInitialAuthState,
useOnAuthStateChange
} from '~/lib/auth/composables/auth'
import { useCreateLoggingTransport } from '~/lib/core/composables/error'
import type { Plugin } from '#app'
import { isH3Error } from '~/lib/common/helpers/error'
import { useRequestId, useServerRequestId } from '~/lib/core/composables/server'
import { isBrave, isSafari } from '@speckle/shared'
import { isString } from 'lodash-es'
type PluginNuxtApp = Parameters<Plugin>[0]
function initRumClient(app: PluginNuxtApp) {
const { keys } = resolveInitParams(app)
const router = useRouter()
const onAuthStateChange = useOnAuthStateChange()
const registerErrorTransport = useCreateLoggingTransport()
const reqId = useRequestId()
// Datadog
const datadog = window.DD_RUM
if (keys.datadog && datadog) {
datadog.onReady(async () => {
if ('setGlobalContextProperty' in datadog && reqId?.length) {
datadog.setGlobalContextProperty('requestId', reqId)
if (isSafari()) {
datadog.setGlobalContextProperty('isSafari', 'true')
}
if (isBrave()) {
datadog.setGlobalContextProperty('isBrave', 'true')
}
}
await onAuthStateChange(
(user, { resolveDistinctId }) => {
const distinctId = resolveDistinctId(user)
// setUser might not be there, if blocked by adblock
if (!datadog || !('setUser' in datadog)) return
if (distinctId && user) {
datadog.setUser({
id: distinctId
})
} else {
datadog.clearUser()
}
},
{ immediate: true }
)
router.beforeEach((to) => {
const pathDefinition = getRouteDefinition(to)
const routeName = (to.meta.datadogName || pathDefinition) as string
const realPath = to.path
window.DD_RUM_START_VIEW?.(realPath, routeName)
})
const resolveH3Data = (error: unknown) =>
error && isH3Error(error)
? {
statusCode: error.statusCode,
fatal: error.fatal,
statusMessage: error.statusMessage,
h3Data: error.data
}
: {}
registerErrorTransport({
onLog: (
{ args, firstError, firstString, otherData, nonObjectOtherData, level },
{ prettifyMessage }
) => {
if (
!datadog ||
!('addError' in datadog) ||
!['error', 'fatal'].includes(level)
)
return
let error = firstError || firstString || args[0]
const mainErrorMessageTemplate = firstString
const mainErrorMessage = mainErrorMessageTemplate
? prettifyMessage(mainErrorMessageTemplate)
: undefined
if (isString(error)) {
error = prettifyMessage(error)
}
datadog.addError(error, {
...otherData,
...resolveH3Data(firstError),
extraData: nonObjectOtherData,
mainErrorMessageTemplate,
mainErrorMessage,
isProperlySentError: true
})
},
onUnhandledError: ({ isUnhandledRejection, error, message }) => {
if (!datadog || !('addError' in datadog)) return
datadog.addError(error || message, {
...resolveH3Data(error),
isUnhandledRejection,
message,
mainErrorMessage: message,
isProperlySentError: true
})
}
})
})
}
}
async function initRumServer(app: PluginNuxtApp) {
const { keys, baseUrl, speckleServerVersion, debugCoreWebVitals } =
resolveInitParams(app)
const initUser = useGetInitialAuthState()
// CWV
if (debugCoreWebVitals) {
app.hook('app:rendered', (context) => {
context.ssrContext!.head.push({
script: [
{
innerHTML: `
import {
onCLS,
onFID,
onLCP,
onINP,
onTTFB
} from 'https://cdn.jsdelivr.net/npm/web-vitals@3/dist/web-vitals.attribution.js?module';
onCLS(console.log);
onFID(console.log);
onLCP(console.log);
onINP(console.log);
onTTFB(console.log);
`,
type: 'module'
}
]
})
})
}
// Datadog
if (keys.datadog) {
const {
datadogAppId,
datadogClientToken,
datadogSite,
datadogService,
datadogEnv
} = keys.datadog
const { distinctId } = await initUser()
app.hook('app:rendered', (context) => {
const serverReqId = useServerRequestId()
const route = app._route
const pathDefinition = getRouteDefinition(route)
const pathReal = route.path
const routeName = route.meta.datadogName || pathDefinition
context.ssrContext!.head.push({
script: [
{
innerHTML:
`
(function(h,o,u,n,d) {
h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
d=o.createElement(u);d.async=1;d.src=n
n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
})(window,document,'script','https://www.datadoghq-browser-agent.com/eu1/v5/datadog-rum.js','DD_RUM')
window.DD_RUM.onReady(function() {
` +
(distinctId ? `window.DD_RUM.setUser({ id: '${distinctId}' });` : '') +
`
window.DD_RUM.setGlobalContextProperty('serverBaseUrl', '${baseUrl}');
` +
(serverReqId.value
? `window.DD_RUM.setGlobalContextProperty('serverRequestId', '${serverReqId.value}');`
: '') +
`
window.DD_RUM.init({
clientToken: '${datadogClientToken}',
applicationId: '${datadogAppId}',
site: '${datadogSite}',
service: '${datadogService}',
env: '${datadogEnv || 'unknown'}',
version: '${speckleServerVersion}',
sessionSampleRate: 100,
sessionReplaySampleRate: 0,
trackUserInteractions: true,
trackResources: true,
trackLongTasks: true,
defaultPrivacyLevel: 'mask-user-input',
trackViewsManually: true,
beforeSend: (event) => {
if (event?.type === 'error') {
if (!event.context?.isProperlySentError) return false
delete event.context.isProperlySentError
}
return true
}
});
window.DD_RUM_START_VIEW = (path, name) => {
if (window.DD_RUM_REGISTERED_PATH === path) return
window.DD_RUM_REGISTERED_PATH = path
window.DD_RUM.startView({
name
})
console.debug('DDR Started view: ' + name)
}
window.DD_RUM_START_VIEW('${pathReal}', '${routeName}')
})
`
}
]
})
})
}
}
function resolveInitParams(app: PluginNuxtApp) {
const {
public: {
speckleServerVersion,
logCsrEmitProps,
baseUrl,
debugCoreWebVitals,
datadogClientToken,
datadogAppId,
datadogSite,
datadogService,
datadogEnv
}
} = useRuntimeConfig()
const logger = useLogger()
const datadog =
datadogClientToken?.length &&
datadogAppId?.length &&
datadogSite?.length &&
datadogService?.length &&
datadogEnv?.length
? { datadogClientToken, datadogAppId, datadogSite, datadogService, datadogEnv }
: null
const shouldDebugCoreWebVitals = debugCoreWebVitals || app._route?.query.cwv === '1'
return {
keys: {
datadog
},
speckleServerVersion,
baseUrl,
debug: logCsrEmitProps && import.meta.dev,
debugCoreWebVitals: shouldDebugCoreWebVitals,
logger
}
}
export default defineNuxtPlugin(async (app) => {
if (import.meta.server) {
await initRumServer(app)
} else {
initRumClient(app)
}
})