@vestra-player/sdk 1.5.7 → 1.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/bin/vestra-plugin.mjs +24 -10
- package/dist/commands.d.ts +1 -2
- package/dist/commands.js +1 -2
- package/dist/plugin/packages.js +1 -65
- package/dist/plugin/resolvers.js +64 -0
- package/dist/plugin/testing.d.ts +22 -1
- package/dist/plugin/testing.js +43 -3
- package/dist/plugin/types.d.ts +53 -26
- package/dist/plugin/window.d.ts +2 -0
- package/dist/plugin/window.js +4 -0
- package/dist/wire.d.ts +3 -12
- package/dist/wire.js +0 -10
- package/llms-full.txt +39 -32
- package/llms.txt +40 -40
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.8
|
|
4
|
+
|
|
5
|
+
**Trial loading is removed; `resolvers.fileTypes`; the CLI under `--permission`.** `apiVersion` stays `vestra/1`. The removal breaks code that called `PackageCandidate.trial()`: only Vestra Player AI did, and it moves to installing (an install can be rolled back).
|
|
6
|
+
|
|
7
|
+
- **Removed: `PackageCandidate.trial()`, the `PackageTrial` type, `PluginInfo.origin` `"trial"` and the error code `cleanup`** (only `PackageTrial.stop` used it). The player (0.4.2) no longer answers `packages.trial`, `packages.trialState`, `packages.trialStateByRequest` or `packages.stopTrial`, and never lists a plugin with origin `"trial"`. Trial loads only supported plugins using `commands`, `ui.window` and `events`, listed the plugin under another id (`<id>.trial.<n>`), and `trial()` returned before activation ended, so the feature was removed instead of extended. Check a package with `validate` and install it; `rollback` restores the previous version.
|
|
8
|
+
- **`plugin.resolvers.fileTypes`** (player 0.4.2): the extensions some plugin's file resolver handles, lower case, sorted, each once, without who registered them; `[]` from older players. Reading needs no capability. **`plugin.resolvers.onFileTypesChange(listener)`** follows it when a file resolver comes or goes (needs `events`). For a playlist that accepts ROMs while an emulator plugin runs. `TestHost`: `host.fileTypes()` (this plugin's file resolvers plus `otherResolvers`) and `host.updateFileTypes()` after changing `otherResolvers`.
|
|
9
|
+
- **`vestra-plugin` runs under Node's permission model** (`--permission`, with `--allow-child-process` for the type checker): looking for `node_modules/@types`, `tsconfig.json` and `.git` in parent folders it may not read threw `ERR_ACCESS_DENIED`; such a folder now counts as absent.
|
|
10
|
+
- `Packages.validate`: the player (0.4.2) keeps the 32 most recent candidates and drops the oldest, whose `install` then fails with `stale`; before, the 33rd validation of a run failed with `busy` until the player restarted.
|
|
11
|
+
|
|
3
12
|
## 1.5.7
|
|
4
13
|
|
|
5
14
|
**Replaced or closed streams stop with `null`.** A fix; no API change, `apiVersion` stays `vestra/1`, any player.
|
package/bin/vestra-plugin.mjs
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import { spawnSync } from "node:child_process";
|
|
36
36
|
import { createHash } from "node:crypto";
|
|
37
|
-
import { existsSync } from "node:fs";
|
|
37
|
+
import { existsSync as exists } from "node:fs";
|
|
38
38
|
import {
|
|
39
39
|
cp,
|
|
40
40
|
mkdir,
|
|
@@ -110,6 +110,20 @@ async function json(path, fallback) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/** A module from the plugin's project first, else the SDK's own dependencies. */
|
|
113
|
+
/**
|
|
114
|
+
* Whether a path exists. Under Node's permission model (`--permission`)
|
|
115
|
+
* `existsSync` throws for a path outside the allowed ones; such a path
|
|
116
|
+
* counts as absent, as for a user who cannot read it.
|
|
117
|
+
*/
|
|
118
|
+
function present(path) {
|
|
119
|
+
try {
|
|
120
|
+
return exists(path);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error?.code === "ERR_ACCESS_DENIED") return false;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
113
127
|
function resolveFrom(directory, specifier) {
|
|
114
128
|
for (const base of [directory, sdkRoot]) {
|
|
115
129
|
try {
|
|
@@ -139,7 +153,7 @@ async function typescript() {
|
|
|
139
153
|
async function plugin(path) {
|
|
140
154
|
const directory = resolve(path);
|
|
141
155
|
const name = relative(process.cwd(), directory) || basename(directory);
|
|
142
|
-
if (!
|
|
156
|
+
if (!present(join(directory, "manifest.json")))
|
|
143
157
|
fail(`${name}: no manifest.json in ${directory}`);
|
|
144
158
|
const manifest = await json(join(directory, "manifest.json"));
|
|
145
159
|
const { problems, hints } = manifestProblems(manifest, directory);
|
|
@@ -165,7 +179,7 @@ function displayFiles(target) {
|
|
|
165
179
|
const field = name === "README.md" ? "readme" : "changelog";
|
|
166
180
|
if (
|
|
167
181
|
target.manifest[field] === undefined &&
|
|
168
|
-
|
|
182
|
+
present(join(target.directory, name))
|
|
169
183
|
)
|
|
170
184
|
files.add(name);
|
|
171
185
|
}
|
|
@@ -200,7 +214,7 @@ function dependencies(target) {
|
|
|
200
214
|
};
|
|
201
215
|
if (
|
|
202
216
|
!Object.keys(deps).length ||
|
|
203
|
-
|
|
217
|
+
present(join(target.directory, "node_modules"))
|
|
204
218
|
)
|
|
205
219
|
return;
|
|
206
220
|
console.log(`${target.name}: installing dependencies (npm ci)`);
|
|
@@ -221,7 +235,7 @@ function* upwards(directory) {
|
|
|
221
235
|
for (let current = directory; ;) {
|
|
222
236
|
yield current;
|
|
223
237
|
const parent = dirname(current);
|
|
224
|
-
if (parent === current ||
|
|
238
|
+
if (parent === current || present(join(current, ".git"))) return;
|
|
225
239
|
current = parent;
|
|
226
240
|
}
|
|
227
241
|
}
|
|
@@ -235,13 +249,13 @@ function checkConfig(target, sources) {
|
|
|
235
249
|
const roots = [];
|
|
236
250
|
for (const directory of upwards(target.directory)) {
|
|
237
251
|
const types = join(directory, "node_modules/@types");
|
|
238
|
-
if (
|
|
252
|
+
if (present(types)) roots.push(types);
|
|
239
253
|
}
|
|
240
254
|
const node = resolveFrom(target.directory, "@types/node/package.json");
|
|
241
255
|
if (node) roots.push(dirname(dirname(node)));
|
|
242
256
|
const base = [...upwards(target.directory)]
|
|
243
257
|
.map((directory) => join(directory, "tsconfig.json"))
|
|
244
|
-
.find((file) =>
|
|
258
|
+
.find((file) => present(file));
|
|
245
259
|
return {
|
|
246
260
|
...(base ? { extends: base } : {}),
|
|
247
261
|
compilerOptions: {
|
|
@@ -262,7 +276,7 @@ async function check(target) {
|
|
|
262
276
|
const own = (await files(target.directory)).filter(
|
|
263
277
|
(file) => isTypeScript(file) || file.endsWith(".mts"),
|
|
264
278
|
);
|
|
265
|
-
const tests =
|
|
279
|
+
const tests = present(join(target.directory, "tests"))
|
|
266
280
|
? (await readdir(join(target.directory, "tests")))
|
|
267
281
|
.filter(isTypeScript)
|
|
268
282
|
.map((file) => join("tests", file))
|
|
@@ -308,7 +322,7 @@ function entries(target) {
|
|
|
308
322
|
const entry = target.manifest.entry.replace(/^dist\//, "");
|
|
309
323
|
const candidates = [entry.replace(/\.js$/, ".ts"), entry];
|
|
310
324
|
const found = candidates.find((file) =>
|
|
311
|
-
|
|
325
|
+
present(join(target.directory, file)),
|
|
312
326
|
);
|
|
313
327
|
if (!found)
|
|
314
328
|
fail(`${target.name}: entry ${target.manifest.entry} has no source file`);
|
|
@@ -454,7 +468,7 @@ async function build(target, flag) {
|
|
|
454
468
|
|
|
455
469
|
async function test(target) {
|
|
456
470
|
const directory = join(target.directory, "tests");
|
|
457
|
-
const tests =
|
|
471
|
+
const tests = present(directory)
|
|
458
472
|
? (await readdir(directory))
|
|
459
473
|
.filter((file) => /\.test\.(ts|mjs)$/.test(file))
|
|
460
474
|
.sort()
|
package/dist/commands.d.ts
CHANGED
|
@@ -20,8 +20,7 @@ import type { Transport } from "./wire.ts";
|
|
|
20
20
|
*/
|
|
21
21
|
export declare const COMMAND_LIMITS: {
|
|
22
22
|
/**
|
|
23
|
-
* Commands one plugin instance may have at the same time (160).
|
|
24
|
-
* package is its own instance.
|
|
23
|
+
* Commands one plugin instance may have at the same time (160).
|
|
25
24
|
*/
|
|
26
25
|
readonly perPlugin: 160;
|
|
27
26
|
/** Commands of every plugin plus the player's own, together (1024). */
|
package/dist/commands.js
CHANGED
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
export const COMMAND_LIMITS = {
|
|
20
20
|
/**
|
|
21
|
-
* Commands one plugin instance may have at the same time (160).
|
|
22
|
-
* package is its own instance.
|
|
21
|
+
* Commands one plugin instance may have at the same time (160).
|
|
23
22
|
*/
|
|
24
23
|
perPlugin: 160,
|
|
25
24
|
/** Commands of every plugin plus the player's own, together (1024). */
|
package/dist/plugin/packages.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Package validation
|
|
1
|
+
/** Package validation and install/rollback (`plugins.manage`). */
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { failure, sleep, uncertain } from "./runtime.js";
|
|
@@ -9,51 +9,6 @@ const TERMINAL = [
|
|
|
9
9
|
"failedDegraded",
|
|
10
10
|
"tooLate",
|
|
11
11
|
];
|
|
12
|
-
const STARTING = ["preparing", "activating", "starting"];
|
|
13
|
-
class TrialHandle {
|
|
14
|
-
#scope;
|
|
15
|
-
#record;
|
|
16
|
-
#stopped;
|
|
17
|
-
constructor(scope, record) {
|
|
18
|
-
this.#scope = scope;
|
|
19
|
-
this.#record = record;
|
|
20
|
-
scope.add(this);
|
|
21
|
-
}
|
|
22
|
-
get status() {
|
|
23
|
-
return this.#record.status;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Concurrent calls share one stop; a confirmed stop is final. A failed one
|
|
27
|
-
* (code `cleanup`: the host did not confirm cleanup) is forgotten, so the
|
|
28
|
-
* next call sends a new stop to the host.
|
|
29
|
-
*/
|
|
30
|
-
stop() {
|
|
31
|
-
const host = this.#scope.runtime.host.packages;
|
|
32
|
-
if (this.#stopped)
|
|
33
|
-
return this.#stopped;
|
|
34
|
-
const stopping = (async () => {
|
|
35
|
-
this.#record = await host.stopTrial(this.#record.trialId);
|
|
36
|
-
while (!this.#record.cleanupConfirmed &&
|
|
37
|
-
this.#record.status !== "stopped" &&
|
|
38
|
-
this.#record.status !== "failed") {
|
|
39
|
-
await sleep(250);
|
|
40
|
-
this.#record = await host.trialState(this.#record.trialId);
|
|
41
|
-
}
|
|
42
|
-
if (!this.#record.cleanupConfirmed)
|
|
43
|
-
throw failure("cleanup", `trial cleanup not confirmed: ${this.#record.status}`);
|
|
44
|
-
})();
|
|
45
|
-
this.#stopped = stopping;
|
|
46
|
-
stopping.catch(() => {
|
|
47
|
-
if (this.#stopped === stopping)
|
|
48
|
-
this.#stopped = undefined;
|
|
49
|
-
});
|
|
50
|
-
return stopping;
|
|
51
|
-
}
|
|
52
|
-
async dispose() {
|
|
53
|
-
this.#scope.forget(this);
|
|
54
|
-
await this.stop().catch(() => { });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
12
|
class ChangeHandle {
|
|
58
13
|
#scope;
|
|
59
14
|
#id;
|
|
@@ -123,24 +78,6 @@ async function change(scope, submit, id) {
|
|
|
123
78
|
}
|
|
124
79
|
return new ChangeHandle(scope, operation);
|
|
125
80
|
}
|
|
126
|
-
async function trial(scope, runtime, token) {
|
|
127
|
-
const host = runtime.host.packages;
|
|
128
|
-
const requestId = randomUUID();
|
|
129
|
-
let record;
|
|
130
|
-
try {
|
|
131
|
-
record = await host.trial(token, requestId);
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
if (!uncertain(error))
|
|
135
|
-
throw error;
|
|
136
|
-
record = await host.trialStateByRequest(requestId);
|
|
137
|
-
}
|
|
138
|
-
while (STARTING.includes(record.status)) {
|
|
139
|
-
await sleep(250);
|
|
140
|
-
record = await host.trialState(record.trialId);
|
|
141
|
-
}
|
|
142
|
-
return new TrialHandle(scope, record);
|
|
143
|
-
}
|
|
144
81
|
function installOptions(options) {
|
|
145
82
|
if (options === undefined)
|
|
146
83
|
return {};
|
|
@@ -171,7 +108,6 @@ function candidate(scope, directory, checked) {
|
|
|
171
108
|
digest: checked.digest,
|
|
172
109
|
manifest: checked.manifest,
|
|
173
110
|
checks: checked.checks,
|
|
174
|
-
trial: () => trial(scope, runtime, checked.token),
|
|
175
111
|
install: async (options) => {
|
|
176
112
|
const wire = installOptions(options);
|
|
177
113
|
return change(scope, (requestId, revision) => runtime.host.packages.install(checked.token, revision, requestId, wire), checked.manifest.id);
|
package/dist/plugin/resolvers.js
CHANGED
|
@@ -5,8 +5,72 @@
|
|
|
5
5
|
import { failure, quiet } from "./runtime.js";
|
|
6
6
|
import { playerOf } from "./player.js";
|
|
7
7
|
import { resolvedStream } from "./stream.js";
|
|
8
|
+
import { windowOf } from "./window.js";
|
|
8
9
|
const isFiles = (options) => "extensions" in options;
|
|
10
|
+
const sameTypes = (a, b) => a.length === b.length && a.every((type, i) => type === b[i]);
|
|
11
|
+
/**
|
|
12
|
+
* One per plugin: the list comes with the `window.state` read before
|
|
13
|
+
* activation (`[]` from older players) and is read again when the plugin
|
|
14
|
+
* first uses it; with `events`, `resolvers.fileTypes` keeps it current.
|
|
15
|
+
*/
|
|
16
|
+
class FileTypesService {
|
|
17
|
+
#runtime;
|
|
18
|
+
types;
|
|
19
|
+
#listeners = new Set();
|
|
20
|
+
#started = false;
|
|
21
|
+
constructor(runtime) {
|
|
22
|
+
this.#runtime = runtime;
|
|
23
|
+
this.types = Object.freeze([...(windowOf(runtime).fileTypes ?? [])]);
|
|
24
|
+
}
|
|
25
|
+
start() {
|
|
26
|
+
if (this.#started)
|
|
27
|
+
return;
|
|
28
|
+
this.#started = true;
|
|
29
|
+
this.#runtime.on("resolvers.fileTypes", (data) => {
|
|
30
|
+
const types = data?.fileTypes;
|
|
31
|
+
if (Array.isArray(types) && types.every((t) => typeof t === "string"))
|
|
32
|
+
this.accept(types);
|
|
33
|
+
});
|
|
34
|
+
// What changed between the start-up read and the subscription.
|
|
35
|
+
const window = windowOf(this.#runtime);
|
|
36
|
+
void window.read().then(() => {
|
|
37
|
+
if (window.fileTypes)
|
|
38
|
+
this.accept(window.fileTypes, this.#runtime.can("events"));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** `notify`: false without `events`, whose listeners never fire. */
|
|
42
|
+
accept(types, notify = true) {
|
|
43
|
+
if (sameTypes(types, this.types))
|
|
44
|
+
return;
|
|
45
|
+
this.types = Object.freeze([...types]);
|
|
46
|
+
if (notify)
|
|
47
|
+
for (const listener of [...this.#listeners])
|
|
48
|
+
listener([...types]);
|
|
49
|
+
this.#runtime.invalidate();
|
|
50
|
+
}
|
|
51
|
+
watch(listener) {
|
|
52
|
+
this.#listeners.add(listener);
|
|
53
|
+
return { dispose: async () => void this.#listeners.delete(listener) };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const fileTypesOf = (runtime) => runtime.service("fileTypes", () => new FileTypesService(runtime));
|
|
9
57
|
export const resolvers = (scope) => ({
|
|
58
|
+
get fileTypes() {
|
|
59
|
+
const service = fileTypesOf(scope.runtime);
|
|
60
|
+
service.start();
|
|
61
|
+
return service.types;
|
|
62
|
+
},
|
|
63
|
+
onFileTypesChange(listener) {
|
|
64
|
+
const service = fileTypesOf(scope.runtime);
|
|
65
|
+
service.start();
|
|
66
|
+
const watch = service.watch((types) => scope.run(listener, types));
|
|
67
|
+
return scope.add({
|
|
68
|
+
dispose: async () => {
|
|
69
|
+
scope.forget(watch);
|
|
70
|
+
await watch.dispose();
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
},
|
|
10
74
|
add(options) {
|
|
11
75
|
const runtime = scope.runtime;
|
|
12
76
|
const registration = isFiles(options)
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -773,6 +773,27 @@ export declare class TestHost implements Transport {
|
|
|
773
773
|
/** Exact host names it resolves (a web resolver), with `schemes`. */
|
|
774
774
|
hosts?: string[];
|
|
775
775
|
}[];
|
|
776
|
+
/**
|
|
777
|
+
* What `plugin.resolvers.fileTypes` reports: the extensions of this
|
|
778
|
+
* plugin's file resolvers and of {@link TestHost.otherResolvers}, sorted,
|
|
779
|
+
* each once.
|
|
780
|
+
*/
|
|
781
|
+
fileTypes(): string[];
|
|
782
|
+
/**
|
|
783
|
+
* Push `resolvers.fileTypes` when {@link TestHost.fileTypes} changed:
|
|
784
|
+
* called after this plugin's resolvers come and go; call it yourself
|
|
785
|
+
* after changing {@link TestHost.otherResolvers} (another plugin started
|
|
786
|
+
* or stopped).
|
|
787
|
+
*
|
|
788
|
+
* @example
|
|
789
|
+
* ```ts
|
|
790
|
+
* host.otherResolvers.push({ owner: "vestra.nes", extensions: ["nes"] });
|
|
791
|
+
* host.updateFileTypes();
|
|
792
|
+
* await host.settle();
|
|
793
|
+
* assert.deepEqual(plugin.resolvers.fileTypes, ["nes"]);
|
|
794
|
+
* ```
|
|
795
|
+
*/
|
|
796
|
+
updateFileTypes(): void;
|
|
776
797
|
/**
|
|
777
798
|
* File opens waiting for this plugin's file resolver (`extensions`), by
|
|
778
799
|
* request id: the path and how it was opened. Opening a matching path
|
|
@@ -1095,7 +1116,7 @@ export declare class TestHost implements Transport {
|
|
|
1095
1116
|
/** Its version, `null` when unknown. */
|
|
1096
1117
|
version: string | null;
|
|
1097
1118
|
/** Where it was loaded from. */
|
|
1098
|
-
origin: "installed" | "development"
|
|
1119
|
+
origin: "installed" | "development";
|
|
1099
1120
|
/** Its run state. */
|
|
1100
1121
|
state: "starting" | "running" | "disabled" | "failed" | "stopped";
|
|
1101
1122
|
/** Why it failed (`state: "failed"`). */
|
package/dist/plugin/testing.js
CHANGED
|
@@ -773,6 +773,43 @@ export class TestHost {
|
|
|
773
773
|
* ```
|
|
774
774
|
*/
|
|
775
775
|
otherResolvers = [];
|
|
776
|
+
/**
|
|
777
|
+
* What `plugin.resolvers.fileTypes` reports: the extensions of this
|
|
778
|
+
* plugin's file resolvers and of {@link TestHost.otherResolvers}, sorted,
|
|
779
|
+
* each once.
|
|
780
|
+
*/
|
|
781
|
+
fileTypes() {
|
|
782
|
+
const types = new Set();
|
|
783
|
+
for (const declaration of [
|
|
784
|
+
...this.resolvers.values(),
|
|
785
|
+
...this.otherResolvers,
|
|
786
|
+
])
|
|
787
|
+
for (const type of declaration.extensions ?? [])
|
|
788
|
+
types.add(type);
|
|
789
|
+
return [...types].sort();
|
|
790
|
+
}
|
|
791
|
+
#sentTypes = "[]";
|
|
792
|
+
/**
|
|
793
|
+
* Push `resolvers.fileTypes` when {@link TestHost.fileTypes} changed:
|
|
794
|
+
* called after this plugin's resolvers come and go; call it yourself
|
|
795
|
+
* after changing {@link TestHost.otherResolvers} (another plugin started
|
|
796
|
+
* or stopped).
|
|
797
|
+
*
|
|
798
|
+
* @example
|
|
799
|
+
* ```ts
|
|
800
|
+
* host.otherResolvers.push({ owner: "vestra.nes", extensions: ["nes"] });
|
|
801
|
+
* host.updateFileTypes();
|
|
802
|
+
* await host.settle();
|
|
803
|
+
* assert.deepEqual(plugin.resolvers.fileTypes, ["nes"]);
|
|
804
|
+
* ```
|
|
805
|
+
*/
|
|
806
|
+
updateFileTypes() {
|
|
807
|
+
const types = this.fileTypes();
|
|
808
|
+
if (JSON.stringify(types) === this.#sentTypes)
|
|
809
|
+
return;
|
|
810
|
+
this.#sentTypes = JSON.stringify(types);
|
|
811
|
+
this.emit("resolvers.fileTypes", { fileTypes: types });
|
|
812
|
+
}
|
|
776
813
|
/**
|
|
777
814
|
* File opens waiting for this plugin's file resolver (`extensions`), by
|
|
778
815
|
* request id: the path and how it was opened. Opening a matching path
|
|
@@ -1361,6 +1398,7 @@ export class TestHost {
|
|
|
1361
1398
|
videoArea: { ...this.mainWindow.videoArea },
|
|
1362
1399
|
pictureInPicture: { ...this.mainWindow.pictureInPicture },
|
|
1363
1400
|
appearance: this.appearance,
|
|
1401
|
+
fileTypes: this.fileTypes(),
|
|
1364
1402
|
};
|
|
1365
1403
|
}
|
|
1366
1404
|
/**
|
|
@@ -2683,8 +2721,6 @@ export class TestHost {
|
|
|
2683
2721
|
const entry = this.packages.find((each) => each.id === id);
|
|
2684
2722
|
if (!entry)
|
|
2685
2723
|
throw new HostError("missing", `no plugin ${id}`);
|
|
2686
|
-
if (entry.origin === "trial")
|
|
2687
|
-
throw new HostError("unsupported", "a trial is stopped with its handle");
|
|
2688
2724
|
this.packageCalls.push({
|
|
2689
2725
|
method: "setEnabled",
|
|
2690
2726
|
id,
|
|
@@ -2944,10 +2980,14 @@ export class TestHost {
|
|
|
2944
2980
|
return { jobId: job.jobId, cancel: "requested" };
|
|
2945
2981
|
},
|
|
2946
2982
|
"jobs.operation": () => ({ status: "unknown" }),
|
|
2947
|
-
"sourceResolvers.register": (p) =>
|
|
2983
|
+
"sourceResolvers.register": (p) => {
|
|
2984
|
+
this.#register(p);
|
|
2985
|
+
this.updateFileTypes();
|
|
2986
|
+
},
|
|
2948
2987
|
"sourceResolvers.unregister": (p) => {
|
|
2949
2988
|
this.resolvers.delete(String(p.id));
|
|
2950
2989
|
this.#cancelResolutions();
|
|
2990
|
+
this.updateFileTypes();
|
|
2951
2991
|
},
|
|
2952
2992
|
"sourceResolvers.respond": (p) => this.#respond(p),
|
|
2953
2993
|
"producer.prepare": async () => {
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -73,9 +73,8 @@ export interface Disposable {
|
|
|
73
73
|
* | `timeout` | A time limit ran out (`openStream` start-up, `tools.run`, `system.*` after 10 s). |
|
|
74
74
|
* | `stream`, `device` | `openStream`: the producer failed / the audio device is not ready. |
|
|
75
75
|
* | `backend-uncertain` | The player's backend blocked a filter stage; call `retry()`. |
|
|
76
|
-
* | `cleanup` | {@link PackageTrial.stop} could not confirm cleanup; call it again. |
|
|
77
76
|
* | `limit` | A player-wide limit was reached (for example the number of commands). |
|
|
78
|
-
* | `reserved` | {@link Packages.validate}, {@link PackageCandidate.
|
|
77
|
+
* | `reserved` | {@link Packages.validate}, {@link PackageCandidate.install}: the id is `vestra` or starts with `vestra.` (any case), which only the player's built-in plugins use (player 0.4.1). |
|
|
79
78
|
* | `versionMismatch` | The package needs a newer player (`minPlayerVersion`) or another `apiVersion`. |
|
|
80
79
|
* | `ui-prepare` | The player could not prepare a mounted panel or a settings page (its Slint file does not compile, a bound property is not declared, or another plugin's panel holds the slot); `message` has the diagnostics. |
|
|
81
80
|
* | `ui` | The player could not apply a property to a tool window or a settings page, or could not build the Options window (a property the Slint file does not declare, a value of the wrong type); `message` has Slint's reason. |
|
|
@@ -195,7 +194,7 @@ export interface Scope extends Disposable {
|
|
|
195
194
|
readonly media: MediaCapture;
|
|
196
195
|
/** Page-URL to stream-URL resolvers (`source.resolve`). */
|
|
197
196
|
readonly resolvers: Resolvers;
|
|
198
|
-
/** The installed plugins; validation,
|
|
197
|
+
/** The installed plugins; validation, installs (`plugins.manage` for changes). */
|
|
199
198
|
readonly packages: Packages;
|
|
200
199
|
/** On-screen messages over the video (`ui.osd`). */
|
|
201
200
|
readonly osd: Osd;
|
|
@@ -320,8 +319,7 @@ export interface Plugin extends Scope {
|
|
|
320
319
|
readonly id: string;
|
|
321
320
|
/**
|
|
322
321
|
* A writable directory for this plugin's own files (settings, remembered
|
|
323
|
-
* folders). Kept across restarts, disabling and upgrades
|
|
324
|
-
* temporary one. Write files atomically: to a temporary name, then rename.
|
|
322
|
+
* folders). Kept across restarts, disabling and upgrades. Write files atomically: to a temporary name, then rename.
|
|
325
323
|
* Throws `unavailable` when the player provided none.
|
|
326
324
|
*
|
|
327
325
|
* Location: `~/Library/Application Support/Vestra/plugin-data/<id>/` on
|
|
@@ -4008,6 +4006,47 @@ export interface Resolvers {
|
|
|
4008
4006
|
* error's message.
|
|
4009
4007
|
*/
|
|
4010
4008
|
add(options: ResolverOptions): Resolver;
|
|
4009
|
+
/**
|
|
4010
|
+
* The extensions some plugin's file resolver handles (SDK 1.5.8, player
|
|
4011
|
+
* 0.4.2): lower case, without the dot, sorted, each once; not who
|
|
4012
|
+
* registered them. Reading needs no capability; `[]` from older players.
|
|
4013
|
+
* Without `events` it is the list from when the plugin first read it.
|
|
4014
|
+
* Use it to accept such files beside the player's own media types, for
|
|
4015
|
+
* example in a playlist's drop or folder scan.
|
|
4016
|
+
*
|
|
4017
|
+
* @example
|
|
4018
|
+
* ```ts
|
|
4019
|
+
* const MEDIA = ["mp4", "mkv", "mp3"];
|
|
4020
|
+
* const accepted = (path: string) => {
|
|
4021
|
+
* const extension = path.split(".").pop()?.toLowerCase() ?? "";
|
|
4022
|
+
* return MEDIA.includes(extension) || plugin.resolvers.fileTypes.includes(extension);
|
|
4023
|
+
* };
|
|
4024
|
+
* ```
|
|
4025
|
+
*
|
|
4026
|
+
* @capability None.
|
|
4027
|
+
* @player 0.4.2
|
|
4028
|
+
* @since 1.5.8
|
|
4029
|
+
*/
|
|
4030
|
+
readonly fileTypes: readonly string[];
|
|
4031
|
+
/**
|
|
4032
|
+
* {@link Resolvers.fileTypes} changed: a file resolver was added, or its
|
|
4033
|
+
* plugin stopped. Needs `events`; without it the listener never fires.
|
|
4034
|
+
*
|
|
4035
|
+
* @param listener - Receives the new list.
|
|
4036
|
+
* @returns A handle that removes the listener.
|
|
4037
|
+
*
|
|
4038
|
+
* @example
|
|
4039
|
+
* ```ts
|
|
4040
|
+
* plugin.resolvers.onFileTypesChange((types) => {
|
|
4041
|
+
* void plugin.osd.show(`Also accepted: ${types.join(", ")}`);
|
|
4042
|
+
* });
|
|
4043
|
+
* ```
|
|
4044
|
+
*
|
|
4045
|
+
* @capability `events`
|
|
4046
|
+
* @player 0.4.2
|
|
4047
|
+
* @since 1.5.8
|
|
4048
|
+
*/
|
|
4049
|
+
onFileTypesChange(listener: (types: string[]) => void): Disposable;
|
|
4011
4050
|
}
|
|
4012
4051
|
/**
|
|
4013
4052
|
* The manifest of a package, as validated. The display fields and
|
|
@@ -4099,11 +4138,6 @@ export interface PackageCandidate {
|
|
|
4099
4138
|
readonly manifest: PackageManifest;
|
|
4100
4139
|
/** The checks the player ran. */
|
|
4101
4140
|
readonly checks: string[];
|
|
4102
|
-
/**
|
|
4103
|
-
* Load it temporarily alongside the installed plugins. A reserved id
|
|
4104
|
-
* (`vestra.*`) fails with `reserved`.
|
|
4105
|
-
*/
|
|
4106
|
-
trial(): Promise<PackageTrial>;
|
|
4107
4141
|
/**
|
|
4108
4142
|
* Install it. The same source upgrades the installed plugin (its data
|
|
4109
4143
|
* stays); another source fails with `conflict` unless `replace` is set;
|
|
@@ -4114,17 +4148,6 @@ export interface PackageCandidate {
|
|
|
4114
4148
|
*/
|
|
4115
4149
|
install(options?: InstallOptions): Promise<PackageChange>;
|
|
4116
4150
|
}
|
|
4117
|
-
/** A trial load of a package. Disposing it stops the trial and waits for cleanup. */
|
|
4118
|
-
export interface PackageTrial extends Disposable {
|
|
4119
|
-
/** The trial's status as the player reports it. */
|
|
4120
|
-
readonly status: string;
|
|
4121
|
-
/**
|
|
4122
|
-
* Stop and wait until the host confirmed cleanup. Rejects with code
|
|
4123
|
-
* `cleanup` when the host stopped the trial without confirming cleanup;
|
|
4124
|
-
* calling `stop()` again then asks the host again.
|
|
4125
|
-
*/
|
|
4126
|
-
stop(): Promise<void>;
|
|
4127
|
-
}
|
|
4128
4151
|
/** The progress of an install, rollback or uninstall. */
|
|
4129
4152
|
export type PackageChangeStatus = "preparing" | "activating" | "restoring" | "committed" | "cancelled" | "failed" | "failedDegraded" | "tooLate";
|
|
4130
4153
|
/**
|
|
@@ -4149,8 +4172,8 @@ export interface PluginInfo {
|
|
|
4149
4172
|
id: string;
|
|
4150
4173
|
/** Version, `null` when unknown. */
|
|
4151
4174
|
version: string | null;
|
|
4152
|
-
/** From the install library, a `--plugin` development load
|
|
4153
|
-
origin: "installed" | "development"
|
|
4175
|
+
/** From the install library, or a `--plugin` development load. */
|
|
4176
|
+
origin: "installed" | "development";
|
|
4154
4177
|
/** Run state. */
|
|
4155
4178
|
state: "starting" | "running" | "disabled" | "failed" | "stopped";
|
|
4156
4179
|
/** Why it failed (`state: "failed"`). */
|
|
@@ -4166,7 +4189,7 @@ export interface PluginInfo {
|
|
|
4166
4189
|
* replaced. `false` from players before 0.2.0.
|
|
4167
4190
|
*/
|
|
4168
4191
|
builtin: boolean;
|
|
4169
|
-
/** Where an installed plugin came from; `null` for development loads
|
|
4192
|
+
/** Where an installed plugin came from; `null` for development loads. */
|
|
4170
4193
|
source: PackageSource | null;
|
|
4171
4194
|
/**
|
|
4172
4195
|
* Its data directory (`plugin.dataDirectory` of that plugin); `null` when
|
|
@@ -4222,6 +4245,10 @@ export interface Packages {
|
|
|
4222
4245
|
* Validate a package directory (`plugins.manage`). A package whose id is
|
|
4223
4246
|
* reserved for the built-in plugins (`vestra` or `vestra.*`) fails with
|
|
4224
4247
|
* `reserved` (player 0.4.1).
|
|
4248
|
+
*
|
|
4249
|
+
* The player keeps the 32 most recent candidates of all plugins; an
|
|
4250
|
+
* older one's `install` fails with `stale` (validate again). Before
|
|
4251
|
+
* player 0.4.2 the 33rd validation of a run failed with `busy` instead.
|
|
4225
4252
|
*/
|
|
4226
4253
|
validate(directory: string): Promise<PackageCandidate>;
|
|
4227
4254
|
/**
|
|
@@ -4240,7 +4267,7 @@ export interface Packages {
|
|
|
4240
4267
|
/**
|
|
4241
4268
|
* Stop or start a plugin (`plugins.manage`). An installed plugin stays
|
|
4242
4269
|
* disabled across launches; a development load only for this run.
|
|
4243
|
-
* `invalid` to disable oneself, `
|
|
4270
|
+
* `invalid` to disable oneself, `missing` unknown.
|
|
4244
4271
|
* `busy` while it is being installed.
|
|
4245
4272
|
*/
|
|
4246
4273
|
setEnabled(id: string, enabled: boolean): Promise<void>;
|
|
@@ -4248,7 +4275,7 @@ export interface Packages {
|
|
|
4248
4275
|
* Remove an installed plugin (`plugins.manage`); `removeData` also deletes
|
|
4249
4276
|
* its data directory, otherwise reinstalling it from the same source
|
|
4250
4277
|
* reconnects to its data. Its user bindings stay (as orphans).
|
|
4251
|
-
* `unsupported` for built-ins
|
|
4278
|
+
* `unsupported` for built-ins and development loads, `missing`
|
|
4252
4279
|
* unknown, `invalid` for oneself.
|
|
4253
4280
|
*/
|
|
4254
4281
|
uninstall(id: string, options?: {
|
package/dist/plugin/window.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export declare class WindowService {
|
|
|
19
19
|
start(): void;
|
|
20
20
|
/** The palette, as the `window.state` reply carried it (`plugin.appearance`). */
|
|
21
21
|
appearance: string | undefined;
|
|
22
|
+
/** The file resolvers' extensions, as the reply carried them (`plugin.resolvers.fileTypes`). */
|
|
23
|
+
fileTypes: string[] | undefined;
|
|
22
24
|
accept(facts: WindowFacts): void;
|
|
23
25
|
read(): Promise<void>;
|
|
24
26
|
watch(listener: (state: WindowState) => void): {
|
package/dist/plugin/window.js
CHANGED
|
@@ -52,9 +52,13 @@ export class WindowService {
|
|
|
52
52
|
}
|
|
53
53
|
/** The palette, as the `window.state` reply carried it (`plugin.appearance`). */
|
|
54
54
|
appearance;
|
|
55
|
+
/** The file resolvers' extensions, as the reply carried them (`plugin.resolvers.fileTypes`). */
|
|
56
|
+
fileTypes;
|
|
55
57
|
accept(facts) {
|
|
56
58
|
if (facts.appearance)
|
|
57
59
|
this.appearance = facts.appearance;
|
|
60
|
+
if (Array.isArray(facts.fileTypes))
|
|
61
|
+
this.fileTypes = [...facts.fileTypes];
|
|
58
62
|
const next = view(facts);
|
|
59
63
|
if (JSON.stringify(next) === JSON.stringify(this.state))
|
|
60
64
|
return;
|
package/dist/wire.d.ts
CHANGED
|
@@ -25,12 +25,6 @@ export interface PackageValidation {
|
|
|
25
25
|
checks: string[];
|
|
26
26
|
trustedExecution: true;
|
|
27
27
|
}
|
|
28
|
-
export interface PackageTrial {
|
|
29
|
-
trialId: number;
|
|
30
|
-
activationOwner?: number;
|
|
31
|
-
status: string;
|
|
32
|
-
cleanupConfirmed?: boolean;
|
|
33
|
-
}
|
|
34
28
|
export interface InstalledPackage {
|
|
35
29
|
revision: number;
|
|
36
30
|
current: null | {
|
|
@@ -102,7 +96,7 @@ export declare class HostError extends Error {
|
|
|
102
96
|
export interface PackageInfo {
|
|
103
97
|
id: string;
|
|
104
98
|
version: string | null;
|
|
105
|
-
origin: "installed" | "development"
|
|
99
|
+
origin: "installed" | "development";
|
|
106
100
|
state: "starting" | "running" | "disabled" | "failed" | "stopped";
|
|
107
101
|
error: string | null;
|
|
108
102
|
capabilities: string[];
|
|
@@ -175,6 +169,8 @@ export interface WindowFacts {
|
|
|
175
169
|
};
|
|
176
170
|
/** The palette (`plugin.appearance`), with the `window.state` reply; a newer player may name a palette this SDK does not list. */
|
|
177
171
|
appearance?: string;
|
|
172
|
+
/** The file resolvers' extensions (`plugin.resolvers.fileTypes`; player 0.4.2). */
|
|
173
|
+
fileTypes?: string[];
|
|
178
174
|
}
|
|
179
175
|
/** One timeline mark as the player merges them (`timeline.marks`). */
|
|
180
176
|
export interface WireMark {
|
|
@@ -530,11 +526,6 @@ export declare function createWire(transport: Transport): {
|
|
|
530
526
|
validate: (directory: string, apiVersion?: string) => Promise<PackageValidation>;
|
|
531
527
|
/** Player 0.2.0: a `.vxp` file. */
|
|
532
528
|
validateArchive: (file: string) => Promise<PackageValidation>;
|
|
533
|
-
/** `requestId` recovers a lost first reply in this owner session. */
|
|
534
|
-
trial: (token: string, requestId: string) => Promise<PackageTrial>;
|
|
535
|
-
trialStateByRequest: (requestId: string) => Promise<PackageTrial>;
|
|
536
|
-
trialState: (trialId: number) => Promise<PackageTrial>;
|
|
537
|
-
stopTrial: (trialId: number) => Promise<PackageTrial>;
|
|
538
529
|
installed: (id: string) => Promise<InstalledPackage>;
|
|
539
530
|
/** `source` and `replace` are sent only when given (player 0.2.0). */
|
|
540
531
|
install: (token: string, expectedRevision: number, requestId: string, options?: {
|
package/dist/wire.js
CHANGED
|
@@ -258,16 +258,6 @@ export function createWire(transport) {
|
|
|
258
258
|
validateArchive: (file) => transport.request("packages.validateArchive", {
|
|
259
259
|
file,
|
|
260
260
|
}),
|
|
261
|
-
/** `requestId` recovers a lost first reply in this owner session. */
|
|
262
|
-
trial: (token, requestId) => transport.request("packages.trial", {
|
|
263
|
-
token,
|
|
264
|
-
requestId,
|
|
265
|
-
}),
|
|
266
|
-
trialStateByRequest: (requestId) => transport.request("packages.trialStateByRequest", {
|
|
267
|
-
requestId,
|
|
268
|
-
}),
|
|
269
|
-
trialState: (trialId) => transport.request("packages.trialState", { trialId }),
|
|
270
|
-
stopTrial: (trialId) => transport.request("packages.stopTrial", { trialId }),
|
|
271
261
|
installed: (id) => transport.request("packages.installed", { id }),
|
|
272
262
|
/** `source` and `replace` are sent only when given (player 0.2.0). */
|
|
273
263
|
install: (token, expectedRevision, requestId, options = {}) => transport.request("packages.install", {
|
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
1
|
+
# @vestra-player/sdk 1.5.8: full reference
|
|
2
2
|
|
|
3
3
|
> Everything needed to write a Vestra Player plugin (plugin API `vestra/1`): the guides, every public declaration of `@vestra-player/sdk` and `@vestra-player/sdk/testing` with its documentation, and the complete source of the example plugins. Generated from the SDK's sources by scripts/llms.mjs; the short index is llms.txt.
|
|
4
4
|
|
|
@@ -124,7 +124,7 @@ A manifest with every field. `id`, `apiVersion`, `entry` and `capabilities` are
|
|
|
124
124
|
}
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
Unknown fields, unknown capabilities and paths that leave the package directory (including through symbolic links) are rejected. The id's characters are ASCII letters, digits, `.`, `-` and `_`; the official plugin repository additionally requires `<GitHub user or organization>.<plugin name>`. The `vestra.` prefix (and the id `vestra`, in any case) is reserved for the plugins built into the player: from player 0.4.1 the player refuses to install
|
|
127
|
+
Unknown fields, unknown capabilities and paths that leave the package directory (including through symbolic links) are rejected. The id's characters are ASCII letters, digits, `.`, `-` and `_`; the official plugin repository additionally requires `<GitHub user or organization>.<plugin name>`. The `vestra.` prefix (and the id `vestra`, in any case) is reserved for the plugins built into the player: from player 0.4.1 the player refuses to install such a package (`reserved`). Every other plugin is a third-party plugin, including those hosted in the official repository.
|
|
128
128
|
|
|
129
129
|
| Capability | Opens |
|
|
130
130
|
| -------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
@@ -998,9 +998,8 @@ change).
|
|
|
998
998
|
| `timeout` | A time limit ran out (`openStream` start-up, `tools.run`, `system.*` after 10 s). |
|
|
999
999
|
| `stream`, `device` | `openStream`: the producer failed / the audio device is not ready. |
|
|
1000
1000
|
| `backend-uncertain` | The player's backend blocked a filter stage; call `retry()`. |
|
|
1001
|
-
| `cleanup` | PackageTrial.stop could not confirm cleanup; call it again. |
|
|
1002
1001
|
| `limit` | A player-wide limit was reached (for example the number of commands). |
|
|
1003
|
-
| `reserved` | Packages.validate, PackageCandidate.
|
|
1002
|
+
| `reserved` | Packages.validate, PackageCandidate.install: the id is `vestra` or starts with `vestra.` (any case), which only the player's built-in plugins use (player 0.4.1). |
|
|
1004
1003
|
| `versionMismatch` | The package needs a newer player (`minPlayerVersion`) or another `apiVersion`. |
|
|
1005
1004
|
| `ui-prepare` | The player could not prepare a mounted panel or a settings page (its Slint file does not compile, a bound property is not declared, or another plugin's panel holds the slot); `message` has the diagnostics. |
|
|
1006
1005
|
| `ui` | The player could not apply a property to a tool window or a settings page, or could not build the Options window (a property the Slint file does not declare, a value of the wrong type); `message` has Slint's reason. |
|
|
@@ -1080,7 +1079,7 @@ interface Scope extends Disposable {
|
|
|
1080
1079
|
readonly media: MediaCapture;
|
|
1081
1080
|
/** Page-URL to stream-URL resolvers (`source.resolve`). */
|
|
1082
1081
|
readonly resolvers: Resolvers;
|
|
1083
|
-
/** The installed plugins; validation,
|
|
1082
|
+
/** The installed plugins; validation, installs (`plugins.manage` for changes). */
|
|
1084
1083
|
readonly packages: Packages;
|
|
1085
1084
|
/** On-screen messages over the video (`ui.osd`). */
|
|
1086
1085
|
readonly osd: Osd;
|
|
@@ -1174,8 +1173,7 @@ interface Plugin extends Scope {
|
|
|
1174
1173
|
readonly id: string;
|
|
1175
1174
|
/**
|
|
1176
1175
|
* A writable directory for this plugin's own files (settings, remembered
|
|
1177
|
-
* folders). Kept across restarts, disabling and upgrades
|
|
1178
|
-
* temporary one. Write files atomically: to a temporary name, then rename.
|
|
1176
|
+
* folders). Kept across restarts, disabling and upgrades. Write files atomically: to a temporary name, then rename.
|
|
1179
1177
|
* Throws `unavailable` when the player provided none.
|
|
1180
1178
|
*
|
|
1181
1179
|
* Location: `~/Library/Application Support/Vestra/plugin-data/<id>/` on
|
|
@@ -3450,6 +3448,20 @@ interface Resolvers {
|
|
|
3450
3448
|
* Register a resolver. When anyone opens a matching URL or file the player calls `resolve`; `signal` aborts when the open is cancelled or replaced. One scheme + host pair, and one extension, has one resolver (`conflict` otherwise). A rejected `resolve` fails the open with the error's message.
|
|
3451
3449
|
*/
|
|
3452
3450
|
add(options: ResolverOptions): Resolver;
|
|
3451
|
+
/**
|
|
3452
|
+
* The extensions some plugin's file resolver handles (SDK 1.5.8, player 0.4.2): lower case, without the dot, sorted, each once; not who registered them. Reading needs no capability; `[]` from older players. Without `events` it is the list from when the plugin first read it. Use it to accept such files beside the player's own media types, for example in a playlist's drop or folder scan.
|
|
3453
|
+
* @capability None.
|
|
3454
|
+
* @player 0.4.2
|
|
3455
|
+
*/
|
|
3456
|
+
readonly fileTypes: readonly string[];
|
|
3457
|
+
/**
|
|
3458
|
+
* Resolvers.fileTypes changed: a file resolver was added, or its plugin stopped. Needs `events`; without it the listener never fires.
|
|
3459
|
+
* @param listener - Receives the new list.
|
|
3460
|
+
* @returns A handle that removes the listener.
|
|
3461
|
+
* @capability `events`
|
|
3462
|
+
* @player 0.4.2
|
|
3463
|
+
*/
|
|
3464
|
+
onFileTypesChange(listener: (types: string[]) => void): Disposable;
|
|
3453
3465
|
}
|
|
3454
3466
|
```
|
|
3455
3467
|
|
|
@@ -3555,8 +3567,6 @@ interface PackageCandidate {
|
|
|
3555
3567
|
readonly manifest: PackageManifest;
|
|
3556
3568
|
/** The checks the player ran. */
|
|
3557
3569
|
readonly checks: string[];
|
|
3558
|
-
/** Load it temporarily alongside the installed plugins. A reserved id (`vestra.*`) fails with `reserved`. */
|
|
3559
|
-
trial(): Promise<PackageTrial>;
|
|
3560
3570
|
/**
|
|
3561
3571
|
* Install it. The same source upgrades the installed plugin (its data stays); another source fails with `conflict` unless `replace` is set; a built-in id fails with `unsupported` (built-ins update with the player). A reserved id (`vestra` or `vestra.*`, which every built-in uses) fails with `reserved` (player 0.4.1; already when validated), as only the player installs built-in plugins.
|
|
3562
3572
|
*/
|
|
@@ -3564,21 +3574,6 @@ interface PackageCandidate {
|
|
|
3564
3574
|
}
|
|
3565
3575
|
```
|
|
3566
3576
|
|
|
3567
|
-
### PackageTrial
|
|
3568
|
-
|
|
3569
|
-
A trial load of a package. Disposing it stops the trial and waits for cleanup.
|
|
3570
|
-
|
|
3571
|
-
```ts
|
|
3572
|
-
interface PackageTrial extends Disposable {
|
|
3573
|
-
/** The trial's status as the player reports it. */
|
|
3574
|
-
readonly status: string;
|
|
3575
|
-
/**
|
|
3576
|
-
* Stop and wait until the host confirmed cleanup. Rejects with code `cleanup` when the host stopped the trial without confirming cleanup; calling `stop()` again then asks the host again.
|
|
3577
|
-
*/
|
|
3578
|
-
stop(): Promise<void>;
|
|
3579
|
-
}
|
|
3580
|
-
```
|
|
3581
|
-
|
|
3582
3577
|
### PackageChangeStatus
|
|
3583
3578
|
|
|
3584
3579
|
The progress of an install, rollback or uninstall.
|
|
@@ -3617,8 +3612,8 @@ interface PluginInfo {
|
|
|
3617
3612
|
id: string;
|
|
3618
3613
|
/** Version, `null` when unknown. */
|
|
3619
3614
|
version: string | null;
|
|
3620
|
-
/** From the install library, a `--plugin` development load
|
|
3621
|
-
origin: "installed" | "development"
|
|
3615
|
+
/** From the install library, or a `--plugin` development load. */
|
|
3616
|
+
origin: "installed" | "development";
|
|
3622
3617
|
/** Run state. */
|
|
3623
3618
|
state: "starting" | "running" | "disabled" | "failed" | "stopped";
|
|
3624
3619
|
/** Why it failed (`state: "failed"`). */
|
|
@@ -3631,7 +3626,7 @@ interface PluginInfo {
|
|
|
3631
3626
|
previousVersion: string | null;
|
|
3632
3627
|
/** Shipped with the player: it can be disabled, not uninstalled or replaced. `false` from players before 0.2.0. */
|
|
3633
3628
|
builtin: boolean;
|
|
3634
|
-
/** Where an installed plugin came from; `null` for development loads
|
|
3629
|
+
/** Where an installed plugin came from; `null` for development loads. */
|
|
3635
3630
|
source: PackageSource | null;
|
|
3636
3631
|
/** Its data directory (`plugin.dataDirectory` of that plugin); `null` when the player has none. The path's layout is not part of the API. */
|
|
3637
3632
|
dataDirectory: string | null;
|
|
@@ -3667,7 +3662,13 @@ need no capability; everything else needs `plugins.manage`.
|
|
|
3667
3662
|
```ts
|
|
3668
3663
|
interface Packages {
|
|
3669
3664
|
/**
|
|
3670
|
-
* Validate a package directory (`plugins.manage`). A package whose id is
|
|
3665
|
+
* Validate a package directory (`plugins.manage`). A package whose id is
|
|
3666
|
+
* reserved for the built-in plugins (`vestra` or `vestra.*`) fails with
|
|
3667
|
+
* `reserved` (player 0.4.1).
|
|
3668
|
+
*
|
|
3669
|
+
* The player keeps the 32 most recent candidates of all plugins; an
|
|
3670
|
+
* older one's `install` fails with `stale` (validate again). Before
|
|
3671
|
+
* player 0.4.2 the 33rd validation of a run failed with `busy` instead.
|
|
3671
3672
|
*/
|
|
3672
3673
|
validate(directory: string): Promise<PackageCandidate>;
|
|
3673
3674
|
/**
|
|
@@ -3681,11 +3682,11 @@ interface Packages {
|
|
|
3681
3682
|
/** The new list after any start, stop, failure, install or uninstall. No capability. */
|
|
3682
3683
|
onChange(listener: (plugins: PluginInfo[]) => void): Disposable;
|
|
3683
3684
|
/**
|
|
3684
|
-
* Stop or start a plugin (`plugins.manage`). An installed plugin stays disabled across launches; a development load only for this run. `invalid` to disable oneself, `
|
|
3685
|
+
* Stop or start a plugin (`plugins.manage`). An installed plugin stays disabled across launches; a development load only for this run. `invalid` to disable oneself, `missing` unknown. `busy` while it is being installed.
|
|
3685
3686
|
*/
|
|
3686
3687
|
setEnabled(id: string, enabled: boolean): Promise<void>;
|
|
3687
3688
|
/**
|
|
3688
|
-
* Remove an installed plugin (`plugins.manage`); `removeData` also deletes its data directory, otherwise reinstalling it from the same source reconnects to its data. Its user bindings stay (as orphans). `unsupported` for built-ins
|
|
3689
|
+
* Remove an installed plugin (`plugins.manage`); `removeData` also deletes its data directory, otherwise reinstalling it from the same source reconnects to its data. Its user bindings stay (as orphans). `unsupported` for built-ins and development loads, `missing` unknown, `invalid` for oneself.
|
|
3689
3690
|
*/
|
|
3690
3691
|
uninstall(id: string, options?: {
|
|
3691
3692
|
removeData?: boolean;
|
|
@@ -4721,6 +4722,12 @@ class TestHost implements Transport {
|
|
|
4721
4722
|
/** Exact host names it resolves (a web resolver), with `schemes`. */
|
|
4722
4723
|
hosts?: string[];
|
|
4723
4724
|
}[];
|
|
4725
|
+
/** What `plugin.resolvers.fileTypes` reports: the extensions of this plugin's file resolvers and of TestHost.otherResolvers, sorted, each once. */
|
|
4726
|
+
fileTypes(): string[];
|
|
4727
|
+
/**
|
|
4728
|
+
* Push `resolvers.fileTypes` when TestHost.fileTypes changed: called after this plugin's resolvers come and go; call it yourself after changing TestHost.otherResolvers (another plugin started or stopped).
|
|
4729
|
+
*/
|
|
4730
|
+
updateFileTypes(): void;
|
|
4724
4731
|
/**
|
|
4725
4732
|
* File opens waiting for this plugin's file resolver (`extensions`), by request id: the path and how it was opened. Opening a matching path (`player.open`, TestHost.openElsewhere) sends `source.resolveRequest`; the answer selects the stream, fails the open or, once the source changed, is `stale`. Any later source change cancels it (`source.resolveCancel`). Web resolvers are not simulated: emit `source.resolveRequest` yourself.
|
|
4726
4733
|
*/
|
|
@@ -4905,7 +4912,7 @@ class TestHost implements Transport {
|
|
|
4905
4912
|
/** Its version, `null` when unknown. */
|
|
4906
4913
|
version: string | null;
|
|
4907
4914
|
/** Where it was loaded from. */
|
|
4908
|
-
origin: "installed" | "development"
|
|
4915
|
+
origin: "installed" | "development";
|
|
4909
4916
|
/** Its run state. */
|
|
4910
4917
|
state: "starting" | "running" | "disabled" | "failed" | "stopped";
|
|
4911
4918
|
/** Why it failed (`state: "failed"`). */
|
|
@@ -5059,7 +5066,7 @@ own sensible display cap well below `perPlugin`.
|
|
|
5059
5066
|
|
|
5060
5067
|
```ts
|
|
5061
5068
|
const COMMAND_LIMITS: {
|
|
5062
|
-
/** Commands one plugin instance may have at the same time (160).
|
|
5069
|
+
/** Commands one plugin instance may have at the same time (160). */
|
|
5063
5070
|
readonly perPlugin: 160;
|
|
5064
5071
|
/** Commands of every plugin plus the player's own, together (1024). */
|
|
5065
5072
|
readonly total: 1024;
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vestra Player SDK
|
|
2
2
|
|
|
3
|
-
> @vestra-player/sdk 1.5.
|
|
3
|
+
> @vestra-player/sdk 1.5.8: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. llms-full.txt, next to this file, has every declaration, the guides and the examples; L<n> below are its line numbers.
|
|
4
4
|
|
|
5
5
|
**How plugins run**
|
|
6
6
|
|
|
@@ -136,7 +136,7 @@ test("shows the position", async () => {
|
|
|
136
136
|
- `aborted`: An `AbortSignal` fired
|
|
137
137
|
- `timeout`: A time limit ran out
|
|
138
138
|
- `limit`: A player-wide limit was reached
|
|
139
|
-
- `reserved`: Packages.validate, PackageCandidate.
|
|
139
|
+
- `reserved`: Packages.validate, PackageCandidate.install: the id is `vestra` or starts with `vestra.`, which only the player's built-in plugins use
|
|
140
140
|
- `versionMismatch`: The package needs a newer player or another `apiVersion`
|
|
141
141
|
- `ui-prepare`: The player could not prepare a mounted panel or a settings page
|
|
142
142
|
- `ui`: The player could not apply a property to a tool window or a settings page, or could not build the Options window
|
|
@@ -150,54 +150,54 @@ test("shows the position", async () => {
|
|
|
150
150
|
- [Concept: Windows, panels, settings pages, dialogs and the OSD](https://vestrasys.github.io/vestra-player-sdk/documents/Windows__panels__settings_pages__dialogs_and_the_OSD.html): L281–626
|
|
151
151
|
- [How-to: Keep a slider responsive while dragging](https://vestrasys.github.io/vestra-player-sdk/documents/Keep_a_slider_responsive_while_dragging.html): L627–801
|
|
152
152
|
- [How-to: Remember a window's state between runs](https://vestrasys.github.io/vestra-player-sdk/documents/Remember_a_window_s_state_between_runs.html): L802–931
|
|
153
|
-
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L932–
|
|
154
|
-
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin):
|
|
153
|
+
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L932–4173
|
|
154
|
+
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin): L4174–5120
|
|
155
155
|
|
|
156
156
|
## API
|
|
157
157
|
|
|
158
158
|
Where each module of `plugin` is declared:
|
|
159
159
|
|
|
160
|
-
- Plugin
|
|
161
|
-
- plugin.commands: Commands
|
|
162
|
-
- plugin.windows: Windows
|
|
163
|
-
- plugin.panels: Panels
|
|
164
|
-
- plugin.dialogs: Dialogs
|
|
165
|
-
- plugin.player: Player
|
|
166
|
-
- plugin.video: VideoFilter
|
|
167
|
-
- plugin.audio: AudioFilter
|
|
168
|
-
- plugin.subtitles: Subtitles
|
|
169
|
-
- plugin.media: MediaCapture
|
|
170
|
-
- plugin.resolvers: Resolvers
|
|
171
|
-
- plugin.packages: Packages
|
|
172
|
-
- plugin.osd: Osd
|
|
173
|
-
- plugin.host: Host
|
|
174
|
-
- plugin.clipboard: Clipboard
|
|
175
|
-
- plugin.power: Power
|
|
176
|
-
- plugin.tools: Tools
|
|
177
|
-
- plugin.storage: Storage
|
|
178
|
-
- plugin.system: System
|
|
179
|
-
- plugin.secrets: Secrets
|
|
180
|
-
- plugin.window: MainWindow
|
|
181
|
-
- plugin.app: App
|
|
182
|
-
- plugin.bindings: Bindings
|
|
183
|
-
- plugin.ui: Ui
|
|
184
|
-
- plugin.timeline: Timeline
|
|
185
|
-
- plugin.drops: Drops
|
|
186
|
-
- plugin.appearance: Appearance
|
|
187
|
-
- plugin.settings: Settings
|
|
160
|
+
- Plugin L1151, Scope L1022, PluginError L974, Disposable L947
|
|
161
|
+
- plugin.commands: Commands L1347
|
|
162
|
+
- plugin.windows: Windows L1552
|
|
163
|
+
- plugin.panels: Panels L2003
|
|
164
|
+
- plugin.dialogs: Dialogs L2285
|
|
165
|
+
- plugin.player: Player L2724
|
|
166
|
+
- plugin.video: VideoFilter L2973, VideoView L3036, RenderOptions L3078
|
|
167
|
+
- plugin.audio: AudioFilter L2997
|
|
168
|
+
- plugin.subtitles: Subtitles L3176
|
|
169
|
+
- plugin.media: MediaCapture L3334
|
|
170
|
+
- plugin.resolvers: Resolvers L3439
|
|
171
|
+
- plugin.packages: Packages L3657
|
|
172
|
+
- plugin.osd: Osd L2910
|
|
173
|
+
- plugin.host: Host L2873
|
|
174
|
+
- plugin.clipboard: Clipboard L3770
|
|
175
|
+
- plugin.power: Power L3790
|
|
176
|
+
- plugin.tools: Tools L3823
|
|
177
|
+
- plugin.storage: Storage L3866
|
|
178
|
+
- plugin.system: System L3889
|
|
179
|
+
- plugin.secrets: Secrets L3907
|
|
180
|
+
- plugin.window: MainWindow L2126
|
|
181
|
+
- plugin.app: App L2207
|
|
182
|
+
- plugin.bindings: Bindings L3740
|
|
183
|
+
- plugin.ui: Ui L1439
|
|
184
|
+
- plugin.timeline: Timeline L2083
|
|
185
|
+
- plugin.drops: Drops L1849
|
|
186
|
+
- plugin.appearance: Appearance L1878
|
|
187
|
+
- plugin.settings: Settings L1936
|
|
188
188
|
|
|
189
189
|
## Examples
|
|
190
190
|
|
|
191
191
|
Complete plugins with tests, in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples) and in llms-full.txt:
|
|
192
192
|
|
|
193
|
-
- hello: The README Quick start: one menu command and an on-screen message.
|
|
194
|
-
- blur: A tool window with one slider that blurs the playing video.
|
|
195
|
-
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone.
|
|
196
|
-
- bookmark: A tool window bound to the playback state: remember the position and jump back to it.
|
|
197
|
-
- panel-menu: A sidebar panel with its own right-click menu.
|
|
198
|
-
- menus: Radio menu items, separator groups and a menu item whose title changes.
|
|
199
|
-
- theme-widgets: A tool window drawn with the player's theme and widgets.
|
|
200
|
-
- markdown: A window that shows a Markdown document.
|
|
193
|
+
- hello: The README Quick start: one menu command and an on-screen message. L5534–5623
|
|
194
|
+
- blur: A tool window with one slider that blurs the playing video. L5624–5885
|
|
195
|
+
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L5886–6218
|
|
196
|
+
- bookmark: A tool window bound to the playback state: remember the position and jump back to it. L6219–6403
|
|
197
|
+
- panel-menu: A sidebar panel with its own right-click menu. L6404–6609
|
|
198
|
+
- menus: Radio menu items, separator groups and a menu item whose title changes. L6610–6769
|
|
199
|
+
- theme-widgets: A tool window drawn with the player's theme and widgets. L6770–6933
|
|
200
|
+
- markdown: A window that shows a Markdown document. L6934–7127
|
|
201
201
|
|
|
202
202
|
## Optional
|
|
203
203
|
|