@owncast/plugin-sdk 0.5.0 → 0.10.1
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/README.md +6 -6
- package/bin/owncast-plugin.js +57 -250
- package/index.d.ts +181 -77
- package/index.js +221 -242
- package/package.json +2 -1
- package/scripts/postinstall.js +11 -66
- package/slug.js +26 -0
- package/testing.js +17 -50
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @owncast/plugin-sdk
|
|
2
2
|
|
|
3
|
-
SDK for authoring [Owncast](https://owncast.online) plugins in JavaScript or TypeScript. Plugins
|
|
3
|
+
SDK for authoring [Owncast](https://owncast.online) plugins in JavaScript or TypeScript. Plugins ship as source and run sandboxed inside the Owncast server, on a JavaScript engine the host embeds, so there's no wasm toolchain to install.
|
|
4
4
|
|
|
5
5
|
Most authors don't install this directly, instead, scaffold a new project with `npx create-owncast-plugin@latest <slug>` and the generated `package.json` already lists it as a dependency.
|
|
6
6
|
|
|
@@ -9,10 +9,10 @@ Most authors don't install this directly, instead, scaffold a new project with `
|
|
|
9
9
|
```sh
|
|
10
10
|
npx create-owncast-plugin@latest my-plugin
|
|
11
11
|
cd my-plugin
|
|
12
|
-
npm install # postinstall fetches the
|
|
13
|
-
npm run build #
|
|
14
|
-
npm
|
|
15
|
-
npm
|
|
12
|
+
npm install # postinstall fetches the prebuilt test/serve host binaries
|
|
13
|
+
npm run build # bundles src/plugin.{js,ts} into my-plugin.js
|
|
14
|
+
npm test # builds, then runs scenarios from __tests__/
|
|
15
|
+
npm run package # zips manifest + my-plugin.js + assets + icon.png into my-plugin.ocpkg
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Then install `my-plugin.ocpkg` in Owncast. From the admin's **Plugins** page click **Upload plugin** and pick the file, or copy it directly to the server's `data/plugins/` directory. Toggle **Enabled** on the plugin's row to load it.
|
|
@@ -43,7 +43,7 @@ Declare the permissions your plugin uses (`chat.send` for the example above) in
|
|
|
43
43
|
- `index.d.ts`, TypeScript declarations for editor autocomplete on every event payload and host API.
|
|
44
44
|
- `testing.js`, JS test API (`runScenarios`) for writing `__tests__/*.test.js` with the full ergonomics of JavaScript instead of static JSON.
|
|
45
45
|
- `bin/owncast-plugin`, CLI: `build`, `test`, `serve`, `package` subcommands.
|
|
46
|
-
- `scripts/postinstall.js`, downloads the
|
|
46
|
+
- `scripts/postinstall.js`, downloads the Go test/serve host binaries on install. Plugins ship as source and run on the engine the host embeds, so no wasm toolchain (`extism-js`, binaryen) is fetched (that's a maintainer-only dependency of the engine build).
|
|
47
47
|
|
|
48
48
|
## License
|
|
49
49
|
|
package/bin/owncast-plugin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// `owncast-plugin build` , bundle src/plugin.{js,ts} into <slug>.
|
|
3
|
-
// `owncast-plugin test` , run scenarios in __tests__/ against the
|
|
2
|
+
// `owncast-plugin build` , bundle src/plugin.{js,ts} into <slug>.js
|
|
3
|
+
// `owncast-plugin test` , run scenarios in __tests__/ against the plugin
|
|
4
4
|
// `owncast-plugin serve` , run a localhost dev HTTP server
|
|
5
5
|
// `owncast-plugin package`, produce a single-file <slug>.ocpkg suitable
|
|
6
6
|
// for distribution / installation
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// "Slug" is the plugin's identifier: lowercase, hyphenated, used in
|
|
9
9
|
// filenames, URL segments, and as the registry's primary key. Plugin
|
|
10
10
|
// authors set the human-readable display name via `name` in their
|
|
11
|
-
// manifest
|
|
11
|
+
// manifest. If they don't set `slug`, the CLI auto-derives it from
|
|
12
12
|
// `name`.
|
|
13
13
|
|
|
14
14
|
const fs = require("fs");
|
|
@@ -16,6 +16,7 @@ const path = require("path");
|
|
|
16
16
|
const { execFileSync } = require("child_process");
|
|
17
17
|
const esbuild = require("esbuild");
|
|
18
18
|
const JSZip = require("jszip");
|
|
19
|
+
const { slugify } = require("../slug");
|
|
19
20
|
|
|
20
21
|
const cmd = process.argv[2] || "build";
|
|
21
22
|
const restArgs = process.argv.slice(3);
|
|
@@ -25,57 +26,11 @@ function fail(e) {
|
|
|
25
26
|
process.exit(1);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
// toolchainEnv extends the current environment with the variables
|
|
29
|
-
// the dynamic linker needs to find `libbinaryen` next to `wasm-merge`
|
|
30
|
-
// and `wasm-opt` (which extism-js shells out to during the wasm
|
|
31
|
-
// pipeline). Linux uses LD_LIBRARY_PATH; macOS uses DYLD_LIBRARY_PATH
|
|
32
|
-
// plus DYLD_FALLBACK_LIBRARY_PATH (Apple Silicon strips
|
|
33
|
-
// DYLD_LIBRARY_PATH in some sandboxed contexts, the FALLBACK
|
|
34
|
-
// variant survives). Setting all three is safe on both OSes; the
|
|
35
|
-
// inactive ones are ignored. This is the difference between "build
|
|
36
|
-
// succeeds" and `library not loaded: @rpath/libbinaryen.dylib` on
|
|
37
|
-
// macOS.
|
|
38
|
-
function toolchainEnv(cache) {
|
|
39
|
-
const libDir = path.join(cache, "lib");
|
|
40
|
-
return {
|
|
41
|
-
...process.env,
|
|
42
|
-
PATH: `${cache}:${process.env.PATH}`,
|
|
43
|
-
LD_LIBRARY_PATH: `${libDir}:${process.env.LD_LIBRARY_PATH || ""}`,
|
|
44
|
-
DYLD_LIBRARY_PATH: `${libDir}:${process.env.DYLD_LIBRARY_PATH || ""}`,
|
|
45
|
-
DYLD_FALLBACK_LIBRARY_PATH: `${libDir}:${process.env.DYLD_FALLBACK_LIBRARY_PATH || "/usr/local/lib:/usr/lib"}`,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
29
|
// slugPattern matches a valid plugin slug: a lowercase letter
|
|
50
30
|
// followed by lowercase letters/digits/hyphens, up to 64 chars total.
|
|
51
31
|
// Same shape the host + SDK + registry all validate against.
|
|
52
32
|
const slugPattern = /^[a-z][a-z0-9-]{0,63}$/;
|
|
53
33
|
|
|
54
|
-
// slugify mirrors the host's Go slugify: ASCII letters and digits
|
|
55
|
-
// pass through lowercased; everything else collapses to a single
|
|
56
|
-
// hyphen; leading and trailing hyphens are trimmed.
|
|
57
|
-
// Non-ASCII names (e.g. "Café") degrade noisily (-> "caf"); plugins
|
|
58
|
-
// with accented or non-Latin display names should pin `slug` in the
|
|
59
|
-
// manifest instead of relying on auto-derivation.
|
|
60
|
-
function slugify(input) {
|
|
61
|
-
let out = "";
|
|
62
|
-
let prevHyphen = false;
|
|
63
|
-
for (const ch of input) {
|
|
64
|
-
const code = ch.codePointAt(0);
|
|
65
|
-
let lower = ch;
|
|
66
|
-
if (code >= 65 && code <= 90) lower = String.fromCodePoint(code + 32);
|
|
67
|
-
const lc = lower.codePointAt(0);
|
|
68
|
-
if ((lc >= 97 && lc <= 122) || (lc >= 48 && lc <= 57)) {
|
|
69
|
-
out += lower;
|
|
70
|
-
prevHyphen = false;
|
|
71
|
-
} else if (!prevHyphen && out.length > 0) {
|
|
72
|
-
out += "-";
|
|
73
|
-
prevHyphen = true;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return out.replace(/-+$/, "");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
34
|
// readAndResolveManifest loads plugin.manifest.json, validates the
|
|
80
35
|
// required fields, and returns a manifest object with `slug` filled
|
|
81
36
|
// in: either the author's explicit `slug`, or one auto-derived from
|
|
@@ -127,17 +82,35 @@ function runBinary(name, args) {
|
|
|
127
82
|
);
|
|
128
83
|
process.exit(1);
|
|
129
84
|
}
|
|
130
|
-
const env = toolchainEnv(cache);
|
|
131
85
|
try {
|
|
132
86
|
execFileSync(bin, args.length > 0 ? args : [process.cwd()], {
|
|
133
87
|
stdio: "inherit",
|
|
134
|
-
env,
|
|
135
88
|
});
|
|
136
89
|
} catch (e) {
|
|
137
90
|
process.exit(typeof e.status === "number" ? e.status : 1);
|
|
138
91
|
}
|
|
139
92
|
}
|
|
140
93
|
|
|
94
|
+
// loadCheck runs `owncast-plugin-test --load-only <dir>` and aborts the
|
|
95
|
+
// current command when the plugin fails the install-time load check.
|
|
96
|
+
function loadCheck(dir) {
|
|
97
|
+
const cache = findCacheDir();
|
|
98
|
+
const bin = path.join(cache, "owncast-plugin-test");
|
|
99
|
+
if (!fs.existsSync(bin)) {
|
|
100
|
+
console.error(
|
|
101
|
+
`owncast-plugin-test not found at ${bin} — cannot verify the plugin ` +
|
|
102
|
+
`loads. Run npm install so the SDK postinstall fetches the host binaries.`,
|
|
103
|
+
);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
execFileSync(bin, ["--load-only", dir], { stdio: "inherit" });
|
|
108
|
+
} catch (e) {
|
|
109
|
+
console.error("package aborted: plugin failed the install-time load check");
|
|
110
|
+
process.exit(typeof e.status === "number" ? e.status : 1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
141
114
|
async function buildMain() {
|
|
142
115
|
const cwd = process.cwd();
|
|
143
116
|
const manifestPath = path.join(cwd, "plugin.manifest.json");
|
|
@@ -166,99 +139,32 @@ async function buildMain() {
|
|
|
166
139
|
"no plugin source found (expected src/plugin.ts or plugin.js)",
|
|
167
140
|
);
|
|
168
141
|
|
|
169
|
-
//
|
|
170
|
-
//
|
|
142
|
+
// Shared-engine model: bundle the author's plugin into a tiny CommonJS
|
|
143
|
+
// script with @owncast/plugin-sdk marked EXTERNAL. It ships in the .ocpkg as
|
|
144
|
+
// plugin.js. The host infers the JavaScript runtime from that filename and
|
|
145
|
+
// runs it on the embedded JS engine, which provides
|
|
146
|
+
// require("@owncast/plugin-sdk"). No per-plugin wasm, no extism-js.
|
|
171
147
|
const buildDir = path.join(cwd, ".owncast-build");
|
|
172
148
|
fs.mkdirSync(buildDir, { recursive: true });
|
|
173
|
-
const
|
|
174
|
-
const manifestJSON = JSON.stringify(manifest);
|
|
175
|
-
// Always emit register/on_event/on_filter as wasm exports. The SDK derives
|
|
176
|
-
// subscriptions at runtime from the plugin's handler methods and merges
|
|
177
|
-
// them into the manifest returned by register(). The host then only calls
|
|
178
|
-
// on_event/on_filter for plugins actually subscribed to that event, so
|
|
179
|
-
// unused exports are harmless.
|
|
180
|
-
const entrySrc = `const sdk = require("@owncast/plugin-sdk");
|
|
181
|
-
const MANIFEST_BASE = ${manifestJSON};
|
|
182
|
-
require(${JSON.stringify(entry)});
|
|
183
|
-
function register() {
|
|
184
|
-
const manifest = Object.assign({}, MANIFEST_BASE, { subscriptions: sdk.describeSubscriptions() });
|
|
185
|
-
Host.outputString(JSON.stringify(manifest));
|
|
186
|
-
return 0;
|
|
187
|
-
}
|
|
188
|
-
function on_event() {
|
|
189
|
-
const envelope = JSON.parse(Host.inputString());
|
|
190
|
-
sdk.dispatchEvent(envelope);
|
|
191
|
-
return 0;
|
|
192
|
-
}
|
|
193
|
-
function on_filter() {
|
|
194
|
-
const envelope = JSON.parse(Host.inputString());
|
|
195
|
-
const result = sdk.dispatchFilter(envelope);
|
|
196
|
-
Host.outputString(JSON.stringify(result));
|
|
197
|
-
return 0;
|
|
198
|
-
}
|
|
199
|
-
function on_http_request() {
|
|
200
|
-
const request = JSON.parse(Host.inputString());
|
|
201
|
-
const response = sdk.dispatchHttp(request);
|
|
202
|
-
Host.outputString(JSON.stringify(response));
|
|
203
|
-
return 0;
|
|
204
|
-
}
|
|
205
|
-
function on_tab_content() {
|
|
206
|
-
const req = JSON.parse(Host.inputString());
|
|
207
|
-
Host.outputString(sdk.dispatchTabContent(req));
|
|
208
|
-
return 0;
|
|
209
|
-
}
|
|
210
|
-
function on_page_content() {
|
|
211
|
-
const req = JSON.parse(Host.inputString());
|
|
212
|
-
Host.outputString(sdk.dispatchPageContent(req));
|
|
213
|
-
return 0;
|
|
214
|
-
}
|
|
215
|
-
module.exports = { register, on_event, on_filter, on_http_request, on_tab_content, on_page_content };
|
|
216
|
-
`;
|
|
217
|
-
fs.writeFileSync(synthEntry, entrySrc);
|
|
218
|
-
|
|
219
|
-
// Bundle to a single CJS file targeting the QuickJS runtime extism-js uses.
|
|
220
|
-
const bundledJS = path.join(buildDir, "bundle.js");
|
|
149
|
+
const scriptOut = path.join(cwd, `${slug}.js`);
|
|
221
150
|
await esbuild.build({
|
|
222
|
-
entryPoints: [
|
|
151
|
+
entryPoints: [entry],
|
|
223
152
|
bundle: true,
|
|
224
153
|
format: "cjs",
|
|
225
154
|
platform: "neutral",
|
|
226
155
|
target: "es2020",
|
|
227
|
-
|
|
156
|
+
external: ["@owncast/plugin-sdk"],
|
|
157
|
+
outfile: scriptOut,
|
|
228
158
|
logLevel: "warning",
|
|
229
159
|
});
|
|
230
160
|
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
fs.writeFileSync(dts, generateInterface(manifest));
|
|
234
|
-
|
|
235
|
-
// Find toolchain.
|
|
236
|
-
const cache = findCacheDir();
|
|
237
|
-
const extismJs = path.join(cache, "extism-js");
|
|
238
|
-
if (!fs.existsSync(extismJs)) {
|
|
239
|
-
throw new Error(
|
|
240
|
-
`extism-js not found at ${extismJs}, run \`npm install\` to fetch the toolchain`,
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
const env = toolchainEnv(cache);
|
|
244
|
-
|
|
245
|
-
const wasmOut = path.join(cwd, `${slug}.wasm`);
|
|
246
|
-
execFileSync(extismJs, [bundledJS, "-i", dts, "-o", wasmOut], {
|
|
247
|
-
stdio: "inherit",
|
|
248
|
-
env,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// public/ and assets/ live at the source root; the host's
|
|
252
|
-
// loose-files loader picks them up as siblings of the built
|
|
253
|
-
// <slug>.wasm without any rename, so the build CLI doesn't need to
|
|
254
|
-
// create or mirror anything for them.
|
|
255
|
-
|
|
256
|
-
console.log(`built ${path.relative(cwd, wasmOut)}`);
|
|
161
|
+
// public/ and assets/ live at the source root, and the packager picks them up.
|
|
162
|
+
console.log(`built ${path.relative(cwd, scriptOut)}`);
|
|
257
163
|
}
|
|
258
164
|
|
|
259
165
|
// `owncast-plugin package`, bundle the project into a single .ocpkg file
|
|
260
|
-
// (zip archive with plugin.manifest.json, plugin.
|
|
261
|
-
// public/ and assets/ directories). Builds the
|
|
166
|
+
// (zip archive with plugin.manifest.json, plugin.js source, and optional
|
|
167
|
+
// public/ and assets/ directories). Builds the source first if it
|
|
262
168
|
// doesn't exist.
|
|
263
169
|
async function packageMain() {
|
|
264
170
|
const cwd = process.cwd();
|
|
@@ -269,16 +175,25 @@ async function packageMain() {
|
|
|
269
175
|
const manifest = readAndResolveManifest(manifestPath);
|
|
270
176
|
const slug = manifest.slug;
|
|
271
177
|
|
|
272
|
-
const
|
|
273
|
-
if (!fs.existsSync(
|
|
178
|
+
const scriptPath = path.join(cwd, `${slug}.js`);
|
|
179
|
+
if (!fs.existsSync(scriptPath)) {
|
|
274
180
|
await buildMain();
|
|
275
181
|
}
|
|
276
182
|
|
|
183
|
+
// Refuse to package a plugin a real Owncast server would refuse to load.
|
|
184
|
+
// owncast-plugin-test --load-only runs the same install-time load path the
|
|
185
|
+
// host runs: register(), manifest/runtime agreement, and permission-gated
|
|
186
|
+
// subscriptions (e.g. a fediverse handler without "fediverse.inbound").
|
|
187
|
+
loadCheck(cwd);
|
|
188
|
+
|
|
189
|
+
// The code entry's name (plugin.js) is what tells the host this is a
|
|
190
|
+
// JavaScript plugin, so there is no "type" field in the manifest. The
|
|
191
|
+
// manifest ships verbatim.
|
|
277
192
|
const publicDir = path.join(cwd, "public");
|
|
278
193
|
const assetsDir = path.join(cwd, "assets");
|
|
279
194
|
const zip = new JSZip();
|
|
280
195
|
zip.file("plugin.manifest.json", fs.readFileSync(manifestPath));
|
|
281
|
-
zip.file("plugin.
|
|
196
|
+
zip.file("plugin.js", fs.readFileSync(scriptPath));
|
|
282
197
|
let fileCount = 2;
|
|
283
198
|
// Bundle a top-level icon.png if the plugin source root has one.
|
|
284
199
|
// The host reads it from /api/plugins/<slug>/icon to render in the
|
|
@@ -291,7 +206,7 @@ async function packageMain() {
|
|
|
291
206
|
}
|
|
292
207
|
// Bundle a top-level INSTRUCTIONS.md if the plugin source root has one.
|
|
293
208
|
// The host serves it to the admin (which renders it as markdown in a
|
|
294
|
-
// details tab)
|
|
209
|
+
// details tab). Like icon.png it needs no manifest field and no
|
|
295
210
|
// http.serve permission. The filename is fixed for simplicity.
|
|
296
211
|
const instructionsPath = path.join(cwd, "INSTRUCTIONS.md");
|
|
297
212
|
if (fs.existsSync(instructionsPath) && fs.statSync(instructionsPath).isFile()) {
|
|
@@ -329,19 +244,19 @@ async function packageMain() {
|
|
|
329
244
|
`packaged ${path.relative(cwd, outPath)} (${sizeKb} KB, ${fileCount} files)`,
|
|
330
245
|
);
|
|
331
246
|
|
|
332
|
-
// Drop the intermediate <slug>.
|
|
247
|
+
// Drop the intermediate <slug>.js now that it's bundled inside the
|
|
333
248
|
// .ocpkg. The .ocpkg is the only artifact authors care about: leaving
|
|
334
|
-
// the loose
|
|
249
|
+
// the loose script next to it just confuses "what do I ship". Only
|
|
335
250
|
// runs on a successful package so a mid-pipeline failure leaves the
|
|
336
251
|
// last good build in place for debugging.
|
|
337
252
|
try {
|
|
338
|
-
fs.unlinkSync(
|
|
253
|
+
fs.unlinkSync(scriptPath);
|
|
339
254
|
} catch (e) {
|
|
340
255
|
// Don't fail the package step over a cleanup miss. The .ocpkg is
|
|
341
|
-
// already written
|
|
256
|
+
// already written, so surface the warning so the author notices the
|
|
342
257
|
// straggler but treat the run as successful.
|
|
343
258
|
if (e.code !== "ENOENT") {
|
|
344
|
-
console.warn(`warning: could not clean up ${path.relative(cwd,
|
|
259
|
+
console.warn(`warning: could not clean up ${path.relative(cwd, scriptPath)}: ${e.message}`);
|
|
345
260
|
}
|
|
346
261
|
}
|
|
347
262
|
}
|
|
@@ -366,112 +281,6 @@ function* walkFiles(dir) {
|
|
|
366
281
|
}
|
|
367
282
|
}
|
|
368
283
|
|
|
369
|
-
function generateInterface(manifest) {
|
|
370
|
-
const exports = [
|
|
371
|
-
"register(): I32",
|
|
372
|
-
"on_event(): I32",
|
|
373
|
-
"on_filter(): I32",
|
|
374
|
-
"on_http_request(): I32",
|
|
375
|
-
"on_tab_content(): I32",
|
|
376
|
-
"on_page_content(): I32",
|
|
377
|
-
];
|
|
378
|
-
|
|
379
|
-
const perms = new Set(manifest.permissions || []);
|
|
380
|
-
const imports = [];
|
|
381
|
-
// Timers are ambient (no permission): the host always provides them, since
|
|
382
|
-
// a plugin can't setTimeout in the sandbox.
|
|
383
|
-
imports.push("owncast_timer_set(id: I64, delayMs: I64, repeat: I32): I32");
|
|
384
|
-
imports.push("owncast_timer_clear(id: I64): void");
|
|
385
|
-
// Config is ambient too: a plugin reading its own manifest-declared config
|
|
386
|
-
// (admin override falling back to the declared default) needs no permission.
|
|
387
|
-
imports.push("owncast_config_get(keyPtr: PTR): PTR");
|
|
388
|
-
// Asset reading is ambient: a plugin reads only files it shipped itself.
|
|
389
|
-
imports.push("owncast_asset_read(pathPtr: PTR): PTR");
|
|
390
|
-
if (perms.has("chat.send")) {
|
|
391
|
-
imports.push("owncast_send_chat(textPtr: PTR): void");
|
|
392
|
-
imports.push("owncast_send_chat_action(textPtr: PTR): void");
|
|
393
|
-
imports.push("owncast_send_chat_system(bodyPtr: PTR): void");
|
|
394
|
-
imports.push("owncast_send_chat_to(clientId: I64, textPtr: PTR): void");
|
|
395
|
-
}
|
|
396
|
-
if (perms.has("chat.history")) {
|
|
397
|
-
imports.push("owncast_chat_history(limit: I32): PTR");
|
|
398
|
-
imports.push("owncast_chat_clients(): PTR");
|
|
399
|
-
}
|
|
400
|
-
if (perms.has("chat.moderate")) {
|
|
401
|
-
imports.push("owncast_delete_message(idPtr: PTR): void");
|
|
402
|
-
imports.push("owncast_kick_client(clientId: I64): void");
|
|
403
|
-
}
|
|
404
|
-
if (perms.has("notifications.send")) {
|
|
405
|
-
imports.push("owncast_notify_discord(textPtr: PTR): void");
|
|
406
|
-
imports.push("owncast_notify_browser_push(payloadPtr: PTR): void");
|
|
407
|
-
imports.push("owncast_notify_fediverse(payloadPtr: PTR): void");
|
|
408
|
-
}
|
|
409
|
-
if (perms.has("users.read")) {
|
|
410
|
-
imports.push("owncast_users_list(): PTR");
|
|
411
|
-
imports.push("owncast_user_get(idPtr: PTR): PTR");
|
|
412
|
-
}
|
|
413
|
-
if (perms.has("users.moderate")) {
|
|
414
|
-
imports.push(
|
|
415
|
-
"owncast_user_set_enabled(idPtr: PTR, enabled: I32, reasonPtr: PTR): void",
|
|
416
|
-
);
|
|
417
|
-
imports.push("owncast_ban_ip(ipPtr: PTR): void");
|
|
418
|
-
}
|
|
419
|
-
if (perms.has("storage.upload")) {
|
|
420
|
-
imports.push("owncast_storage_upload(namePtr: PTR, dataPtr: PTR): PTR");
|
|
421
|
-
}
|
|
422
|
-
if (perms.has("storage.fs")) {
|
|
423
|
-
imports.push("owncast_fs_read(pathPtr: PTR): PTR");
|
|
424
|
-
imports.push("owncast_fs_write(pathPtr: PTR, dataPtr: PTR): PTR");
|
|
425
|
-
imports.push("owncast_fs_list(dirPtr: PTR): PTR");
|
|
426
|
-
imports.push("owncast_fs_delete(pathPtr: PTR): PTR");
|
|
427
|
-
imports.push("owncast_fs_exists(pathPtr: PTR): I32");
|
|
428
|
-
}
|
|
429
|
-
if (perms.has("fediverse.post")) {
|
|
430
|
-
imports.push("owncast_fediverse_post(textPtr: PTR): PTR");
|
|
431
|
-
}
|
|
432
|
-
if (perms.has("storage.kv")) {
|
|
433
|
-
imports.push("owncast_kv_get(keyPtr: PTR): PTR");
|
|
434
|
-
imports.push("owncast_kv_set(keyPtr: PTR, valPtr: PTR): void");
|
|
435
|
-
}
|
|
436
|
-
if (perms.has("events.emit"))
|
|
437
|
-
imports.push(
|
|
438
|
-
"owncast_emit_event(eventTypePtr: PTR, payloadPtr: PTR): void",
|
|
439
|
-
);
|
|
440
|
-
if (perms.has("http.sse"))
|
|
441
|
-
imports.push(
|
|
442
|
-
"owncast_sse_send(channelPtr: PTR, eventPtr: PTR, dataPtr: PTR): void",
|
|
443
|
-
);
|
|
444
|
-
if (perms.has("server.read")) {
|
|
445
|
-
imports.push("owncast_stream_current(): PTR");
|
|
446
|
-
imports.push("owncast_server_info(): PTR");
|
|
447
|
-
imports.push("owncast_server_socials(): PTR");
|
|
448
|
-
imports.push("owncast_server_emotes(): PTR");
|
|
449
|
-
imports.push("owncast_server_federation(): PTR");
|
|
450
|
-
imports.push("owncast_stream_broadcaster(): PTR");
|
|
451
|
-
imports.push("owncast_server_tags(): PTR");
|
|
452
|
-
}
|
|
453
|
-
if (perms.has("videoconfig.read")) {
|
|
454
|
-
imports.push("owncast_video_config_read(): PTR");
|
|
455
|
-
}
|
|
456
|
-
if (perms.has("videoconfig.write")) {
|
|
457
|
-
imports.push("owncast_video_config_write(configPtr: PTR): PTR");
|
|
458
|
-
}
|
|
459
|
-
if (perms.has("ui.modify")) {
|
|
460
|
-
imports.push("owncast_add_actions(actionsPtr: PTR): void");
|
|
461
|
-
imports.push("owncast_clear_actions(): void");
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
let out = `declare module 'main' {\n`;
|
|
465
|
-
for (const e of exports) out += ` export function ${e};\n`;
|
|
466
|
-
out += `}\n`;
|
|
467
|
-
if (imports.length > 0) {
|
|
468
|
-
out += `\ndeclare module 'extism:host' {\n interface user {\n`;
|
|
469
|
-
for (const i of imports) out += ` ${i};\n`;
|
|
470
|
-
out += ` }\n}\n`;
|
|
471
|
-
}
|
|
472
|
-
return out;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
284
|
function findCacheDir() {
|
|
476
285
|
// Look in node_modules/@owncast/plugin-sdk/bin/.cache (when used as a dep)
|
|
477
286
|
// and in the repo's tools/ dir (when developing). The dev candidate
|
|
@@ -482,12 +291,10 @@ function findCacheDir() {
|
|
|
482
291
|
path.join(__dirname, "..", "bin", ".cache"),
|
|
483
292
|
path.join(__dirname, "..", "..", "..", "tools"),
|
|
484
293
|
];
|
|
485
|
-
// Pick the first candidate that has
|
|
486
|
-
//
|
|
487
|
-
// owncast-plugin-test) but they share a cache.
|
|
294
|
+
// Pick the first candidate that has the prebuilt host binaries (the only
|
|
295
|
+
// tooling the SDK ships now, since `build` is pure esbuild and needs nothing here).
|
|
488
296
|
for (const c of candidates) {
|
|
489
297
|
if (
|
|
490
|
-
fs.existsSync(path.join(c, "extism-js")) ||
|
|
491
298
|
fs.existsSync(path.join(c, "owncast-plugin-test")) ||
|
|
492
299
|
fs.existsSync(path.join(c, "owncast-plugin-serve"))
|
|
493
300
|
) {
|