Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions packages/widget/src/providers/api/api-client-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PropsWithChildren } from "react";
import { createContext, useContext, useEffect, useMemo } from "react";
import { createContext, useContext, useMemo } from "react";
import { config } from "../../config";
import { useSettings } from "../settings";
import { type ApiClient, createApiClient } from "./api-client";
Expand All @@ -19,8 +19,6 @@ export const SKApiClientProvider = ({ children }: PropsWithChildren) => {
[apiKey, baseUrl, yieldsApiUrl]
);

useEffect(() => () => void apiClient.dispose(), [apiClient]);

return <Context.Provider value={apiClient}>{children}</Context.Provider>;
};

Expand Down
46 changes: 7 additions & 39 deletions packages/widget/src/providers/api/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type RunOptions = {
readonly signal?: AbortSignal;
};

const runtime = ManagedRuntime.make(FetchHttpClient.layer);

const inspectResponse = ({
response,
}: {
Expand Down Expand Up @@ -66,8 +68,6 @@ const configureClient = ({
HttpClient.tap((response) => inspectResponse({ response }))
);

type ApiRuntime = ManagedRuntime.ManagedRuntime<HttpClient.HttpClient, never>;

type BoundOperation<Operation> = Operation extends (
...args: infer Args
) => Effect.Effect<infer A, infer _E>
Expand All @@ -77,7 +77,6 @@ type BoundOperation<Operation> = Operation extends (
const bindOperation = <
Operation extends (...args: never[]) => Effect.Effect<unknown, unknown>,
>(
runtime: ApiRuntime,
operation: Operation,
runOptions?: RunOptions
): BoundOperation<Operation> =>
Expand All @@ -90,45 +89,36 @@ const bindOperation = <
const bindLegacyApi = ({
api,
runOptions,
runtime,
}: {
readonly api: LegacyApi.LegacyApi;
readonly runOptions?: RunOptions;
readonly runtime: ApiRuntime;
}) => ({
TokenControllerGetTokenBalances: bindOperation(
runtime,
api.TokenControllerGetTokenBalances,
runOptions
),
TokenControllerGetTokenPrices: bindOperation(
runtime,
api.TokenControllerGetTokenPrices,
runOptions
),
TokenControllerGetTokens: bindOperation(
runtime,
api.TokenControllerGetTokens,
runOptions
),
TokenControllerTokenBalancesScan: bindOperation(
runtime,
api.TokenControllerTokenBalancesScan,
runOptions
),
TransactionControllerGetTransactionVerificationMessageForNetwork:
bindOperation(
runtime,
api.TransactionControllerGetTransactionVerificationMessageForNetwork,
runOptions
),
YieldControllerGetSingleYieldRewardsSummary: bindOperation(
runtime,
api.YieldControllerGetSingleYieldRewardsSummary,
runOptions
),
YieldControllerYieldOpportunity: bindOperation(
runtime,
api.YieldControllerYieldOpportunity,
runOptions
),
Expand All @@ -137,74 +127,56 @@ const bindLegacyApi = ({
const bindYieldApi = ({
api,
runOptions,
runtime,
}: {
readonly api: YieldApi.YieldApi;
readonly runOptions?: RunOptions;
readonly runtime: ApiRuntime;
}) => ({
ActionsControllerEnterYield: bindOperation(
runtime,
api.ActionsControllerEnterYield,
runOptions
),
ActionsControllerExitYield: bindOperation(
runtime,
api.ActionsControllerExitYield,
runOptions
),
ActionsControllerGetActions: bindOperation(
runtime,
api.ActionsControllerGetActions,
runOptions
),
ActionsControllerManageYield: bindOperation(
runtime,
api.ActionsControllerManageYield,
runOptions
),
HealthControllerHealth: bindOperation(
runtime,
api.HealthControllerHealth,
runOptions
),
HealthControllerHealth: bindOperation(api.HealthControllerHealth, runOptions),
NetworksControllerGetNetworks: bindOperation(
runtime,
api.NetworksControllerGetNetworks,
runOptions
),
TransactionsControllerGetTransaction: bindOperation(
runtime,
api.TransactionsControllerGetTransaction,
runOptions
),
TransactionsControllerSubmitTransaction: bindOperation(
runtime,
api.TransactionsControllerSubmitTransaction,
runOptions
),
TransactionsControllerSubmitTransactionHash: bindOperation(
runtime,
api.TransactionsControllerSubmitTransactionHash,
runOptions
),
YieldsControllerGetAggregateBalances: bindOperation(
runtime,
api.YieldsControllerGetAggregateBalances,
runOptions
),
YieldsControllerGetYield: bindOperation(
runtime,
api.YieldsControllerGetYield,
runOptions
),
YieldsControllerGetYieldBalances: bindOperation(
runtime,
api.YieldsControllerGetYieldBalances,
runOptions
),
YieldsControllerGetYieldValidators: bindOperation(
runtime,
api.YieldsControllerGetYieldValidators,
runOptions
),
Expand All @@ -213,24 +185,21 @@ const bindYieldApi = ({
const bindApiClients = ({
legacyApi,
runOptions,
runtime,
yieldApi,
}: {
readonly legacyApi: LegacyApi.LegacyApi;
readonly runOptions?: RunOptions;
readonly runtime: ApiRuntime;
readonly yieldApi: YieldApi.YieldApi;
}) => ({
legacy: bindLegacyApi({ api: legacyApi, runOptions, runtime }),
yield: bindYieldApi({ api: yieldApi, runOptions, runtime }),
legacy: bindLegacyApi({ api: legacyApi, runOptions }),
yield: bindYieldApi({ api: yieldApi, runOptions }),
});

export const createApiClient = ({
apiKey,
baseUrl,
yieldsApiUrl,
}: WidgetApiClientOptions) => {
const runtime = ManagedRuntime.make(FetchHttpClient.layer);
const baseClient = runtime.runSync(HttpClient.HttpClient);

const legacyHttpClient = configureClient({
Expand All @@ -245,13 +214,12 @@ export const createApiClient = ({
});
const legacyApi = LegacyApi.make(legacyHttpClient);
const yieldApi = YieldApi.make(yieldHttpClient);
const boundClients = bindApiClients({ legacyApi, runtime, yieldApi });
const boundClients = bindApiClients({ legacyApi, yieldApi });

return {
...boundClients,
withRunOptions: (runOptions: RunOptions) =>
bindApiClients({ legacyApi, runOptions, runtime, yieldApi }),
dispose: () => runtime.dispose(),
bindApiClients({ legacyApi, runOptions, yieldApi }),
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};

Expand Down
99 changes: 38 additions & 61 deletions packages/widget/tests/providers/api-client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,39 +41,31 @@ describe("API client", () => {
);
const client = createTestClient();

try {
await expect(
client.legacy.TokenControllerGetTokens(undefined)
).resolves.toEqual([]);
await expect(
client.yield.HealthControllerHealth(undefined)
).resolves.toMatchObject({
status: "OK",
});

expect(calls.map((call) => call.url)).toEqual([
"https://api.example.com/v1/tokens",
"https://yield.example.com/health",
]);
expect(
calls.every((call) => call.headers.get("X-API-KEY") === "test-key")
).toBe(true);
} finally {
client.dispose();
}
await expect(
client.legacy.TokenControllerGetTokens(undefined)
).resolves.toEqual([]);
await expect(
client.yield.HealthControllerHealth(undefined)
).resolves.toMatchObject({
status: "OK",
});

expect(calls.map((call) => call.url)).toEqual([
"https://api.example.com/v1/tokens",
"https://yield.example.com/health",
]);
expect(
calls.every((call) => call.headers.get("X-API-KEY") === "test-key")
).toBe(true);
});

it("exposes only the generated operations currently used by the app", () => {
const client = createTestClient();

try {
expect("TokenControllerGetTokens" in client.legacy).toBe(true);
expect("AuthControllerMe" in client.legacy).toBe(false);
expect("YieldsControllerGetAggregateBalances" in client.yield).toBe(true);
expect("ProvidersControllerGetProviders" in client.yield).toBe(false);
} finally {
client.dispose();
}
expect("TokenControllerGetTokens" in client.legacy).toBe(true);
expect("AuthControllerMe" in client.legacy).toBe(false);
expect("YieldsControllerGetAggregateBalances" in client.yield).toBe(true);
expect("ProvidersControllerGetProviders" in client.yield).toBe(false);
});

it("records rich errors for failed StakeKit API responses", async ({
Expand Down Expand Up @@ -103,7 +95,6 @@ describe("API client", () => {
.poll(() => richError.result.current.error?.message)
.toBe("Rich failure");
} finally {
client.dispose();
richError.unmount();
}
});
Expand Down Expand Up @@ -142,7 +133,6 @@ describe("API client", () => {
const value = geoBlock.result.current;
expect(value === false ? [] : [...value.tags]).toEqual(["staking"]);
} finally {
client.dispose();
geoBlock.unmount();
}
});
Expand All @@ -163,14 +153,10 @@ describe("API client", () => {
);
const client = createTestClient();

try {
await expect(
client.legacy.TokenControllerGetTokens(undefined)
).resolves.toEqual([]);
expect(attempts).toBe(3);
} finally {
client.dispose();
}
await expect(
client.legacy.TokenControllerGetTokens(undefined)
).resolves.toEqual([]);
expect(attempts).toBe(3);
});

it("does not retry non-transient response statuses", async ({ worker }) => {
Expand All @@ -187,18 +173,14 @@ describe("API client", () => {
);
const client = createTestClient();

try {
await expect(
client.legacy.TokenControllerGetTokens(undefined)
).rejects.toMatchObject({
_tag: "TokenControllerGetTokens400",
cause: { code: 400, message: "bad request" },
response: { status: 400 },
});
expect(attempts).toBe(1);
} finally {
client.dispose();
}
await expect(
client.legacy.TokenControllerGetTokens(undefined)
).rejects.toMatchObject({
_tag: "TokenControllerGetTokens400",
cause: { code: 400, message: "bad request" },
response: { status: 400 },
});
expect(attempts).toBe(1);
});

it("does not retry aborted requests", async ({ worker }) => {
Expand All @@ -218,16 +200,12 @@ describe("API client", () => {
);
const client = createTestClient();

try {
await expect(
client
.withRunOptions({ signal: controller.signal })
.legacy.TokenControllerGetTokens(undefined)
).rejects.toBeTruthy();
expect(attempts).toBeLessThanOrEqual(1);
} finally {
client.dispose();
}
await expect(
client
.withRunOptions({ signal: controller.signal })
.legacy.TokenControllerGetTokens(undefined)
).rejects.toBeTruthy();
expect(attempts).toBeLessThanOrEqual(1);
});

it("waits for delayed API requests before resolving successful responses", async ({
Expand Down Expand Up @@ -261,7 +239,6 @@ describe("API client", () => {

expect(resolved).toBe(true);
} finally {
client.dispose();
releaseDelay();
env.isTestMode = originalIsTestMode;
}
Expand Down
Loading