fluxplugin 1.0.0
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/LICENSE +9 -0
- package/README.md +50 -0
- package/dist/browser/chunks/chunk-Q75AOCXD.js +2544 -0
- package/dist/browser/chunks/chunk-ZZ25CRMR.js +759 -0
- package/dist/browser/index.js +138 -0
- package/dist/browser/manager-ui.js +165 -0
- package/dist/browser/react.js +73 -0
- package/dist/cli.js +1635 -0
- package/dist/node/chunks/chunk-BZ7NZF7I.js +990 -0
- package/dist/node/chunks/chunk-XPLGAEBH.js +2623 -0
- package/dist/node/cli-api.js +843 -0
- package/dist/node/index.js +138 -0
- package/dist/node/main.js +61 -0
- package/dist/node/preload.js +56 -0
- package/dist/types/cli/bin.d.ts +1 -0
- package/dist/types/cli/build.d.ts +7 -0
- package/dist/types/cli/cli.d.ts +2 -0
- package/dist/types/cli/config.d.ts +9 -0
- package/dist/types/cli/host-templates.d.ts +6 -0
- package/dist/types/cli/host.d.ts +16 -0
- package/dist/types/cli/index.d.ts +6 -0
- package/dist/types/cli/shared-modules.d.ts +8 -0
- package/dist/types/cli/templates.d.ts +10 -0
- package/dist/types/core/api.d.ts +11 -0
- package/dist/types/core/common.d.ts +69 -0
- package/dist/types/core/deps.d.ts +34 -0
- package/dist/types/core/events.d.ts +58 -0
- package/dist/types/core/hooks.d.ts +55 -0
- package/dist/types/core/index.d.ts +18 -0
- package/dist/types/core/ipc.d.ts +148 -0
- package/dist/types/core/manager.d.ts +155 -0
- package/dist/types/core/manifest.d.ts +88 -0
- package/dist/types/core/permissions.d.ts +45 -0
- package/dist/types/core/plugin.d.ts +214 -0
- package/dist/types/core/protocol.d.ts +86 -0
- package/dist/types/core/registry.d.ts +88 -0
- package/dist/types/core/routes.d.ts +105 -0
- package/dist/types/core/schema.d.ts +99 -0
- package/dist/types/core/semver.d.ts +16 -0
- package/dist/types/core/slots.d.ts +75 -0
- package/dist/types/core/storage.d.ts +55 -0
- package/dist/types/core/styles.d.ts +34 -0
- package/dist/types/main/adapters.d.ts +81 -0
- package/dist/types/main/discover.d.ts +21 -0
- package/dist/types/main/framework.d.ts +78 -0
- package/dist/types/main/fs-storage.d.ts +33 -0
- package/dist/types/main/index.d.ts +8 -0
- package/dist/types/main/registry.d.ts +75 -0
- package/dist/types/main/security.d.ts +24 -0
- package/dist/types/main/serve-web.d.ts +14 -0
- package/dist/types/main/tar.d.ts +10 -0
- package/dist/types/manager-ui/index.d.ts +7 -0
- package/dist/types/preload/index.d.ts +27 -0
- package/dist/types/react/base-css.d.ts +2 -0
- package/dist/types/react/components.d.ts +89 -0
- package/dist/types/react/http-bridge.d.ts +10 -0
- package/dist/types/react/index.d.ts +6 -0
- package/dist/types/react/palette.d.ts +10 -0
- package/dist/types/react/router.d.ts +47 -0
- package/dist/types/react/runtime.d.ts +99 -0
- package/docs/ARCHITECTURE.md +31 -0
- package/docs/PLUGIN-AUTHORS.md +11 -0
- package/docs/RELEASE.md +20 -0
- package/docs/SECURITY.md +18 -0
- package/docs/screenshots/command-palette.png +0 -0
- package/docs/screenshots/dashboard.png +0 -0
- package/docs/screenshots/plugin-details.png +0 -0
- package/docs/screenshots/plugin-manager.png +0 -0
- package/docs/screenshots/stats.png +0 -0
- package/package.json +112 -0
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MGMT,
|
|
3
|
+
PermissionManager,
|
|
4
|
+
PluginManager,
|
|
5
|
+
errorMessage,
|
|
6
|
+
normalizePath,
|
|
7
|
+
scopeCss
|
|
8
|
+
} from "./chunk-Q75AOCXD.js";
|
|
9
|
+
|
|
10
|
+
// src/react/runtime.ts
|
|
11
|
+
var RemoteError = class extends Error {
|
|
12
|
+
constructor(payload) {
|
|
13
|
+
super(payload.message);
|
|
14
|
+
this.payload = payload;
|
|
15
|
+
this.name = "RemoteError";
|
|
16
|
+
}
|
|
17
|
+
payload;
|
|
18
|
+
get code() {
|
|
19
|
+
return this.payload.code;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var PluginsClient = class {
|
|
23
|
+
constructor(bridge) {
|
|
24
|
+
this.bridge = bridge;
|
|
25
|
+
}
|
|
26
|
+
bridge;
|
|
27
|
+
call(channel, input) {
|
|
28
|
+
return this.bridge.invoke(channel, input);
|
|
29
|
+
}
|
|
30
|
+
list = () => this.call(MGMT.list);
|
|
31
|
+
enable = (id) => this.call(MGMT.enable, { id });
|
|
32
|
+
disable = (id) => this.call(MGMT.disable, { id });
|
|
33
|
+
reload = (id) => this.call(MGMT.reload, { id });
|
|
34
|
+
grant = (id, permission) => this.call(MGMT.grant, { id, permission });
|
|
35
|
+
revoke = (id, permission) => this.call(MGMT.revoke, { id, permission });
|
|
36
|
+
uninstall = (id) => this.call(MGMT.uninstall, { id });
|
|
37
|
+
search = (query) => this.call(MGMT.search, { query });
|
|
38
|
+
updates = () => this.call(MGMT.updates);
|
|
39
|
+
install = (entry) => this.call(MGMT.install, { entry });
|
|
40
|
+
update = (entry) => this.call(MGMT.update, { entry });
|
|
41
|
+
rollback = (id, version) => this.call(MGMT.rollback, { id, version });
|
|
42
|
+
configGet = (id) => this.call(MGMT.configGet, { id });
|
|
43
|
+
configSet = (id, patch) => this.call(MGMT.configSet, { id, patch });
|
|
44
|
+
configReset = (id) => this.call(MGMT.configReset, { id });
|
|
45
|
+
configSchema = (id) => this.call(MGMT.configSchema, { id });
|
|
46
|
+
logs = (id) => this.call(MGMT.logs, { id });
|
|
47
|
+
diagnostics = (id) => this.call(MGMT.diagnostics, { id });
|
|
48
|
+
};
|
|
49
|
+
var asDefinition = (mod) => {
|
|
50
|
+
const d = mod && typeof mod === "object" && "default" in mod ? mod.default : mod;
|
|
51
|
+
if (!d || typeof d !== "object") throw new Error("renderer entry must default-export definePlugin({...})");
|
|
52
|
+
return d;
|
|
53
|
+
};
|
|
54
|
+
var RendererFramework = class {
|
|
55
|
+
constructor(opts) {
|
|
56
|
+
this.opts = opts;
|
|
57
|
+
this.bridge = opts.bridge;
|
|
58
|
+
this.client = new PluginsClient(opts.bridge);
|
|
59
|
+
this.manager = new PluginManager({
|
|
60
|
+
runtime: "renderer",
|
|
61
|
+
permissions: this.permissions,
|
|
62
|
+
ipcBridge: {
|
|
63
|
+
invoke: (name, input, caller) => opts.bridge.invoke(name, input, caller),
|
|
64
|
+
send: (channel, payload, caller) => opts.bridge.send(channel, payload, caller),
|
|
65
|
+
on: (channel, fn) => ({ dispose: opts.bridge.on(channel, fn) })
|
|
66
|
+
},
|
|
67
|
+
// Renderer plugins that crash must never take the app down; the main side persists auto-disable.
|
|
68
|
+
...opts.manager
|
|
69
|
+
});
|
|
70
|
+
this.manager.subscribe(() => this.touch());
|
|
71
|
+
}
|
|
72
|
+
opts;
|
|
73
|
+
bridge;
|
|
74
|
+
client;
|
|
75
|
+
manager;
|
|
76
|
+
permissions = new PermissionManager({ autoGrant: false });
|
|
77
|
+
revision = -1;
|
|
78
|
+
bootstrap;
|
|
79
|
+
syncing = Promise.resolve();
|
|
80
|
+
renderErrors = /* @__PURE__ */ new Map();
|
|
81
|
+
unsubscribers = [];
|
|
82
|
+
storeListeners = /* @__PURE__ */ new Set();
|
|
83
|
+
_storeVersion = 0;
|
|
84
|
+
/** `useSyncExternalStore` plumbing for the bootstrap-derived view (plugin list etc.). */
|
|
85
|
+
get storeVersion() {
|
|
86
|
+
return this._storeVersion + this.manager.version;
|
|
87
|
+
}
|
|
88
|
+
subscribe = (l) => {
|
|
89
|
+
this.storeListeners.add(l);
|
|
90
|
+
const off = this.manager.subscribe(l);
|
|
91
|
+
return () => {
|
|
92
|
+
this.storeListeners.delete(l);
|
|
93
|
+
off();
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
touch() {
|
|
97
|
+
this._storeVersion++;
|
|
98
|
+
for (const l of [...this.storeListeners]) l();
|
|
99
|
+
}
|
|
100
|
+
get currentBootstrap() {
|
|
101
|
+
return this.bootstrap;
|
|
102
|
+
}
|
|
103
|
+
/** Fetches the bootstrap, activates enabled plugins and starts listening for changes. */
|
|
104
|
+
async start() {
|
|
105
|
+
await this.sync();
|
|
106
|
+
this.unsubscribers.push(
|
|
107
|
+
this.bridge.on(MGMT.changed, () => {
|
|
108
|
+
void this.sync().catch((e) => console.error("[fluxplugin] sync failed", e));
|
|
109
|
+
}),
|
|
110
|
+
this.bridge.on("flux.event.plugin.hotreload", ({ id } = {}) => void this.hotReload(id))
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
stop() {
|
|
114
|
+
for (const u of this.unsubscribers.splice(0)) u();
|
|
115
|
+
}
|
|
116
|
+
hotReload(id) {
|
|
117
|
+
this.cacheBust = Date.now();
|
|
118
|
+
return this.sync(true).then(async () => {
|
|
119
|
+
if (id && this.manager.has(id)) await this.manager.reload(id).catch(() => void 0);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
cacheBust = 0;
|
|
123
|
+
/** Mirrors main-process state into the renderer manager. Calls are serialized. */
|
|
124
|
+
sync(force = false) {
|
|
125
|
+
const run = this.syncing.then(async () => {
|
|
126
|
+
const boot = await this.bridge.bootstrap();
|
|
127
|
+
if (!force && boot.revision === this.revision && this.bootstrap) return;
|
|
128
|
+
this.revision = boot.revision;
|
|
129
|
+
this.bootstrap = boot;
|
|
130
|
+
await this.reconcile(boot);
|
|
131
|
+
this.installWindowPlugins(boot.preload);
|
|
132
|
+
this.touch();
|
|
133
|
+
});
|
|
134
|
+
this.syncing = run.catch(() => void 0);
|
|
135
|
+
return run;
|
|
136
|
+
}
|
|
137
|
+
async reconcile(boot) {
|
|
138
|
+
const seen = /* @__PURE__ */ new Set();
|
|
139
|
+
for (const d of boot.plugins) {
|
|
140
|
+
seen.add(d.id);
|
|
141
|
+
this.permissions.restore({ [d.id]: d.granted });
|
|
142
|
+
if (!this.manager.has(d.id)) {
|
|
143
|
+
if (d.state === "error" && !d.rendererUrl) continue;
|
|
144
|
+
this.manager.register({ manifest: d.manifest, origin: "renderer", load: () => this.load(d) });
|
|
145
|
+
this.permissions.restore({ [d.id]: d.granted });
|
|
146
|
+
} else {
|
|
147
|
+
this.permissions.restore({ [d.id]: d.granted });
|
|
148
|
+
}
|
|
149
|
+
const info = this.manager.info(d.id);
|
|
150
|
+
if (!d.enabled && info.enabled) await this.manager.disable(d.id).catch(() => void 0);
|
|
151
|
+
else if (d.enabled && !info.enabled) await this.manager.enable(d.id, { activate: false }).catch(() => void 0);
|
|
152
|
+
}
|
|
153
|
+
for (const info of this.manager.list()) if (!seen.has(info.id)) await this.manager.unregister(info.id);
|
|
154
|
+
await this.manager.startup();
|
|
155
|
+
}
|
|
156
|
+
async load(d) {
|
|
157
|
+
let def = {};
|
|
158
|
+
if (d.rendererUrl) {
|
|
159
|
+
const url = this.cacheBust ? `${d.rendererUrl}${d.rendererUrl.includes("?") ? "&" : "?"}flux=${this.cacheBust}` : d.rendererUrl;
|
|
160
|
+
const mod = await (this.opts.importPlugin ? this.opts.importPlugin(url, d) : import(
|
|
161
|
+
/* @vite-ignore */
|
|
162
|
+
url
|
|
163
|
+
));
|
|
164
|
+
def = asDefinition(mod);
|
|
165
|
+
}
|
|
166
|
+
const hrefs = d.styleUrls;
|
|
167
|
+
if (!hrefs.length) return def;
|
|
168
|
+
const activate = def.onActivate ?? def.activate;
|
|
169
|
+
return {
|
|
170
|
+
...def,
|
|
171
|
+
onActivate: async (ctx) => {
|
|
172
|
+
hrefs.forEach((href, i) => ctx.lifecycle.subscriptions.add(this.manager.styles.register({ id: `manifest-${i}`, href, priority: 50 }, ctx.pluginId)));
|
|
173
|
+
await activate?.(ctx);
|
|
174
|
+
},
|
|
175
|
+
activate: void 0
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/** Called by plugin error boundaries. Repeated render failures disable the plugin. */
|
|
179
|
+
reportRenderError(pluginId, error) {
|
|
180
|
+
this.manager.logs.logger(pluginId).error(`render error: ${errorMessage(error)}`, error instanceof Error ? error.stack : void 0);
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
const list = [...(this.renderErrors.get(pluginId) ?? []).filter((t) => now - t < 6e4), now];
|
|
183
|
+
this.renderErrors.set(pluginId, list);
|
|
184
|
+
if (list.length >= (this.opts.maxRenderErrors ?? 3) && this.manager.has(pluginId) && this.manager.info(pluginId).enabled) {
|
|
185
|
+
this.manager.logs.logger(pluginId).warn("disabled automatically after repeated render errors");
|
|
186
|
+
this.renderErrors.delete(pluginId);
|
|
187
|
+
void this.client.disable(pluginId).catch(() => this.manager.disable(pluginId));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** Builds `window.plugins.<ns>.<member>` from the preload manifest. Rebuilt on every sync. */
|
|
191
|
+
installWindowPlugins(spec) {
|
|
192
|
+
if (typeof window === "undefined") return;
|
|
193
|
+
window.plugins = buildPluginApis(this.bridge, spec);
|
|
194
|
+
}
|
|
195
|
+
/** Config handle usable from React (mirrors ctx.config through the management API). */
|
|
196
|
+
configOf(id) {
|
|
197
|
+
return {
|
|
198
|
+
get: () => this.client.configGet(id),
|
|
199
|
+
set: (p) => this.client.configSet(id, p),
|
|
200
|
+
reset: () => this.client.configReset(id)
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
function buildPluginApis(bridge, spec) {
|
|
205
|
+
const out = {};
|
|
206
|
+
for (const [ns, members] of Object.entries(spec)) {
|
|
207
|
+
const api = /* @__PURE__ */ Object.create(null);
|
|
208
|
+
for (const [name, m] of Object.entries(members)) {
|
|
209
|
+
if (m.kind === "invoke") api[name] = (input) => bridge.invoke(m.channel, input, ns);
|
|
210
|
+
else if (m.kind === "send") api[name] = (payload) => bridge.send(m.channel, payload, ns);
|
|
211
|
+
else api[name] = (listener) => bridge.on(m.channel, listener);
|
|
212
|
+
}
|
|
213
|
+
out[ns] = Object.freeze(api);
|
|
214
|
+
}
|
|
215
|
+
return Object.freeze(out);
|
|
216
|
+
}
|
|
217
|
+
function installSharedModules(modules) {
|
|
218
|
+
const g = globalThis;
|
|
219
|
+
g.__FLUXPLUGIN_SHARED__ = { ...g.__FLUXPLUGIN_SHARED__ ?? {}, ...modules };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/react/components.tsx
|
|
223
|
+
import {
|
|
224
|
+
Component,
|
|
225
|
+
createContext,
|
|
226
|
+
useCallback as useCallback2,
|
|
227
|
+
useContext,
|
|
228
|
+
useEffect as useEffect2,
|
|
229
|
+
useMemo as useMemo2,
|
|
230
|
+
useRef,
|
|
231
|
+
useState as useState2,
|
|
232
|
+
useSyncExternalStore as useSyncExternalStore2
|
|
233
|
+
} from "react";
|
|
234
|
+
|
|
235
|
+
// src/react/router.tsx
|
|
236
|
+
import {
|
|
237
|
+
Suspense,
|
|
238
|
+
lazy,
|
|
239
|
+
useCallback,
|
|
240
|
+
useEffect,
|
|
241
|
+
useMemo,
|
|
242
|
+
useState,
|
|
243
|
+
useSyncExternalStore
|
|
244
|
+
} from "react";
|
|
245
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
246
|
+
function currentPath() {
|
|
247
|
+
if (typeof window === "undefined") return "/";
|
|
248
|
+
const h = window.location.hash.replace(/^#/, "");
|
|
249
|
+
return normalizePath(h || "/");
|
|
250
|
+
}
|
|
251
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
252
|
+
if (typeof window !== "undefined") window.addEventListener("hashchange", () => listeners.forEach((l) => l()));
|
|
253
|
+
var subscribeLocation = (l) => (listeners.add(l), () => void listeners.delete(l));
|
|
254
|
+
function navigate(to, opts = {}) {
|
|
255
|
+
const target = "#" + normalizePath(to) + (to.includes("?") ? "?" + to.split("?")[1] : "");
|
|
256
|
+
if (opts.replace) window.location.replace(target);
|
|
257
|
+
else window.location.hash = target;
|
|
258
|
+
listeners.forEach((l) => l());
|
|
259
|
+
}
|
|
260
|
+
function useLocation() {
|
|
261
|
+
return useSyncExternalStore(subscribeLocation, currentPath, () => "/");
|
|
262
|
+
}
|
|
263
|
+
function useNavigate() {
|
|
264
|
+
return navigate;
|
|
265
|
+
}
|
|
266
|
+
function Link({ to, children, ...rest }) {
|
|
267
|
+
const here = useLocation();
|
|
268
|
+
const active = here === normalizePath(to) || to !== "/" && here.startsWith(normalizePath(to) + "/");
|
|
269
|
+
return /* @__PURE__ */ jsx("a", { ...rest, href: "#" + normalizePath(to), "data-active": active || void 0, "aria-current": active ? "page" : void 0, children });
|
|
270
|
+
}
|
|
271
|
+
function usePluginRoute() {
|
|
272
|
+
const fw = usePluginFramework();
|
|
273
|
+
const path = useLocation();
|
|
274
|
+
const routes = fw.manager.routes;
|
|
275
|
+
const version = useSyncExternalStore(routes.subscribe, () => routes.version, () => routes.version);
|
|
276
|
+
return useMemo(() => {
|
|
277
|
+
const match = routes.match(path);
|
|
278
|
+
return { match, params: match?.params ?? {}, breadcrumbs: match ? routes.breadcrumbs(match) : [], path };
|
|
279
|
+
}, [routes, path, version]);
|
|
280
|
+
}
|
|
281
|
+
var useParams = () => usePluginRoute().params;
|
|
282
|
+
function Breadcrumbs({ separator = "/" }) {
|
|
283
|
+
const { breadcrumbs } = usePluginRoute();
|
|
284
|
+
if (!breadcrumbs.length) return null;
|
|
285
|
+
return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: "flux-breadcrumbs", children: breadcrumbs.map((b, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
286
|
+
i > 0 && /* @__PURE__ */ jsxs("span", { className: "flux-breadcrumb-sep", children: [
|
|
287
|
+
" ",
|
|
288
|
+
separator,
|
|
289
|
+
" "
|
|
290
|
+
] }),
|
|
291
|
+
i === breadcrumbs.length - 1 ? /* @__PURE__ */ jsx("span", { "aria-current": "page", children: b.label }) : /* @__PURE__ */ jsx(Link, { to: b.path, children: b.label })
|
|
292
|
+
] }, b.path)) });
|
|
293
|
+
}
|
|
294
|
+
var lazyCache = /* @__PURE__ */ new WeakMap();
|
|
295
|
+
function resolveComponent(def) {
|
|
296
|
+
if (def.component) return def.component;
|
|
297
|
+
const loader = def.lazy;
|
|
298
|
+
let c = lazyCache.get(loader);
|
|
299
|
+
if (!c) {
|
|
300
|
+
c = lazy(async () => {
|
|
301
|
+
const mod = await loader();
|
|
302
|
+
const comp = mod && typeof mod === "object" && "default" in mod ? mod.default : mod;
|
|
303
|
+
return { default: comp };
|
|
304
|
+
});
|
|
305
|
+
lazyCache.set(loader, c);
|
|
306
|
+
}
|
|
307
|
+
return c;
|
|
308
|
+
}
|
|
309
|
+
function PluginRoutes({ notFound, forbidden, loading, defaultLayout: DefaultLayout }) {
|
|
310
|
+
const fw = usePluginFramework();
|
|
311
|
+
const path = useLocation();
|
|
312
|
+
const routes = fw.manager.routes;
|
|
313
|
+
useSyncExternalStore(routes.subscribe, () => routes.version, () => routes.version);
|
|
314
|
+
const [resolution, setResolution] = useState(null);
|
|
315
|
+
const version = routes.version;
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
let live = true;
|
|
318
|
+
(async () => {
|
|
319
|
+
const match2 = await routes.matchAsync(path);
|
|
320
|
+
if (!match2) return live && setResolution({ path });
|
|
321
|
+
const guard2 = await routes.checkGuards(match2, path);
|
|
322
|
+
if (live) setResolution({ path, match: match2, guard: guard2 });
|
|
323
|
+
})().catch(() => live && setResolution({ path }));
|
|
324
|
+
return () => {
|
|
325
|
+
live = false;
|
|
326
|
+
};
|
|
327
|
+
}, [routes, path, version]);
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
if (resolution?.path === path && resolution.guard && !resolution.guard.allow && resolution.guard.redirect) navigate(resolution.guard.redirect, { replace: true });
|
|
330
|
+
}, [resolution, path]);
|
|
331
|
+
if (!resolution || resolution.path !== path) return /* @__PURE__ */ jsx(Fragment, { children: loading ?? null });
|
|
332
|
+
const { match, guard } = resolution;
|
|
333
|
+
if (!match) return /* @__PURE__ */ jsx(Fragment, { children: notFound ?? /* @__PURE__ */ jsxs("div", { className: "flux-not-found", children: [
|
|
334
|
+
"Page not found: ",
|
|
335
|
+
path
|
|
336
|
+
] }) });
|
|
337
|
+
if (guard && !guard.allow) {
|
|
338
|
+
if (guard.redirect) return /* @__PURE__ */ jsx(Fragment, { children: loading ?? null });
|
|
339
|
+
return /* @__PURE__ */ jsx(Fragment, { children: forbidden ? forbidden(guard.reason) : /* @__PURE__ */ jsxs("div", { className: "flux-forbidden", children: [
|
|
340
|
+
"Access denied: ",
|
|
341
|
+
guard.reason
|
|
342
|
+
] }) });
|
|
343
|
+
}
|
|
344
|
+
const owner = match.route.owner ?? "host";
|
|
345
|
+
const Page = resolveComponent(match.route.definition);
|
|
346
|
+
let node = /* @__PURE__ */ jsx(Page, { params: match.params });
|
|
347
|
+
for (const r of [...match.chain].reverse()) {
|
|
348
|
+
const l = r.definition.layout;
|
|
349
|
+
if (!l) continue;
|
|
350
|
+
const Layout = typeof l === "string" ? routes.getLayout(l) : l;
|
|
351
|
+
if (Layout) node = /* @__PURE__ */ jsx(Layout, { children: node });
|
|
352
|
+
}
|
|
353
|
+
if (!match.chain.some((r) => r.definition.layout) && DefaultLayout) node = /* @__PURE__ */ jsx(DefaultLayout, { children: node });
|
|
354
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(PluginBoundary, { owner, children: /* @__PURE__ */ jsx(Suspense, { fallback: loading ?? /* @__PURE__ */ jsx("div", { className: "flux-loading", children: "Loading\u2026" }), children: node }) }, match.route.fullPath) });
|
|
355
|
+
}
|
|
356
|
+
function useMenu(location) {
|
|
357
|
+
const fw = usePluginFramework();
|
|
358
|
+
const menus = fw.manager.menus;
|
|
359
|
+
useSyncExternalStore(menus.subscribe, () => menus.version, () => menus.version);
|
|
360
|
+
const run = useCallback(
|
|
361
|
+
(item) => {
|
|
362
|
+
if (item.command) return fw.manager.commands.execute(item.command, ...item.args ?? []);
|
|
363
|
+
if (item.path) navigate(item.path);
|
|
364
|
+
},
|
|
365
|
+
[fw]
|
|
366
|
+
);
|
|
367
|
+
return { items: menus.items(location), run };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/react/components.tsx
|
|
371
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
372
|
+
var FrameworkContext = createContext(null);
|
|
373
|
+
function PluginProvider({ framework, children }) {
|
|
374
|
+
return /* @__PURE__ */ jsx2(FrameworkContext.Provider, { value: framework, children });
|
|
375
|
+
}
|
|
376
|
+
function usePluginFramework() {
|
|
377
|
+
const fw = useContext(FrameworkContext);
|
|
378
|
+
if (!fw) throw new Error("usePlugin* hooks must be used inside <PluginProvider>");
|
|
379
|
+
return fw;
|
|
380
|
+
}
|
|
381
|
+
var PluginErrorBoundary = class extends Component {
|
|
382
|
+
state = { error: null };
|
|
383
|
+
static getDerivedStateFromError(error) {
|
|
384
|
+
return { error };
|
|
385
|
+
}
|
|
386
|
+
componentDidCatch(error, _info) {
|
|
387
|
+
this.props.framework.reportRenderError(this.props.owner, error);
|
|
388
|
+
}
|
|
389
|
+
reset = () => this.setState({ error: null });
|
|
390
|
+
render() {
|
|
391
|
+
const { error } = this.state;
|
|
392
|
+
if (!error) return this.props.children;
|
|
393
|
+
if (this.props.fallback) return this.props.fallback(error, this.reset);
|
|
394
|
+
return /* @__PURE__ */ jsxs2("div", { role: "alert", className: "fluxplugin-error", "data-flux-error": this.props.owner, children: [
|
|
395
|
+
/* @__PURE__ */ jsx2("strong", { children: this.props.owner }),
|
|
396
|
+
" failed to render.",
|
|
397
|
+
" ",
|
|
398
|
+
/* @__PURE__ */ jsx2("button", { type: "button", onClick: this.reset, children: "Retry" })
|
|
399
|
+
] });
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function PluginBoundary({ owner, children }) {
|
|
403
|
+
const fw = usePluginFramework();
|
|
404
|
+
return /* @__PURE__ */ jsx2("div", { "data-fluxplugin": owner, style: { display: "contents" }, children: /* @__PURE__ */ jsx2(PluginErrorBoundary, { owner, framework: fw, children }) });
|
|
405
|
+
}
|
|
406
|
+
function useRegistryVersion(subscribe, version) {
|
|
407
|
+
return useSyncExternalStore2(subscribe, version, version);
|
|
408
|
+
}
|
|
409
|
+
function renderContribution(c, props) {
|
|
410
|
+
const C = c.component;
|
|
411
|
+
return /* @__PURE__ */ jsx2(PluginBoundary, { owner: c.owner ?? "unknown", children: /* @__PURE__ */ jsx2(C, { ...c.props ?? {}, ...props }) }, c.id);
|
|
412
|
+
}
|
|
413
|
+
function PluginSlot({ name, children, props = {} }) {
|
|
414
|
+
const fw = usePluginFramework();
|
|
415
|
+
useRegistryVersion(fw.manager.slots.subscribe, () => fw.manager.slots.version);
|
|
416
|
+
const resolved = fw.manager.slots.resolve(name, props);
|
|
417
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
418
|
+
resolved.before.map((c) => renderContribution(c, props)),
|
|
419
|
+
resolved.replacement ? renderContribution(resolved.replacement, { ...props, children }) : children,
|
|
420
|
+
resolved.after.map((c) => renderContribution(c, props))
|
|
421
|
+
] });
|
|
422
|
+
}
|
|
423
|
+
function useExtensionItems(point, key) {
|
|
424
|
+
const fw = usePluginFramework();
|
|
425
|
+
useRegistryVersion(fw.manager.extensionPoints.subscribe, () => fw.manager.extensionPoints.version);
|
|
426
|
+
return fw.manager.extensionPoints.items(point, key);
|
|
427
|
+
}
|
|
428
|
+
function usePlugin(id) {
|
|
429
|
+
const fw = usePluginFramework();
|
|
430
|
+
useRegistryVersion(fw.subscribe, () => fw.storeVersion);
|
|
431
|
+
return fw.manager.has(id) ? fw.manager.info(id) : void 0;
|
|
432
|
+
}
|
|
433
|
+
function usePlugins() {
|
|
434
|
+
const fw = usePluginFramework();
|
|
435
|
+
useRegistryVersion(fw.subscribe, () => fw.storeVersion);
|
|
436
|
+
return fw.manager.list();
|
|
437
|
+
}
|
|
438
|
+
function usePluginState(pluginId, key, initial) {
|
|
439
|
+
const fw = usePluginFramework();
|
|
440
|
+
const store = fw.manager.stateOf(pluginId);
|
|
441
|
+
const sub = useCallback2((l) => store.subscribe(l, key).dispose, [store, key]);
|
|
442
|
+
const value = useSyncExternalStore2(
|
|
443
|
+
sub,
|
|
444
|
+
() => store.get(key, initial),
|
|
445
|
+
() => initial
|
|
446
|
+
);
|
|
447
|
+
const set = useCallback2((v) => store.set(key, typeof v === "function" ? v(store.get(key, initial)) : v), [store, key, initial]);
|
|
448
|
+
return [value, set];
|
|
449
|
+
}
|
|
450
|
+
function usePluginEvent(name, onEvent) {
|
|
451
|
+
const fw = usePluginFramework();
|
|
452
|
+
const [last, setLast] = useState2(void 0);
|
|
453
|
+
const cb = useRef(onEvent);
|
|
454
|
+
cb.current = onEvent;
|
|
455
|
+
useEffect2(() => {
|
|
456
|
+
const handle = (p) => {
|
|
457
|
+
setLast(p);
|
|
458
|
+
cb.current?.(p);
|
|
459
|
+
};
|
|
460
|
+
const subs = [fw.manager.events.on(name, (p) => handle(p)), fw.bridge.on(name, handle), fw.bridge.on(`flux.event.${name}`, handle)];
|
|
461
|
+
return () => {
|
|
462
|
+
subs.forEach((s) => typeof s === "function" ? s() : s.dispose());
|
|
463
|
+
};
|
|
464
|
+
}, [fw, name]);
|
|
465
|
+
return last;
|
|
466
|
+
}
|
|
467
|
+
function usePluginCommand(id) {
|
|
468
|
+
const fw = usePluginFramework();
|
|
469
|
+
useRegistryVersion(fw.manager.commands.subscribe, () => fw.manager.commands.version);
|
|
470
|
+
const cmd = fw.manager.commands.get(id);
|
|
471
|
+
const run = useCallback2((...args) => fw.manager.commands.execute(id, ...args), [fw, id]);
|
|
472
|
+
return { run, available: !!cmd && (!cmd.when || cmd.when()), title: cmd?.title };
|
|
473
|
+
}
|
|
474
|
+
function usePluginService(name) {
|
|
475
|
+
const fw = usePluginFramework();
|
|
476
|
+
useRegistryVersion(fw.manager.services.subscribe, () => fw.manager.services.version);
|
|
477
|
+
useEffect2(() => {
|
|
478
|
+
if (!fw.manager.services.has(name)) void fw.manager.services.resolveAsync(name).catch(() => void 0);
|
|
479
|
+
}, [fw, name]);
|
|
480
|
+
return fw.manager.services.resolve(name);
|
|
481
|
+
}
|
|
482
|
+
function usePluginIPC(endpoint, opts = {}) {
|
|
483
|
+
const fw = usePluginFramework();
|
|
484
|
+
const name = typeof endpoint === "string" ? endpoint : endpoint.name;
|
|
485
|
+
const [state, setState] = useState2({ data: void 0, error: void 0, loading: false });
|
|
486
|
+
const alive = useRef(true);
|
|
487
|
+
useEffect2(() => () => void (alive.current = false), []);
|
|
488
|
+
const call = useCallback2(
|
|
489
|
+
async (input) => {
|
|
490
|
+
setState((s) => ({ ...s, loading: true, error: void 0 }));
|
|
491
|
+
try {
|
|
492
|
+
const data = await fw.bridge.invoke(name, input, opts.caller);
|
|
493
|
+
if (alive.current) setState({ data, error: void 0, loading: false });
|
|
494
|
+
return data;
|
|
495
|
+
} catch (e) {
|
|
496
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
497
|
+
if (alive.current) setState({ data: void 0, error, loading: false });
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
[fw, name, opts.caller]
|
|
502
|
+
);
|
|
503
|
+
return { ...state, call };
|
|
504
|
+
}
|
|
505
|
+
function usePluginConfig(pluginId) {
|
|
506
|
+
const fw = usePluginFramework();
|
|
507
|
+
const handle = useMemo2(() => fw.configOf(pluginId), [fw, pluginId]);
|
|
508
|
+
const [config, setConfig] = useState2();
|
|
509
|
+
const [error, setError] = useState2();
|
|
510
|
+
const [loading, setLoading] = useState2(true);
|
|
511
|
+
useEffect2(() => {
|
|
512
|
+
let live = true;
|
|
513
|
+
setLoading(true);
|
|
514
|
+
handle.get().then((c) => live && (setConfig(c), setError(void 0))).catch((e) => live && setError(e)).finally(() => live && setLoading(false));
|
|
515
|
+
const sub = fw.manager.events.on("plugin.config.changed", (p) => {
|
|
516
|
+
if (p?.id === pluginId) setConfig(p.next);
|
|
517
|
+
});
|
|
518
|
+
return () => {
|
|
519
|
+
live = false;
|
|
520
|
+
sub.dispose();
|
|
521
|
+
};
|
|
522
|
+
}, [fw, handle, pluginId]);
|
|
523
|
+
return {
|
|
524
|
+
config,
|
|
525
|
+
loading,
|
|
526
|
+
error,
|
|
527
|
+
set: async (patch) => {
|
|
528
|
+
const next = await handle.set(patch);
|
|
529
|
+
setConfig(next);
|
|
530
|
+
return next;
|
|
531
|
+
},
|
|
532
|
+
reset: async () => {
|
|
533
|
+
const next = await handle.reset();
|
|
534
|
+
setConfig(next);
|
|
535
|
+
return next;
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function StyleHost() {
|
|
540
|
+
const fw = usePluginFramework();
|
|
541
|
+
useRegistryVersion(fw.manager.styles.subscribe, () => fw.manager.styles.version);
|
|
542
|
+
const entries = fw.manager.styles.ordered();
|
|
543
|
+
const sig = entries.map((e) => `${e.owner}/${e.id}/${e.priority}/${e.css?.length ?? 0}/${e.href ?? ""}`).join("|");
|
|
544
|
+
useEffect2(() => {
|
|
545
|
+
const created = [];
|
|
546
|
+
for (const e of entries) {
|
|
547
|
+
let el;
|
|
548
|
+
if (e.href) {
|
|
549
|
+
const link = document.createElement("link");
|
|
550
|
+
link.rel = "stylesheet";
|
|
551
|
+
link.href = e.href;
|
|
552
|
+
el = link;
|
|
553
|
+
} else {
|
|
554
|
+
const style = document.createElement("style");
|
|
555
|
+
style.textContent = e.scoped ? scopeCss(e.css ?? "", e.owner) : e.css ?? "";
|
|
556
|
+
el = style;
|
|
557
|
+
}
|
|
558
|
+
el.setAttribute("data-flux-style", `${e.owner}:${e.id}`);
|
|
559
|
+
document.head.appendChild(el);
|
|
560
|
+
created.push(el);
|
|
561
|
+
}
|
|
562
|
+
return () => created.forEach((el) => el.remove());
|
|
563
|
+
}, [sig]);
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/react/palette.tsx
|
|
568
|
+
import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState3, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
569
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
570
|
+
function useKeybindings(opts = {}) {
|
|
571
|
+
const fw = usePluginFramework();
|
|
572
|
+
useEffect3(() => {
|
|
573
|
+
if (opts.enabled === false) return;
|
|
574
|
+
const onKey = (e) => {
|
|
575
|
+
const target = e.target;
|
|
576
|
+
if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) && !(e.ctrlKey || e.metaKey)) return;
|
|
577
|
+
const parts = [];
|
|
578
|
+
if (e.ctrlKey || e.metaKey) parts.push("mod");
|
|
579
|
+
if (e.shiftKey) parts.push("shift");
|
|
580
|
+
if (e.altKey) parts.push("alt");
|
|
581
|
+
const key = e.key.toLowerCase();
|
|
582
|
+
if (["control", "shift", "alt", "meta"].includes(key)) return;
|
|
583
|
+
parts.push(key);
|
|
584
|
+
const cmd = fw.manager.commands.byKeybinding(parts.join("+"));
|
|
585
|
+
if (cmd) {
|
|
586
|
+
e.preventDefault();
|
|
587
|
+
void fw.manager.commands.execute(cmd.id).catch((err) => fw.manager.logs.logger("host").error(`command ${cmd.id} failed: ${String(err)}`));
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
window.addEventListener("keydown", onKey);
|
|
591
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
592
|
+
}, [fw, opts.enabled]);
|
|
593
|
+
}
|
|
594
|
+
function CommandPalette({ open, onClose }) {
|
|
595
|
+
const fw = usePluginFramework();
|
|
596
|
+
const cmds = fw.manager.commands;
|
|
597
|
+
useSyncExternalStore3(cmds.subscribe, () => cmds.version, () => cmds.version);
|
|
598
|
+
const [internal, setInternal] = useState3(false);
|
|
599
|
+
const isOpen = open ?? internal;
|
|
600
|
+
const close = () => onClose ? onClose() : setInternal(false);
|
|
601
|
+
const [query, setQuery] = useState3("");
|
|
602
|
+
const [index, setIndex] = useState3(0);
|
|
603
|
+
const input = useRef2(null);
|
|
604
|
+
useEffect3(() => {
|
|
605
|
+
const onKey = (e) => {
|
|
606
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
|
|
607
|
+
e.preventDefault();
|
|
608
|
+
setInternal((v) => !v);
|
|
609
|
+
} else if (e.key === "Escape") close();
|
|
610
|
+
};
|
|
611
|
+
window.addEventListener("keydown", onKey);
|
|
612
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
613
|
+
}, []);
|
|
614
|
+
useEffect3(() => {
|
|
615
|
+
if (isOpen) {
|
|
616
|
+
setQuery("");
|
|
617
|
+
setIndex(0);
|
|
618
|
+
setTimeout(() => input.current?.focus(), 0);
|
|
619
|
+
}
|
|
620
|
+
}, [isOpen]);
|
|
621
|
+
const results = useMemo3(() => cmds.palette(query), [cmds, query, cmds.version]);
|
|
622
|
+
if (!isOpen) return null;
|
|
623
|
+
const run = (i) => {
|
|
624
|
+
const c = results[i];
|
|
625
|
+
if (!c) return;
|
|
626
|
+
close();
|
|
627
|
+
void cmds.execute(c.id).catch((e) => fw.manager.logs.logger("host").error(String(e)));
|
|
628
|
+
};
|
|
629
|
+
return /* @__PURE__ */ jsx3("div", { className: "flux-palette-backdrop", onMouseDown: close, role: "presentation", children: /* @__PURE__ */ jsxs3("div", { className: "flux-palette", role: "dialog", "aria-label": "Command palette", onMouseDown: (e) => e.stopPropagation(), children: [
|
|
630
|
+
/* @__PURE__ */ jsx3(
|
|
631
|
+
"input",
|
|
632
|
+
{
|
|
633
|
+
ref: input,
|
|
634
|
+
className: "flux-palette-input",
|
|
635
|
+
placeholder: "Type a command\u2026",
|
|
636
|
+
value: query,
|
|
637
|
+
onChange: (e) => (setQuery(e.target.value), setIndex(0)),
|
|
638
|
+
onKeyDown: (e) => {
|
|
639
|
+
if (e.key === "ArrowDown") e.preventDefault(), setIndex((i) => Math.min(i + 1, results.length - 1));
|
|
640
|
+
else if (e.key === "ArrowUp") e.preventDefault(), setIndex((i) => Math.max(i - 1, 0));
|
|
641
|
+
else if (e.key === "Enter") run(index);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
),
|
|
645
|
+
/* @__PURE__ */ jsxs3("ul", { className: "flux-palette-list", role: "listbox", children: [
|
|
646
|
+
results.length === 0 && /* @__PURE__ */ jsx3("li", { className: "flux-palette-empty", children: "No matching commands" }),
|
|
647
|
+
results.map((c, i) => /* @__PURE__ */ jsxs3("li", { role: "option", "aria-selected": i === index, className: "flux-palette-item" + (i === index ? " is-active" : ""), onMouseEnter: () => setIndex(i), onClick: () => run(i), children: [
|
|
648
|
+
/* @__PURE__ */ jsxs3("span", { children: [
|
|
649
|
+
c.category && /* @__PURE__ */ jsxs3("span", { className: "flux-palette-cat", children: [
|
|
650
|
+
c.category,
|
|
651
|
+
": "
|
|
652
|
+
] }),
|
|
653
|
+
c.title
|
|
654
|
+
] }),
|
|
655
|
+
c.keybinding && /* @__PURE__ */ jsx3("kbd", { children: c.keybinding.replace("Mod", /Mac/.test(navigator.platform) ? "\u2318" : "Ctrl") })
|
|
656
|
+
] }, c.id))
|
|
657
|
+
] })
|
|
658
|
+
] }) });
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/react/base-css.ts
|
|
662
|
+
var baseCss = `
|
|
663
|
+
:root{--flux-bg:#fff;--flux-fg:#0f172a;--flux-muted:#64748b;--flux-border:#e2e8f0;--flux-accent:#4f46e5;--flux-accent-fg:#fff;--flux-surface:#f8fafc;--flux-danger:#dc2626;--flux-ok:#16a34a;--flux-warn:#d97706;--flux-radius:10px}
|
|
664
|
+
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]){--flux-bg:#0b1020;--flux-fg:#e5e7eb;--flux-muted:#94a3b8;--flux-border:#1f2a44;--flux-surface:#111936}}
|
|
665
|
+
.fluxplugin-error{padding:8px 12px;border:1px solid var(--flux-danger);border-radius:var(--flux-radius);color:var(--flux-danger);font-size:13px}
|
|
666
|
+
.flux-breadcrumbs{font-size:13px;color:var(--flux-muted)}.flux-breadcrumbs a{color:inherit}
|
|
667
|
+
.flux-not-found,.flux-forbidden,.flux-loading{padding:24px;color:var(--flux-muted)}
|
|
668
|
+
.flux-palette-backdrop{position:fixed;inset:0;background:rgba(2,6,23,.45);display:flex;justify-content:center;align-items:flex-start;padding-top:14vh;z-index:9999}
|
|
669
|
+
.flux-palette{width:min(560px,92vw);background:var(--flux-bg);color:var(--flux-fg);border:1px solid var(--flux-border);border-radius:14px;box-shadow:0 24px 60px rgba(0,0,0,.35);overflow:hidden}
|
|
670
|
+
.flux-palette-input{width:100%;box-sizing:border-box;padding:14px 16px;border:0;border-bottom:1px solid var(--flux-border);background:transparent;color:inherit;font-size:15px;outline:none}
|
|
671
|
+
.flux-palette-list{list-style:none;margin:0;padding:6px;max-height:340px;overflow:auto}
|
|
672
|
+
.flux-palette-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:9px 12px;border-radius:8px;cursor:pointer;font-size:14px}
|
|
673
|
+
.flux-palette-item.is-active{background:var(--flux-accent);color:var(--flux-accent-fg)}
|
|
674
|
+
.flux-palette-cat{opacity:.65}.flux-palette-empty{padding:14px;color:var(--flux-muted);font-size:14px}
|
|
675
|
+
.flux-palette kbd{font:11px ui-monospace,monospace;padding:2px 6px;border:1px solid currentColor;border-radius:5px;opacity:.7}
|
|
676
|
+
`;
|
|
677
|
+
|
|
678
|
+
// src/react/http-bridge.ts
|
|
679
|
+
var HttpBridgeError = class extends Error {
|
|
680
|
+
constructor(message, code) {
|
|
681
|
+
super(message);
|
|
682
|
+
this.code = code;
|
|
683
|
+
this.name = "HttpBridgeError";
|
|
684
|
+
}
|
|
685
|
+
code;
|
|
686
|
+
};
|
|
687
|
+
function createHttpBridge(baseUrl = "") {
|
|
688
|
+
const listeners2 = /* @__PURE__ */ new Map();
|
|
689
|
+
let source;
|
|
690
|
+
const ensureSource = () => {
|
|
691
|
+
if (source || typeof EventSource === "undefined") return;
|
|
692
|
+
source = new EventSource(`${baseUrl}/flux/events`);
|
|
693
|
+
source.onmessage = (ev) => {
|
|
694
|
+
try {
|
|
695
|
+
const msg = JSON.parse(ev.data);
|
|
696
|
+
for (const l of [...listeners2.get(msg.channel) ?? []]) l(msg.payload);
|
|
697
|
+
} catch {
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
};
|
|
701
|
+
const post = (path, body) => fetch(`${baseUrl}${path}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
702
|
+
return {
|
|
703
|
+
async invoke(channel, input, caller) {
|
|
704
|
+
const res = await (await post("/flux/invoke", { channel, input, caller })).json();
|
|
705
|
+
if (!res.ok) throw new HttpBridgeError(res.error.message, res.error.code);
|
|
706
|
+
return res.value;
|
|
707
|
+
},
|
|
708
|
+
send(channel, payload, caller) {
|
|
709
|
+
void post("/flux/send", { channel, payload, caller });
|
|
710
|
+
},
|
|
711
|
+
on(channel, listener) {
|
|
712
|
+
ensureSource();
|
|
713
|
+
let set = listeners2.get(channel);
|
|
714
|
+
if (!set) listeners2.set(channel, set = /* @__PURE__ */ new Set());
|
|
715
|
+
set.add(listener);
|
|
716
|
+
return () => void set.delete(listener);
|
|
717
|
+
},
|
|
718
|
+
async bootstrap() {
|
|
719
|
+
return await (await fetch(`${baseUrl}/flux/bootstrap`)).json();
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
export {
|
|
725
|
+
RemoteError,
|
|
726
|
+
PluginsClient,
|
|
727
|
+
RendererFramework,
|
|
728
|
+
buildPluginApis,
|
|
729
|
+
installSharedModules,
|
|
730
|
+
navigate,
|
|
731
|
+
useLocation,
|
|
732
|
+
useNavigate,
|
|
733
|
+
Link,
|
|
734
|
+
usePluginRoute,
|
|
735
|
+
useParams,
|
|
736
|
+
Breadcrumbs,
|
|
737
|
+
PluginRoutes,
|
|
738
|
+
useMenu,
|
|
739
|
+
PluginProvider,
|
|
740
|
+
usePluginFramework,
|
|
741
|
+
PluginErrorBoundary,
|
|
742
|
+
PluginBoundary,
|
|
743
|
+
PluginSlot,
|
|
744
|
+
useExtensionItems,
|
|
745
|
+
usePlugin,
|
|
746
|
+
usePlugins,
|
|
747
|
+
usePluginState,
|
|
748
|
+
usePluginEvent,
|
|
749
|
+
usePluginCommand,
|
|
750
|
+
usePluginService,
|
|
751
|
+
usePluginIPC,
|
|
752
|
+
usePluginConfig,
|
|
753
|
+
StyleHost,
|
|
754
|
+
useKeybindings,
|
|
755
|
+
CommandPalette,
|
|
756
|
+
baseCss,
|
|
757
|
+
HttpBridgeError,
|
|
758
|
+
createHttpBridge
|
|
759
|
+
};
|