Use URL objects for API endpoint construction

Store baseUrl as URL and build endpoints with new URL(path, base).
Accept URL | string in fetchJSON to avoid unnecessary .href calls.

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-16 15:46:00 +04:00
parent ce77091e6c
commit f0b5a13f6f
2 changed files with 13 additions and 10 deletions

View File

@@ -64,7 +64,7 @@ export interface CookieBannerClientOptions {
} }
export class CookieBannerClient { export class CookieBannerClient {
private readonly baseUrl: string; private readonly baseUrl: URL;
private readonly bannerId: string; private readonly bannerId: string;
private readonly visitorId: string; private readonly visitorId: string;
@@ -74,16 +74,16 @@ export class CookieBannerClient {
constructor(config: CookieBannerClientOptions) { constructor(config: CookieBannerClientOptions) {
let base = config.baseUrl; let base = config.baseUrl;
while (base.endsWith("/")) { if (!base.endsWith("/")) {
base = base.slice(0, -1); base += "/";
} }
this.baseUrl = base; this.baseUrl = new URL(base);
this.bannerId = config.bannerId; this.bannerId = config.bannerId;
this.visitorId = getOrCreateVisitorId(config.bannerId); this.visitorId = getOrCreateVisitorId(config.bannerId);
} }
async load(): Promise<void> { async load(): Promise<void> {
const configUrl = `${this.baseUrl}/${this.bannerId}/config`; const configUrl = new URL(`${this.bannerId}/config`, this.baseUrl);
const config = await fetchJSON<BannerConfig>(configUrl); const config = await fetchJSON<BannerConfig>(configUrl);
this.bannerConfig = config; this.bannerConfig = config;
@@ -101,7 +101,10 @@ export class CookieBannerClient {
return; return;
} }
const consentUrl = `${this.baseUrl}/${this.bannerId}/consents/${this.visitorId}`; const consentUrl = new URL(
`${this.bannerId}/consents/${this.visitorId}`,
this.baseUrl,
);
const apiConsent = await fetchJSON<VisitorConsent>(consentUrl).catch( const apiConsent = await fetchJSON<VisitorConsent>(consentUrl).catch(
(err) => { (err) => {
if (err instanceof NotFoundError) { if (err instanceof NotFoundError) {
@@ -185,7 +188,7 @@ export class CookieBannerClient {
consentData: Record<string, boolean>, consentData: Record<string, boolean>,
): Promise<ConsentRecord> { ): Promise<ConsentRecord> {
const cfg = this.config; const cfg = this.config;
const url = `${this.baseUrl}/${this.bannerId}/consents`; const url = new URL(`${this.bannerId}/consents`, this.baseUrl);
const body = { const body = {
visitor_id: this.visitorId, visitor_id: this.visitorId,
version: cfg.version, version: cfg.version,
@@ -201,7 +204,7 @@ export class CookieBannerClient {
}); });
void flush(this.bannerId); void flush(this.bannerId);
} catch { } catch {
enqueue(this.bannerId, url, body); enqueue(this.bannerId, url.href, body);
} }
this.consent = { this.consent = {

View File

@@ -76,7 +76,7 @@ async function parseErrorBody(response: Response): Promise<ApiErrorBody> {
} }
async function fetchWithTimeout( async function fetchWithTimeout(
url: string, url: URL | string,
init: RequestInit, init: RequestInit,
timeout: number, timeout: number,
): Promise<Response> { ): Promise<Response> {
@@ -109,7 +109,7 @@ async function fetchWithTimeout(
} }
export async function fetchJSON<T>( export async function fetchJSON<T>(
url: string, url: URL | string,
options: RequestOptions = {}, options: RequestOptions = {},
): Promise<T> { ): Promise<T> {
const { method = "GET", headers, body, timeout = DEFAULT_TIMEOUT_MS, signal } = options; const { method = "GET", headers, body, timeout = DEFAULT_TIMEOUT_MS, signal } = options;