this repo has no description

Merge pull request #101 from moonlight-mod/kasimir/ext-compat

Allow extensions to specify their platform compatability

Changed files
+136 -32
packages
core
src
extension
core-extensions
src
moonbase
webpackModules
types
+3
packages/core-extensions/src/moonbase/types.ts
···
+
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
import { DetectedExtension, ExtensionManifest } from "@moonlight-mod/types";
export type MoonbaseNatives = {
···
manifest: ExtensionManifest | RepositoryManifest;
source: DetectedExtension["source"];
state: ExtensionState;
+
compat: ExtensionCompat;
+
hasUpdate: boolean;
};
+37 -9
packages/core-extensions/src/moonbase/webpackModules/stores.ts
···
-
import { Config, constants, ExtensionLoadSource } from "@moonlight-mod/types";
-
import { ExtensionState, MoonbaseExtension, MoonbaseNatives } from "../types";
+
import { Config, ExtensionLoadSource } from "@moonlight-mod/types";
+
import {
+
ExtensionState,
+
MoonbaseExtension,
+
MoonbaseNatives,
+
RepositoryManifest
+
} from "../types";
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
import getNatives from "../native";
import { mainRepo } from "@moonlight-mod/types/constants";
+
import {
+
checkExtensionCompat,
+
ExtensionCompat
+
} from "@moonlight-mod/core/extension/loader";
const logger = moonlight.getLogger("moonbase");
···
shouldShowNotice: boolean;
extensions: { [id: number]: MoonbaseExtension };
-
updates: { [id: number]: { version: string; download: string } };
+
updates: {
+
[id: number]: {
+
version: string;
+
download: string;
+
updateManifest: RepositoryManifest;
+
};
+
};
constructor() {
super(Dispatcher);
···
uniqueId,
state: moonlight.enabledExtensions.has(ext.id)
? ExtensionState.Enabled
-
: ExtensionState.Disabled
+
: ExtensionState.Disabled,
+
compat: checkExtensionCompat(ext.manifest),
+
hasUpdate: false
};
}
···
uniqueId,
manifest: ext,
source: { type: ExtensionLoadSource.Normal, url: repo },
-
state: ExtensionState.NotDownloaded
+
state: ExtensionState.NotDownloaded,
+
compat: ExtensionCompat.Compatible,
+
hasUpdate: false
};
-
const apiLevel = ext.apiLevel ?? 1;
-
if (apiLevel !== constants.apiLevel) continue;
+
// Don't present incompatible updates
+
if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible)
+
continue;
const existing = this.getExisting(extensionData);
if (existing != null) {
···
if (this.hasUpdate(extensionData)) {
this.updates[existing.uniqueId] = {
version: ext.version!,
-
download: ext.download
+
download: ext.download,
+
updateManifest: ext
};
+
existing.hasUpdate = true;
}
continue;
···
this.installing = true;
try {
-
const url = this.updates[uniqueId]?.download ?? ext.manifest.download;
+
const update = this.updates[uniqueId];
+
const url = update?.download ?? ext.manifest.download;
await natives!.installExtension(ext.manifest, url, ext.source.url!);
if (ext.state === ExtensionState.NotDownloaded) {
this.extensions[uniqueId].state = ExtensionState.Disabled;
}
+
+
if (update != null)
+
this.extensions[uniqueId].compat = checkExtensionCompat(
+
update.updateManifest
+
);
delete this.updates[uniqueId];
} catch (e) {
+42 -18
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
···
import { ExtensionState } from "../../../types";
import { ExtensionLoadSource } from "@moonlight-mod/types";
+
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
···
const BuildOverrideClasses = spacepack.findByExports(
"disabledButtonOverride"
)[0].exports;
+
+
const COMPAT_TEXT_MAP: Record<ExtensionCompat, string> = {
+
[ExtensionCompat.Compatible]: "huh?",
+
[ExtensionCompat.InvalidApiLevel]: "Incompatible API level",
+
[ExtensionCompat.InvalidEnvironment]: "Incompatible platform"
+
};
export default function ExtensionCard({ uniqueId }: { uniqueId: number }) {
const [tab, setTab] = React.useState(ExtensionPage.Info);
···
justify={Flex.Justify.END}
>
{ext.state === ExtensionState.NotDownloaded ? (
-
<Button
-
color={Button.Colors.BRAND}
-
submitting={busy}
-
disabled={conflicting}
-
onClick={async () => {
-
await installWithDependencyPopup(uniqueId);
-
}}
+
<Tooltip
+
text={COMPAT_TEXT_MAP[ext.compat]}
+
shouldShow={ext.compat !== ExtensionCompat.Compatible}
>
-
Install
-
</Button>
+
{(props: any) => (
+
<Button
+
{...props}
+
color={Button.Colors.BRAND}
+
submitting={busy}
+
disabled={
+
ext.compat !== ExtensionCompat.Compatible || conflicting
+
}
+
onClick={async () => {
+
await installWithDependencyPopup(uniqueId);
+
}}
+
>
+
Install
+
</Button>
+
)}
+
</Tooltip>
) : (
<div
// too lazy to learn how <Flex /> works lmao
···
)}
<FormSwitch
-
value={enabled || implicitlyEnabled}
-
disabled={implicitlyEnabled}
+
value={
+
ext.compat === ExtensionCompat.Compatible &&
+
(enabled || implicitlyEnabled)
+
}
+
disabled={
+
implicitlyEnabled || ext.compat !== ExtensionCompat.Compatible
+
}
hideBorder={true}
style={{ marginBottom: "0px" }}
tooltipNote={
-
implicitlyEnabled
-
? `This extension is a dependency of the following enabled extension${
-
enabledDependants.length > 1 ? "s" : ""
-
}: ${enabledDependants
-
.map((a) => a.manifest.meta?.name ?? a.id)
-
.join(", ")}`
-
: undefined
+
ext.compat !== ExtensionCompat.Compatible
+
? COMPAT_TEXT_MAP[ext.compat]
+
: implicitlyEnabled
+
? `This extension is a dependency of the following enabled extension${
+
enabledDependants.length > 1 ? "s" : ""
+
}: ${enabledDependants
+
.map((a) => a.manifest.meta?.name ?? a.id)
+
.join(", ")}`
+
: undefined
}
onChange={() => {
setRestartNeeded(true);
+9 -2
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
···
Enabled = 1 << 3,
Disabled = 1 << 4,
Installed = 1 << 5,
-
Repository = 1 << 6
+
Repository = 1 << 6,
+
Incompatible = 1 << 7
}
-
export const defaultFilter = ~(~0 << 7);
+
export const defaultFilter = 127 as Filter;
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0]
···
/>
</MenuGroup>
<MenuGroup>
+
<MenuCheckboxItem
+
id="l-incompatible"
+
label="Show incompatible"
+
checked={filter & Filter.Incompatible}
+
action={() => toggleFilter(Filter.Incompatible)}
+
/>
<MenuItem
id="reset-all"
className={SortMenuClasses.clearText}
+5 -1
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
···
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import { ExtensionCompat } from "core/src/extension/loader";
const SearchBar: any = Object.values(
spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0].exports
···
ext.state !== ExtensionState.NotDownloaded) ||
(!(filter & Filter.Repository) &&
ext.state === ExtensionState.NotDownloaded)
-
)
+
) &&
+
(filter & Filter.Incompatible ||
+
ext.compat === ExtensionCompat.Compatible ||
+
(ext.compat === ExtensionCompat.InvalidApiLevel && ext.hasUpdate))
);
return (
+33 -2
packages/core/src/extension/loader.ts
···
DetectedExtension,
ProcessedExtensions,
WebpackModuleFunc,
-
constants
+
constants,
+
ExtensionManifest,
+
ExtensionEnvironment
} from "@moonlight-mod/types";
import { readConfig } from "../config";
import Logger from "../util/logger";
···
}
}
+
export enum ExtensionCompat {
+
Compatible,
+
InvalidApiLevel,
+
InvalidEnvironment
+
}
+
+
export function checkExtensionCompat(
+
manifest: ExtensionManifest
+
): ExtensionCompat {
+
let environment;
+
webTarget: {
+
environment = ExtensionEnvironment.Web;
+
}
+
nodeTarget: {
+
environment = ExtensionEnvironment.Desktop;
+
}
+
+
if (manifest.apiLevel !== constants.apiLevel)
+
return ExtensionCompat.InvalidApiLevel;
+
if (
+
(manifest.environment ?? "both") !== "both" &&
+
manifest.environment !== environment
+
)
+
return ExtensionCompat.InvalidEnvironment;
+
return ExtensionCompat.Compatible;
+
}
+
/*
This function resolves extensions and loads them, split into a few stages:
···
export async function loadExtensions(
exts: DetectedExtension[]
): Promise<ProcessedExtensions> {
-
exts = exts.filter((x) => x.manifest.apiLevel === constants.apiLevel);
+
exts = exts.filter(
+
(ext) => checkExtensionCompat(ext.manifest) === ExtensionCompat.Compatible
+
);
const config = await readConfig();
const items = exts
+7
packages/types/src/extension.ts
···
id: string;
version?: string;
apiLevel?: number;
+
environment?: ExtensionEnvironment;
meta?: {
name?: string;
···
cors?: string[];
blocked?: string[];
};
+
+
export enum ExtensionEnvironment {
+
Both = "both",
+
Desktop = "desktop",
+
Web = "web"
+
}
export enum ExtensionLoadSource {
Developer,