Skip to content

Commit b5cd716

Browse files
committed
new schema version format
1 parent 5b0f7a8 commit b5cd716

20 files changed

+59
-49
lines changed

.changeset/fluffy-ties-stop.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@saleor/app-sdk": major
3+
---
4+
5+
`requiredEnvVars` param was removed trom SaleorApp. Field was not used internally. Apps should validate it's envs.

.changeset/smooth-wolves-smell.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@saleor/app-sdk": major
3+
---
4+
5+
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

src/APL/vercel-kv/index.ts

+1-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
11
import { VercelKvApl } from "./vercel-kv-apl";
22

3-
/**
4-
* @deprecated - use VercelKvApl
5-
*/
6-
const _experimental_VercelKvApl = VercelKvApl;
7-
8-
export {
9-
// TODO Remove in next minor
10-
_experimental_VercelKvApl,
11-
VercelKvApl,
12-
};
3+
export { VercelKvApl };

src/handlers/actions/manifest-action-handler.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe("ManifestActionHandler", () => {
3939
expect(manifestFactory).toHaveBeenCalledWith({
4040
appBaseUrl: "http://example.com",
4141
request: {},
42-
schemaVersion: "3.20",
42+
schemaVersion: [3, 20],
4343
});
4444
});
4545

src/handlers/actions/manifest-action-handler.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createDebug } from "@/debug";
2-
import { AppManifest } from "@/types";
2+
import { AppManifest, SaleorSchemaVersion } from "@/types";
3+
import { parseSchemaVersion } from "@/util";
34

45
import {
56
ActionHandlerInterface,
@@ -19,7 +20,7 @@ export type CreateManifestHandlerOptions<T> = {
1920
* so manifest can be generated according to the version. But it may
2021
* be also requested from plain GET from the browser, so it may not be available
2122
*/
22-
schemaVersion?: string;
23+
schemaVersion?: SaleorSchemaVersion;
2324
}): AppManifest | Promise<AppManifest>;
2425
};
2526

