Skip to content

Commit c417772

Browse files
committed
Fixed fetch implementation, added rate limit header support.
1 parent ea513ba commit c417772

File tree

10 files changed

+36
-40
lines changed

10 files changed

+36
-40
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"lage",
2626
"maxcdn",
2727
"ncaught",
28+
"ratelimit",
2829
"tsproject",
2930
"webcompat"
3031
],

packages/browser/src/submission/BrowserFetchSubmissionClient.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from "@exceptionless/core";
66

77
export class BrowserFetchSubmissionClient extends SubmissionClientBase {
8-
protected async fetch<T>(url: string, options: FetchOptions): Promise<Response<T>> {
8+
protected async fetch(url: string, options: FetchOptions): Promise<Response> {
99
// TODO: Figure out how to set a 10000 timeout.
1010
const requestOptions: RequestInit = {
1111
method: options.method,
@@ -23,7 +23,8 @@ export class BrowserFetchSubmissionClient extends SubmissionClientBase {
2323
}
2424

2525
const response = await fetch(url, requestOptions);
26+
const rateLimitRemaining: number = parseInt(response.headers.get(this.RateLimitRemainingHeader), 10);
2627
const settingsVersion: number = parseInt(response.headers.get(this.ConfigurationVersionHeader), 10);
27-
return new Response(response.status, response.statusText, settingsVersion, response.ok ? await response.json() : await response.text())
28+
return new Response(response.status, response.statusText, rateLimitRemaining, settingsVersion, await response.text())
2829
}
2930
}

packages/core/src/queue/DefaultEventQueue.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class DefaultEventQueue implements IEventQueue {
1212
* @private
1313
*/
1414
private _handlers: Array<
15-
(events: Event[], response: Response<void>) => void
15+
(events: Event[], response: Response) => void
1616
> = [];
1717

1818
/**
@@ -150,11 +150,11 @@ export class DefaultEventQueue implements IEventQueue {
150150
}
151151

152152
// TODO: See if this makes sense.
153-
public onEventsPosted(handler: (events: Event[], response: Response<void>) => void): void {
153+
public onEventsPosted(handler: (events: Event[], response: Response) => void): void {
154154
handler && this._handlers.push(handler);
155155
}
156156

157-
private eventsPosted(events: Event[], response: Response<void>) {
157+
private eventsPosted(events: Event[], response: Response) {
158158
const handlers = this._handlers; // optimization for minifier.
159159
for (const handler of handlers) {
160160
try {
@@ -188,11 +188,8 @@ export class DefaultEventQueue implements IEventQueue {
188188
}
189189
}
190190

191-
private processSubmissionResponse(
192-
response: Response<void>,
193-
events: IStorageItem[],
194-
): void {
195-
const noSubmission: string = "The event will not be submitted."; // Optimization for minifier.
191+
private processSubmissionResponse(response: Response, events: IStorageItem[]): void {
192+
const noSubmission: string = "The event will not be submitted"; // Optimization for minifier.
196193
const config: Configuration = this.config; // Optimization for minifier.
197194
const log: ILog = config.services.log; // Optimization for minifier.
198195

@@ -202,7 +199,7 @@ export class DefaultEventQueue implements IEventQueue {
202199
return;
203200
}
204201

205-
if (response.status === 429 || response.status === 503) {
202+
if (response.status === 429 || response.rateLimitRemaining === 0 || response.status === 503) {
206203
// You are currently over your rate limit or the servers are under stress.
207204
log.error("Server returned service unavailable.");
208205
this.suspendProcessing();

packages/core/src/queue/IEventQueue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ export interface IEventQueue {
1111
/** Suspends processing of events for a specific duration */
1212
suspendProcessing(durationInMinutes?: number, discardFutureQueuedItems?: boolean, clearQueue?: boolean): void;
1313
// TODO: See if this makes sense.
14-
onEventsPosted(handler: (events: Event[], response: Response<void>) => void): void;
14+
onEventsPosted(handler: (events: Event[], response: Response) => void): void;
1515
}

packages/core/src/submission/ISubmissionClient.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { UserDescription } from "../models/data/UserDescription.js";
44
import { Response } from "./Response.js";
55

66
export interface ISubmissionClient {
7-
getSettings(version: number): Promise<Response<ClientSettings>>;
8-
submitEvents(events: Event[]): Promise<Response<void>>;
9-
submitUserDescription(referenceId: string, description: UserDescription): Promise<Response<void>>;
10-
submitHeartbeat(sessionIdOrUserId: string, closeSession: boolean): Promise<Response<void>>;
7+
getSettings(version: number): Promise<Response>;
8+
submitEvents(events: Event[]): Promise<Response>;
9+
submitUserDescription(referenceId: string, description: UserDescription): Promise<Response>;
10+
submitHeartbeat(sessionIdOrUserId: string, closeSession: boolean): Promise<Response>;
1111
}

packages/core/src/submission/Response.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11

2-
export class Response<T> {
2+
export class Response {
33
constructor(
44
public status: number,
55
public message: string,
6+
public rateLimitRemaining: number,
67
public settingsVersion: number,
7-
public data: T
8+
public data: string
89
) { }
910

1011
public get success(): boolean {

packages/core/src/submission/SubmissionClientBase.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,19 @@ export interface FetchOptions {
1414
}
1515

1616
export abstract class SubmissionClientBase implements ISubmissionClient {
17+
protected readonly RateLimitRemainingHeader: string = "x-ratelimit-remaining";
1718
protected readonly ConfigurationVersionHeader: string = "x-exceptionless-configversion";
1819

1920
public constructor(protected config: Configuration) { }
2021

21-
public getSettings(version: number): Promise<Response<ClientSettings>> {
22+
public getSettings(version: number): Promise<Response> {
2223
const url = `${this.config.serverUrl}/api/v2/projects/config?v=${version}`;
2324
return this.fetch<ClientSettings>(url, {
2425
method: "GET",
2526
});
2627
}
2728

28-
public async submitEvents(events: Event[]): Promise<Response<void>> {
29+
public async submitEvents(events: Event[]): Promise<Response> {
2930
const url = `${this.config.serverUrl}/api/v2/events`;
3031
const response = await this.fetch<void>(url, {
3132
method: "POST",
@@ -36,10 +37,8 @@ export abstract class SubmissionClientBase implements ISubmissionClient {
3637
return response;
3738
}
3839

39-
public async submitUserDescription(referenceId: string, description: UserDescription): Promise<Response<void>> {
40-
const url = `${this.config.serverUrl}/api/v2/events/by-ref/${
41-
encodeURIComponent(referenceId)
42-
}/user-description`;
40+
public async submitUserDescription(referenceId: string, description: UserDescription): Promise<Response> {
41+
const url = `${this.config.serverUrl}/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`;
4342

4443
const response = await this.fetch<void>(url, {
4544
method: "POST",
@@ -50,15 +49,11 @@ export abstract class SubmissionClientBase implements ISubmissionClient {
5049
return response;
5150
}
5251

53-
public async submitHeartbeat(sessionIdOrUserId: string, closeSession: boolean): Promise<Response<void>> {
52+
public async submitHeartbeat(sessionIdOrUserId: string, closeSession: boolean): Promise<Response> {
5453
const url = `${this.config.heartbeatServerUrl}/api/v2/events/session/heartbeat?id=${sessionIdOrUserId}&close=${closeSession}`;
55-
56-
const response = await this.fetch<void>(url, {
54+
return await this.fetch<void>(url, {
5755
method: "GET"
5856
});
59-
60-
await this.updateSettingsVersion(response.settingsVersion);
61-
return response;
6257
}
6358

6459
protected async updateSettingsVersion(settingsVersion: number): Promise<void> {
@@ -69,5 +64,5 @@ export abstract class SubmissionClientBase implements ISubmissionClient {
6964
}
7065
}
7166

72-
protected abstract fetch<T>(url: string, options: FetchOptions): Promise<Response<T>>;
67+
protected abstract fetch<T>(url: string, options: FetchOptions): Promise<Response>;
7368
}

packages/core/test/submission/TestSubmissionClient.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe("TestSubmissionClient", () => {
1414

1515
test("should submit events", async () => {
1616
const fetchMock = TestSubmissionClient.prototype.fetch = jest.fn()
17-
.mockReturnValueOnce(new Response<void>(202, "", -1, undefined));
17+
.mockReturnValueOnce(new Response(202, "", undefined, undefined, undefined));
1818

1919
const events = [{ type: "log", message: "From js client", reference_id: "123454321" }];
2020
const client = new TestSubmissionClient(config);
@@ -29,7 +29,7 @@ describe("TestSubmissionClient", () => {
2929

3030
test("should submit invalid object data", async () => {
3131
const fetchMock = TestSubmissionClient.prototype.fetch = jest.fn()
32-
.mockReturnValueOnce(new Response<void>(202, "", -1, undefined));
32+
.mockReturnValueOnce(new Response(202, "", undefined, undefined, undefined));
3333

3434
const events: Event[] = [{
3535
type: "log", message: "From js client", reference_id: "123454321", data: {
@@ -50,7 +50,7 @@ describe("TestSubmissionClient", () => {
5050

5151
test("should submit user description", async () => {
5252
const fetchMock = TestSubmissionClient.prototype.fetch = jest.fn()
53-
.mockReturnValueOnce(new Response<void>(202, "", -1, undefined));
53+
.mockReturnValueOnce(new Response(202, "", undefined, undefined, undefined));
5454

5555
const description: UserDescription = {
5656
email_address: "[email protected]",
@@ -69,8 +69,8 @@ describe("TestSubmissionClient", () => {
6969

7070
test("should submit heartbeat", async () => {
7171
const fetchMock = TestSubmissionClient.prototype.fetch = jest.fn()
72-
.mockReturnValueOnce(new Response<void>(200, "", 1, undefined))
73-
.mockReturnValueOnce(new Response<ClientSettings>(200, "", 1, new ClientSettings({}, 1)));
72+
.mockReturnValueOnce(new Response(200, "", undefined, 1, undefined))
73+
.mockReturnValueOnce(new Response(200, "", undefined, 1, JSON.stringify(new ClientSettings({}, 1))));
7474

7575
const client = config.services.submissionClient = new TestSubmissionClient(config);
7676
await client.submitHeartbeat("sessionId", true);
@@ -83,7 +83,7 @@ describe("TestSubmissionClient", () => {
8383

8484
test("should get project settings", async () => {
8585
const fetchMock = TestSubmissionClient.prototype.fetch = jest.fn()
86-
.mockReturnValueOnce(new Response<ClientSettings>(200, "", undefined, new ClientSettings({}, 1)));
86+
.mockReturnValueOnce(new Response(200, "", undefined, undefined, JSON.stringify(new ClientSettings({}, 1))));
8787

8888
const client = new TestSubmissionClient(config);
8989
await client.getSettings(0);

packages/core/test/submission/TestSubmissionClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from "../../src/submission/SubmissionClientBase.js";
66

77
export class TestSubmissionClient extends SubmissionClientBase {
8-
public fetch<T>(url: string, options: FetchOptions): Promise<Response<T>> {
8+
public fetch<T>(url: string, options: FetchOptions): Promise<Response> {
99
throw new Error("Missing mock");
1010
}
1111
}

packages/node/src/submission/NodeFetchSubmissionClient.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from "@exceptionless/core";
1212

1313
export class NodeFetchSubmissionClient extends SubmissionClientBase {
14-
protected async fetch<T>(url: string, options: FetchOptions): Promise<Response<T>> {
14+
protected async fetch<T>(url: string, options: FetchOptions): Promise<Response> {
1515
// TODO: Figure out how to set a 10000 timeout.
1616
const requestOptions: RequestInit = {
1717
method: options.method,
@@ -29,7 +29,8 @@ export class NodeFetchSubmissionClient extends SubmissionClientBase {
2929
}
3030

3131
const response = await fetch(url, requestOptions);
32+
const rateLimitRemaining: number = parseInt(response.headers.get(this.RateLimitRemainingHeader), 10);
3233
const settingsVersion: number = parseInt(response.headers.get(this.ConfigurationVersionHeader), 10);
33-
return new Response(response.status, response.statusText, settingsVersion, await response.json())
34+
return new Response(response.status, response.statusText, rateLimitRemaining, settingsVersion, await response.text())
3435
}
3536
}

0 commit comments

Comments
 (0)