diff --git a/.changeset/fluffy-ties-stop.md b/.changeset/fluffy-ties-stop.md new file mode 100644 index 00000000..f831b52f --- /dev/null +++ b/.changeset/fluffy-ties-stop.md @@ -0,0 +1,5 @@ +--- +"@saleor/app-sdk": major +--- + +`requiredEnvVars` param was removed trom SaleorApp. Field was not used internally. Apps should validate it's envs. diff --git a/.changeset/smooth-wolves-smell.md b/.changeset/smooth-wolves-smell.md new file mode 100644 index 00000000..ad40d35a --- /dev/null +++ b/.changeset/smooth-wolves-smell.md @@ -0,0 +1,5 @@ +--- +"@saleor/app-sdk": major +--- + +Saleor version that was previously represented as a floating number (eg Saleor 3.20 was represented as 3.2) is now a `SaleorSchemaVersion` which is a tuple `major: number, minor: number`. This format is now passed to Manifest handler and webhooks handler diff --git a/src/APL/vercel-kv/index.ts b/src/APL/vercel-kv/index.ts index 92dc6ffc..2169af5f 100644 --- a/src/APL/vercel-kv/index.ts +++ b/src/APL/vercel-kv/index.ts @@ -1,12 +1,3 @@ import { VercelKvApl } from "./vercel-kv-apl"; -/** - * @deprecated - use VercelKvApl - */ -const _experimental_VercelKvApl = VercelKvApl; - -export { - // TODO Remove in next minor - _experimental_VercelKvApl, - VercelKvApl, -}; +export { VercelKvApl }; diff --git a/src/handlers/actions/manifest-action-handler.test.ts b/src/handlers/actions/manifest-action-handler.test.ts index cdee68b9..99cfd3f8 100644 --- a/src/handlers/actions/manifest-action-handler.test.ts +++ b/src/handlers/actions/manifest-action-handler.test.ts @@ -39,7 +39,7 @@ describe("ManifestActionHandler", () => { expect(manifestFactory).toHaveBeenCalledWith({ appBaseUrl: "http://example.com", request: {}, - schemaVersion: "3.20", + schemaVersion: [3, 20], }); }); diff --git a/src/handlers/actions/manifest-action-handler.ts b/src/handlers/actions/manifest-action-handler.ts index 1ae409b2..34ad0dd3 100644 --- a/src/handlers/actions/manifest-action-handler.ts +++ b/src/handlers/actions/manifest-action-handler.ts @@ -1,5 +1,6 @@ import { createDebug } from "@/debug"; -import { AppManifest } from "@/types"; +import { AppManifest, SaleorSchemaVersion } from "@/types"; +import { parseSchemaVersion } from "@/util"; import { ActionHandlerInterface, @@ -19,7 +20,7 @@ export type CreateManifestHandlerOptions = { * so manifest can be generated according to the version. But it may * be also requested from plain GET from the browser, so it may not be available */ - schemaVersion?: string; + schemaVersion?: SaleorSchemaVersion; }): AppManifest | Promise; }; @@ -30,6 +31,7 @@ export class ManifestActionHandler implements ActionHandlerInterface { async handleAction(options: CreateManifestHandlerOptions): Promise { const { schemaVersion } = this.requestProcessor.getSaleorHeaders(); + const parsedSchemaVersion = parseSchemaVersion(schemaVersion) ?? undefined; const baseURL = this.adapter.getBaseUrl(); debug("Received request with schema version \"%s\" and base URL \"%s\"", schemaVersion, baseURL); @@ -44,7 +46,7 @@ export class ManifestActionHandler implements ActionHandlerInterface { const manifest = await options.manifestFactory({ appBaseUrl: baseURL, request: this.adapter.request, - schemaVersion, + schemaVersion: parsedSchemaVersion, }); debug("Executed manifest file"); diff --git a/src/handlers/platforms/aws-lambda/create-manifest-handler.test.ts b/src/handlers/platforms/aws-lambda/create-manifest-handler.test.ts index 7f4c2d1a..67ac0b60 100644 --- a/src/handlers/platforms/aws-lambda/create-manifest-handler.test.ts +++ b/src/handlers/platforms/aws-lambda/create-manifest-handler.test.ts @@ -43,7 +43,7 @@ describe("AWS Lambda createManifestHandler", () => { expect.objectContaining({ appBaseUrl: expectedBaseUrl, request: event, - schemaVersion: "3.20", + schemaVersion: [3, 20], }), ); expect(response.statusCode).toBe(200); @@ -98,7 +98,7 @@ describe("AWS Lambda createManifestHandler", () => { expect.objectContaining({ appBaseUrl: expectedBaseUrl, request: event, - schemaVersion: "3.20", + schemaVersion: [3, 20], }), ); expect(response.statusCode).toBe(200); diff --git a/src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts b/src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts index f7fa38dd..4d1eeb8b 100644 --- a/src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts +++ b/src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts @@ -74,7 +74,7 @@ describe("AWS Lambda SaleorSyncWebhook", () => { baseUrl: "example.com", event: "CHECKOUT_CALCULATE_TAXES", payload: { data: "test_payload" }, - schemaVersion: 3.19, + schemaVersion: [3, 19], authData: { token: webhookConfig.apl.mockToken, jwks: webhookConfig.apl.mockJwks, diff --git a/src/handlers/platforms/fetch-api/create-manifest-handler.test.ts b/src/handlers/platforms/fetch-api/create-manifest-handler.test.ts index 3ea4117e..39d18b86 100644 --- a/src/handlers/platforms/fetch-api/create-manifest-handler.test.ts +++ b/src/handlers/platforms/fetch-api/create-manifest-handler.test.ts @@ -36,7 +36,7 @@ describe("Fetch API createManifestHandler", () => { expect(mockManifestFactory).toHaveBeenCalledWith({ appBaseUrl: baseUrl, request, - schemaVersion: "3.20", + schemaVersion: [3, 20], }); expect(response.status).toBe(200); await expect(response.json()).resolves.toStrictEqual({ diff --git a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-async-webhook.test.ts b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-async-webhook.test.ts index ac6c0f6e..10bf85e2 100644 --- a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-async-webhook.test.ts +++ b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-async-webhook.test.ts @@ -33,7 +33,7 @@ describe("Web API SaleorAsyncWebhook", () => { baseUrl: "example.com", event: "product_updated", payload: { data: "test_payload" }, - schemaVersion: 3.2, + schemaVersion: [3, 20], authData: { saleorApiUrl: mockAPL.workingSaleorApiUrl, token: mockAPL.mockToken, @@ -62,7 +62,7 @@ describe("Web API SaleorAsyncWebhook", () => { authData: expect.objectContaining({ saleorApiUrl: mockAPL.workingSaleorApiUrl, }), - }) + }), ); }); diff --git a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts index 9cf9c741..bf0c29ee 100644 --- a/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts +++ b/src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts @@ -31,7 +31,7 @@ describe("Web API SaleorSyncWebhook", () => { baseUrl: "example.com", event: "checkout_calculate_taxes", payload: { data: "test_payload" }, - schemaVersion: 3.19, + schemaVersion: [3, 19], authData: { token: webhookConfiguration.apl.mockToken, jwks: webhookConfiguration.apl.mockJwks, diff --git a/src/handlers/platforms/next/create-manifest-handler.test.ts b/src/handlers/platforms/next/create-manifest-handler.test.ts index 1d494af5..e8928107 100644 --- a/src/handlers/platforms/next/create-manifest-handler.test.ts +++ b/src/handlers/platforms/next/create-manifest-handler.test.ts @@ -38,7 +38,7 @@ describe("Next.js createManifestHandler", () => { expect(mockManifestFactory).toHaveBeenCalledWith({ appBaseUrl: baseUrl, request: req, - schemaVersion: "3.20", + schemaVersion: [3, 20], }); expect(res.statusCode).toBe(200); diff --git a/src/handlers/platforms/next/saleor-webhooks/saleor-async-webhook.test.ts b/src/handlers/platforms/next/saleor-webhooks/saleor-async-webhook.test.ts index 637f3f35..346ebad2 100644 --- a/src/handlers/platforms/next/saleor-webhooks/saleor-async-webhook.test.ts +++ b/src/handlers/platforms/next/saleor-webhooks/saleor-async-webhook.test.ts @@ -33,7 +33,7 @@ describe("Next.js SaleorAsyncWebhook", () => { expect(saleorAsyncWebhook.getWebhookManifest(baseUrl)).toEqual( expect.objectContaining({ targetUrl: `${baseUrl}/${webhookPath}`, - }) + }), ); }); @@ -56,7 +56,7 @@ describe("Next.js SaleorAsyncWebhook", () => { baseUrl: "example.com", event: "product_updated", payload: { data: "test_payload" }, - schemaVersion: 3.2, + schemaVersion: [3, 20], authData: { saleorApiUrl: mockAPL.workingSaleorApiUrl, token: mockAPL.mockToken, diff --git a/src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts b/src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts index 7c75f6fa..5f1ffb14 100644 --- a/src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts +++ b/src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts @@ -55,7 +55,7 @@ describe("Next.js SaleorSyncWebhook", () => { baseUrl: "example.com", event: "checkout_calculate_taxes", payload: { data: "test_payload" }, - schemaVersion: 3.19, + schemaVersion: [3, 19], authData: { token: validSyncWebhookConfiguration.apl.mockToken, jwks: validSyncWebhookConfiguration.apl.mockJwks, diff --git a/src/handlers/shared/saleor-webhook-validator.ts b/src/handlers/shared/saleor-webhook-validator.ts index e223f6e1..58fa722c 100644 --- a/src/handlers/shared/saleor-webhook-validator.ts +++ b/src/handlers/shared/saleor-webhook-validator.ts @@ -4,6 +4,7 @@ import { APL } from "@/APL"; import { createDebug } from "@/debug"; import { fetchRemoteJwks } from "@/fetch-remote-jwks"; import { getOtelTracer } from "@/open-telemetry"; +import { SaleorSchemaVersion } from "@/types"; import { parseSchemaVersion } from "@/util"; import { verifySignatureWithJwks } from "@/verify-signature"; @@ -94,7 +95,7 @@ export class SaleorWebhookValidator { throw new WebhookError( `Wrong incoming request event: ${event}. Expected: ${expected}`, - "WRONG_EVENT" + "WRONG_EVENT", ); } @@ -121,7 +122,10 @@ export class SaleorWebhookValidator { throw new WebhookError("Request body can't be parsed", "CANT_BE_PARSED"); } - let parsedSchemaVersion: number | null = null; + /** + * Can be undefined - subscription must contain "version", otherwise nothing to parse + */ + let parsedSchemaVersion: SaleorSchemaVersion | null = null; try { parsedSchemaVersion = parseSchemaVersion(parsedBody.version); @@ -139,7 +143,7 @@ export class SaleorWebhookValidator { throw new WebhookError( `Can't find auth data for ${saleorApiUrl}. Please register the application`, - "NOT_REGISTERED" + "NOT_REGISTERED", ); } @@ -162,7 +166,7 @@ export class SaleorWebhookValidator { throw new WebhookError( "Fetching remote JWKS failed", - "SIGNATURE_VERIFICATION_FAILED" + "SIGNATURE_VERIFICATION_FAILED", ); }); @@ -170,7 +174,7 @@ export class SaleorWebhookValidator { try { this.debug( - "Second attempt to validate the signature JWKS, using fresh tokens from the API" + "Second attempt to validate the signature JWKS, using fresh tokens from the API", ); await verifySignatureWithJwks(newJwks, signature, rawBody); @@ -183,7 +187,7 @@ export class SaleorWebhookValidator { throw new WebhookError( "Request signature check failed", - "SIGNATURE_VERIFICATION_FAILED" + "SIGNATURE_VERIFICATION_FAILED", ); } } @@ -211,7 +215,7 @@ export class SaleorWebhookValidator { } finally { span.end(); } - } + }, ); } } diff --git a/src/handlers/shared/saleor-webhook.ts b/src/handlers/shared/saleor-webhook.ts index 053de609..3a922cbd 100644 --- a/src/handlers/shared/saleor-webhook.ts +++ b/src/handlers/shared/saleor-webhook.ts @@ -1,3 +1,5 @@ +import { SaleorSchemaVersion } from "@/types"; + import { AuthData } from "../../APL"; export const WebhookErrorCodeMap: Record = { @@ -50,9 +52,11 @@ export type WebhookContext = { event: string; payload: TPayload; authData: AuthData; - // TODO: Make this required - /** Added in Saleor 3.15 */ - schemaVersion: number | null; + /** + * Schema version is passed in subscription payload. Webhook must request it, otherwise it will be null. + * If subscription contains version, it will be parsed to SaleorSchemaVersion + */ + schemaVersion: SaleorSchemaVersion | null; }; export type FormatWebhookErrorResult = { diff --git a/src/headers.ts b/src/headers.ts index b33ab93d..5570fdde 100644 --- a/src/headers.ts +++ b/src/headers.ts @@ -9,9 +9,6 @@ import { const toStringOrUndefined = (value: string | string[] | undefined) => value ? value.toString() : undefined; -const toFloatOrNull = (value: string | string[] | undefined) => - value ? parseFloat(value.toString()) : null; - /** * Extracts Saleor-specific headers from the response. */ @@ -20,7 +17,7 @@ export const getSaleorHeaders = (headers: { [name: string]: string | string[] | signature: toStringOrUndefined(headers[SALEOR_SIGNATURE_HEADER]), event: toStringOrUndefined(headers[SALEOR_EVENT_HEADER]), saleorApiUrl: toStringOrUndefined(headers[SALEOR_API_URL_HEADER]), - schemaVersion: toFloatOrNull(headers[SALEOR_SCHEMA_VERSION]), + schemaVersion: toStringOrUndefined(headers[SALEOR_SCHEMA_VERSION]), }); /** diff --git a/src/saleor-app.ts b/src/saleor-app.ts index 15b87b55..a8dde398 100644 --- a/src/saleor-app.ts +++ b/src/saleor-app.ts @@ -6,16 +6,12 @@ export interface HasAPL { export interface SaleorAppParams { apl: APL; - requiredEnvVars?: string[]; } export class SaleorApp implements HasAPL { readonly apl: APL; - readonly requiredEnvVars: string[]; - constructor(options: SaleorAppParams) { this.apl = options.apl; - this.requiredEnvVars = options.requiredEnvVars ?? []; } } diff --git a/src/types.ts b/src/types.ts index 3e52e0d4..3da6923a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -320,3 +320,5 @@ export interface AppManifest { }; }; } + +export type SaleorSchemaVersion = [major: number, minor: number]; diff --git a/src/util/schema-version.test.ts b/src/util/schema-version.test.ts index b850efaf..f27755d3 100644 --- a/src/util/schema-version.test.ts +++ b/src/util/schema-version.test.ts @@ -10,11 +10,11 @@ describe("parseSchemaVersion", () => { }, { rawVersion: "3.19", - parsedVersion: 3.19, + parsedVersion: [3, 19], }, { rawVersion: "3.19.1", - parsedVersion: 3.19, + parsedVersion: [3, 19], }, { rawVersion: "malformed", @@ -39,7 +39,7 @@ describe("parseSchemaVersion", () => { ])( "Parses version string from: $rawVersion to: $parsedVersion", ({ rawVersion, parsedVersion }) => { - expect(parseSchemaVersion(rawVersion)).toBe(parsedVersion); - } + expect(parseSchemaVersion(rawVersion)).toEqual(parsedVersion); + }, ); }); diff --git a/src/util/schema-version.ts b/src/util/schema-version.ts index d1201c30..6eeb3730 100644 --- a/src/util/schema-version.ts +++ b/src/util/schema-version.ts @@ -1,4 +1,8 @@ -export const parseSchemaVersion = (rawVersion: string | undefined | null): number | null => { +import { SaleorSchemaVersion } from "@/types"; + +export const parseSchemaVersion = ( + rawVersion: string | undefined | null, +): SaleorSchemaVersion | null => { if (!rawVersion) { return null; } @@ -8,7 +12,7 @@ export const parseSchemaVersion = (rawVersion: string | undefined | null): numbe const minor = parseInt(minorString, 10); if (major && minor) { - return parseFloat(`${major}.${minor}`); + return [major, minor]; } return null;