import { onLCP, onINP, onCLS, onTTFB, onFCP, Metric } from "web-vitals";
/**
* Represents a timestamp with milliseconds and date.
*
* @interface Timestamp
*/
interface Timestamp {
/**
* The timestamp in milliseconds.
*
* @type {number}
*/
milliseconds: number;
/**
* The timestamp as a Date object.
*
* @type {Date}
*/
date: Date;
}
/**
* Represents the data returned from web vitals metrics.
*
* @interface ReturnedData
*/
interface ReturnedData {
/**
* An array of web vitals metrics.
*
* @type {Metric[]}
*/
data: Metric[];
/**
* The timestamp when the data was collected.
*
* @type {?Timestamp}
*/
timestamp?: Timestamp;
/**
* The location of the page where metrics were collected.
*
* @type {?Location}
*/
location?: Location;
}
/**
* The key used to store web vitals data in local storage.
*
* @type {string}
*/
const LOCAL_STORAGE_KEY = "pw-web-vitals";
/**
* The expiration time for the stored data in milliseconds.
*
* @type {number}
*/
const EXPIRATION_TIME_MS = 1 * 60 * 1000; // 1 minute in milliseconds
// Initialize returnedData properly
/**
* The data structure to store collected web vitals metrics.
*
* @type {ReturnedData}
*/
const returnedData: ReturnedData = {
data: [], // Ensure data is initialized as an array
};
// Handle web vitals metrics
/**
* Handles a web vitals metric by adding it to the returnedData if not already present.
*
* @param {Metric} metric - The web vitals metric to handle.
*/
const handleMetric = (metric: Metric) => {
const { data } = returnedData;
// Initialize location only once
if (!returnedData.location) {
returnedData.location = { ...window.location };
}
// Add new metric if it doesn't already exist and the array length is less than 5
if (
!data.some((existingMetric) => existingMetric.name === metric.name) &&
data.length < 5
) {
data.push(metric);
}
// Update the timestamp and store in local storage if 5 metrics are collected
if (data.length === 5) {
returnedData.timestamp = {
milliseconds: Date.now(),
date: new Date(),
};
saveWebVitalsData();
// send to endpoint
}
// Log the updated returnedData
console.log(returnedData);
};
// Save web vitals data to local storage
/**
* Saves the web vitals data to local storage.
*/
const saveWebVitalsData = () => {
const currentPath = returnedData.location!.pathname;
const timestamp = returnedData.timestamp!.milliseconds;
const storedDataString = localStorage.getItem(LOCAL_STORAGE_KEY);
const webVitalsData: { [path: string]: number } = storedDataString
? JSON.parse(storedDataString)
: {};
// Update or add entry for the current path
webVitalsData[currentPath] = timestamp;
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(webVitalsData));
};
// Main function to start monitoring metrics
/**
* Starts monitoring web vitals metrics.
*/
function main() {
onCLS(handleMetric);
onFCP(handleMetric);
onLCP(handleMetric);
onTTFB(handleMetric);
onINP(handleMetric);
}
// Initialize monitoring based on local storage data
/**
* Initializes monitoring based on the data stored in local storage.
*/
function initializeMonitoring() {
if (typeof window !== "undefined") {
const webVitalsDataString = localStorage.getItem(LOCAL_STORAGE_KEY);
try {
if (!webVitalsDataString) {
main();
return;
}
const webVitalsData: { [path: string]: number } =
JSON.parse(webVitalsDataString);
const currentPath = window.location.pathname;
const timestamp = webVitalsData[currentPath];
if (timestamp) {
// Check if data is expired
if (Date.now() - timestamp > EXPIRATION_TIME_MS) {
// Remove the expired data
delete webVitalsData[currentPath];
localStorage.setItem(
LOCAL_STORAGE_KEY,
JSON.stringify(webVitalsData)
);
// Start monitoring metrics
main();
}
} else {
// No data for current path, start monitoring metrics
main();
}
} catch (error) {
console.error("Error handling web vitals data:", error);
main();
}
}
}
export { initializeMonitoring };