@@ -30,6 +31,7 @@ export class ManifestActionHandler<I> implements ActionHandlerInterface {
3031

3132
async handleAction(options: CreateManifestHandlerOptions<I>): Promise<ActionHandlerResult> {
3233
const { schemaVersion } = this.requestProcessor.getSaleorHeaders();
34+
const parsedSchemaVersion = parseSchemaVersion(schemaVersion) ?? undefined;
3335
const baseURL = this.adapter.getBaseUrl();
3436

3537
debug("Received request with schema version \"%s\" and base URL \"%s\"", schemaVersion, baseURL);
@@ -44,7 +46,7 @@ export class ManifestActionHandler<I> implements ActionHandlerInterface {
4446
const manifest = await options.manifestFactory({
4547
appBaseUrl: baseURL,
4648
request: this.adapter.request,
47-
schemaVersion,
49+
schemaVersion: parsedSchemaVersion,
4850
});
4951

5052
debug("Executed manifest file");

src/handlers/platforms/aws-lambda/create-manifest-handler.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe("AWS Lambda createManifestHandler", () => {
4343
expect.objectContaining({
4444
appBaseUrl: expectedBaseUrl,
4545
request: event,
46-
schemaVersion: "3.20",
46+
schemaVersion: [3, 20],
4747
}),
4848
);
4949
expect(response.statusCode).toBe(200);
@@ -98,7 +98,7 @@ describe("AWS Lambda createManifestHandler", () => {
9898
expect.objectContaining({
9999
appBaseUrl: expectedBaseUrl,
100100
request: event,
101-
schemaVersion: "3.20",
101+
schemaVersion: [3, 20],
102102
}),
103103
);
104104
expect(response.statusCode).toBe(200);

src/handlers/platforms/aws-lambda/saleor-webhooks/saleor-sync-webhook.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ describe("AWS Lambda SaleorSyncWebhook", () => {
7474
baseUrl: "example.com",
7575
event: "CHECKOUT_CALCULATE_TAXES",
7676
payload: { data: "test_payload" },
77-
schemaVersion: 3.19,
77+
schemaVersion: [3, 19],
7878
authData: {
7979
token: webhookConfig.apl.mockToken,
8080
jwks: webhookConfig.apl.mockJwks,

src/handlers/platforms/fetch-api/create-manifest-handler.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe("Fetch API createManifestHandler", () => {
3636
expect(mockManifestFactory).toHaveBeenCalledWith({
3737
appBaseUrl: baseUrl,
3838
request,
39-
schemaVersion: "3.20",
39+
schemaVersion: [3, 20],
4040
});
4141
expect(response.status).toBe(200);
4242
await expect(response.json()).resolves.toStrictEqual({

src/handlers/platforms/fetch-api/saleor-webhooks/saleor-async-webhook.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe("Web API SaleorAsyncWebhook", () => {
3333
baseUrl: "example.com",
3434
event: "product_updated",
3535
payload: { data: "test_payload" },
36-
schemaVersion: 3.2,
36+
schemaVersion: [3, 20],
3737
authData: {
3838
saleorApiUrl: mockAPL.workingSaleorApiUrl,
3939
token: mockAPL.mockToken,
@@ -62,7 +62,7 @@ describe("Web API SaleorAsyncWebhook", () => {
6262
authData: expect.objectContaining({
6363
saleorApiUrl: mockAPL.workingSaleorApiUrl,
6464
}),
65-
})
65+
}),
6666
);
6767
});
6868

src/handlers/platforms/fetch-api/saleor-webhooks/saleor-sync-webhook.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe("Web API SaleorSyncWebhook", () => {
3131
baseUrl: "example.com",
3232
event: "checkout_calculate_taxes",
3333
payload: { data: "test_payload" },
34-
schemaVersion: 3.19,
34+
schemaVersion: [3, 19],
3535
authData: {
3636
token: webhookConfiguration.apl.mockToken,
3737
jwks: webhookConfiguration.apl.mockJwks,

src/handlers/platforms/next/create-manifest-handler.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe("Next.js createManifestHandler", () => {
3838
expect(mockManifestFactory).toHaveBeenCalledWith({
3939
appBaseUrl: baseUrl,
4040
request: req,
41-
schemaVersion: "3.20",
41+
schemaVersion: [3, 20],
4242
});
4343

4444
expect(res.statusCode).toBe(200);

src/handlers/platforms/next/saleor-webhooks/saleor-async-webhook.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe("Next.js SaleorAsyncWebhook", () => {
3333
expect(saleorAsyncWebhook.getWebhookManifest(baseUrl)).toEqual(
3434
expect.objectContaining({
3535
targetUrl: `${baseUrl}/${webhookPath}`,
36-
})
36+
}),
3737
);
3838
});
3939

@@ -56,7 +56,7 @@ describe("Next.js SaleorAsyncWebhook", () => {
5656
baseUrl: "example.com",
5757
event: "product_updated",
5858
payload: { data: "test_payload" },
59-
schemaVersion: 3.2,
59+
schemaVersion: [3, 20],
6060
authData: {
6161
saleorApiUrl: mockAPL.workingSaleorApiUrl,
6262
token: mockAPL.mockToken,

src/handlers/platforms/next/saleor-webhooks/saleor-sync-webhook.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe("Next.js SaleorSyncWebhook", () => {
5555
baseUrl: "example.com",
5656
event: "checkout_calculate_taxes",
5757
payload: { data: "test_payload" },
58-
schemaVersion: 3.19,
58+
schemaVersion: [3, 19],
5959
authData: {
6060
token: validSyncWebhookConfiguration.apl.mockToken,
6161
jwks: validSyncWebhookConfiguration.apl.mockJwks,

src/handlers/shared/saleor-webhook-validator.ts

+11-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { APL } from "@/APL";
44
import { createDebug } from "@/debug";
55
import { fetchRemoteJwks } from "@/fetch-remote-jwks";
66
import { getOtelTracer } from "@/open-telemetry";
7+
import { SaleorSchemaVersion } from "@/types";
78
import { parseSchemaVersion } from "@/util";
89
import { verifySignatureWithJwks } from "@/verify-signature";
910

@@ -94,7 +95,7 @@ export class SaleorWebhookValidator {
9495

9596
throw new WebhookError(
9697
`Wrong incoming request event: ${event}. Expected: ${expected}`,
97-
"WRONG_EVENT"
98+
"WRONG_EVENT",
9899
);
99100
}
100101

@@ -121,7 +122,10 @@ export class SaleorWebhookValidator {
121122
throw new WebhookError("Request body can't be parsed", "CANT_BE_PARSED");
122123
}
123124

124-
let parsedSchemaVersion: number | null = null;
125+
/**
126+
* Can be undefined - subscription must contain "version", otherwise nothing to parse
127+
*/
128+
let parsedSchemaVersion: SaleorSchemaVersion | null = null;
125129

126130
try {
127131
parsedSchemaVersion = parseSchemaVersion(parsedBody.version);
@@ -139,7 +143,7 @@ export class SaleorWebhookValidator {
139143

140144
throw new WebhookError(
141145
`Can't find auth data for ${saleorApiUrl}. Please register the application`,
142-
"NOT_REGISTERED"
146+
"NOT_REGISTERED",
143147
);
144148
}
145149

@@ -162,15 +166,15 @@ export class SaleorWebhookValidator {
162166

163167
throw new WebhookError(
164168
"Fetching remote JWKS failed",
165-
"SIGNATURE_VERIFICATION_FAILED"
169+
"SIGNATURE_VERIFICATION_FAILED",
166170
);
167171
});
168172

169173
this.debug("Fetched refreshed JWKS");
170174

171175
try {
172176
this.debug(
173-
"Second attempt to validate the signature JWKS, using fresh tokens from the API"
177+
"Second attempt to validate the signature JWKS, using fresh tokens from the API",
174178
);
175179

176180
await verifySignatureWithJwks(newJwks, signature, rawBody);
@@ -183,7 +187,7 @@ export class SaleorWebhookValidator {
183187

184188
throw new WebhookError(
185189
"Request signature check failed",
186-
"SIGNATURE_VERIFICATION_FAILED"
190+
"SIGNATURE_VERIFICATION_FAILED",
187191
);
188192
}
189193
}
@@ -211,7 +215,7 @@ export class SaleorWebhookValidator {
211215
} finally {
212216
span.end();
213217
}
214-
}
218+
},
215219
);
216220
}
217221
}

src/handlers/shared/saleor-webhook.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { SaleorSchemaVersion } from "@/types";
2+
13
import { AuthData } from "../../APL";
24

35
export const WebhookErrorCodeMap: Record<SaleorWebhookError, number> = {
@@ -50,9 +52,11 @@ export type WebhookContext<TPayload> = {
5052
event: string;
5153
payload: TPayload;
5254
authData: AuthData;
53-
// TODO: Make this required
54-
/** Added in Saleor 3.15 */
55-
schemaVersion: number | null;
55+
/**
56+
* Schema version is passed in subscription payload. Webhook must request it, otherwise it will be null.
57+
* If subscription contains version, it will be parsed to SaleorSchemaVersion
58+
*/
59+
schemaVersion: SaleorSchemaVersion | null;
5660
};
5761

5862
export type FormatWebhookErrorResult = {

src/headers.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ import {
99
const toStringOrUndefined = (value: string | string[] | undefined) =>
1010
value ? value.toString() : undefined;
1111

12-
const toFloatOrNull = (value: string | string[] | undefined) =>
13-
value ? parseFloat(value.toString()) : null;
14-
1512
/**
1613
* Extracts Saleor-specific headers from the response.
1714
*/
@@ -20,7 +17,7 @@ export const getSaleorHeaders = (headers: { [name: string]: string | string[] |
2017
signature: toStringOrUndefined(headers[SALEOR_SIGNATURE_HEADER]),
2118
event: toStringOrUndefined(headers[SALEOR_EVENT_HEADER]),
2219
saleorApiUrl: toStringOrUndefined(headers[SALEOR_API_URL_HEADER]),
23-
schemaVersion: toFloatOrNull(headers[SALEOR_SCHEMA_VERSION]),
20+
schemaVersion: toStringOrUndefined(headers[SALEOR_SCHEMA_VERSION]),
2421
});
2522

2623
/**

src/saleor-app.ts

-4
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,12 @@ export interface HasAPL {
66

77
export interface SaleorAppParams {
88
apl: APL;
9-
requiredEnvVars?: string[];
109
}
1110

1211
export class SaleorApp implements HasAPL {
1312
readonly apl: APL;
1413

15-
readonly requiredEnvVars: string[];
16-
1714
constructor(options: SaleorAppParams) {
1815
this.apl = options.apl;
19-
this.requiredEnvVars = options.requiredEnvVars ?? [];
2016
}
2117
}

src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,5 @@ export interface AppManifest {
320320
};
321321
};
322322
}
323+
324+
export type SaleorSchemaVersion = [major: number, minor: number];

src/util/schema-version.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ describe("parseSchemaVersion", () => {
1010
},
1111
{
1212
rawVersion: "3.19",
13-
parsedVersion: 3.19,
13+
parsedVersion: [3, 19],
1414
},
1515
{
1616
rawVersion: "3.19.1",
17-
parsedVersion: 3.19,
17+
parsedVersion: [3, 19],
1818
},
1919
{
2020
rawVersion: "malformed",
@@ -39,7 +39,7 @@ describe("parseSchemaVersion", () => {
3939
])(
4040
"Parses version string from: $rawVersion to: $parsedVersion",
4141
({ rawVersion, parsedVersion }) => {
42-
expect(parseSchemaVersion(rawVersion)).toBe(parsedVersion);
43-
}
42+
expect(parseSchemaVersion(rawVersion)).toEqual(parsedVersion);
43+
},
4444
);
4545
});

src/util/schema-version.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
export const parseSchemaVersion = (rawVersion: string | undefined | null): number | null => {
1+
import { SaleorSchemaVersion } from "@/types";
2+
3+
export const parseSchemaVersion = (
4+
rawVersion: string | undefined | null,
5+
): SaleorSchemaVersion | null => {
26
if (!rawVersion) {
37
return null;
48
}
@@ -8,7 +12,7 @@ export const parseSchemaVersion = (rawVersion: string | undefined | null): numbe
812
const minor = parseInt(minorString, 10);
913

1014
if (major && minor) {
11-
return parseFloat(`${major}.${minor}`);
15+
return [major, minor];
1216
}
1317

1418
return null;

0 commit comments

Comments
 (0)