mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: list plugin engine ranges for the mobile app
This commit is contained in:
1 parent
1be507b13c
commit
90f5aebafb
4 files changed
+48
-1
No files matched your search
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
- The mobile app can sign in through your browser with any login method, including passkeys and security keys
|
- The mobile app can sign in through your browser with any login method, including passkeys and security keys
|
||||||
- Plugins can say how they show in the mobile app
|
- Plugins can say how they show in the mobile app
|
||||||
|
- The plugin list tells the mobile app which plugin API each plugin needs, so the app can say when it or a plugin needs an update
|
||||||
|
|
||||||
## 26.10.5
|
## 26.10.5
|
||||||
|
|
||||||
|
|||||||
@@ -210,6 +210,11 @@ How a `webview` plugin runs in the app:
|
|||||||
- Nothing in `localStorage` survives closing the view. Keep preferences in settings.
|
- Nothing in `localStorage` survives closing the view. Keep preferences in settings.
|
||||||
- Other plugins' views, extensions and components are not there, and neither is the shell (`useShell()`
|
- Other plugins' views, extensions and components are not there, and neither is the shell (`useShell()`
|
||||||
is null). `/ui` names newer than the app's copy of the SDK make the view say it needs a newer app.
|
is null). `/ui` names newer than the app's copy of the SDK make the view say it needs a newer app.
|
||||||
|
- The app checks your `engine.api` against the plugin API of its own copy of the SDK. When the app's API
|
||||||
|
is too old for the range, it asks the user to update the app instead of opening the view. When the range
|
||||||
|
is for an older API major, it asks to update the plugin. Ask only for the API you use.
|
||||||
|
- Turning a plugin off on the server hides it in the app the next time the app checks: when it comes back
|
||||||
|
to the front, when the list is pulled down, or when any request answers 503 with your `pluginId`.
|
||||||
|
|
||||||
Termix 26.10.5 and older refuse any manifest with `mobile`, so a plugin that declares it needs
|
Termix 26.10.5 and older refuse any manifest with `mobile`, so a plugin that declares it needs
|
||||||
`"engine": { "termix": ">=26.10.6", "api": "^1.13" }`.
|
`"engine": { "termix": ">=26.10.6", "api": "^1.13" }`.
|
||||||
|
|||||||
@@ -244,7 +244,9 @@ export async function listPreLoginPlugins(): Promise<
|
|||||||
* `assetVersion` is its cache key, `css` says a stylesheet sits beside
|
* `assetVersion` is its cache key, `css` says a stylesheet sits beside
|
||||||
* it, and `locales` lists the languages it ships ("en" and xx_YY).
|
* it, and `locales` lists the languages it ships ("en" and xx_YY).
|
||||||
* `dependencies` and `optionalDependencies` let the loader activate
|
* `dependencies` and `optionalDependencies` let the loader activate
|
||||||
* frontends in the same order the server does.
|
* frontends in the same order the server does. `engine` holds the
|
||||||
|
* manifest's `api` and `termix` ranges, so other clients (the mobile
|
||||||
|
* app) can tell whether they understand the plugin.
|
||||||
*
|
*
|
||||||
* On a desktop linked to a server, `runsOnServer` marks plugins that
|
* On a desktop linked to a server, `runsOnServer` marks plugins that
|
||||||
* only run on that server and stay off here on purpose, and, for
|
* only run on that server and stay off here on purpose, and, for
|
||||||
@@ -303,6 +305,7 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => {
|
|||||||
let features: string[] = [];
|
let features: string[] = [];
|
||||||
let docs: string | undefined;
|
let docs: string | undefined;
|
||||||
let env: PluginEnvVar[] = [];
|
let env: PluginEnvVar[] = [];
|
||||||
|
const engine: { api?: string; termix?: string } = {};
|
||||||
let desktopMode: unknown = loaded
|
let desktopMode: unknown = loaded
|
||||||
? resolvePluginDesktopMode(loaded.manifest)
|
? resolvePluginDesktopMode(loaded.manifest)
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -322,7 +325,14 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => {
|
|||||||
optionalDependencies?: Record<string, string>;
|
optionalDependencies?: Record<string, string>;
|
||||||
desktop?: unknown;
|
desktop?: unknown;
|
||||||
runtime?: unknown;
|
runtime?: unknown;
|
||||||
|
engine?: { api?: unknown; termix?: unknown };
|
||||||
};
|
};
|
||||||
|
if (typeof manifest?.engine?.api === "string") {
|
||||||
|
engine.api = manifest.engine.api;
|
||||||
|
}
|
||||||
|
if (typeof manifest?.engine?.termix === "string") {
|
||||||
|
engine.termix = manifest.engine.termix;
|
||||||
|
}
|
||||||
desktopMode = loaded
|
desktopMode = loaded
|
||||||
? resolvePluginDesktopMode(loaded.manifest)
|
? resolvePluginDesktopMode(loaded.manifest)
|
||||||
: (manifest?.desktop ??
|
: (manifest?.desktop ??
|
||||||
@@ -365,6 +375,7 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => {
|
|||||||
? "safe-mode"
|
? "safe-mode"
|
||||||
: (loaded?.state ?? record.state),
|
: (loaded?.state ?? record.state),
|
||||||
runsOnServer: linked && desktopMode === "server",
|
runsOnServer: linked && desktopMode === "server",
|
||||||
|
engine,
|
||||||
contributes,
|
contributes,
|
||||||
icon,
|
icon,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
|||||||
@@ -488,6 +488,35 @@ describe("plugins route", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("lists a plugin's engine ranges to any user", async () => {
|
||||||
|
state.plugins.set(
|
||||||
|
"sample-plugin",
|
||||||
|
makePlugin({
|
||||||
|
manifestJson: JSON.stringify({
|
||||||
|
engine: { api: "^1.13", termix: ">=26.10.6", extra: 1 },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
state.plugins.set(
|
||||||
|
"bad-engine",
|
||||||
|
makePlugin({
|
||||||
|
id: "bad-engine",
|
||||||
|
manifestJson: JSON.stringify({ engine: { api: 2 } }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = await fetch(`${baseUrl}/plugins`, {
|
||||||
|
headers: { "x-test-user-id": "regular-user" },
|
||||||
|
});
|
||||||
|
const body = (await res.json()) as Array<{ id: string; engine: unknown }>;
|
||||||
|
|
||||||
|
expect(body.find((p) => p.id === "sample-plugin")?.engine).toEqual({
|
||||||
|
api: "^1.13",
|
||||||
|
termix: ">=26.10.6",
|
||||||
|
});
|
||||||
|
expect(body.find((p) => p.id === "bad-engine")?.engine).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
it("reports an enabled plugin that safe mode keeps off", async () => {
|
it("reports an enabled plugin that safe mode keeps off", async () => {
|
||||||
state.plugins.set("sample-plugin", makePlugin({ state: "enabled" }));
|
state.plugins.set("sample-plugin", makePlugin({ state: "enabled" }));
|
||||||
|
|
||||||
@@ -643,6 +672,7 @@ describe("plugins route", () => {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
state: "enabled",
|
state: "enabled",
|
||||||
runsOnServer: false,
|
runsOnServer: false,
|
||||||
|
engine: {},
|
||||||
contributes: null,
|
contributes: null,
|
||||||
dependencies: {},
|
dependencies: {},
|
||||||
optionalDependencies: {},
|
optionalDependencies: {},
|
||||||
|
|||||||
Reference in new issue
Block a user