@abstractframework/continuum 0.3.2 → 0.5.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/README.md +41 -7
- package/bin/cli.js +16 -160
- package/bin/hub_proxy.js +52 -43
- package/bin/request_gate.js +43 -0
- package/bin/server.js +232 -0
- package/bin/settings.js +45 -28
- package/dist/assets/{abnfDiagram-VRR7QNED-C3nZSPxd.js → abnfDiagram-VRR7QNED-E9lbmtBV.js} +2 -2
- package/dist/assets/{arc-Co6CTENJ.js → arc-XVGGKtJ-.js} +2 -2
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-CEz8QvCN.js → architectureDiagram-ZJ3FMSHR-GRV2Ej0c.js} +2 -2
- package/dist/assets/{blockDiagram-677ZJIJ3-B_5v6oR-.js → blockDiagram-677ZJIJ3-Dz8wPMHC.js} +2 -2
- package/dist/assets/{c4Diagram-LMCZKHZV-B2dYGCte.js → c4Diagram-LMCZKHZV-CAHne5Am.js} +2 -2
- package/dist/assets/channel-BbrZydfX.js +2 -0
- package/dist/assets/{chunk-2Q5K7J3B-2eGZLgzu.js → chunk-2Q5K7J3B-DFXMgoy3.js} +2 -2
- package/dist/assets/{chunk-32BRIVSS-szXiGdYg.js → chunk-32BRIVSS-D9T7Whin.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-F7rd4WOC.js → chunk-5VM5RSS4-D8jr-VV1.js} +2 -2
- package/dist/assets/{chunk-EX3LRPZG-BBaSONus.js → chunk-EX3LRPZG-BSkCDWzU.js} +2 -2
- package/dist/assets/{chunk-JWPE2WC7-BzQLSiNr.js → chunk-JWPE2WC7-iYmSeuV1.js} +2 -2
- package/dist/assets/{chunk-MOJQB5TN-CV4r6R-P.js → chunk-MOJQB5TN-BxgBSbz5.js} +2 -2
- package/dist/assets/{chunk-RYQCIY6F-lSwdPTG2.js → chunk-RYQCIY6F-D5JkyQAc.js} +2 -2
- package/dist/assets/{chunk-V7JOEXUC-BVe47Wh6.js → chunk-V7JOEXUC-B_6OmmFi.js} +2 -2
- package/dist/assets/{chunk-VR4S4FIN-Xsn7KCEd.js → chunk-VR4S4FIN-BsoDEIUS.js} +2 -2
- package/dist/assets/{chunk-XXDRQBXY-DdG9ww2-.js → chunk-XXDRQBXY-DG-goPoO.js} +2 -2
- package/dist/assets/classDiagram-OUVF2IWQ-BvV49U6G.js +2 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BvV49U6G.js +2 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-DDdy2AQ-.js → cose-bilkent-JH36ORCC-C95YoC9P.js} +2 -2
- package/dist/assets/{cynefin-VYW2F7L2-PkaGuTvX.js → cynefin-VYW2F7L2-A7Vh09t2.js} +3 -3
- package/dist/assets/{cynefinDiagram-TSTJHNR4-BHIvz7Or.js → cynefinDiagram-TSTJHNR4-BI8-04G-.js} +2 -2
- package/dist/assets/{dagre-VKFMJZFB-Dt336PVs.js → dagre-VKFMJZFB-owAzFOdh.js} +2 -2
- package/dist/assets/{diagram-FQU43EPY-DrmE36bq.js → diagram-FQU43EPY-B8FPxd72.js} +2 -2
- package/dist/assets/{diagram-G47NLZAW-tpUlukyV.js → diagram-G47NLZAW-CwooMJF9.js} +2 -2
- package/dist/assets/{diagram-NH7WQ7WH-CO5Zb4_d.js → diagram-NH7WQ7WH-Cr-bMH-G.js} +2 -2
- package/dist/assets/{diagram-OA4YK3LP-B0FwAFRE.js → diagram-OA4YK3LP-3rHTMgXR.js} +2 -2
- package/dist/assets/{diagram-WEI45ONY-CcLxvhZ0.js → diagram-WEI45ONY-BG4PjrBn.js} +2 -2
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CdGlfEot.js → ebnfDiagram-CCIWWBDH-B6uukG4X.js} +2 -2
- package/dist/assets/{erDiagram-Q63AITRT-CxYfUHK8.js → erDiagram-Q63AITRT-D3OgqYHT.js} +2 -2
- package/dist/assets/{flowDiagram-23GEKE2U-fW4F9z69.js → flowDiagram-23GEKE2U-CxTaDERr.js} +2 -2
- package/dist/assets/{ganttDiagram-NO4QXBWP-BWM-HcND.js → ganttDiagram-NO4QXBWP-CN7iAeQh.js} +2 -2
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-BlcnohGg.js → gitGraphDiagram-IHSO6WYX-DbvcWmmM.js} +2 -2
- package/dist/assets/index-Ca1Qebcu.js +121 -0
- package/dist/assets/index-fh60G2KG.css +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-pJSOWVdi.js → infoDiagram-FWYZ7A6U-DX4NPNgR.js} +2 -2
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CpvrrPMQ.js → ishikawaDiagram-FXEZZL3T-DtL3v287.js} +2 -2
- package/dist/assets/{journeyDiagram-5HDEW3XC-DzFKBKHk.js → journeyDiagram-5HDEW3XC-CtAZFiGl.js} +2 -2
- package/dist/assets/{kanban-definition-HUTT4EX6-BPmBQCPR.js → kanban-definition-HUTT4EX6-CvrCkrKt.js} +2 -2
- package/dist/assets/{linear-DmNa6TPI.js → linear-CbLBdTPp.js} +2 -2
- package/dist/assets/{mermaid.core-_-YW0o95.js → mermaid.core-zt0bcQ8D.js} +6 -6
- package/dist/assets/{mindmap-definition-LN4V7U3C-Bdwo0rlk.js → mindmap-definition-LN4V7U3C-DU-r_mE6.js} +2 -2
- package/dist/assets/{pegDiagram-2B236MQR-CHZoedfQ.js → pegDiagram-2B236MQR-CoiPuGzc.js} +2 -2
- package/dist/assets/{pieDiagram-ENE6RG2P-Blp676Hr.js → pieDiagram-ENE6RG2P-ClUZORQg.js} +2 -2
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-DhWHDdBX.js → quadrantDiagram-ABIIQ3AL-D0i0p2xo.js} +2 -2
- package/dist/assets/{railroadDiagram-RFXS5EU6-CEgbBDEg.js → railroadDiagram-RFXS5EU6-wI6Hdpa5.js} +2 -2
- package/dist/assets/{requirementDiagram-TGXJPOKE-Ds8x8FbV.js → requirementDiagram-TGXJPOKE-Bm4qvEra.js} +2 -2
- package/dist/assets/{sankeyDiagram-HTMAVEWB-yKIsBDhP.js → sankeyDiagram-HTMAVEWB-DX8is9uB.js} +2 -2
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-DAgYlOsI.js → sequenceDiagram-DBY2YBRQ-5RpIQiJh.js} +2 -2
- package/dist/assets/{sizeCapture-X5ZJPWSS-C-LAlxdw.js → sizeCapture-X5ZJPWSS-ChX6cFSk.js} +2 -2
- package/dist/assets/{stateDiagram-2N3HPSRC-BLiS6piZ.js → stateDiagram-2N3HPSRC-Tea20pcW.js} +2 -2
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CCBRDLbX.js +2 -0
- package/dist/assets/swimlanes-5IMT3BWC-BY7gSBH7.js +3 -0
- package/dist/assets/swimlanesDiagram-G3AALYLV-Dtz4rZCK.js +9 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-BrJ5JJYj.js → timeline-definition-FHXFAJF6-Bq52M8-r.js} +2 -2
- package/dist/assets/{vennDiagram-L72KCM5P-BT9M9-9s.js → vennDiagram-L72KCM5P-BGBcMmhw.js} +2 -2
- package/dist/assets/{wardleyDiagram-EHGQE667-CukiKHz8.js → wardleyDiagram-EHGQE667-T3JqsmMn.js} +2 -2
- package/dist/assets/{xychartDiagram-FW5EYKEG-CIidttLL.js → xychartDiagram-FW5EYKEG-B0yTh8-d.js} +2 -2
- package/dist/index.html +7 -3
- package/package.json +5 -3
- package/dist/assets/channel-D3yEdLfb.js +0 -2
- package/dist/assets/classDiagram-OUVF2IWQ-DJJxVI5p.js +0 -2
- package/dist/assets/classDiagram-v2-EOCWNBFH-DJJxVI5p.js +0 -2
- package/dist/assets/index-CFGJJTR4.js +0 -123
- package/dist/assets/index-DAHZ6K6R.css +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CdmuVCk9.js +0 -2
- package/dist/assets/swimlanes-5IMT3BWC-Bj_dHHPI.js +0 -3
- package/dist/assets/swimlanesDiagram-G3AALYLV-CYrgsk-L.js +0 -9
package/bin/server.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AbstractContinuum HTTP server: the built console, the app-origin
|
|
3
|
+
* gateway session proxy (/api/* → the gateway), the Team page's hub proxy
|
|
4
|
+
* (/api/hub/*, HTTP + the /api/hub/ws relay) and the Settings route.
|
|
5
|
+
*
|
|
6
|
+
* Mountable under the gateway at /apps/continuum/ (the
|
|
7
|
+
* @abstractframework/app-server mount contract): every response, the
|
|
8
|
+
* WebSocket 101 included, announces `X-AbstractFramework-App: continuum;
|
|
9
|
+
* mount=1`; the page gets `<base href="<base path>/">` and `base_path`;
|
|
10
|
+
* cookies carry `Path=<base path>/`; and every "is this the operator's own
|
|
11
|
+
* computer?" check reads the kit's requestContext (bin/request_gate.js),
|
|
12
|
+
* never the socket peer. Served at `/` on its own port it works exactly as
|
|
13
|
+
* before.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as http from 'http';
|
|
17
|
+
import { createHash } from 'crypto';
|
|
18
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
19
|
+
import { join, extname } from 'path';
|
|
20
|
+
import {
|
|
21
|
+
MountRequestError,
|
|
22
|
+
createGatewaySessionProxy,
|
|
23
|
+
createGatewayUrlResolver,
|
|
24
|
+
createMountedHandler,
|
|
25
|
+
injectShell,
|
|
26
|
+
rejectUpgrade,
|
|
27
|
+
requestContext,
|
|
28
|
+
} from '@abstractframework/app-server';
|
|
29
|
+
import { APP_ID, createHubProxy } from './hub_proxy.js';
|
|
30
|
+
import { createSettingsRoute, expand_home } from './settings.js';
|
|
31
|
+
|
|
32
|
+
const MIME_TYPES = {
|
|
33
|
+
'.html': 'text/html',
|
|
34
|
+
'.js': 'application/javascript',
|
|
35
|
+
'.css': 'text/css',
|
|
36
|
+
'.json': 'application/json',
|
|
37
|
+
'.png': 'image/png',
|
|
38
|
+
'.jpg': 'image/jpeg',
|
|
39
|
+
'.jpeg': 'image/jpeg',
|
|
40
|
+
'.gif': 'image/gif',
|
|
41
|
+
'.svg': 'image/svg+xml',
|
|
42
|
+
'.ico': 'image/x-icon',
|
|
43
|
+
'.webmanifest': 'application/manifest+json',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function getMimeType(filePath) {
|
|
47
|
+
const ext = extname(filePath).toLowerCase();
|
|
48
|
+
return MIME_TYPES[ext] || 'application/octet-stream';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Content-Security-Policy for the app document (security adversary P1
|
|
52
|
+
// 2026-07-16): untrusted markdown (channel files, messages, attachments)
|
|
53
|
+
// renders in this app, and standard markdown allows remote images — a
|
|
54
|
+
// crafted  would beacon the operator's IP
|
|
55
|
+
// and read-timing to an external host the moment a preview opens. img-src
|
|
56
|
+
// 'self' data: kills that class app-wide. script-src 'self' is a backstop
|
|
57
|
+
// belt (the renderer already cannot emit script). ws:/wss: are needed
|
|
58
|
+
// because 'self' does not reliably cover scheme-different WebSocket dials;
|
|
59
|
+
// only our own bundle can open sockets (script-src), so this stays tight.
|
|
60
|
+
const CSP = [
|
|
61
|
+
"default-src 'self'",
|
|
62
|
+
"script-src 'self'",
|
|
63
|
+
"style-src 'self' 'unsafe-inline'", // React style={} attributes
|
|
64
|
+
"img-src 'self' data:",
|
|
65
|
+
"font-src 'self' data:",
|
|
66
|
+
"connect-src 'self' ws: wss:",
|
|
67
|
+
"object-src 'none'",
|
|
68
|
+
"base-uri 'self'",
|
|
69
|
+
"form-action 'self'",
|
|
70
|
+
"frame-ancestors 'none'",
|
|
71
|
+
].join('; ');
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* settings: live settings (bin/settings.js createLiveSettings); distDir: the
|
|
75
|
+
* built console. The gateway URL, like the port and host, is read once from
|
|
76
|
+
* the settings at creation.
|
|
77
|
+
*/
|
|
78
|
+
export function createContinuumServer({ settings, distDir }) {
|
|
79
|
+
const AT_START = settings.get();
|
|
80
|
+
const DEFAULT_GATEWAY_URL = AT_START.gateway_url.value;
|
|
81
|
+
const DIST_DIR = distDir;
|
|
82
|
+
// A gateway URL nobody chose (the local gateway pointer, or the built-in
|
|
83
|
+
// default) follows the pointer while the server runs: the kit's resolver
|
|
84
|
+
// re-reads it when the gateway refuses a connection. A flag, setting or
|
|
85
|
+
// environment choice never moves.
|
|
86
|
+
const CHOSEN_GATEWAY = ['flag', 'setting', 'env'].includes(AT_START.gateway_url.source);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The console document for one request: the kit's injectShell puts
|
|
90
|
+
* `<base href="<base path>/">` first in <head> (every relative asset and
|
|
91
|
+
* fetch resolves under /apps/continuum/ behind the gateway, under / on the
|
|
92
|
+
* app's own port) plus `window.__ABSTRACT_UI_CONFIG__` (gateway_url,
|
|
93
|
+
* base_path) in one inline script. The CSP admits exactly that script by
|
|
94
|
+
* its hash; every other inline script stays refused.
|
|
95
|
+
*/
|
|
96
|
+
function shellResponse(html, ctx) {
|
|
97
|
+
const page = injectShell(html, { basePath: ctx.basePath, config: { gateway_url: gatewaySessionProxy.defaultGatewayUrl } });
|
|
98
|
+
const inline = /<base [^>]*><script>([\s\S]*?)<\/script>/.exec(page);
|
|
99
|
+
if (!inline) throw new Error('injectShell produced no inline config script');
|
|
100
|
+
const hash = createHash('sha256').update(inline[1], 'utf8').digest('base64');
|
|
101
|
+
const csp = CSP.replace("script-src 'self'", `script-src 'self' 'sha256-${hash}'`);
|
|
102
|
+
return { page, csp };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function serveFile(res, filePath, ctx) {
|
|
106
|
+
try {
|
|
107
|
+
if (!existsSync(filePath) || !statSync(filePath).isFile()) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const mime = getMimeType(filePath);
|
|
111
|
+
const headers = {
|
|
112
|
+
'Content-Type': mime,
|
|
113
|
+
'Cache-Control': 'no-cache',
|
|
114
|
+
'X-Content-Type-Options': 'nosniff',
|
|
115
|
+
};
|
|
116
|
+
if (mime === 'text/html') {
|
|
117
|
+
// CSP is a document policy — attach it where a document loads.
|
|
118
|
+
const { page, csp } = shellResponse(readFileSync(filePath, 'utf8'), ctx);
|
|
119
|
+
headers['Content-Type'] = 'text/html; charset=utf-8';
|
|
120
|
+
headers['Content-Security-Policy'] = csp;
|
|
121
|
+
res.writeHead(200, headers);
|
|
122
|
+
res.end(page);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
res.writeHead(200, headers);
|
|
126
|
+
res.end(readFileSync(filePath));
|
|
127
|
+
return true;
|
|
128
|
+
} catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Cookies abstractcontinuum_gateway_* and header x-abstractcontinuum-csrf
|
|
134
|
+
// derive from appId; mounted, the cookies carry Path=/apps/continuum/.
|
|
135
|
+
const gatewaySessionProxy = createGatewaySessionProxy({
|
|
136
|
+
appId: 'abstractcontinuum',
|
|
137
|
+
defaultGatewayUrl: CHOSEN_GATEWAY ? DEFAULT_GATEWAY_URL : createGatewayUrlResolver({}),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Team page transport: /api/hub/* forwards an allowlisted hub API subset
|
|
141
|
+
// with the OPERATOR seat's key attached server-side (proposal c1692,
|
|
142
|
+
// agency contract c1696 — authorship stays the operator's own).
|
|
143
|
+
// Every hub setting is a getter: a seat saved on the Settings page or with
|
|
144
|
+
// `abstractcontinuum config set` applies without a restart.
|
|
145
|
+
const hubProxy = createHubProxy({
|
|
146
|
+
hubUrl: () => settings.value('hub_url'),
|
|
147
|
+
seat: () => settings.value('hub_seat'),
|
|
148
|
+
keysPath: () => expand_home(settings.value('hub_keys')),
|
|
149
|
+
token: () => settings.value('hub_token'),
|
|
150
|
+
allowRemote: () => settings.value('hub_allow_remote'),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// The Settings page's door to this server's own settings (hub seat).
|
|
154
|
+
const settingsRoute = createSettingsRoute({ live: settings });
|
|
155
|
+
|
|
156
|
+
const server = http.createServer(createMountedHandler({ appId: APP_ID }, (req, res, ctx) => {
|
|
157
|
+
// Parse against a FIXED base: a malformed Host header (e.g. "a b") makes
|
|
158
|
+
// new URL throw synchronously, which would kill the whole process from
|
|
159
|
+
// one bad request (adversarial P1 2026-07-13). The host plays no role in
|
|
160
|
+
// routing here — only the pathname does.
|
|
161
|
+
let pathname = '/';
|
|
162
|
+
let search = '';
|
|
163
|
+
try {
|
|
164
|
+
const u = new URL(req.url, 'http://local');
|
|
165
|
+
pathname = u.pathname;
|
|
166
|
+
search = u.search;
|
|
167
|
+
} catch {
|
|
168
|
+
res.writeHead(400);
|
|
169
|
+
res.end('Bad Request');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (settingsRoute.handle(req, res, pathname)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (hubProxy.handle(req, res, pathname, search)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (gatewaySessionProxy.handle(req, res, pathname)) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (pathname.includes('..')) {
|
|
186
|
+
res.writeHead(400);
|
|
187
|
+
res.end('Bad Request');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (pathname === '/' || pathname === '') {
|
|
192
|
+
pathname = '/index.html';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const filePath = join(DIST_DIR, pathname);
|
|
196
|
+
if (serveFile(res, filePath, ctx)) return;
|
|
197
|
+
if (serveFile(res, filePath + '.html', ctx)) return;
|
|
198
|
+
|
|
199
|
+
// SPA fallback
|
|
200
|
+
if (serveFile(res, join(DIST_DIR, 'index.html'), ctx)) return;
|
|
201
|
+
|
|
202
|
+
res.writeHead(404);
|
|
203
|
+
res.end('Not Found');
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
// Live hub updates: relay browser WebSockets to the hub with the seat key
|
|
207
|
+
// attached server-side (operator c2240 — realtime, not 5s polling).
|
|
208
|
+
server.on('upgrade', (req, socket, head) => {
|
|
209
|
+
// F7 (dm-99 tail): a raw upgrade socket with no 'error' listener is a
|
|
210
|
+
// process-killer (browser aborts mid-handshake raise uncaught 'error');
|
|
211
|
+
// and a rejected relay promise without .catch is an unhandled rejection.
|
|
212
|
+
socket.on('error', () => {
|
|
213
|
+
try { socket.destroy(); } catch { /* already gone */ }
|
|
214
|
+
});
|
|
215
|
+
// A malformed forwarded header or an unknown peer: refused before any
|
|
216
|
+
// route (the kit's rule; the hub relay applies it again for its gate).
|
|
217
|
+
try {
|
|
218
|
+
requestContext(req);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
if (!(e instanceof MountRequestError)) throw e;
|
|
221
|
+
rejectUpgrade(socket, e.status, e.message);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
void hubProxy.handleUpgrade(req, socket, head).then((handled) => {
|
|
225
|
+
if (!handled) rejectUpgrade(socket, 404, 'No WebSocket here.');
|
|
226
|
+
}).catch(() => {
|
|
227
|
+
try { socket.destroy(); } catch { /* already gone */ }
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return server;
|
|
232
|
+
}
|
package/bin/settings.js
CHANGED
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'fs';
|
|
23
23
|
import { homedir } from 'os';
|
|
24
24
|
import { dirname, join } from 'path';
|
|
25
|
+
import { MountRequestError, resolveGatewayUrl } from '@abstractframework/app-server';
|
|
26
|
+
import { request_gate } from './request_gate.js';
|
|
25
27
|
|
|
26
28
|
const TRUE_VALUES = new Set(['1', 'true', 'yes', 'y', 'on']);
|
|
27
29
|
const FALSE_VALUES = new Set(['0', 'false', 'no', 'n', 'off']);
|
|
@@ -50,7 +52,12 @@ export const SETTINGS = [
|
|
|
50
52
|
{
|
|
51
53
|
key: 'gateway_url',
|
|
52
54
|
flag: '--gateway-url',
|
|
55
|
+
// The same spellings as every AbstractFramework app
|
|
56
|
+
// (@abstractframework/app-server parseAppFlags).
|
|
57
|
+
aliases: ['--gateway', '--url'],
|
|
53
58
|
type: 'url',
|
|
59
|
+
// With no flag, setting or environment: the gateway installed on this
|
|
60
|
+
// computer (~/.abstractframework/gateway.json), else this URL.
|
|
54
61
|
default: 'http://127.0.0.1:8080',
|
|
55
62
|
env: ['ABSTRACTCONTINUUM_GATEWAY_URL', 'ABSTRACTGATEWAY_URL'],
|
|
56
63
|
metavar: '<url>',
|
|
@@ -134,7 +141,7 @@ export const SETTINGS = [
|
|
|
134
141
|
export const UI_WRITABLE_KEYS = ['hub_seat'];
|
|
135
142
|
|
|
136
143
|
const BY_KEY = new Map(SETTINGS.map((s) => [s.key, s]));
|
|
137
|
-
const BY_FLAG = new Map(SETTINGS.
|
|
144
|
+
const BY_FLAG = new Map(SETTINGS.flatMap((s) => [s.flag, ...(s.aliases || [])].map((f) => [f, s])));
|
|
138
145
|
|
|
139
146
|
export function setting_spec(key) {
|
|
140
147
|
return BY_KEY.get(key) || null;
|
|
@@ -195,11 +202,16 @@ export function coerce(spec, raw) {
|
|
|
195
202
|
/**
|
|
196
203
|
* THE precedence function: flag > settings file > environment (legacy) >
|
|
197
204
|
* default, per key. Returns `{ [key]: { value, source } }` with source one
|
|
198
|
-
* of "flag" | "setting" | "env" | "default". An invalid file or
|
|
199
|
-
* is skipped (reported in `problems`) so one bad entry cannot
|
|
200
|
-
* server; an invalid FLAG never reaches here (parse_args refuses
|
|
205
|
+
* of "flag" | "setting" | "env" | "pointer" | "default". An invalid file or
|
|
206
|
+
* env value is skipped (reported in `problems`) so one bad entry cannot
|
|
207
|
+
* stop the server; an invalid FLAG never reaches here (parse_args refuses
|
|
208
|
+
* it).
|
|
209
|
+
*
|
|
210
|
+
* gateway_url with nothing chosen follows the kit's local gateway pointer
|
|
211
|
+
* (`~/.abstractframework/gateway.json` under `home`, source "pointer"),
|
|
212
|
+
* else the built-in default: the same reader as every other app.
|
|
201
213
|
*/
|
|
202
|
-
export function resolve_settings({ flags = {}, file = {}, env = {} } = {}) {
|
|
214
|
+
export function resolve_settings({ flags = {}, file = {}, env = {}, home, pointer } = {}) {
|
|
203
215
|
const out = {};
|
|
204
216
|
const problems = [];
|
|
205
217
|
for (const spec of SETTINGS) {
|
|
@@ -227,6 +239,10 @@ export function resolve_settings({ flags = {}, file = {}, env = {} } = {}) {
|
|
|
227
239
|
break;
|
|
228
240
|
}
|
|
229
241
|
}
|
|
242
|
+
if (!picked && k === 'gateway_url') {
|
|
243
|
+
const r = resolveGatewayUrl({ home, pointer, warn: (m) => problems.push(m) });
|
|
244
|
+
picked = { value: r.url, source: r.source === 'pointer' ? 'pointer' : 'default' };
|
|
245
|
+
}
|
|
230
246
|
if (!picked) picked = { value: spec.default, source: 'default' };
|
|
231
247
|
out[k] = picked;
|
|
232
248
|
}
|
|
@@ -276,7 +292,13 @@ export function display_value(spec, value) {
|
|
|
276
292
|
return String(value);
|
|
277
293
|
}
|
|
278
294
|
|
|
279
|
-
export const SOURCE_WORDS = {
|
|
295
|
+
export const SOURCE_WORDS = {
|
|
296
|
+
flag: 'launch flag',
|
|
297
|
+
setting: 'setting',
|
|
298
|
+
env: 'environment (legacy)',
|
|
299
|
+
pointer: 'the gateway installed on this computer',
|
|
300
|
+
default: 'default',
|
|
301
|
+
};
|
|
280
302
|
|
|
281
303
|
/**
|
|
282
304
|
* Parse argv. Shapes:
|
|
@@ -379,7 +401,7 @@ export function apply_session_proxy_gates(settings, env) {
|
|
|
379
401
|
* without a restart). Port, host, gateway URL and the sign-in gates are
|
|
380
402
|
* read once at start.
|
|
381
403
|
*/
|
|
382
|
-
export function createLiveSettings({ flags = {}, settingsPath, env = process.env, onProblem = () => {} }) {
|
|
404
|
+
export function createLiveSettings({ flags = {}, settingsPath, env = process.env, home, onProblem = () => {} }) {
|
|
383
405
|
let stamp = '';
|
|
384
406
|
let file = {};
|
|
385
407
|
let resolved = null;
|
|
@@ -398,7 +420,7 @@ export function createLiveSettings({ flags = {}, settingsPath, env = process.env
|
|
|
398
420
|
} catch (e) {
|
|
399
421
|
onProblem(`settings file ${settingsPath}: ${e.message}`);
|
|
400
422
|
}
|
|
401
|
-
const r = resolve_settings({ flags, file, env });
|
|
423
|
+
const r = resolve_settings({ flags, file, env, home });
|
|
402
424
|
for (const p of r.problems) onProblem(p);
|
|
403
425
|
resolved = r.settings;
|
|
404
426
|
return resolved;
|
|
@@ -421,21 +443,6 @@ function send_json(res, status, body) {
|
|
|
421
443
|
res.end(JSON.stringify(body));
|
|
422
444
|
}
|
|
423
445
|
|
|
424
|
-
function is_loopback_peer(req) {
|
|
425
|
-
const peer = String(req.socket && req.socket.remoteAddress ? req.socket.remoteAddress : '');
|
|
426
|
-
return peer === '127.0.0.1' || peer === '::1' || peer === '::ffff:127.0.0.1' || peer === '';
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function same_origin(req) {
|
|
430
|
-
const origin = String((req.headers && req.headers.origin) || '').trim();
|
|
431
|
-
if (!origin) return true;
|
|
432
|
-
try {
|
|
433
|
-
return new URL(origin).host.toLowerCase() === String((req.headers && req.headers.host) || '').trim().toLowerCase();
|
|
434
|
-
} catch {
|
|
435
|
-
return false;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
446
|
/** Public view: every setting with its source; secrets as presence only. */
|
|
440
447
|
export function settings_view(live, extra = {}) {
|
|
441
448
|
const s = live.get();
|
|
@@ -449,9 +456,11 @@ export function settings_view(live, extra = {}) {
|
|
|
449
456
|
|
|
450
457
|
/**
|
|
451
458
|
* GET/PUT /api/continuum/settings — the Settings page's door to this
|
|
452
|
-
* server's own settings. Local only: the
|
|
453
|
-
* (
|
|
454
|
-
*
|
|
459
|
+
* server's own settings. Local only: the BROWSER must run on this computer
|
|
460
|
+
* (bin/request_gate.js — behind the gateway's /apps/continuum/ that is the
|
|
461
|
+
* forwarded client address, never the socket peer; the same gate as the hub
|
|
462
|
+
* proxy, which this seat setting steers) and a browser Origin must match
|
|
463
|
+
* the host it addressed. PUT takes `{ hub_seat: "<seat>" }`
|
|
455
464
|
* or `{ hub_seat: null }` (back to the default) and persists to the
|
|
456
465
|
* settings file.
|
|
457
466
|
*/
|
|
@@ -462,11 +471,19 @@ export function createSettingsRoute({ live, extra = () => ({}) }) {
|
|
|
462
471
|
path: SETTINGS_ROUTE,
|
|
463
472
|
handle(req, res, pathname) {
|
|
464
473
|
if (pathname !== SETTINGS_ROUTE) return false;
|
|
465
|
-
|
|
474
|
+
let gate;
|
|
475
|
+
try {
|
|
476
|
+
gate = request_gate(req);
|
|
477
|
+
} catch (e) {
|
|
478
|
+
if (!(e instanceof MountRequestError)) throw e;
|
|
479
|
+
send_json(res, e.status, { error: 'bad_request', detail: e.message });
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
if (!gate.loopback) {
|
|
466
483
|
send_json(res, 403, { error: 'settings_non_loopback', detail: "Continuum's settings can only be changed from the computer it runs on." });
|
|
467
484
|
return true;
|
|
468
485
|
}
|
|
469
|
-
if (!same_origin
|
|
486
|
+
if (!gate.same_origin) {
|
|
470
487
|
send_json(res, 403, { error: 'settings_cross_origin', detail: 'Cross-origin requests to the settings route are refused.' });
|
|
471
488
|
return true;
|
|
472
489
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-
|
|
2
|
-
//# sourceMappingURL=abnfDiagram-VRR7QNED-
|
|
1
|
+
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-BxgBSbz5.js";import{p as f}from"./chunk-JWPE2WC7-iYmSeuV1.js";import{_ as n,l as o}from"./mermaid.core-zt0bcQ8D.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-A7Vh09t2.js";import"./index-Ca1Qebcu.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
|
|
2
|
+
//# sourceMappingURL=abnfDiagram-VRR7QNED-E9lbmtBV.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-
|
|
2
|
-
//# sourceMappingURL=arc-
|
|
1
|
+
import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-zt0bcQ8D.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
|
|
2
|
+
//# sourceMappingURL=arc-XVGGKtJ-.js.map
|