|
|
|
|
|
|
|
|
|
type SiteDirective = "same-origin" | "same-site" | "cross-site" | "none";
|
|
interface RedirectTracker {
|
|
originalReferrer: string;
|
|
mostRestrictiveSite: SiteDirective;
|
|
referrerPolicy: string;
|
|
chainStarted: number;
|
|
}
|
|
interface ReferrerPolicyData {
|
|
policy: string;
|
|
referrer: string;
|
|
}
|
|
|
|
|
|
const TRACKER_EXPIRY = 60 * 60 * 1000;
|
|
const SITE_HIERARCHY: Record<SiteDirective, number> = {
|
|
none: 0,
|
|
"same-origin": 1,
|
|
"same-site": 2,
|
|
"cross-site": 3,
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function getDB(): Promise<IDBDatabase> {
|
|
const request = indexedDB.open("$scramjet", 1);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
request.onerror = () => reject(request.error);
|
|
request.onsuccess = () => resolve(request.result);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function getTracker(url: string): Promise<RedirectTracker | null> {
|
|
const db = await getDB();
|
|
const tx = db.transaction("redirectTrackers", "readonly");
|
|
const store = tx.objectStore("redirectTrackers");
|
|
|
|
return new Promise((resolve) => {
|
|
const request = store.get(url);
|
|
request.onsuccess = () => resolve(request.result || null);
|
|
request.onerror = () => resolve(null);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function setTracker(
|
|
url: string,
|
|
tracker: RedirectTracker
|
|
): Promise<void> {
|
|
const db = await getDB();
|
|
const tx = db.transaction("redirectTrackers", "readwrite");
|
|
const store = tx.objectStore("redirectTrackers");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = store.put(tracker, url);
|
|
request.onsuccess = () => resolve();
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteTracker(url: string): Promise<void> {
|
|
const db = await getDB();
|
|
const tx = db.transaction("redirectTrackers", "readwrite");
|
|
const store = tx.objectStore("redirectTrackers");
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = store.delete(url);
|
|
request.onsuccess = () => resolve();
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function initializeTracker(
|
|
requestUrl: string,
|
|
referrer: string | null,
|
|
initialSite: string
|
|
): Promise<void> {
|
|
const existing = await getTracker(requestUrl);
|
|
if (existing) {
|
|
return;
|
|
}
|
|
|
|
await setTracker(requestUrl, {
|
|
originalReferrer: referrer || "",
|
|
mostRestrictiveSite: initialSite as SiteDirective,
|
|
referrerPolicy: "",
|
|
chainStarted: Date.now(),
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function updateTracker(
|
|
originalUrl: string,
|
|
redirectUrl: string,
|
|
newReferrerPolicy?: string
|
|
): Promise<void> {
|
|
const tracker = await getTracker(originalUrl);
|
|
if (!tracker) return;
|
|
|
|
await deleteTracker(originalUrl);
|
|
if (newReferrerPolicy) {
|
|
tracker.referrerPolicy = newReferrerPolicy;
|
|
}
|
|
await setTracker(redirectUrl, tracker);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function getMostRestrictiveSite(
|
|
requestUrl: string,
|
|
currentSite: string
|
|
): Promise<string> {
|
|
const tracker = await getTracker(requestUrl);
|
|
if (!tracker) return currentSite;
|
|
|
|
const trackedValue = SITE_HIERARCHY[tracker.mostRestrictiveSite];
|
|
const currentValue = SITE_HIERARCHY[currentSite as SiteDirective] ?? 0;
|
|
|
|
if (currentValue > trackedValue) {
|
|
tracker.mostRestrictiveSite = currentSite as SiteDirective;
|
|
await setTracker(requestUrl, tracker);
|
|
|
|
return currentSite;
|
|
}
|
|
|
|
return tracker.mostRestrictiveSite;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function cleanTracker(requestUrl: string): Promise<void> {
|
|
await deleteTracker(requestUrl);
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function cleanExpiredTrackers(): Promise<void> {
|
|
const now = Date.now();
|
|
const db = await getDB();
|
|
const tx = db.transaction("redirectTrackers", "readwrite");
|
|
const store = tx.objectStore("redirectTrackers");
|
|
|
|
const request = store.openCursor();
|
|
|
|
request.onsuccess = (event) => {
|
|
const cursor = (event.target as IDBRequest).result;
|
|
if (cursor) {
|
|
const tracker = cursor.value as RedirectTracker;
|
|
if (now - tracker.chainStarted > TRACKER_EXPIRY) {
|
|
cursor.delete();
|
|
}
|
|
cursor.continue();
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function storeReferrerPolicy(
|
|
url: string,
|
|
policy: string,
|
|
referrer: string
|
|
): Promise<void> {
|
|
const db = await getDB();
|
|
const tx = db.transaction("referrerPolicies", "readwrite");
|
|
const store = tx.objectStore("referrerPolicies");
|
|
|
|
const data: ReferrerPolicyData = { policy, referrer };
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const request = store.put(data, url);
|
|
request.onsuccess = () => resolve();
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function getReferrerPolicy(
|
|
url: string
|
|
): Promise<ReferrerPolicyData | null> {
|
|
const db = await getDB();
|
|
const tx = db.transaction("referrerPolicies", "readonly");
|
|
const store = tx.objectStore("referrerPolicies");
|
|
|
|
return new Promise((resolve) => {
|
|
const request = store.get(url);
|
|
request.onsuccess = () => resolve(request.result || null);
|
|
request.onerror = () => resolve(null);
|
|
});
|
|
}
|
|
|