Skip to content

Commit

Permalink
feat(tracker): embed tracker in dashboard on localhost and infer api …
Browse files Browse the repository at this point in the history
…via src
  • Loading branch information
ayuhito committed Jun 13, 2024
1 parent 110e458 commit 2740b29
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 35 deletions.
6 changes: 2 additions & 4 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.1/schema.json",
"files": {
"ignore": [
"types.d.ts",
"*.js"
]
"ignore": ["types.d.ts", "**/dist"]
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"ignore": ["types.d.ts", "*.js"],
"rules": {
"recommended": true
}
Expand Down
2 changes: 1 addition & 1 deletion dashboard/app/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,4 @@ const client = async (
return res;
};

export { client, DEFAULT_HEADERS };
export { client, DEFAULT_HEADERS, API_URL, API_BASE };
20 changes: 19 additions & 1 deletion dashboard/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { AppShell } from '@/components/layout/AppShell';
import theme from '@/styles/theme';
import { hasSession } from '@/utils/cookies';
import { API_BASE } from '@/api/client';

enableReactUse();

Expand All @@ -36,6 +37,15 @@ export const clientLoader = () => {
};

export const Document = ({ children }: DocumentProps) => {
// While end users will have their API servers at a fixed domain, development mode will have the API server
// running on localhost.
let isLocalhost = false;
let scriptSrc = '/script.js';
if (API_BASE === 'localhost') {
isLocalhost = true;
scriptSrc = 'http://localhost:8080/script.js';
}

return (
<html lang="en">
<head>
Expand All @@ -44,7 +54,7 @@ export const Document = ({ children }: DocumentProps) => {
<Meta />
<Links />
<ColorSchemeScript />
<script defer data-api="localhost:8080" src="/script.js" />
{isLocalhost && <script defer src={scriptSrc} />}
</head>
<body>
<MantineProvider classNamesPrefix="me" theme={theme}>
Expand All @@ -65,6 +75,14 @@ export default function App() {
);
}

export const HydrateFallback = () => {
return (
<Document>
<p>Loading...</p>
</Document>
);
};

export const ErrorBoundary = () => {
const error = useRouteError();

Expand Down
2 changes: 1 addition & 1 deletion tracker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Our tracker is designed with compression in mind, given that web traffic is usua

| File | Size | Compressed (gzip) | Compressed (brotli) |
| ------------ | ------------------- | ------------------- | ------------------- |
| `default.js` | 1487 bytes (1.45kb) | 700 bytes (0.68 KB) | 545 bytes (0.53 KB) |
| `default.js` | 1567 bytes (1.53kb) | 739 bytes (0.72 KB) | 579 bytes (0.57 KB) |

## License

Expand Down
7 changes: 4 additions & 3 deletions tracker/dist/default.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion tracker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"build": "npm run build:default && npm run build:size",
"build:default": "google-closure-compiler -O ADVANCED src/default.js --js_output_file dist/default.js --language_out ECMASCRIPT_2015",
"build:size": "node ./scripts/size.js"
"build:size": "node ./scripts/size.js",
"format": "biome format --write ."
},
"author": "Ayuhito",
"license": "MIT",
Expand Down
53 changes: 29 additions & 24 deletions tracker/src/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const EventType = {
/**
* @typedef {Object} HitPayload
* @property {string} b Beacon ID.
* @property {EventType} e Event type.
* @property {string} u Page URL.
* @property {string} r Referrer URL.
* @property {boolean} p If the user is unique or not.
Expand All @@ -33,6 +34,7 @@ var HitPayload;
/**
* @typedef {Object} DurationPayload
* @property {string} b Beacon ID.
* @property {EventType} e Event type.
* @property {number} m Time spent on page.
*/
var DurationPayload;
Expand All @@ -45,7 +47,8 @@ var DurationPayload;
* @see https://github.com/google/closure-compiler/wiki/FAQ#closure-compiler-inlined-all-my-strings-which-made-my-code-size-bigger-why-did-it-do-that
*/
(function () {
// If server-side rendering, bail out.
// If server-side rendering, bail out. We use document instead of window here as Deno does have
// a window object even on the server.
if (!document) {
return;
}
Expand All @@ -59,9 +62,13 @@ var DurationPayload;

/**
* Get API URL from data-host in script tag with the correct protocol.
* If the data-host attribute is not set, then we use the current script's
* src attribute to determine the host.
*/
const host =
document.location.protocol + '//' + currentScript.getAttribute('data-api');
const host = currentScript.getAttribute('data-api')
? `${document.location.protocol}//${currentScript.getAttribute('data-api')}`
// @ts-ignore - We know this won't be an SVGScriptElement.
: currentScript.src.replace(/[^\/]+$/, 'api/');

/**
* Generate a unique ID for linking multiple beacon events together for the same page
Expand Down Expand Up @@ -164,22 +171,21 @@ var DurationPayload;
// without protocol or query parameters.
pingCache(
host +
'/event/ping?u=' +
encodeURIComponent(location.host + location.pathname)
'event/ping?u=' +
encodeURIComponent(location.host + location.pathname),
).then((response) => {
isFirstVisit = response;

navigator.sendBeacon(
host + '/event/hit',
host + 'event/hit',
JSON.stringify(
// prettier-ignore
// biome-ignore format: We use string literals for the keys to tell Closure Compiler to not rename them.
/**
* Payload to send to the server.
* @type {HitPayload}
* @remarks We use string literals for the keys to tell Closure Compiler
* to not rename them.
*/ ({
"b": uid,
"e": EventType.LOAD,
"u": location.href,
"r": document.referrer,
"p": isUnique,
Expand All @@ -192,8 +198,8 @@ var DurationPayload;
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#return_value
*/
"t": Intl.DateTimeFormat().resolvedOptions().timeZone,
})
)
}),
),
);
});
};
Expand All @@ -205,20 +211,19 @@ var DurationPayload;
const sendUnloadBeacon = () => {
if (!isUnloadCalled) {
navigator.sendBeacon(
host + '/event/hit',
host + 'event/hit',
JSON.stringify(
// prettier-ignore
// biome-ignore format: We use string literals for the keys to tell Closure Compiler to not rename them.
/**
* Payload to send to the server.
* @type {DurationPayload}
* @remarks We use string literals for the keys to tell Closure Compiler
* to not rename them.
*/
({
"b": uid,
"e": EventType.UNLOAD,
"m": Date.now() - hiddenTotalTime,
})
)
}),
),
);
}

Expand All @@ -237,7 +242,7 @@ var DurationPayload;
() => {
sendUnloadBeacon();
},
{ capture: true }
{ capture: true },
);
} else {
// Otherwise, use unload and beforeunload. Using both is significantly more
Expand All @@ -248,14 +253,14 @@ var DurationPayload;
() => {
sendUnloadBeacon();
},
{ capture: true }
{ capture: true },
);
document.addEventListener(
EventType.UNLOAD,
() => {
sendUnloadBeacon();
},
{ capture: true }
{ capture: true },
);
}

Expand All @@ -273,10 +278,10 @@ var DurationPayload;
hiddenStartTime = 0;
}
},
{ capture: true }
{ capture: true },
);

pingCache(host + '/event/ping').then((response) => {
pingCache(host + 'event/ping').then((response) => {
// The response is a boolean indicating if the user is unique or not.
isUnique = response;

Expand All @@ -294,7 +299,7 @@ var DurationPayload;
},
{
capture: true,
}
},
);
} else {
// Add pushState event listeners to track navigation changes with
Expand Down Expand Up @@ -330,7 +335,7 @@ var DurationPayload;
},
{
capture: true,
}
},
);
}
});
Expand Down

0 comments on commit 2740b29

Please sign in to comment.