Skip to content

Commit 77a0807

Browse files
committed
feat: implement version compatibility checks for plugins and add version checker utility similar to react and web js sdks
1 parent 8aeedc6 commit 77a0807

File tree

7 files changed

+256
-21
lines changed

7 files changed

+256
-21
lines changed

lib/build/plugins.js

Lines changed: 12 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/recipe/webauthn/api/implementation.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/versionChecker.d.ts

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/versionChecker.js

Lines changed: 109 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/ts/plugins.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { PluginRouteHandler, AllRecipeConfigs, TypeInput, NormalisedAppinfo } fr
44
import { version } from "./version";
55
import { PostSuperTokensInitCallbacks } from "./postSuperTokensInitCallbacks";
66
import { getPublicConfig } from "./utils";
7+
import { isVersionCompatible } from "./versionChecker";
78

89
export function getPublicPlugin(plugin: SuperTokensPlugin): SuperTokensPublicPlugin {
910
return {
@@ -149,16 +150,17 @@ export function loadPlugins({
149150
continue;
150151
}
151152

152-
const versionContraints = Array.isArray(plugin.compatibleSDKVersions)
153-
? plugin.compatibleSDKVersions
154-
: [plugin.compatibleSDKVersions];
155-
if (!versionContraints.includes(version)) {
156-
// TODO: better checks
157-
throw new Error(
158-
`Plugin version mismatch. Version ${version} not found in compatible versions: ${versionContraints.join(
159-
", "
160-
)}`
161-
);
153+
if (plugin.compatibleSDKVersions) {
154+
const versionCheck = isVersionCompatible(version, plugin.compatibleSDKVersions);
155+
if (!versionCheck) {
156+
throw new Error(
157+
`Incompatible SDK version for plugin ${
158+
plugin.id
159+
}. Version "${version}" not found in compatible versions: ${JSON.stringify(
160+
plugin.compatibleSDKVersions
161+
)}`
162+
);
163+
}
162164
}
163165

164166
const dependencies = getPluginDependencies({

lib/ts/recipe/webauthn/api/implementation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,7 @@ export default function getAPIImplementation(): APIInterface {
11001100
webauthnGeneratedOptionsId,
11011101
credential,
11021102
userContext,
1103-
recipeUserId: session.getRecipeUserId().getAsString(),
1103+
recipeUserId: loginMethod.recipeUserId.getAsString(),
11041104
});
11051105

11061106
if (registerCredentialResponse.status !== "OK") {

lib/ts/versionChecker.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
const parseVersion = (version: string): { major: number; minor: number; patch: number; prerelease?: string } => {
2+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
3+
if (!match) {
4+
throw new Error(`Invalid version format: ${version}`);
5+
}
6+
7+
return {
8+
major: parseInt(match[1]),
9+
minor: parseInt(match[2]),
10+
patch: parseInt(match[3]),
11+
prerelease: match[4],
12+
};
13+
};
14+
15+
const compareVersions = (a: ReturnType<typeof parseVersion>, b: ReturnType<typeof parseVersion>): number => {
16+
if (a.major !== b.major) {
17+
return a.major - b.major;
18+
}
19+
if (a.minor !== b.minor) {
20+
return a.minor - b.minor;
21+
}
22+
if (a.patch !== b.patch) {
23+
return a.patch - b.patch;
24+
}
25+
26+
// canary versions
27+
if (a.prerelease && !b.prerelease) {
28+
return -1;
29+
}
30+
if (!a.prerelease && b.prerelease) {
31+
return 1;
32+
}
33+
if (a.prerelease && b.prerelease) {
34+
return a.prerelease.localeCompare(b.prerelease);
35+
}
36+
37+
return 0;
38+
};
39+
40+
// checks if a version satisfies a range constraint. supports: ">=0.49.0", "0.49.x", "~0.49.0", "^0.49.0", "0.49.1"
41+
const satisfiesRange = (version: string, range: string): boolean => {
42+
const parsedVersion = parseVersion(version);
43+
44+
if (range === version) {
45+
return true;
46+
}
47+
48+
const rangeMatch = range.match(/^([<>=~^]+)\s*(.+)$/);
49+
if (rangeMatch) {
50+
const operator = rangeMatch[1];
51+
const rangeVersion = rangeMatch[2];
52+
const parsedRangeVersion = parseVersion(rangeVersion);
53+
const comparison = compareVersions(parsedVersion, parsedRangeVersion);
54+
55+
switch (operator) {
56+
case ">=":
57+
return comparison >= 0;
58+
case ">":
59+
return comparison > 0;
60+
case "<=":
61+
return comparison <= 0;
62+
case "<":
63+
return comparison < 0;
64+
case "=":
65+
case "==":
66+
return comparison === 0;
67+
case "~":
68+
return (
69+
parsedVersion.major === parsedRangeVersion.major &&
70+
parsedVersion.minor === parsedRangeVersion.minor &&
71+
parsedVersion.patch >= parsedRangeVersion.patch
72+
);
73+
case "^":
74+
if (parsedRangeVersion.major === 0) {
75+
return (
76+
parsedVersion.major === 0 &&
77+
parsedVersion.minor === parsedRangeVersion.minor &&
78+
parsedVersion.patch >= parsedRangeVersion.patch
79+
);
80+
} else {
81+
return (
82+
parsedVersion.major === parsedRangeVersion.major &&
83+
parsedVersion.minor >= parsedRangeVersion.minor
84+
);
85+
}
86+
default:
87+
return false;
88+
}
89+
}
90+
91+
// x-ranges like "0.49.x"
92+
const xRangeMatch = range.match(/^(\d+)\.(\d+)\.x$/);
93+
if (xRangeMatch) {
94+
return (
95+
parsedVersion.major === parseInt(xRangeMatch[1], 10) && parsedVersion.minor === parseInt(xRangeMatch[2], 10)
96+
);
97+
}
98+
99+
// wildcard ranges like "0.x"
100+
const wildcardMatch = range.match(/^(\d+)\.x$/);
101+
if (wildcardMatch) {
102+
return parsedVersion.major === parseInt(wildcardMatch[1], 10);
103+
}
104+
105+
const exactRangeVersion = parseVersion(range);
106+
return compareVersions(parsedVersion, exactRangeVersion) === 0;
107+
};
108+
109+
export const isVersionCompatible = (currentVersion: string, constraints: string | string[]): boolean => {
110+
const constraintArray = Array.isArray(constraints) ? constraints : [constraints];
111+
112+
for (const constraint of constraintArray) {
113+
if (satisfiesRange(currentVersion, constraint)) {
114+
return true;
115+
}
116+
}
117+
118+
return false;
119+
};

0 commit comments

Comments
 (0)