Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
115 lines
3.4 KiB
TypeScript
115 lines
3.4 KiB
TypeScript
/**
|
|
* Pure Consent Mode v2 helpers (no $app / $env) — safe for node:test.
|
|
*
|
|
* Categories:
|
|
* - Necessary: always on (session, CSRF, theme, locale, consent preference)
|
|
* - Analytics → analytics_storage
|
|
* - Marketing → ad_storage, ad_user_data, ad_personalization
|
|
*/
|
|
|
|
export const CONSENT_STORAGE_KEY = "descrybe-cookie-consent";
|
|
export const CONSENT_VERSION = 1;
|
|
export const CONSENT_WAIT_FOR_UPDATE_MS = 500;
|
|
|
|
export type ConsentPreferences = {
|
|
analytics: boolean;
|
|
marketing: boolean;
|
|
};
|
|
|
|
export type StoredConsent = ConsentPreferences & {
|
|
v: number;
|
|
updatedAt: string;
|
|
};
|
|
|
|
/** Google Consent Mode v2 signal map (string literals for gtag). */
|
|
export type ConsentModeSignals = {
|
|
ad_storage: "granted" | "denied";
|
|
ad_user_data: "granted" | "denied";
|
|
ad_personalization: "granted" | "denied";
|
|
analytics_storage: "granted" | "denied";
|
|
functionality_storage: "granted" | "denied";
|
|
personalization_storage: "granted" | "denied";
|
|
security_storage: "granted" | "denied";
|
|
};
|
|
|
|
export const DENIED_CONSENT_DEFAULTS: ConsentModeSignals = {
|
|
ad_storage: "denied",
|
|
ad_user_data: "denied",
|
|
ad_personalization: "denied",
|
|
analytics_storage: "denied",
|
|
functionality_storage: "granted",
|
|
personalization_storage: "denied",
|
|
security_storage: "granted"
|
|
};
|
|
|
|
export function preferencesToConsentSignals(
|
|
prefs: ConsentPreferences
|
|
): ConsentModeSignals {
|
|
const analytics = prefs.analytics ? "granted" : "denied";
|
|
const marketing = prefs.marketing ? "granted" : "denied";
|
|
return {
|
|
ad_storage: marketing,
|
|
ad_user_data: marketing,
|
|
ad_personalization: marketing,
|
|
analytics_storage: analytics,
|
|
functionality_storage: "granted",
|
|
personalization_storage: analytics,
|
|
security_storage: "granted"
|
|
};
|
|
}
|
|
|
|
export function parseStoredConsent(raw: string | null | undefined): StoredConsent | null {
|
|
if (!raw?.trim()) return null;
|
|
try {
|
|
const parsed: unknown = JSON.parse(raw);
|
|
if (!parsed || typeof parsed !== "object") return null;
|
|
const obj = parsed as Record<string, unknown>;
|
|
if (obj.v !== CONSENT_VERSION) return null;
|
|
if (typeof obj.analytics !== "boolean" || typeof obj.marketing !== "boolean") {
|
|
return null;
|
|
}
|
|
const updatedAt =
|
|
typeof obj.updatedAt === "string" && obj.updatedAt.trim()
|
|
? obj.updatedAt
|
|
: new Date(0).toISOString();
|
|
return {
|
|
v: CONSENT_VERSION,
|
|
analytics: obj.analytics,
|
|
marketing: obj.marketing,
|
|
updatedAt
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function serializeConsent(prefs: ConsentPreferences, updatedAt = new Date().toISOString()): string {
|
|
const stored: StoredConsent = {
|
|
v: CONSENT_VERSION,
|
|
analytics: prefs.analytics,
|
|
marketing: prefs.marketing,
|
|
updatedAt
|
|
};
|
|
return JSON.stringify(stored);
|
|
}
|
|
|
|
/** Accept all non-necessary categories. */
|
|
export function acceptAllPreferences(): ConsentPreferences {
|
|
return { analytics: true, marketing: true };
|
|
}
|
|
|
|
/** Reject analytics and marketing (necessary remains on). */
|
|
export function rejectNonEssentialPreferences(): ConsentPreferences {
|
|
return { analytics: false, marketing: false };
|
|
}
|
|
|
|
/** Coarse SKU-volume bucket for lead events — never send the raw count as PII-adjacent precision. */
|
|
export function estimatedSkusBucket(n: number | null | undefined): string {
|
|
if (n == null || !Number.isFinite(n) || n < 0) return "unknown";
|
|
if (n < 1_000) return "0_999";
|
|
if (n < 5_000) return "1000_4999";
|
|
if (n < 20_000) return "5000_19999";
|
|
if (n < 100_000) return "20000_99999";
|
|
return "100000_plus";
|
|
}
|