anbaric-cloud-hosting 1.6.0 → 1.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anbaric-cloud-hosting",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Anbaric Cloud hosting service: Postgres-backed job persistence and queuing exposed over an HTTP API",
5
5
  "license": "MIT",
6
6
  "author": "chris@anbaric.ai",
@@ -17,15 +17,17 @@
17
17
  "@aws-sdk/client-s3": "^3.1110.0",
18
18
  "@aws-sdk/client-secrets-manager": "^3.700.0",
19
19
  "@aws-sdk/client-servicediscovery": "^3.1110.0",
20
- "anbaric-data-store": "^1.6.0",
21
- "anbaric-tsapi": "^1.6.0",
20
+ "anbaric-data-store": "^1.7.0",
21
+ "anbaric-plugins": "^1.7.0",
22
+ "anbaric-tsapi": "^1.7.0",
23
+ "esbuild": "^0.28.2",
22
24
  "pg": "^8.16.0"
23
25
  },
24
26
  "devDependencies": {
25
27
  "@types/node": "^26.2.0",
26
28
  "@types/pg": "^8.15.0",
27
- "anbaric-impl-cloud": "^1.6.0",
28
- "anbaric-state-machine": "^1.6.0",
29
+ "anbaric-impl-cloud": "^1.7.0",
30
+ "anbaric-state-machine": "^1.7.0",
29
31
  "tsx": "^4.20.0",
30
32
  "typescript": "^7.0.2"
31
33
  },
@@ -20,8 +20,11 @@ import {PingHandler} from "./handlers/PingHandler";
20
20
  import {QueueHandler} from "./handlers/QueueHandler";
21
21
  import {SecretsHandler} from "./handlers/SecretsHandler";
22
22
  import {StateMachinesHandler} from "./handlers/StateMachinesHandler";
23
+ import {PluginsHandler} from "./handlers/PluginsHandler";
23
24
  import {AuthenticationMiddleware} from "./middleware/AuthenticationMiddleware";
24
25
  import {SessionMiddleware} from "./middleware/SessionMiddleware";
26
+ import {PageDirectory} from "../plugins/PageDirectory";
27
+ import {LoadedPlugin} from "../plugins/Plugin";
25
28
  import {Request} from "./Request";
26
29
  import {Router} from "./Router";
27
30
  import {Server} from "./Server";
@@ -47,7 +50,8 @@ class HostingServer {
47
50
  cliAuthorizer? : CliAuthorizer,
48
51
  tokenAuthenticator? : TokenAuthenticator,
49
52
  tenant? : string,
50
- auditRecords? : AuditRecordStore) {
53
+ auditRecords? : AuditRecordStore,
54
+ plugins : Array<LoadedPlugin> = []) {
51
55
  const pages = new PagesHandler();
52
56
  const ping = new PingHandler(tenant);
53
57
  const jobs = new JobsHandler(persistence);
@@ -60,6 +64,12 @@ class HostingServer {
60
64
 
61
65
  const publicRouter = new Router();
62
66
  publicRouter.registerRoot(pages);
67
+ if (plugins.length > 0) {
68
+ for (const segment of new PageDirectory(plugins).topLevelSegments) {
69
+ publicRouter.register(segment, pages);
70
+ }
71
+ publicRouter.register("plugins", new PluginsHandler(plugins));
72
+ }
63
73
  publicRouter.register("ping", ping);
64
74
  publicRouter.register("audit", pages);
65
75
  publicRouter.register("whoami", new WhoamiHandler());
@@ -94,6 +94,11 @@ class Request {
94
94
  this.response.end(page);
95
95
  }
96
96
 
97
+ replyJavaScript(script : string) : void {
98
+ this.response.writeHead(200, { "content-type": "text/javascript" });
99
+ this.response.end(script);
100
+ }
101
+
97
102
  notFound() : void {
98
103
  this.reply(404, { error: "Not found" });
99
104
  }
@@ -0,0 +1,52 @@
1
+ import {LoadedPlugin} from "../../plugins/Plugin";
2
+ import {Request} from "../Request";
3
+ import {RequestHandler} from "../RequestHandler";
4
+
5
+ class PluginsHandler implements RequestHandler {
6
+
7
+ constructor(private plugins : Array<LoadedPlugin>) {
8
+ }
9
+
10
+ async handle(request : Request) : Promise<void> {
11
+ if (request.method !== "GET") return request.notFound();
12
+ if (!request.id) return this.manifest(request);
13
+ if (request.id === "data") return this.data(request);
14
+ if (request.id.endsWith(".js")) return this.bundle(request);
15
+ request.notFound();
16
+ }
17
+
18
+ private manifest(request : Request) : void {
19
+ request.reply(200, this.plugins.map(({ name, plugin }) => ({
20
+ name,
21
+ bundle: `/plugins/${name}.js`,
22
+ pages: plugin.pages,
23
+ widgets: plugin.widgets.map(({ page, id, title, position, data }) =>
24
+ ({ page, id, title, position, hasData: data !== undefined })),
25
+ })));
26
+ }
27
+
28
+ private bundle(request : Request) : void {
29
+ const name = request.id!.slice(0, -".js".length);
30
+ const found = this.plugins.find(plugin => plugin.name === name);
31
+ if (!found) return request.notFound();
32
+ request.replyJavaScript(found.bundle);
33
+ }
34
+
35
+ private async data(request : Request) : Promise<void> {
36
+ const pluginName = request.query("plugin");
37
+ const widgetId = request.query("widget");
38
+ const found = this.plugins.find(plugin => plugin.name === pluginName);
39
+ const widget = found?.plugin.widgets.find(candidate => candidate.id === widgetId);
40
+ if (!widget?.data) {
41
+ return request.reply(404, { error: `No data function found for widget "${widgetId}" of plugin "${pluginName}"` });
42
+ }
43
+ const parameters : Record<string, string> = {};
44
+ for (const [key, value] of request.url.searchParams) {
45
+ if (key !== "plugin" && key !== "widget") parameters[key] = value;
46
+ }
47
+ request.reply(200, (await widget.data(parameters)) ?? null);
48
+ }
49
+
50
+ }
51
+
52
+ export { PluginsHandler }