Skip to content

Commit 60d4870

Browse files
committed
Add mf version check in marketplace
1 parent b3db7c9 commit 60d4870

File tree

8 files changed

+260
-40
lines changed

8 files changed

+260
-40
lines changed

npm-packages/shared-utils/src/types/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export enum ExtensionTypeEnum {
116116
export type ExtensionConfig = {
117117
id: string;
118118
version: string;
119+
mfVersion: string;
119120
author?: string;
120121
displayName?: string;
121122
description?: string;

web/components/extension/preview.tsx

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import {
22
Button,
33
Chip,
4+
Popover,
5+
PopoverContent,
6+
PopoverTrigger,
47
Skeleton,
58
tv,
69
useCheckbox,
@@ -10,11 +13,12 @@ import Icon from "../misc/icon";
1013
import { ContextMenuState, Extension, PlatformEnum } from "@/lib/types";
1114
import useExtensionManager from "@/lib/hooks/use-extension-manager";
1215
import toast from "react-hot-toast";
13-
import { useContext, useEffect, useState } from "react";
16+
import { use, useContext, useEffect, useState } from "react";
1417
import ContextMenu from "../interface/context-menu";
1518
import { EditorContext } from "../providers/editor-context-provider";
1619
import { getPlatform } from "@/lib/platform-api/platform-checker";
1720
import { getRemoteClientBaseURL } from "@/lib/module-federation/remote";
21+
import { getHostMFVersion } from "@/lib/module-federation/version";
1822

1923
export default function ExtensionPreview({
2024
extension,
@@ -43,6 +47,15 @@ export default function ExtensionPreview({
4347

4448
const [isShowInfo, setIsShowInfo] = useState(false);
4549

50+
const [hostMFVersion, setHostMFVersion] = useState<string>("unknown");
51+
const [showMFVersionInfo, setShowMFVersionInfo] = useState(false);
52+
53+
useEffect(() => {
54+
getHostMFVersion().then((version) => {
55+
setHostMFVersion(version);
56+
});
57+
}, []);
58+
4659
useEffect(() => {
4760
setIsLoaded(true);
4861

@@ -99,8 +112,110 @@ export default function ExtensionPreview({
99112
{isInstalled && (
100113
<EnableCheckBox isActive={isEnabled} onPress={toggleExtension} />
101114
)}
115+
116+
{extension.config.mfVersion === "unknown" ? (
117+
<Popover
118+
isOpen={showMFVersionInfo}
119+
onOpenChange={setShowMFVersionInfo}
120+
>
121+
<PopoverTrigger>
122+
<Button
123+
isIconOnly
124+
variant="light"
125+
radius="full"
126+
size="sm"
127+
onMouseEnter={(e) => {
128+
e.stopPropagation();
129+
setShowMFVersionInfo(true);
130+
}}
131+
onMouseLeave={(e) => {
132+
e.stopPropagation();
133+
setShowMFVersionInfo(false);
134+
}}
135+
>
136+
<Icon name="warning" className="text-danger!" />
137+
</Button>
138+
</PopoverTrigger>
139+
<PopoverContent>
140+
<div className="max-w-xs">
141+
<p>
142+
This app is outdated and no longer a valid module
143+
federation app. Please update the app to the latest
144+
version.
145+
</p>
146+
</div>
147+
</PopoverContent>
148+
</Popover>
149+
) : extension.config.mfVersion !== hostMFVersion ? (
150+
<Popover
151+
isOpen={showMFVersionInfo}
152+
onOpenChange={setShowMFVersionInfo}
153+
>
154+
<PopoverTrigger>
155+
<Button
156+
isIconOnly
157+
variant="light"
158+
radius="full"
159+
size="sm"
160+
onMouseEnter={(e) => {
161+
e.stopPropagation();
162+
setShowMFVersionInfo(true);
163+
}}
164+
onMouseLeave={(e) => {
165+
e.stopPropagation();
166+
setShowMFVersionInfo(false);
167+
}}
168+
>
169+
<Icon name="warning" className="text-warning!" />
170+
</Button>
171+
</PopoverTrigger>
172+
<PopoverContent>
173+
<div className="max-w-xs">
174+
<p>
175+
This app's module federation version (
176+
{extension.config.mfVersion}) does not match the host
177+
version ({hostMFVersion}). This may cause issues when
178+
using the app. Please update the app to the latest
179+
version.
180+
</p>
181+
</div>
182+
</PopoverContent>
183+
</Popover>
184+
) : (
185+
<Popover
186+
isOpen={showMFVersionInfo}
187+
onOpenChange={setShowMFVersionInfo}
188+
>
189+
<PopoverTrigger>
190+
<Chip
191+
variant="faded"
192+
color="success"
193+
onMouseEnter={(e) => {
194+
e.stopPropagation();
195+
setShowMFVersionInfo(true);
196+
}}
197+
onMouseLeave={(e) => {
198+
e.stopPropagation();
199+
setShowMFVersionInfo(false);
200+
}}
201+
>
202+
Compatible
203+
</Chip>
204+
</PopoverTrigger>
205+
<PopoverContent>
206+
<div className="max-w-xs">
207+
<p>
208+
This app's module federation version (
209+
{extension.config.mfVersion}) matches the host version (
210+
{hostMFVersion}). The app should work correctly.
211+
</p>
212+
</div>
213+
</PopoverContent>
214+
</Popover>
215+
)}
102216
</div>
103217
</div>
218+
104219
<Button
105220
className="relative m-0 h-full w-full rounded-md p-0"
106221
onPress={() => {

web/components/interface/extension-suggestion-overlay.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default function ExtensionSuggestionOverlay() {
1616
description: "Extension Description",
1717
version: "1.0.0",
1818
visibility: "public",
19+
mfVersion: "1.0.0",
1920
},
2021
isEnabled: false,
2122
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
@@ -30,6 +31,7 @@ export default function ExtensionSuggestionOverlay() {
3031
description: "Extension Description",
3132
version: "1.0.0",
3233
visibility: "public",
34+
mfVersion: "1.0.0",
3335
},
3436
isEnabled: false,
3537
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
@@ -44,6 +46,7 @@ export default function ExtensionSuggestionOverlay() {
4446
description: "Extension Description",
4547
version: "1.0.0",
4648
visibility: "public",
49+
mfVersion: "1.0.0",
4750
},
4851
isEnabled: false,
4952
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,

web/components/modals/app-settings-modal.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ import { getAPIKey, setAPIKey } from "@/lib/settings/api-manager-utils";
2626
import { imageGenProviderOptions } from "@/lib/modalities/image-gen/options";
2727
import { videoGenProviderOptions } from "@/lib/modalities/video-gen/options";
2828
import { useAuth } from "@/lib/hooks/use-auth";
29+
import { getRemoteClientBaseURL } from "@/lib/module-federation/remote";
30+
import {
31+
getHostMFVersion,
32+
getRemoteMFVersion,
33+
} from "@/lib/module-federation/version";
2934

3035
export default function AppSettingsModal({
3136
isOpen,
@@ -960,25 +965,40 @@ function DevExtensionSettings({
960965
/>
961966
<div className="flex gap-x-1">
962967
<Button
963-
onPress={() => {
968+
onPress={async () => {
964969
if (
965970
devExtensionRemoteOrigin &&
966971
devExtensionId &&
967972
devExtensionVersion
968973
) {
974+
const remoteMFVersion = await getRemoteMFVersion(
975+
devExtensionRemoteOrigin,
976+
devExtensionId,
977+
devExtensionVersion,
978+
);
979+
980+
const hostMFVersion = await getHostMFVersion();
981+
982+
if (remoteMFVersion !== hostMFVersion) {
983+
toast.error(
984+
`Extension MF version ${remoteMFVersion} is not compatible with host MF version ${hostMFVersion}`,
985+
);
986+
return;
987+
}
988+
969989
const ext: Extension = {
970990
remoteOrigin: devExtensionRemoteOrigin,
971991
config: {
972992
id: devExtensionId,
973993
version: devExtensionVersion,
974994
visibility: "private",
995+
mfVersion: remoteMFVersion,
975996
},
976997
isEnabled: true,
977998
};
978999

979-
installExtension(ext).then(() => {
980-
toast.success("Extension installed");
981-
});
1000+
await installExtension(ext);
1001+
toast.success("Extension installed");
9821002
}
9831003
}}
9841004
>

web/components/modals/extension-marketplace-modal.tsx

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { EditorContext } from "../providers/editor-context-provider";
66
import useSWR from "swr";
77
import ExtensionList from "../extension/extension-list";
88
import { fetchAPI } from "@/lib/pulse-editor-website/backend";
9+
import { getRemoteMFVersion } from "@/lib/module-federation/version";
910

1011
export default function ExtensionMarketplaceModal({
1112
isOpen,
@@ -45,28 +46,45 @@ export default function ExtensionMarketplaceModal({
4546
mutate: mutateMarketplaceExtensions,
4647
} = useSWR<Extension[]>(
4748
isOpen ? `/api/extension/list` : null,
48-
(url: string) =>
49-
fetchAPI(url)
50-
.then((res) => res.json())
51-
.then((body) => {
52-
const fetchedExts: ExtensionMeta[] = body;
53-
const extensions: Extension[] = fetchedExts.map((extMeta) => {
54-
return {
55-
config: {
56-
id: extMeta.name,
57-
version: extMeta.version,
58-
author: extMeta.user ? extMeta.user.name : extMeta.org.name,
59-
description: extMeta.description ?? "No description available",
60-
displayName: extMeta.displayName ?? extMeta.name,
61-
visibility: extMeta.visibility,
62-
thumbnail: extMeta.thumbnail,
63-
},
64-
isEnabled: true,
65-
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
66-
};
67-
});
68-
return extensions;
49+
async (url: string) => {
50+
const res = await fetchAPI(url);
51+
const body = await res.json();
52+
53+
const fetchedExts: ExtensionMeta[] = body;
54+
const extensions: Extension[] = await Promise.all(
55+
fetchedExts.map(async (extMeta) => {
56+
// If backend does not provide mfVersion, try to load it from the manifest
57+
if (!extMeta.mfVersion) {
58+
console.warn(
59+
`Server does not provide mfVersion for extension ${extMeta.name}. Trying to load from manifest...`,
60+
);
61+
}
62+
const mfVersion =
63+
extMeta.mfVersion ??
64+
(await getRemoteMFVersion(
65+
`${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
66+
extMeta.name,
67+
extMeta.version,
68+
));
69+
70+
return {
71+
config: {
72+
id: extMeta.name,
73+
version: extMeta.version,
74+
mfVersion: mfVersion,
75+
author: extMeta.user ? extMeta.user.name : extMeta.org.name,
76+
description: extMeta.description ?? "No description available",
77+
displayName: extMeta.displayName ?? extMeta.name,
78+
visibility: extMeta.visibility,
79+
thumbnail: extMeta.thumbnail,
80+
},
81+
isEnabled: true,
82+
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
83+
};
6984
}),
85+
);
86+
return extensions;
87+
},
7088
);
7189

7290
useEffect(() => {

0 commit comments

Comments
 (0)