Store cookie durations as max_age_seconds
Replace the free-form duration TEXT column with a nullable max_age_seconds INTEGER on both cookies and cookie_patterns tables. The SDK detector now sends raw seconds instead of humanized strings, eliminating locale-dependent comparisons in the pattern merge worker. Humanization happens at display time in the widget and console UI. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -31,7 +31,7 @@ import { getOrCreateVisitorId } from "./visitor";
|
||||
|
||||
export interface CookieItem {
|
||||
name: string;
|
||||
duration: string;
|
||||
max_age_seconds: number | null;
|
||||
description: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import type { CookieItem } from "../client";
|
||||
import { humanizeDuration } from "../cookie-utils";
|
||||
import { getCookieDetailLabels } from "../i18n";
|
||||
import { ProboElement } from "./base";
|
||||
import type { ProboCategory } from "./category";
|
||||
@@ -43,24 +44,28 @@ export class ProboCookieList extends ProboElement {
|
||||
|
||||
const cookies = this.category.cookies;
|
||||
for (const cookie of cookies) {
|
||||
this.stampCookie(cookie, labels);
|
||||
this.stampCookie(cookie, labels, lang);
|
||||
}
|
||||
}
|
||||
|
||||
private stampCookie(cookie: CookieItem, labels: Record<string, string>): void {
|
||||
private stampCookie(cookie: CookieItem, labels: Record<string, string>, lang: string): void {
|
||||
if (!this.template) return;
|
||||
|
||||
const duration = cookie.max_age_seconds != null
|
||||
? humanizeDuration(cookie.max_age_seconds, lang)
|
||||
: humanizeDuration(0, lang);
|
||||
|
||||
const wrapper = document.createElement("probo-cookie");
|
||||
wrapper.setAttribute("name", cookie.name);
|
||||
const clone = this.template.content.cloneNode(true) as DocumentFragment;
|
||||
this.fillSlots(clone, {
|
||||
name: cookie.name,
|
||||
duration: cookie.duration,
|
||||
duration,
|
||||
description: cookie.description,
|
||||
});
|
||||
this.fillLabels(clone, labels, {
|
||||
description: cookie.description,
|
||||
duration: cookie.duration,
|
||||
duration,
|
||||
});
|
||||
|
||||
wrapper.appendChild(clone);
|
||||
|
||||
@@ -98,7 +98,7 @@ const DURATION_UNITS: [number, string, number][] = [
|
||||
[60, "duration_minute", 15],
|
||||
];
|
||||
|
||||
function humanizeDuration(seconds: number, lang?: string): string {
|
||||
export function humanizeDuration(seconds: number, lang?: string): string {
|
||||
const texts = getDurationTexts(lang);
|
||||
if (seconds <= 0) return texts.duration_session;
|
||||
|
||||
@@ -133,17 +133,15 @@ export function parseCookieName(raw: string): string {
|
||||
return raw.substring(0, eqIdx).trim();
|
||||
}
|
||||
|
||||
export function parseDuration(raw: string, lang?: string): string {
|
||||
const texts = getDurationTexts(lang);
|
||||
const session = texts.duration_session;
|
||||
export function parseMaxAgeSeconds(raw: string): number | null {
|
||||
const parts = raw.split(";").map((s) => s.trim());
|
||||
|
||||
for (const part of parts) {
|
||||
const lower = part.toLowerCase();
|
||||
if (lower.startsWith("max-age=")) {
|
||||
const val = parseInt(part.substring(8), 10);
|
||||
if (isNaN(val) || val <= 0) return session;
|
||||
return humanizeDuration(val, lang);
|
||||
if (isNaN(val) || val <= 0) return null;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,16 +150,16 @@ export function parseDuration(raw: string, lang?: string): string {
|
||||
if (lower.startsWith("expires=")) {
|
||||
const dateStr = part.substring(8);
|
||||
const expires = new Date(dateStr);
|
||||
if (isNaN(expires.getTime())) return session;
|
||||
if (isNaN(expires.getTime())) return null;
|
||||
const deltaSeconds = Math.round(
|
||||
(expires.getTime() - Date.now()) / 1000,
|
||||
);
|
||||
if (deltaSeconds <= 0) return session;
|
||||
return humanizeDuration(deltaSeconds, lang);
|
||||
if (deltaSeconds <= 0) return null;
|
||||
return deltaSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
return session;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isDeletion(raw: string): boolean {
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { isDeletion, parseCookieName, parseDuration } from "./cookie-utils";
|
||||
import { isDeletion, parseCookieName, parseMaxAgeSeconds } from "./cookie-utils";
|
||||
import { fetchJSON } from "./http";
|
||||
|
||||
interface DetectedCookieEntry {
|
||||
name: string;
|
||||
duration: string;
|
||||
max_age_seconds: number | null;
|
||||
source: "script" | "pre-existing";
|
||||
}
|
||||
|
||||
@@ -93,10 +93,10 @@ export class CookieDetector {
|
||||
const name = parseCookieName(raw);
|
||||
if (!name || this.knownNames.has(name) || this.reported.has(name)) return;
|
||||
|
||||
const duration = parseDuration(raw);
|
||||
const maxAgeSeconds = parseMaxAgeSeconds(raw);
|
||||
|
||||
this.reported.add(name);
|
||||
this.pending.set(name, { name, duration, source: "script" });
|
||||
this.pending.set(name, { name, max_age_seconds: maxAgeSeconds, source: "script" });
|
||||
this.scheduleFlush();
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export class CookieDetector {
|
||||
continue;
|
||||
}
|
||||
this.reported.add(name);
|
||||
this.pending.set(name, { name, duration: "session", source: "pre-existing" });
|
||||
this.pending.set(name, { name, max_age_seconds: null, source: "pre-existing" });
|
||||
}
|
||||
|
||||
if (this.pending.size > 0) {
|
||||
|
||||
Reference in New Issue
Block a user