@lamalibre/portlama-agent 1.0.21 → 1.0.22

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.
@@ -11,11 +11,14 @@
11
11
  */
12
12
 
13
13
  import Fastify from 'fastify';
14
+ import cors from '@fastify/cors';
14
15
  import rateLimit from '@fastify/rate-limit';
15
16
  import fastifyStatic from '@fastify/static';
16
17
  import path from 'node:path';
17
18
  import { fileURLToPath } from 'node:url';
18
19
  import panelApiRoutes from './panel-api-routes.js';
20
+ import agentPluginRouter from './agent-plugin-router.js';
21
+ import { readAgentPluginBundle } from './agent-plugins.js';
19
22
 
20
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
24
 
@@ -33,6 +36,18 @@ export async function startPanelServer(label, { port = 9393 } = {}) {
33
36
  },
34
37
  });
35
38
 
39
+ // Allow Tauri webview and localhost origins to call plugin APIs.
40
+ // credentials: true is required because plugin microfrontends use
41
+ // fetch(..., { credentials: 'include' }) for session cookies.
42
+ await server.register(cors, {
43
+ origin: [
44
+ 'tauri://localhost',
45
+ 'https://tauri.localhost',
46
+ /^http:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?$/,
47
+ ],
48
+ credentials: true,
49
+ });
50
+
36
51
  await server.register(rateLimit, {
37
52
  max: 100,
38
53
  timeWindow: '1 minute',
@@ -57,6 +72,19 @@ export async function startPanelServer(label, { port = 9393 } = {}) {
57
72
 
58
73
  const verify = request.headers['x-ssl-client-verify'];
59
74
  if (verify !== 'SUCCESS') {
75
+ // Allow localhost browser requests (desktop app plugin microfrontends).
76
+ // The server binds 127.0.0.1 only, so only local processes can reach it.
77
+ // The Origin header is browser-enforced and cannot be forged by web pages.
78
+ const origin = request.headers.origin || '';
79
+ const isLocalOrigin =
80
+ /^http:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?$/.test(origin) ||
81
+ origin === 'tauri://localhost' ||
82
+ origin === 'https://tauri.localhost';
83
+ if (isLocalOrigin) {
84
+ request.certCN = `agent:${label}`;
85
+ request.certRole = 'agent';
86
+ return;
87
+ }
60
88
  return reply.code(403).send({ error: 'Valid mTLS certificate required' });
61
89
  }
62
90
 
@@ -82,6 +110,33 @@ export async function startPanelServer(label, { port = 9393 } = {}) {
82
110
  // --- REST API routes ---
83
111
  await server.register(panelApiRoutes, { prefix: '/api', label });
84
112
 
113
+ // --- Agent plugin routes ---
114
+ // Mounts enabled plugin server routes at /<name>/... (root level),
115
+ // matching the local plugin host pattern that plugins expect.
116
+ // Plugins construct URLs as ${panelUrl}/${pluginName}/api/${pluginName}/...
117
+ await server.register(agentPluginRouter, { label });
118
+
119
+ // --- Public plugin bundle endpoint (outside /api — no mTLS required) ---
120
+ // Desktop app loads bundles via <script> tag to bypass Tauri IPC JSON size limits.
121
+ // Script tags are not subject to CORS, so cross-origin loading works.
122
+ const PLUGIN_NAME_RE = /^[a-z0-9-]+$/;
123
+ server.get('/plugin-bundles/:name/panel.js', async (request, reply) => {
124
+ const { name } = request.params;
125
+ if (!PLUGIN_NAME_RE.test(name)) {
126
+ reply.type('application/javascript');
127
+ return reply.code(400).send('// invalid plugin name');
128
+ }
129
+ try {
130
+ const source = await readAgentPluginBundle(label, name);
131
+ reply.type('application/javascript');
132
+ reply.header('Cache-Control', 'public, max-age=3600');
133
+ return source;
134
+ } catch {
135
+ reply.type('application/javascript');
136
+ return reply.code(404).send(`// plugin bundle not found: ${name}`);
137
+ }
138
+ });
139
+
85
140
  // --- Static SPA files ---
86
141
  const staticRoot = path.resolve(__dirname, '..', 'panel-dist');
87
142
  try {
@@ -19,6 +19,7 @@ import {
19
19
  panelLogFile,
20
20
  panelErrorLogFile,
21
21
  agentLogsDir,
22
+ agentDataDir,
22
23
  } from './platform.js';
23
24
 
24
25
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -218,6 +219,7 @@ function generatePanelSystemdUnit(label, port) {
218
219
  const logFile = panelLogFile(label);
219
220
  const errorLogFile = panelErrorLogFile(label);
220
221
  const logsDir = agentLogsDir(label);
222
+ const dataDir = agentDataDir(label);
221
223
 
222
224
  const execStart = [nodePath, entryPath, '--label', label, '--port', String(port)]
223
225
  .map(systemdQuote)
@@ -238,7 +240,7 @@ StandardError=append:${errorLogFile}
238
240
  Environment=PATH=/usr/local/bin:/usr/bin:/bin
239
241
  NoNewPrivileges=true
240
242
  ProtectSystem=strict
241
- ReadWritePaths=${logsDir}
243
+ ReadWritePaths=${logsDir} ${dataDir}
242
244
 
243
245
  [Install]
244
246
  WantedBy=multi-user.target