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:
@@ -33,7 +33,7 @@ import type {
|
|||||||
Regulation,
|
Regulation,
|
||||||
VisitorConsent,
|
VisitorConsent,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { getOrCreateVisitorId } from "./visitor";
|
import { getOrCreateVisitorId, getVisitorId } from "./visitor";
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
BannerConfig,
|
BannerConfig,
|
||||||
@@ -49,7 +49,7 @@ export type {
|
|||||||
export class CookieBannerClient {
|
export class CookieBannerClient {
|
||||||
private readonly baseUrl: URL;
|
private readonly baseUrl: URL;
|
||||||
private readonly bannerId: string;
|
private readonly bannerId: string;
|
||||||
private readonly visitorId: string;
|
private visitorId: string | null;
|
||||||
private readonly lang: string;
|
private readonly lang: string;
|
||||||
|
|
||||||
private readonly integrations: ConsentIntegration[];
|
private readonly integrations: ConsentIntegration[];
|
||||||
@@ -68,7 +68,7 @@ export class CookieBannerClient {
|
|||||||
}
|
}
|
||||||
this.baseUrl = new URL(base);
|
this.baseUrl = new URL(base);
|
||||||
this.bannerId = config.bannerId;
|
this.bannerId = config.bannerId;
|
||||||
this.visitorId = getOrCreateVisitorId(config.bannerId);
|
this.visitorId = getVisitorId(config.bannerId);
|
||||||
this.lang = detectLanguage(config.lang);
|
this.lang = detectLanguage(config.lang);
|
||||||
this.integrations = createDefaultIntegrations();
|
this.integrations = createDefaultIntegrations();
|
||||||
}
|
}
|
||||||
@@ -97,50 +97,52 @@ export class CookieBannerClient {
|
|||||||
}
|
}
|
||||||
this.startDetector(config);
|
this.startDetector(config);
|
||||||
|
|
||||||
const cookie = getConsentCookie();
|
if (this.visitorId) {
|
||||||
if (cookie && cookie.bid === this.bannerId && cookie.v === config.version && cookie.vid === this.visitorId) {
|
const cookie = getConsentCookie();
|
||||||
this.consent = {
|
if (cookie && cookie.bid === this.bannerId && cookie.v === config.version && cookie.vid === this.visitorId) {
|
||||||
visitor_id: cookie.vid,
|
this.consent = {
|
||||||
version: cookie.v,
|
visitor_id: cookie.vid,
|
||||||
action: cookie.action,
|
version: cookie.v,
|
||||||
consent_data: cookie.data,
|
action: cookie.action,
|
||||||
created_at: "",
|
consent_data: cookie.data,
|
||||||
};
|
created_at: "",
|
||||||
this._gpcApplied = cookie.action === "GPC";
|
};
|
||||||
this.activate(cookie.data);
|
this._gpcApplied = cookie.action === "GPC";
|
||||||
void flush(this.bannerId);
|
this.activate(cookie.data);
|
||||||
return;
|
void flush(this.bannerId);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const consentUrl = new URL(
|
const consentUrl = new URL(
|
||||||
`${this.bannerId}/consents/${this.visitorId}`,
|
`${this.bannerId}/consents/${this.visitorId}`,
|
||||||
this.baseUrl,
|
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,
|
|
||||||
);
|
);
|
||||||
this.activate(apiConsent.consent_data);
|
const apiConsent = await fetchJSON<VisitorConsent>(consentUrl).catch(
|
||||||
} else {
|
(err) => {
|
||||||
this.consent = null;
|
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) {
|
if (!this.consent && this.gpcDetected) {
|
||||||
@@ -225,6 +227,13 @@ export class CookieBannerClient {
|
|||||||
this.recordConsent("CUSTOMIZE", consentData);
|
this.recordConsent("CUSTOMIZE", consentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ensureVisitorId(): string {
|
||||||
|
if (!this.visitorId) {
|
||||||
|
this.visitorId = getOrCreateVisitorId(this.bannerId);
|
||||||
|
}
|
||||||
|
return this.visitorId;
|
||||||
|
}
|
||||||
|
|
||||||
private recordConsent(
|
private recordConsent(
|
||||||
action: ConsentAction,
|
action: ConsentAction,
|
||||||
consentData: Record<string, boolean>,
|
consentData: Record<string, boolean>,
|
||||||
@@ -232,9 +241,10 @@ export class CookieBannerClient {
|
|||||||
this._gpcApplied = action === "GPC";
|
this._gpcApplied = action === "GPC";
|
||||||
|
|
||||||
const cfg = this.config;
|
const cfg = this.config;
|
||||||
|
const visitorId = this.ensureVisitorId();
|
||||||
|
|
||||||
this.consent = {
|
this.consent = {
|
||||||
visitor_id: this.visitorId,
|
visitor_id: visitorId,
|
||||||
version: cfg.version,
|
version: cfg.version,
|
||||||
action,
|
action,
|
||||||
consent_data: consentData,
|
consent_data: consentData,
|
||||||
@@ -245,7 +255,7 @@ export class CookieBannerClient {
|
|||||||
{
|
{
|
||||||
bid: this.bannerId,
|
bid: this.bannerId,
|
||||||
v: cfg.version,
|
v: cfg.version,
|
||||||
vid: this.visitorId,
|
vid: visitorId,
|
||||||
action,
|
action,
|
||||||
data: consentData,
|
data: consentData,
|
||||||
},
|
},
|
||||||
@@ -256,7 +266,7 @@ export class CookieBannerClient {
|
|||||||
|
|
||||||
const url = new URL(`${this.bannerId}/consents`, this.baseUrl);
|
const url = new URL(`${this.bannerId}/consents`, this.baseUrl);
|
||||||
const body = {
|
const body = {
|
||||||
visitor_id: this.visitorId,
|
visitor_id: visitorId,
|
||||||
version: cfg.version,
|
version: cfg.version,
|
||||||
action,
|
action,
|
||||||
consent_data: consentData,
|
consent_data: consentData,
|
||||||
|
|||||||
@@ -14,18 +14,23 @@
|
|||||||
|
|
||||||
import { COOKIE_NAME } from "./cookie";
|
import { COOKIE_NAME } from "./cookie";
|
||||||
|
|
||||||
export function getOrCreateVisitorId(bannerId: string): string {
|
export function getVisitorId(bannerId: string): string | null {
|
||||||
const key = `${COOKIE_NAME}:${bannerId}:vid`;
|
const key = `${COOKIE_NAME}:${bannerId}:vid`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(key);
|
return localStorage.getItem(key);
|
||||||
if (stored) {
|
|
||||||
return stored;
|
|
||||||
}
|
|
||||||
} catch {
|
} 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);
|
const array = new Uint8Array(16);
|
||||||
crypto.getRandomValues(array);
|
crypto.getRandomValues(array);
|
||||||
const id = Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
|
const id = Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
|
||||||
|
|||||||
Reference in New Issue
Block a user