Skip consent API fetch for new visitors

When visitorId is null (no localStorage entry), the client now
skips both the cookie check and the /consents/:id fetch that
was always returning 404 for first-time visitors. The visitor ID
is created lazily on first consent action instead.

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-05-13 11:57:01 +04:00
parent 6c6137f9f2
commit e9d786c5d6
2 changed files with 69 additions and 54 deletions

View File

@@ -33,7 +33,7 @@ import type {
Regulation,
VisitorConsent,
} from "./types";
import { getOrCreateVisitorId } from "./visitor";
import { getOrCreateVisitorId, getVisitorId } from "./visitor";
export type {
BannerConfig,
@@ -49,7 +49,7 @@ export type {
export class CookieBannerClient {
private readonly baseUrl: URL;
private readonly bannerId: string;
private readonly visitorId: string;
private visitorId: string | null;
private readonly lang: string;
private readonly integrations: ConsentIntegration[];
@@ -68,7 +68,7 @@ export class CookieBannerClient {
}
this.baseUrl = new URL(base);
this.bannerId = config.bannerId;
this.visitorId = getOrCreateVisitorId(config.bannerId);
this.visitorId = getVisitorId(config.bannerId);
this.lang = detectLanguage(config.lang);
this.integrations = createDefaultIntegrations();
}
@@ -97,50 +97,52 @@ export class CookieBannerClient {
}
this.startDetector(config);
const cookie = getConsentCookie();
if (cookie && cookie.bid === this.bannerId && cookie.v === config.version && cookie.vid === this.visitorId) {
this.consent = {
visitor_id: cookie.vid,
version: cookie.v,
action: cookie.action,
consent_data: cookie.data,
created_at: "",
};
this._gpcApplied = cookie.action === "GPC";
this.activate(cookie.data);
void flush(this.bannerId);
return;
}
if (this.visitorId) {
const cookie = getConsentCookie();
if (cookie && cookie.bid === this.bannerId && cookie.v === config.version && cookie.vid === this.visitorId) {
this.consent = {
visitor_id: cookie.vid,
version: cookie.v,
action: cookie.action,
consent_data: cookie.data,
created_at: "",
};
this._gpcApplied = cookie.action === "GPC";
this.activate(cookie.data);
void flush(this.bannerId);
return;
}
const consentUrl = new URL(
`${this.bannerId}/consents/${this.visitorId}`,
this.baseUrl,
);
const apiConsent = await fetchJSON<VisitorConsent>(consentUrl).catch(
(err) => {
if (err instanceof NotFoundError) {
return null;
}
throw err;
},
);
if (apiConsent && apiConsent.version === config.version) {
this.consent = apiConsent;
this._gpcApplied = apiConsent.action === "GPC";
setConsentCookie(
{
bid: this.bannerId,
v: apiConsent.version,
vid: apiConsent.visitor_id,
action: apiConsent.action,
data: apiConsent.consent_data,
},
config.consent_expiry_days,
const consentUrl = new URL(
`${this.bannerId}/consents/${this.visitorId}`,
this.baseUrl,
);
this.activate(apiConsent.consent_data);
} else {
this.consent = null;
const apiConsent = await fetchJSON<VisitorConsent>(consentUrl).catch(
(err) => {
if (err instanceof NotFoundError) {
return null;
}
throw err;
},
);
if (apiConsent && apiConsent.version === config.version) {
this.consent = apiConsent;
this._gpcApplied = apiConsent.action === "GPC";
setConsentCookie(
{
bid: this.bannerId,
v: apiConsent.version,
vid: apiConsent.visitor_id,
action: apiConsent.action,
data: apiConsent.consent_data,
},
config.consent_expiry_days,
);
this.activate(apiConsent.consent_data);
} else {
this.consent = null;
}
}
if (!this.consent && this.gpcDetected) {
@@ -225,6 +227,13 @@ export class CookieBannerClient {
this.recordConsent("CUSTOMIZE", consentData);
}
private ensureVisitorId(): string {
if (!this.visitorId) {
this.visitorId = getOrCreateVisitorId(this.bannerId);
}
return this.visitorId;
}
private recordConsent(
action: ConsentAction,
consentData: Record<string, boolean>,
@@ -232,9 +241,10 @@ export class CookieBannerClient {
this._gpcApplied = action === "GPC";
const cfg = this.config;
const visitorId = this.ensureVisitorId();
this.consent = {
visitor_id: this.visitorId,
visitor_id: visitorId,
version: cfg.version,
action,
consent_data: consentData,
@@ -245,7 +255,7 @@ export class CookieBannerClient {
{
bid: this.bannerId,
v: cfg.version,
vid: this.visitorId,
vid: visitorId,
action,
data: consentData,
},
@@ -256,7 +266,7 @@ export class CookieBannerClient {
const url = new URL(`${this.bannerId}/consents`, this.baseUrl);
const body = {
visitor_id: this.visitorId,
visitor_id: visitorId,
version: cfg.version,
action,
consent_data: consentData,

View File

@@ -14,18 +14,23 @@
import { COOKIE_NAME } from "./cookie";
export function getOrCreateVisitorId(bannerId: string): string {
export function getVisitorId(bannerId: string): string | null {
const key = `${COOKIE_NAME}:${bannerId}:vid`;
try {
const stored = localStorage.getItem(key);
if (stored) {
return stored;
}
return localStorage.getItem(key);
} catch {
// localStorage unavailable
return null;
}
}
export function getOrCreateVisitorId(bannerId: string): string {
const existing = getVisitorId(bannerId);
if (existing) {
return existing;
}
const key = `${COOKIE_NAME}:${bannerId}:vid`;
const array = new Uint8Array(16);
crypto.getRandomValues(array);
const id = Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");