Skip to content

Commit e2dcc41

Browse files
authored
fix: post with http client (#137)
* Add method for post * Change request to a post * Fix payload format * v4.3.1-alpha.2
1 parent 59764a2 commit e2dcc41

File tree

7 files changed

+81
-14
lines changed

7 files changed

+81
-14
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eppo/js-client-sdk-common",
3-
"version": "4.3.1-alpha.1",
3+
"version": "4.3.1-alpha.2",
44
"description": "Eppo SDK for client-side JavaScript applications (base for both web and react native)",
55
"main": "dist/index.js",
66
"files": [

src/client/eppo-precomputed-client.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ describe('EppoPrecomputedClient E2E test', () => {
8484
},
8585
});
8686
const httpClient = new FetchHttpClient(apiEndpoints, 1000);
87-
const precomputedFlagRequestor = new PrecomputedRequestor(httpClient, storage);
87+
const precomputedFlagRequestor = new PrecomputedRequestor(httpClient, storage, 'subject-key', {
88+
'attribute-key': 'attribute-value',
89+
});
8890
await precomputedFlagRequestor.fetchAndStorePrecomputedFlags();
8991
});
9092

src/client/eppo-precomputed-client.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,15 @@ export default class EppoPrecomputedClient {
102102
// todo: Inject the chain of dependencies below
103103
const apiEndpoints = new ApiEndpoints({
104104
baseUrl: baseUrl ?? PRECOMPUTED_BASE_URL,
105-
queryParams: { apiKey, sdkName, sdkVersion, subjectKey, subjectAttributes },
105+
queryParams: { apiKey, sdkName, sdkVersion },
106106
});
107107
const httpClient = new FetchHttpClient(apiEndpoints, requestTimeoutMs);
108-
const precomputedRequestor = new PrecomputedRequestor(httpClient, this.precomputedFlagStore);
108+
const precomputedRequestor = new PrecomputedRequestor(
109+
httpClient,
110+
this.precomputedFlagStore,
111+
subjectKey,
112+
subjectAttributes,
113+
);
109114

110115
const pollingCallback = async () => {
111116
if (await this.precomputedFlagStore.isExpired()) {

src/http-client.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {
66
Flag,
77
FormatEnum,
88
PrecomputedFlag,
9+
PrecomputedFlagsPayload,
910
} from './interfaces';
11+
import { Attributes } from './types';
1012

1113
export interface IQueryParams {
1214
apiKey: string;
@@ -16,7 +18,7 @@ export interface IQueryParams {
1618

1719
export interface IQueryParamsWithSubject extends IQueryParams {
1820
subjectKey: string;
19-
subjectAttributes: Record<string, any>;
21+
subjectAttributes: Attributes;
2022
}
2123

2224
export class HttpRequestError extends Error {
@@ -50,8 +52,11 @@ export interface IPrecomputedFlagsResponse {
5052
export interface IHttpClient {
5153
getUniversalFlagConfiguration(): Promise<IUniversalFlagConfigResponse | undefined>;
5254
getBanditParameters(): Promise<IBanditParametersResponse | undefined>;
53-
getPrecomputedFlags(): Promise<IPrecomputedFlagsResponse | undefined>;
55+
getPrecomputedFlags(
56+
payload: PrecomputedFlagsPayload,
57+
): Promise<IPrecomputedFlagsResponse | undefined>;
5458
rawGet<T>(url: URL): Promise<T | undefined>;
59+
rawPost<T, P>(url: URL, payload: P): Promise<T | undefined>;
5560
}
5661

5762
export default class FetchHttpClient implements IHttpClient {
@@ -67,9 +72,11 @@ export default class FetchHttpClient implements IHttpClient {
6772
return await this.rawGet<IBanditParametersResponse>(url);
6873
}
6974

70-
async getPrecomputedFlags(): Promise<IPrecomputedFlagsResponse | undefined> {
75+
async getPrecomputedFlags(
76+
payload: PrecomputedFlagsPayload,
77+
): Promise<IPrecomputedFlagsResponse | undefined> {
7178
const url = this.apiEndpoints.precomputedFlagsEndpoint();
72-
return await this.rawGet<IPrecomputedFlagsResponse>(url);
79+
return await this.rawPost<IPrecomputedFlagsResponse, PrecomputedFlagsPayload>(url, payload);
7380
}
7481

7582
async rawGet<T>(url: URL): Promise<T | undefined> {
@@ -97,4 +104,37 @@ export default class FetchHttpClient implements IHttpClient {
97104
throw new HttpRequestError('Network error', 0, error);
98105
}
99106
}
107+
108+
async rawPost<T, P>(url: URL, payload: P): Promise<T | undefined> {
109+
try {
110+
const controller = new AbortController();
111+
const signal = controller.signal;
112+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
113+
114+
const response = await fetch(url.toString(), {
115+
method: 'POST',
116+
headers: {
117+
'Content-Type': 'application/json',
118+
},
119+
body: JSON.stringify(payload),
120+
signal,
121+
});
122+
123+
clearTimeout(timeoutId);
124+
125+
if (!response?.ok) {
126+
const errorBody = await response.text();
127+
throw new HttpRequestError(errorBody || 'Failed to post data', response?.status);
128+
}
129+
return await response.json();
130+
} catch (error: any) {
131+
if (error.name === 'AbortError') {
132+
throw new HttpRequestError('Request timed out', 408, error);
133+
} else if (error instanceof HttpRequestError) {
134+
throw error;
135+
}
136+
137+
throw new HttpRequestError('Network error', 0, error);
138+
}
139+
}
100140
}

src/interfaces.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Rule } from './rules';
2+
import { Attributes } from './types';
23

34
export enum VariationType {
45
STRING = 'STRING',
@@ -155,3 +156,8 @@ export interface PrecomputedFlagsDetails {
155156
precomputedFlagsPublishedAt: string;
156157
precomputedFlagsEnvironment: Environment;
157158
}
159+
160+
export interface PrecomputedFlagsPayload {
161+
subject_key: string;
162+
subject_attributes: Attributes;
163+
}

src/precomputed-requestor.spec.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { IConfigurationStore } from './configuration-store/configuration-store';
33
import { MemoryOnlyConfigurationStore } from './configuration-store/memory.store';
44
import FetchHttpClient, { IHttpClient } from './http-client';
55
import { PrecomputedFlag } from './interfaces';
6-
import ConfigurationRequestor from './precomputed-requestor';
6+
import PrecomputedFlagRequestor from './precomputed-requestor';
77

88
const MOCK_PRECOMPUTED_RESPONSE = {
99
flags: {
@@ -34,7 +34,7 @@ const MOCK_PRECOMPUTED_RESPONSE = {
3434
describe('PrecomputedRequestor', () => {
3535
let precomputedFlagStore: IConfigurationStore<PrecomputedFlag>;
3636
let httpClient: IHttpClient;
37-
let configurationRequestor: ConfigurationRequestor;
37+
let precomputedFlagRequestor: PrecomputedFlagRequestor;
3838
let fetchSpy: jest.Mock;
3939

4040
beforeEach(() => {
@@ -48,7 +48,14 @@ describe('PrecomputedRequestor', () => {
4848
});
4949
httpClient = new FetchHttpClient(apiEndpoints, 1000);
5050
precomputedFlagStore = new MemoryOnlyConfigurationStore<PrecomputedFlag>();
51-
configurationRequestor = new ConfigurationRequestor(httpClient, precomputedFlagStore);
51+
precomputedFlagRequestor = new PrecomputedFlagRequestor(
52+
httpClient,
53+
precomputedFlagStore,
54+
'subject-key',
55+
{
56+
'attribute-key': 'attribute-value',
57+
},
58+
);
5259

5360
fetchSpy = jest.fn(() => {
5461
return Promise.resolve({
@@ -70,7 +77,7 @@ describe('PrecomputedRequestor', () => {
7077

7178
describe('Precomputed flags', () => {
7279
it('Fetches and stores precomputed flag configuration', async () => {
73-
await configurationRequestor.fetchAndStorePrecomputedFlags();
80+
await precomputedFlagRequestor.fetchAndStorePrecomputedFlags();
7481

7582
expect(fetchSpy).toHaveBeenCalledTimes(1);
7683

@@ -110,7 +117,7 @@ describe('PrecomputedRequestor', () => {
110117
}),
111118
);
112119

113-
await configurationRequestor.fetchAndStorePrecomputedFlags();
120+
await precomputedFlagRequestor.fetchAndStorePrecomputedFlags();
114121

115122
expect(fetchSpy).toHaveBeenCalledTimes(1);
116123
expect(precomputedFlagStore.getKeys().length).toBe(0);

src/precomputed-requestor.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@ import { IConfigurationStore } from './configuration-store/configuration-store';
22
import { hydrateConfigurationStore } from './configuration-store/configuration-store-utils';
33
import { IHttpClient } from './http-client';
44
import { PrecomputedFlag } from './interfaces';
5+
import { Attributes } from './types';
56

67
// Requests AND stores precomputed flags, reuses the configuration store
78
export default class PrecomputedFlagRequestor {
89
constructor(
910
private readonly httpClient: IHttpClient,
1011
private readonly precomputedFlagStore: IConfigurationStore<PrecomputedFlag>,
12+
private readonly subjectKey: string,
13+
private readonly subjectAttributes: Attributes,
1114
) {}
1215

1316
async fetchAndStorePrecomputedFlags(): Promise<void> {
14-
const precomputedResponse = await this.httpClient.getPrecomputedFlags();
17+
const precomputedResponse = await this.httpClient.getPrecomputedFlags({
18+
subject_key: this.subjectKey,
19+
subject_attributes: this.subjectAttributes,
20+
});
21+
1522
if (!precomputedResponse?.flags) {
1623
return;
1724
}

0 commit comments

Comments
 (0)