Skip to content

Commit

Permalink
[fix] implement a new way to install trackjs before loading app (#224)
Browse files Browse the repository at this point in the history
  • Loading branch information
sijav authored May 22, 2024
1 parent 326e543 commit 8227112
Show file tree
Hide file tree
Showing 33 changed files with 266 additions and 101 deletions.
6 changes: 4 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ VITE_WEBSOCKET_RETRY_TIMEOUT=5000
# Development host url
HOST=127.0.0.1
# Development port address
PORT=3000
PORT=8081
# Whether or not to use mock data instead of real server
VITE_USE_MOCK=false
# Discord address
Expand All @@ -25,4 +25,6 @@ VITE_GTM_DEV_ID=X-XXXXXXXXXX
# Google analytics for global published
VITE_GTM_PROD_ID=X-XXXXXXXXXX
# TrackJS error token
VITE_TRACKJS_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
VITE_TRACKJS_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Loading page timeout in ms (it will refresh itself in case of not loading correctly)
VITE_LOAD_PAGE_TIMEOUT=30000
2 changes: 2 additions & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
echo "VITE_GTM_PROD_ID=${{ secrets.GTM_PROD_ID }}" >> .env.production
echo "VITE_TRACKJS_TOKEN=${{ secrets.TRACKJS_TOKEN }}" >> .env.production
echo "VITE_MUI_LICENSE_KEY=${{ secrets.MUI_LICENSE_KEY }}" >> .env.production
echo "VITE_VERSION=${{ github.sha }}" >> .env.production
echo "VITE_LOAD_PAGE_TIMEOUT=${{ vars.LOAD_PAGE_TIMEOUT }}" >> .env.production
- name: Add models
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publich-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ jobs:
echo "VITE_DISCORD_URL=${{ vars.DISCORD_URL }}" >> .env.production
echo "VITE_GTM_DEV_ID=${{ secrets.GTM_DEV_ID }}" >> .env.production
echo "VITE_GTM_PROD_ID=${{ secrets.GTM_PROD_ID }}" >> .env.production
echo "VITE_TRACKJS_TOKEN=${{ secrets.TRACKJS_TOKEN }}" >> .env.production
echo "VITE_MUI_LICENSE_KEY=${{ secrets.MUI_LICENSE_KEY }}" >> .env.production
echo "VITE_LOAD_PAGE_TIMEOUT=${{ vars.LOAD_PAGE_TIMEOUT }}" >> .env.production
- name: Add models
run: |
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
echo "VITE_GTM_PROD_ID=${{ secrets.GTM_PROD_ID }}" >> .env.production
echo "VITE_TRACKJS_TOKEN=${{ secrets.TRACKJS_TOKEN }}" >> .env.production
echo "VITE_MUI_LICENSE_KEY=${{ secrets.MUI_LICENSE_KEY }}" >> .env.production
echo "VITE_VERSION=${{ github.sha }}" >> .env.production
echo "VITE_LOAD_PAGE_TIMEOUT=${{ vars.LOAD_PAGE_TIMEOUT }}" >> .env.production
- name: Add models
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
storybook.log*

# dependencies
node_modules
public/t.min.js

# Editor directories and files
.vscode/*
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ coverage/
storybook-static/
src/locales/*/
public/*.min.js
public/initial-t.js
26 changes: 13 additions & 13 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"singleQuote": true,
"printWidth": 140,
"semi": false,
"overrides": [
{
"files": "**/*.json",
"options": {
"trailingComma": "none"
}
}
],
"plugins": ["prettier-plugin-organize-imports"]
}
"singleQuote": true,
"printWidth": 140,
"semi": false,
"overrides": [
{
"files": "**/*.json",
"options": {
"trailingComma": "none"
}
}
],
"plugins": ["prettier-plugin-organize-imports"]
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ This project needs [NodeJS](https://nodejs.org/en) version 18 (at least 18.17.1)
- `VITE_GTM_PROD_ID: string` - Google Analytics token for production (Default: `undefined`, if empty it won't install GTM)
- `VITE_TRACKJS_TOKEN: string` - TrackJS token (Default: `undefined`, if empty it won't install TrackJS)
- `VITE_MUI_LICENSE_KEY: string` - MUI premium license key (Default: `undefined`, if empty it won't install the license for MUI)
- `VITE_LOAD_PAGE_TIMEOUT: number` - Loading page timeout in ms (Default: `30000`, it will refresh itself in case of not loading correctly)

## Available Scripts

Expand Down
4 changes: 4 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
}
}
</style>
<script type="text/javascript" src="/t.min.js"></script>
<script nonce="{{ nonce }}" id="remove-me-t">window._T="%VITE_TRACKJS_TOKEN%";window._webapp_version="%VITE_VERSION%";window._load_page_timeout="%VITE_LOAD_PAGE_TIMEOUT%";</script>
<script type="text/javascript" src="/initial-t.js"></script>
<script type="text/javascript" src="/particles.min.js"></script>
<title>Fix by Some Engineering Inc.</title>
</head>
Expand Down Expand Up @@ -155,5 +158,6 @@
</script>
</div>
<script type="module" src="/src/Main.tsx"></script>
<script nonce="{{ nonce }}"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"lint:tsc:org": "tsc --noEmit",
"lint:tsc": "tsc-files --noEmit",
"lint": "eslint . --ext ts,tsx --max-warnings 0",
"postinstall": "shx cp ./node_modules/trackjs/t.js ./public/t.min.js",
"prebuild": "yarn i18n:compile",
"prepare": "husky",
"prestart": "yarn i18n:compile",
Expand Down Expand Up @@ -106,6 +107,7 @@
"prettier-plugin-organize-imports": "^3.2.4",
"prop-types": "15",
"react-refresh": "^0.14.2",
"shx": "^0.3.4",
"storybook": "^8.1.1",
"tsc-files": "^1.1.4",
"type-fest": "^4.18.2",
Expand Down
115 changes: 115 additions & 0 deletions public/initial-t.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* eslint-disable prettier/prettier */
(function initialT() {
// remove unnecessary script
window.document.getElementById('remove-me-t')?.remove()
var token = window._T
var version = window._webapp_version
delete window._T
delete window._webapp_version
// if track js exists
if (window.location.host.indexOf('localhost') !== 0 && window.location.host.indexOf('127.0.0.1') !== 0 && window.TrackJS && token) {
// for offline support
var offlineErrors = []

var sendOrQueueError = function (errorPayload) {
var xhr = new XMLHttpRequest()
xhr.open('POST', 'https://capture.trackjs.com/capture?token=' + token + '&v=' + window.TrackJS.version, true)
xhr.onerror = function () {
offlineErrors.push(errorPayload)
}
xhr.setRequestHeader('Content-type', 'text/plain')
xhr.send(JSON.stringify(errorPayload).substring(0, 100 * 1024))
}

window.addEventListener('online', () => {
while (offlineErrors.length) {
window.setTimeout(
function (offlineError) {
sendOrQueueError(offlineError)
},
0,
offlineErrors.shift(),
)
}
})

// observe for resource
var Observer = window.MutationObserver || window.WebKitMutationObserver
if (Observer) {
var listenForLoadError = function (node) {
if (['SCRIPT', 'LINK', 'IMG'].indexOf(node.tagName) >= 0) {
var origOnError = node.onerror
node.onerror = function (evt) {
if (!evt || !evt.srcElement) {
return
}
evt.path = evt.path || []
var path = ''
for (var elIdx = 0; elIdx < evt.path.length; elIdx++) {
var currentEl = evt.path[elIdx]
if (currentEl === window) {
path += 'Window'
continue
}
path += currentEl.nodeName
path += currentEl.id ? '#' + currentEl.id : ''
path += currentEl.className ? '.' + currentEl.className.split(' ').join('.') : ''
if (elIdx < evt.path.length) {
path += ' > '
}
}
// this is how we send error
console.info({
asset: evt.srcElement.src,
integrity: evt.srcElement.integrity,
element: evt.srcElement.outerHTML,
path: path,
})
console.error('Failed to load ' + evt.srcElement.tagName + ': ' + (evt.srcElement.src || evt.srcElement.href))
if (origOnError) {
origOnError.call(node, evt)
}
}
}
}

new Observer(function (mutations) {
[].forEach.call(mutations, function (mutation) {
[].forEach.call(mutation.addedNodes, listenForLoadError)
})
}).observe(window.document, { childList: true, subtree: true })
}

// if it's not installed, install it
if (!window.TrackJS.isInstalled()) {
window.TrackJS.install({
token: token,
version: version,
application: 'fix',
console: { display: false },
onError: function (payload) {
sendOrQueueError(payload)
return false
},
})
}
}
// vite preload error
window.addEventListener('vite:preloadError', (event) => {
if (window.TrackJS && window.TrackJS.isInstalled()) {
window.TrackJS.track(event.payload)
}
window.setTimeout(function () {
window.location.reload()
}, 3_000)
})

window._load_page_timeout = window.setTimeout(function () {
if (window.TrackJS && window.TrackJS.isInstalled()) {
window.TrackJS.track(new Error('It took more than ' + (window._load_page_timeout / 1000) + 's to load the page'))
}
window.setTimeout(function () {
window.location.reload()
}, 3_000)
}, window._load_page_timeout)
})()
5 changes: 5 additions & 0 deletions src/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ if (nonceEl) {
nonceEl = undefined
}

if (window._load_page_timeout) {
window.clearTimeout(window._load_page_timeout)
delete window._load_page_timeout
}

const root = ReactDOM.createRoot(window.document.getElementById('root') as HTMLElement)
root.render(<App nonce={nonce} />)

Expand Down
9 changes: 4 additions & 5 deletions src/core/auth/AuthGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { axiosWithAuth, defaultAxiosConfig, setAxiosWithAuth } from 'src/shared/
import { clearAllCookies, isAuthenticated as isCookieAuthenticated } from 'src/shared/utils/cookie'
import { jsonToStr } from 'src/shared/utils/jsonToStr'
import { getAuthData as getPersistedAuthData, setAuthData as setPersistedAuthData } from 'src/shared/utils/localstorage'
import { TrackJS } from 'trackjs'
import { UserContext, UserContextRealValues, UserContextValue } from './UserContext'
import { getCurrentUserQuery } from './getCurrentUser.query'
import { Permissions, getPermissions, maxPermissionNumber } from './getPermissions'
Expand Down Expand Up @@ -143,8 +142,8 @@ export function AuthGuard({ children }: PropsWithChildren) {
(request) => request,
(error: AxiosError | Error) => {
if ((error as AxiosError)?.code !== 'ERR_CANCELED') {
if (TrackJS.isInstalled()) {
TrackJS.track(error)
if (window.TrackJS?.isInstalled()) {
window.TrackJS.track(error)
}
const { message, name, stack = 'unknown' } = error ?? {}
const authorized = isCookieAuthenticated()
Expand All @@ -167,8 +166,8 @@ export function AuthGuard({ children }: PropsWithChildren) {
return handleLogout()
}
if ('isAxiosError' in error && error.isAxiosError && error.code !== 'ERR_CANCELED') {
if (TrackJS.isInstalled()) {
TrackJS.track(error)
if (window.TrackJS?.isInstalled()) {
window.TrackJS.track(error)
}
const { response, name, message, cause, status, stack, config, code } = error
const request = error.request as unknown
Expand Down
17 changes: 8 additions & 9 deletions src/core/events/WebSocketEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { sendToGTM } from 'src/shared/google-tag-manager'
import { WebSocketEvent } from 'src/shared/types/server'
import { isAuthenticated as getIsAuthenticated } from 'src/shared/utils/cookie'
import { getAuthData } from 'src/shared/utils/localstorage'
import { TrackJS } from 'trackjs'
import { WebSocketEventsContext } from './WebSocketEventsContext'

const WS_CLOSE_CODE_NO_RETRY = 4001
Expand Down Expand Up @@ -36,8 +35,8 @@ export const WebSocketEvents = ({ children }: PropsWithChildren) => {
const message = JSON.parse(ev.data) as WebSocketEvent
onMessage(message)
} catch (err) {
if (TrackJS.isInstalled()) {
TrackJS.track(err as Error)
if (window.TrackJS?.isInstalled()) {
window.TrackJS.track(err as Error)
}
const { message, name, stack = 'unknown' } = err as Error
const authorized = getIsAuthenticated()
Expand Down Expand Up @@ -79,8 +78,8 @@ export const WebSocketEvents = ({ children }: PropsWithChildren) => {
messagesToSend.current.push({ message, resolve, reject })
}
} catch (err) {
if (TrackJS.isInstalled()) {
TrackJS.track(err as Error)
if (window.TrackJS?.isInstalled()) {
window.TrackJS.track(err as Error)
}
const { message, name, stack = 'unknown' } = err as Error
const authorized = getIsAuthenticated()
Expand All @@ -107,8 +106,8 @@ export const WebSocketEvents = ({ children }: PropsWithChildren) => {
const onClose = (ev: CloseEvent) => {
if (ev.code !== 1000) {
const err = new Error('Websocket connection closed')
if (TrackJS.isInstalled()) {
TrackJS.track(err)
if (window.TrackJS?.isInstalled()) {
window.TrackJS.track(err)
}
const { stack = 'unknown', name, message } = err
const authorized = getIsAuthenticated()
Expand Down Expand Up @@ -149,8 +148,8 @@ export const WebSocketEvents = ({ children }: PropsWithChildren) => {
websocket.current?.send(message)
resolve(message)
} catch (err) {
if (TrackJS.isInstalled()) {
TrackJS.track(err as Error)
if (window.TrackJS?.isInstalled()) {
window.TrackJS.track(err as Error)
}
const { message, name, stack = 'unknown' } = err as Error
const authorized = getIsAuthenticated()
Expand Down
Loading

0 comments on commit 8227112

Please sign in to comment.