From 6086e677bc157d620ab63d0f4737509a860e64d3 Mon Sep 17 00:00:00 2001 From: RickjanHoornbeeck <51879@hoornbeeck.nl> Date: Fri, 10 Apr 2026 13:09:42 +0200 Subject: [PATCH 1/4] fix: handle empty responses in HttpHandler --- src/core/http.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/core/http.ts b/src/core/http.ts index 9685aff..3bf8ff3 100644 --- a/src/core/http.ts +++ b/src/core/http.ts @@ -24,6 +24,10 @@ export class HttpHandler { readonly #apiKey: string readonly #baseUrl: string + #emptyResponse(): T { + return undefined! + } + constructor(props: ClientProps) { this.#apiKey = props.apiKey this.#baseUrl = props.urlEndpoint ?? DefaultEndpoint @@ -66,16 +70,31 @@ export class HttpHandler { } async #handleResponse(response: Response): Promise { + if (!response.ok) { throw await this.#mapError(response) } + if (response.status === 204 || response.status === 205) { + return this.#emptyResponse() + } + + const contentLength = response.headers.get('content-length') + if (contentLength === '0') { + return this.#emptyResponse() + } + const contentType = response.headers.get('content-type') if (!contentType || !contentType.includes('application/json')) { - return undefined as T + return this.#emptyResponse() + } + + const text = await response.text() + if (!text.trim()) { + return this.#emptyResponse() } - return response.json() as Promise + return JSON.parse(text) } async #mapError(response: Response): Promise { From e45dc87d26cbb98e1fa49a14e0c5ece40b63def1 Mon Sep 17 00:00:00 2001 From: RickjanHoornbeeck <51879@hoornbeeck.nl> Date: Fri, 10 Apr 2026 13:26:57 +0200 Subject: [PATCH 2/4] fix: suggested changes --- src/core/http.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/core/http.ts b/src/core/http.ts index 3bf8ff3..38118e7 100644 --- a/src/core/http.ts +++ b/src/core/http.ts @@ -75,10 +75,6 @@ export class HttpHandler { throw await this.#mapError(response) } - if (response.status === 204 || response.status === 205) { - return this.#emptyResponse() - } - const contentLength = response.headers.get('content-length') if (contentLength === '0') { return this.#emptyResponse() @@ -89,12 +85,7 @@ export class HttpHandler { return this.#emptyResponse() } - const text = await response.text() - if (!text.trim()) { - return this.#emptyResponse() - } - - return JSON.parse(text) + return response.json() } async #mapError(response: Response): Promise { From 2aefecb8b37000a9fbda03a29770220e6bf6dac5 Mon Sep 17 00:00:00 2001 From: IAmKirbki Date: Fri, 10 Apr 2026 14:04:56 +0200 Subject: [PATCH 3/4] fix: add generic type parameters to post and remove methods for better type safety --- src/core/resources/organizations/events.ts | 2 +- src/core/resources/organizations/organization.ts | 8 ++++---- src/core/resources/organizations/scheduled.ts | 4 ++-- src/core/resources/users/events.ts | 2 +- src/core/resources/users/scheduled.ts | 4 ++-- src/core/resources/users/user.ts | 4 ++-- src/platform/browser.ts | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/core/resources/organizations/events.ts b/src/core/resources/organizations/events.ts index 8e7face..d826e5c 100644 --- a/src/core/resources/organizations/events.ts +++ b/src/core/resources/organizations/events.ts @@ -9,6 +9,6 @@ export class OrganizationEventsResource extends BaseResource { * @param data - Array of organization events */ async post(data: OrganizationEvent[]): Promise { - return super.post(data) + return super.post(data) } } diff --git a/src/core/resources/organizations/organization.ts b/src/core/resources/organizations/organization.ts index 19b291a..1537a86 100644 --- a/src/core/resources/organizations/organization.ts +++ b/src/core/resources/organizations/organization.ts @@ -27,7 +27,7 @@ export class OrganizationResource extends BaseResource { * @returns Promise resolving to the created/updated organization */ async upsert(data: OrganizationRequest): Promise { - return this.post(data) + return this.post(data) } /** @@ -36,7 +36,7 @@ export class OrganizationResource extends BaseResource { * @returns Promise resolving when organization is deleted */ async delete(data: DeleteOrganizationRequest): Promise { - return this.remove(data) + return this.remove(data) } /** @@ -45,7 +45,7 @@ export class OrganizationResource extends BaseResource { * @returns Promise resolving when user is added */ async addUser(data: OrganizationUserRequest): Promise { - return this.post(data, 'organizations/users') + return this.post(data, 'organizations/users') } /** @@ -54,6 +54,6 @@ export class OrganizationResource extends BaseResource { * @returns Promise resolving when user is removed */ async removeUser(data: RemoveOrganizationUserRequest): Promise { - return this.remove(data, 'organizations/users') + return this.remove(data, 'organizations/users') } } diff --git a/src/core/resources/organizations/scheduled.ts b/src/core/resources/organizations/scheduled.ts index af2f77d..031c5d5 100644 --- a/src/core/resources/organizations/scheduled.ts +++ b/src/core/resources/organizations/scheduled.ts @@ -17,7 +17,7 @@ export class OrganizationScheduledResource extends BaseResource { * @returns Promise resolving to the accepted scheduled resource */ async upsert(data: UpsertOrganizationScheduledRequest): Promise { - return this.post(data) + return this.post(data) } /** @@ -26,6 +26,6 @@ export class OrganizationScheduledResource extends BaseResource { * @returns Promise resolving when scheduled resource is deleted */ async delete(data: DeleteOrganizationScheduledRequest): Promise { - return this.remove(data) + return this.remove(data) } } diff --git a/src/core/resources/users/events.ts b/src/core/resources/users/events.ts index 39c3c48..a38fa4a 100644 --- a/src/core/resources/users/events.ts +++ b/src/core/resources/users/events.ts @@ -9,6 +9,6 @@ export class UserEventsResource extends BaseResource { * @param data - Array of user events */ async post(data: UserEvent[]): Promise { - return super.post(data) + return super.post(data) } } diff --git a/src/core/resources/users/scheduled.ts b/src/core/resources/users/scheduled.ts index bc033af..cae569d 100644 --- a/src/core/resources/users/scheduled.ts +++ b/src/core/resources/users/scheduled.ts @@ -17,7 +17,7 @@ export class UserScheduledResource extends BaseResource { * @returns Promise resolving to the accepted scheduled resource */ async upsert(data: UpsertUserScheduledRequest): Promise { - return this.post(data) + return this.post(data) } /** @@ -26,6 +26,6 @@ export class UserScheduledResource extends BaseResource { * @returns Promise resolving when scheduled resource is deleted */ async delete(data: DeleteUserScheduledRequest): Promise { - return this.remove(data) + return this.remove(data) } } diff --git a/src/core/resources/users/user.ts b/src/core/resources/users/user.ts index 5af36fb..1a903b4 100644 --- a/src/core/resources/users/user.ts +++ b/src/core/resources/users/user.ts @@ -25,7 +25,7 @@ export class UserResource extends BaseResource { * @returns Promise resolving to the created/updated user */ async upsert(data: UpsertUserRequest): Promise { - return this.post(data) + return this.post(data) } /** @@ -34,6 +34,6 @@ export class UserResource extends BaseResource { * @returns Promise resolving when user is deleted */ async delete(data: DeleteUserRequest): Promise { - return this.remove(data) + return this.remove(data) } } diff --git a/src/platform/browser.ts b/src/platform/browser.ts index 99e068c..3f66831 100644 --- a/src/platform/browser.ts +++ b/src/platform/browser.ts @@ -31,7 +31,7 @@ class BrowserUserEventsResource extends UserEventsResource { // Do not inject identifier when `match` is used (they are mutually exclusive) identifier: event.match ? event.identifier : (event.identifier ?? identifier), })) - return super.post(injected) as Promise + return super.post(injected) } } From 2aa90650b25638d0b9c52d054614d3c1467b7685 Mon Sep 17 00:00:00 2001 From: RickjanHoornbeeck <51879@hoornbeeck.nl> Date: Fri, 10 Apr 2026 14:27:27 +0200 Subject: [PATCH 4/4] fix: some cleanup done --- src/core/http.ts | 19 +++++++------------ src/core/resources/base.ts | 6 +++--- src/core/resources/organizations/events.ts | 2 +- .../resources/organizations/organization.ts | 2 +- src/core/resources/organizations/scheduled.ts | 2 +- src/core/resources/users/events.ts | 2 +- src/core/resources/users/scheduled.ts | 2 +- src/core/resources/users/user.ts | 2 +- src/platform/browser.ts | 6 +++--- 9 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/core/http.ts b/src/core/http.ts index 38118e7..34817e9 100644 --- a/src/core/http.ts +++ b/src/core/http.ts @@ -24,28 +24,24 @@ export class HttpHandler { readonly #apiKey: string readonly #baseUrl: string - #emptyResponse(): T { - return undefined! - } - constructor(props: ClientProps) { this.#apiKey = props.apiKey this.#baseUrl = props.urlEndpoint ?? DefaultEndpoint } - async get(path: string, data?: unknown): Promise { + async get(path: string, data?: unknown): Promise { return this.#request('GET', path, data) } - async post(path: string, data?: unknown): Promise { + async post(path: string, data?: unknown): Promise { return this.#request('POST', path, data) } - async delete(path: string, data?: unknown): Promise { + async delete(path: string, data?: unknown): Promise { return this.#request('DELETE', path, data) } - async #request(method: HttpMethod, path: string, data?: unknown): Promise { + async #request(method: HttpMethod, path: string, data?: unknown): Promise { const url = `${this.#baseUrl}/client/${path}` try { @@ -69,20 +65,19 @@ export class HttpHandler { } } - async #handleResponse(response: Response): Promise { - + async #handleResponse(response: Response): Promise { if (!response.ok) { throw await this.#mapError(response) } const contentLength = response.headers.get('content-length') if (contentLength === '0') { - return this.#emptyResponse() + return undefined } const contentType = response.headers.get('content-type') if (!contentType || !contentType.includes('application/json')) { - return this.#emptyResponse() + return undefined } return response.json() diff --git a/src/core/resources/base.ts b/src/core/resources/base.ts index 5f418bf..d1c05df 100644 --- a/src/core/resources/base.ts +++ b/src/core/resources/base.ts @@ -9,15 +9,15 @@ export abstract class BaseResource { protected abstract readonly endpoint: string - protected async get(data?: unknown): Promise { + protected async get(data?: unknown): Promise { return this.#http.get(this.endpoint, data) } - protected async post(data?: unknown, pathOverride?: string): Promise { + protected async post(data?: unknown, pathOverride?: string): Promise { return this.#http.post(pathOverride ?? this.endpoint, data) } - protected async remove(data?: unknown, pathOverride?: string): Promise { + protected async remove(data?: unknown, pathOverride?: string): Promise { return this.#http.delete(pathOverride ?? this.endpoint, data) } } diff --git a/src/core/resources/organizations/events.ts b/src/core/resources/organizations/events.ts index d826e5c..6a70c85 100644 --- a/src/core/resources/organizations/events.ts +++ b/src/core/resources/organizations/events.ts @@ -8,7 +8,7 @@ export class OrganizationEventsResource extends BaseResource { * Posts organization events for asynchronous processing. * @param data - Array of organization events */ - async post(data: OrganizationEvent[]): Promise { + async post(data: OrganizationEvent[]): Promise { return super.post(data) } } diff --git a/src/core/resources/organizations/organization.ts b/src/core/resources/organizations/organization.ts index 1537a86..10c370b 100644 --- a/src/core/resources/organizations/organization.ts +++ b/src/core/resources/organizations/organization.ts @@ -26,7 +26,7 @@ export class OrganizationResource extends BaseResource { * @param data - Organization data including identifier, name, data, etc. * @returns Promise resolving to the created/updated organization */ - async upsert(data: OrganizationRequest): Promise { + async upsert(data: OrganizationRequest): Promise { return this.post(data) } diff --git a/src/core/resources/organizations/scheduled.ts b/src/core/resources/organizations/scheduled.ts index 031c5d5..97d7f1f 100644 --- a/src/core/resources/organizations/scheduled.ts +++ b/src/core/resources/organizations/scheduled.ts @@ -16,7 +16,7 @@ export class OrganizationScheduledResource extends BaseResource { * @param data - Scheduled resource data including name, identifier, scheduledAt, interval, etc. * @returns Promise resolving to the accepted scheduled resource */ - async upsert(data: UpsertOrganizationScheduledRequest): Promise { + async upsert(data: UpsertOrganizationScheduledRequest): Promise { return this.post(data) } diff --git a/src/core/resources/users/events.ts b/src/core/resources/users/events.ts index a38fa4a..31b0d65 100644 --- a/src/core/resources/users/events.ts +++ b/src/core/resources/users/events.ts @@ -8,7 +8,7 @@ export class UserEventsResource extends BaseResource { * Posts user events for asynchronous processing. * @param data - Array of user events */ - async post(data: UserEvent[]): Promise { + async post(data: UserEvent[]): Promise { return super.post(data) } } diff --git a/src/core/resources/users/scheduled.ts b/src/core/resources/users/scheduled.ts index cae569d..a25a597 100644 --- a/src/core/resources/users/scheduled.ts +++ b/src/core/resources/users/scheduled.ts @@ -16,7 +16,7 @@ export class UserScheduledResource extends BaseResource { * @param data - Scheduled resource data including name, identifier, scheduledAt, interval, etc. * @returns Promise resolving to the accepted scheduled resource */ - async upsert(data: UpsertUserScheduledRequest): Promise { + async upsert(data: UpsertUserScheduledRequest): Promise { return this.post(data) } diff --git a/src/core/resources/users/user.ts b/src/core/resources/users/user.ts index 1a903b4..e72abc8 100644 --- a/src/core/resources/users/user.ts +++ b/src/core/resources/users/user.ts @@ -24,7 +24,7 @@ export class UserResource extends BaseResource { * @param data - User data including identifier, email, phone, etc. * @returns Promise resolving to the created/updated user */ - async upsert(data: UpsertUserRequest): Promise { + async upsert(data: UpsertUserRequest): Promise { return this.post(data) } diff --git a/src/platform/browser.ts b/src/platform/browser.ts index 3f66831..91c11e0 100644 --- a/src/platform/browser.ts +++ b/src/platform/browser.ts @@ -24,7 +24,7 @@ class BrowserUserEventsResource extends UserEventsResource { this.#getIdentifier = getIdentifier } - async post(data: UserEvent[]): Promise { + async post(data: UserEvent[]): Promise { const identifier = this.#getIdentifier() const injected = data.map((event) => ({ ...event, @@ -43,7 +43,7 @@ class BrowserUserScheduledResource extends UserScheduledResource { this.#getIdentifier = getIdentifier } - async upsert(data: UpsertUserScheduledRequest): Promise { + async upsert(data: UpsertUserScheduledRequest): Promise { return super.upsert({ ...data, identifier: data.identifier ?? this.#getIdentifier(), @@ -113,7 +113,7 @@ class BrowserUserResource extends UserResource { return identifier } - async upsert(data: UpsertUserRequest): Promise { + async upsert(data: UpsertUserRequest): Promise { const identifier = this.#buildIdentifier(data.identifier) return super.upsert({ ...data, identifier }) }