From 5a09e133a436d7a9d6a76c7bac00c6f3fe49999c Mon Sep 17 00:00:00 2001 From: Lukasz Ostrowski Date: Fri, 1 Dec 2023 12:40:11 +0100 Subject: [PATCH 1/2] add cloud apl cache --- .changeset/four-carpets-watch.md | 5 +++ .../saleor-cloud/saleor-cloud-apl-errors.ts | 8 +++++ src/APL/saleor-cloud/saleor-cloud-apl.test.ts | 31 +++++++++++++++++++ src/APL/saleor-cloud/saleor-cloud-apl.ts | 26 ++++++++++------ 4 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 .changeset/four-carpets-watch.md diff --git a/.changeset/four-carpets-watch.md b/.changeset/four-carpets-watch.md new file mode 100644 index 00000000..487fccd0 --- /dev/null +++ b/.changeset/four-carpets-watch.md @@ -0,0 +1,5 @@ +--- +"@saleor/app-sdk": minor +--- + +Saleor Cloud APL will now use built-in cache by default. diff --git a/src/APL/saleor-cloud/saleor-cloud-apl-errors.ts b/src/APL/saleor-cloud/saleor-cloud-apl-errors.ts index e26e0b7e..6bf25ac8 100644 --- a/src/APL/saleor-cloud/saleor-cloud-apl-errors.ts +++ b/src/APL/saleor-cloud/saleor-cloud-apl-errors.ts @@ -4,3 +4,11 @@ export class SaleorCloudAplError extends Error { this.name = "SaleorCloudAplError"; } } + +export const CloudAplError = { + FAILED_TO_REACH_API: "FAILED_TO_REACH_API", + RESPONSE_BODY_INVALID: "RESPONSE_BODY_INVALID", + RESPONSE_NON_200: "RESPONSE_NON_200", + ERROR_SAVING_DATA: "ERROR_SAVING_DATA", + ERROR_DELETING_DATA: "ERROR_DELETING_DATA", +}; diff --git a/src/APL/saleor-cloud/saleor-cloud-apl.test.ts b/src/APL/saleor-cloud/saleor-cloud-apl.test.ts index d97f5d09..198ea2e3 100644 --- a/src/APL/saleor-cloud/saleor-cloud-apl.test.ts +++ b/src/APL/saleor-cloud/saleor-cloud-apl.test.ts @@ -161,6 +161,37 @@ describe("APL", () => { expect(await apl.get("http://unknown-domain.example.com/graphql/")).toBe(undefined); }); + + it("Uses cache when GET call is called 2nd time", async () => { + fetchMock.mockResolvedValue({ + status: 200, + ok: true, + json: async () => ({ + saleor_app_id: stubAuthData.appId, + saleor_api_url: stubAuthData.saleorApiUrl, + jwks: stubAuthData.jwks, + domain: stubAuthData.domain, + token: stubAuthData.token, + }), + }); + + const apl = new SaleorCloudAPL(aplConfig); + + expect(await apl.get(stubAuthData.saleorApiUrl)).toStrictEqual(stubAuthData); + expect(await apl.get(stubAuthData.saleorApiUrl)).toStrictEqual(stubAuthData); + + expect(fetchMock).toBeCalledTimes(1); + expect(fetchMock).toBeCalledWith( + "https://example.com/aHR0cHM6Ly9leGFtcGxlLmNvbS9ncmFwaHFsLw", // base64 encoded api url + { + headers: { + "Content-Type": "application/json", + Authorization: "Bearer token", + }, + method: "GET", + } + ); + }); }); }); diff --git a/src/APL/saleor-cloud/saleor-cloud-apl.ts b/src/APL/saleor-cloud/saleor-cloud-apl.ts index efecdcde..45894a07 100644 --- a/src/APL/saleor-cloud/saleor-cloud-apl.ts +++ b/src/APL/saleor-cloud/saleor-cloud-apl.ts @@ -6,13 +6,14 @@ import { getOtelTracer, OTEL_APL_SERVICE_NAME } from "../../open-telemetry"; import { APL, AplConfiguredResult, AplReadyResult, AuthData } from "../apl"; import { createAPLDebug } from "../apl-debug"; import { authDataFromObject } from "../auth-data-from-object"; -import { SaleorCloudAplError } from "./saleor-cloud-apl-errors"; +import { CloudAplError, SaleorCloudAplError } from "./saleor-cloud-apl-errors"; const debug = createAPLDebug("SaleorCloudAPL"); export type SaleorCloudAPLConfig = { resourceUrl: string; token: string; + cacheManager?: Map; }; type CloudAPLAuthDataShape = { @@ -28,14 +29,6 @@ export type GetAllAplResponseShape = { results: CloudAPLAuthDataShape[]; }; -export const CloudAplError = { - FAILED_TO_REACH_API: "FAILED_TO_REACH_API", - RESPONSE_BODY_INVALID: "RESPONSE_BODY_INVALID", - RESPONSE_NON_200: "RESPONSE_NON_200", - ERROR_SAVING_DATA: "ERROR_SAVING_DATA", - ERROR_DELETING_DATA: "ERROR_DELETING_DATA", -}; - const validateResponseStatus = (response: Response) => { if (!response.ok) { debug("Response failed with status %s", response.status); @@ -92,6 +85,8 @@ export class SaleorCloudAPL implements APL { private tracer: Tracer; + private cacheManager: Map; + constructor(config: SaleorCloudAPLConfig) { this.resourceUrl = config.resourceUrl; this.headers = { @@ -99,6 +94,7 @@ export class SaleorCloudAPL implements APL { }; this.tracer = getOtelTracer(); + this.cacheManager = config.cacheManager ?? new Map(); } private getUrlForDomain(saleorApiUrl: string) { @@ -107,6 +103,12 @@ export class SaleorCloudAPL implements APL { } async get(saleorApiUrl: string): Promise { + const cachedData = this.cacheManager.get(saleorApiUrl); + + if (cachedData) { + return cachedData; + } + debug("Will fetch data from SaleorCloudAPL for saleorApiUrl %s", saleorApiUrl); return this.tracer.startActiveSpan( @@ -224,6 +226,8 @@ export class SaleorCloudAPL implements APL { span.setAttribute("appId", authData.appId); + this.cacheManager.set(saleorApiUrl, authData); + span.end(); return authData; @@ -270,6 +274,8 @@ export class SaleorCloudAPL implements APL { debug("Set command finished successfully for saleorApiUrl: %", authData.saleorApiUrl); + this.cacheManager.set(authData.saleorApiUrl, authData); + span.setStatus({ code: SpanStatusCode.OK, }); @@ -289,6 +295,8 @@ export class SaleorCloudAPL implements APL { headers: { "Content-Type": "application/json", ...this.headers }, }); + this.cacheManager.delete(saleorApiUrl); + debug(`Delete responded with ${response.status} code`); } catch (error) { const errorMessage = extractErrorMessage(error); From 51cb611018091886c5c78600b32213ae87c80724 Mon Sep 17 00:00:00 2001 From: Lukasz Ostrowski Date: Fri, 1 Dec 2023 13:44:07 +0100 Subject: [PATCH 2/2] Update src/APL/saleor-cloud/saleor-cloud-apl.ts Co-authored-by: Jonatan Witoszek --- src/APL/saleor-cloud/saleor-cloud-apl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/APL/saleor-cloud/saleor-cloud-apl.ts b/src/APL/saleor-cloud/saleor-cloud-apl.ts index 45894a07..8330be81 100644 --- a/src/APL/saleor-cloud/saleor-cloud-apl.ts +++ b/src/APL/saleor-cloud/saleor-cloud-apl.ts @@ -106,6 +106,7 @@ export class SaleorCloudAPL implements APL { const cachedData = this.cacheManager.get(saleorApiUrl); if (cachedData) { + debug("Returning authData from cache for saleorApiUrl %s", saleorApiUrl) return cachedData; }