@ibgib/space-gib 0.0.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/CHANGELOG.md +31 -0
- package/Dockerfile +14 -0
- package/IMPLEMENTATION.md +484 -0
- package/README.md +46 -0
- package/dist/client/bootstrap.mjs +58 -0
- package/dist/client/bootstrap.mjs.map +7 -0
- package/dist/client/chunk-CT47Z5WU.mjs +21 -0
- package/dist/client/chunk-CT47Z5WU.mjs.map +7 -0
- package/dist/client/chunk-RHEDTRKF.mjs +235 -0
- package/dist/client/chunk-RHEDTRKF.mjs.map +7 -0
- package/dist/client/index.html +147 -0
- package/dist/client/index.mjs +2 -0
- package/dist/client/index.mjs.map +7 -0
- package/dist/client/script.mjs +2 -0
- package/dist/client/script.mjs.map +7 -0
- package/dist/client/style.css +605 -0
- package/dist/respec-gib.node.mjs +5 -0
- package/dist/server/server.mjs +20157 -0
- package/dist/server/server.mjs.map +7 -0
- package/generate-version-file.js +35 -0
- package/package.json +27 -0
- package/src/client/AUTO-GENERATED-version.mts +11 -0
- package/src/client/README.md +19 -0
- package/src/client/api/function-infos.web.mts +38 -0
- package/src/client/api/space-gib-api-bridge.mts +85 -0
- package/src/client/bootstrap.mts +49 -0
- package/src/client/components/keystone-creator/keystone-creator.css +139 -0
- package/src/client/components/keystone-creator/keystone-creator.html +26 -0
- package/src/client/components/keystone-creator/keystone-creator.mts +229 -0
- package/src/client/constants.mts +76 -0
- package/src/client/custom.d.ts +11 -0
- package/src/client/dev-tools.mts +540 -0
- package/src/client/helpers.web.mts +178 -0
- package/src/client/index.html +147 -0
- package/src/client/index.mts +59 -0
- package/src/client/script.mts +13 -0
- package/src/client/style.css +605 -0
- package/src/client/types.mts +85 -0
- package/src/client/ui/shell/space-gib-shell-constants.mts +24 -0
- package/src/client/ui/shell/space-gib-shell-service.mts +233 -0
- package/src/client/ui/shell/space-gib-shell-types.mts +5 -0
- package/src/client/witness/app/space-gib/space-gib-app-v1.mts +160 -0
- package/src/client/witness/app/space-gib/space-gib-constants.mts +38 -0
- package/src/client/witness/app/space-gib/space-gib-helper.mts +72 -0
- package/src/client/witness/app/space-gib/space-gib-types.mts +47 -0
- package/src/common/keystone-policies.mts +159 -0
- package/src/respec-gib.node.mts +6 -0
- package/src/server/README.md +18 -0
- package/src/server/bootstrap-helper.mts +141 -0
- package/src/server/bootstrap-helper.respec.mts +100 -0
- package/src/server/metaspace-nodeindexedspace/metaspace-nodeindexedspace.mts +85 -0
- package/src/server/path-constants.mts +89 -0
- package/src/server/path-helper.mts +101 -0
- package/src/server/path-helper.respec.mts +94 -0
- package/src/server/serve-gib/CHANGELOG.md +29 -0
- package/src/server/serve-gib/README.md +34 -0
- package/src/server/serve-gib/constants.mts +1 -0
- package/src/server/serve-gib/handlers/api/debug/ws-echo.handler.mts +104 -0
- package/src/server/serve-gib/handlers/api/health.handler.mts +23 -0
- package/src/server/serve-gib/handlers/api/health.respec.mts +51 -0
- package/src/server/serve-gib/handlers/api/ibgib/ibgib-handler-types.mts +49 -0
- package/src/server/serve-gib/handlers/api/ibgib/ibgib.handler.mts +176 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +261 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +146 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.handler.mts +198 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +107 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-handler-types.mts +29 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-post.handler.mts +70 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-post.respec.mts +130 -0
- package/src/server/serve-gib/handlers/error-handler.mts +36 -0
- package/src/server/serve-gib/handlers/handler-base.mts +383 -0
- package/src/server/serve-gib/handlers/static-handler.mts +82 -0
- package/src/server/serve-gib/handlers/ws/sync-upgrade.handler.mts +498 -0
- package/src/server/serve-gib/handlers/ws/ws-helper.mts +111 -0
- package/src/server/serve-gib/handlers/ws/ws-types.mts +53 -0
- package/src/server/serve-gib/serve-gib-helpers.mts +32 -0
- package/src/server/serve-gib/serve-gib-v1.mts +172 -0
- package/src/server/serve-gib/serve-gib.respec.mts +90 -0
- package/src/server/serve-gib/types.mts +102 -0
- package/src/server/server-constants.mts +2 -0
- package/src/server/server.mts +96 -0
- package/tsconfig.json +29 -0
- package/tsconfig.server.json +29 -0
- package/tsconfig.test.json +27 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module serve-gib/serve-gib-v1
|
|
3
|
+
*
|
|
4
|
+
* The main entry point for processing incoming requests in the serve-gib
|
|
5
|
+
* framework.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
9
|
+
|
|
10
|
+
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
11
|
+
|
|
12
|
+
import { GLOBAL_LOG_A_LOT } from './constants.mjs';
|
|
13
|
+
import { RequestContext, ResponseResult, ServeGibHandler, ServeGibHttpMethod } from './types.mjs';
|
|
14
|
+
|
|
15
|
+
const logalot = GLOBAL_LOG_A_LOT;
|
|
16
|
+
|
|
17
|
+
export interface ServeGibOptions {
|
|
18
|
+
/** Pipeline of handlers to check in order. */
|
|
19
|
+
handlers: ServeGibHandler<any, any>[];
|
|
20
|
+
/** Dedicated handler for errors (404, 500, etc.). */
|
|
21
|
+
errorHandler: ServeGibHandler<any, any>;
|
|
22
|
+
/** Port the server is listening on. */
|
|
23
|
+
port: number;
|
|
24
|
+
/**
|
|
25
|
+
* Root data directory for the server. We will use this as the root dir for
|
|
26
|
+
* all spaces that house multitenant ibgib data.
|
|
27
|
+
*/
|
|
28
|
+
dataDir: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ServeGib_V1 {
|
|
32
|
+
private lc = `[${ServeGib_V1.name}]`;
|
|
33
|
+
|
|
34
|
+
constructor(private options: ServeGibOptions) { }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Entry point for all incoming HTTP requests.
|
|
38
|
+
*/
|
|
39
|
+
async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
40
|
+
const lc = `${this.lc}[${this.handleRequest.name}]`;
|
|
41
|
+
const start = Date.now();
|
|
42
|
+
let reqCtx: RequestContext | undefined;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Contextualize the request
|
|
46
|
+
reqCtx = await this.prepareContext(req);
|
|
47
|
+
if (!reqCtx) { throw new Error(`(UNEXPECTED) reqCtx falsy? couldn't prepare context? (E: 401888c0f649dfdf0e2d21f86b3ff826)`); }
|
|
48
|
+
|
|
49
|
+
// Execute pipeline
|
|
50
|
+
let result: ResponseResult | undefined;
|
|
51
|
+
for (const handler of this.options.handlers) {
|
|
52
|
+
result = await handler.handleRoute(reqCtx);
|
|
53
|
+
if (result) { break; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fallback if no handler matched
|
|
57
|
+
if (!result) {
|
|
58
|
+
result = await this.options.errorHandler.handleRoute(reqCtx);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Final output
|
|
62
|
+
if (result) {
|
|
63
|
+
this.sendResponse(res, result);
|
|
64
|
+
} else {
|
|
65
|
+
// If even the error handler didn't produce a result
|
|
66
|
+
this.sendResponse(res, { status: 404, body: 'Not Found' });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`${lc} Fatal error handling ${req.method} ${req.url}: ${extractErrorMsg(error)}`);
|
|
71
|
+
try {
|
|
72
|
+
// Try to use the error handler for the fatal error
|
|
73
|
+
if (reqCtx) {
|
|
74
|
+
const errorResult = await this.options.errorHandler.handleRoute(reqCtx);
|
|
75
|
+
this.sendResponse(res, errorResult ?? { status: 500, body: 'Internal Server Error' });
|
|
76
|
+
} else {
|
|
77
|
+
throw new Error(`errored before reqCtx could be set. (E: d77ab84b4f8874e188cfa8e9895f1226)`);
|
|
78
|
+
}
|
|
79
|
+
} catch (innerError) {
|
|
80
|
+
// Total collapse fallback
|
|
81
|
+
console.error(`${lc} Collapse in error handler: ${extractErrorMsg(innerError)}`);
|
|
82
|
+
res.writeHead(500);
|
|
83
|
+
res.end('Critical Server Error');
|
|
84
|
+
}
|
|
85
|
+
} finally {
|
|
86
|
+
const duration = Date.now() - start;
|
|
87
|
+
// Standard access logging
|
|
88
|
+
console.log(`${lc} ${req.method} ${req.url} -> ${res.statusCode} (${duration}ms)`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Builds request context (e.g. extracting/decoding URL, path segments, body
|
|
94
|
+
* content, etc.), and initializes the request's metaspace if applicable.
|
|
95
|
+
*/
|
|
96
|
+
public async prepareContext(req: IncomingMessage): Promise<RequestContext> {
|
|
97
|
+
const lc = `${this.lc}[${this.prepareContext.name}]`;
|
|
98
|
+
try {
|
|
99
|
+
if (logalot) { console.log(`${lc} starting... (I: ad9e9c678d0538c2d1e16ca8063d4426)`); }
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* These are NOT all of the validation we do on the context, rather,
|
|
103
|
+
* it is just simple initial validation.
|
|
104
|
+
*/
|
|
105
|
+
const initialValidationErrors: string[] = [];
|
|
106
|
+
|
|
107
|
+
const rawUrl = req.url ?? '/';
|
|
108
|
+
const protocol = (req.headers['x-forwarded-proto'] as string) || 'http'; // todo: who owns the req.headers? Can an attacker manipulate this?
|
|
109
|
+
const url = new URL(rawUrl, `${protocol}://localhost:${this.options.port}`); // todo: wth?? Why are we hard-coding localhost? in the URL object?
|
|
110
|
+
|
|
111
|
+
const pathname = url.pathname;
|
|
112
|
+
const rawPathSegments = pathname.split('/');
|
|
113
|
+
const decodedPathSegments = rawPathSegments.map(s => decodeURIComponent(s));
|
|
114
|
+
|
|
115
|
+
const body = await this.readBody(req);
|
|
116
|
+
|
|
117
|
+
const reqCtx: RequestContext = {
|
|
118
|
+
rawUrl,
|
|
119
|
+
pathname,
|
|
120
|
+
method: req.method?.toUpperCase() as ServeGibHttpMethod ?? 'GET',
|
|
121
|
+
protocol,
|
|
122
|
+
body,
|
|
123
|
+
rawPathSegments,
|
|
124
|
+
decodedPathSegments,
|
|
125
|
+
url,
|
|
126
|
+
dataDir: this.options.dataDir,
|
|
127
|
+
headers: req.headers,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return reqCtx;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
133
|
+
throw error;
|
|
134
|
+
} finally {
|
|
135
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Reads the full request body as UTF-8. */
|
|
140
|
+
private readBody(req: IncomingMessage): Promise<string> {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const chunks: Buffer[] = [];
|
|
143
|
+
req.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
144
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
145
|
+
req.on('error', reject);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Sends the response result to the client. */
|
|
150
|
+
private sendResponse(res: ServerResponse, result: ResponseResult): void {
|
|
151
|
+
const lc = `${this.lc}[${this.sendResponse.name}]`;
|
|
152
|
+
try {
|
|
153
|
+
if (logalot) { console.log(`${lc} starting... (I: 37369cf120c14780e8652bf27ada1726)`); }
|
|
154
|
+
|
|
155
|
+
const { status, headers = {}, body, isJson = false } = result;
|
|
156
|
+
|
|
157
|
+
let payload: any = body;
|
|
158
|
+
if (isJson) {
|
|
159
|
+
headers['Content-Type'] = 'application/json; charset=utf-8';
|
|
160
|
+
payload = JSON.stringify(body);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
res.writeHead(status, headers);
|
|
164
|
+
res.end(payload);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
167
|
+
throw error;
|
|
168
|
+
} finally {
|
|
169
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { respecfully, iReckon, ifWe } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
2
|
+
import { ServeGib_V1 } from './serve-gib-v1.mjs';
|
|
3
|
+
import { HealthHandler } from './handlers/api/health.handler.mjs';
|
|
4
|
+
import { ErrorHandler } from './handlers/error-handler.mjs';
|
|
5
|
+
|
|
6
|
+
const sir = `[${import.meta.url}]`;
|
|
7
|
+
|
|
8
|
+
await respecfully(sir, 'ServeGib ServeGib_V1', async () => {
|
|
9
|
+
|
|
10
|
+
await ifWe(sir, 'construct a RouteInfo object', async () => {
|
|
11
|
+
const serveGib = new ServeGib_V1({
|
|
12
|
+
port: 3000,
|
|
13
|
+
dataDir: './test-data',
|
|
14
|
+
handlers: [],
|
|
15
|
+
errorHandler: new ErrorHandler()
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Mock request
|
|
19
|
+
const req: any = {
|
|
20
|
+
url: '/api/health?foo=bar',
|
|
21
|
+
method: 'GET',
|
|
22
|
+
headers: { 'x-forwarded-proto': 'https' },
|
|
23
|
+
on: (event: string, cb: any) => {
|
|
24
|
+
if (event === 'end') { cb(); }
|
|
25
|
+
} // immediate end for readBody
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const info = await (serveGib as any).buildRouteInfo(req);
|
|
29
|
+
|
|
30
|
+
iReckon(sir, info.pathname).isGonnaBe('/api/health');
|
|
31
|
+
iReckon(sir, info.method).isGonnaBe('GET');
|
|
32
|
+
iReckon(sir, info.protocol).isGonnaBe('https');
|
|
33
|
+
iReckon(sir, info.pathSegments).includes('api');
|
|
34
|
+
iReckon(sir, info.pathSegments).includes('health');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await ifWe(sir, 'execute a handler in the pipeline', async () => {
|
|
38
|
+
const health = new HealthHandler();
|
|
39
|
+
const serveGib = new ServeGib_V1({
|
|
40
|
+
port: 3000,
|
|
41
|
+
dataDir: './test-data',
|
|
42
|
+
handlers: [health],
|
|
43
|
+
errorHandler: new ErrorHandler()
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Mock objects
|
|
47
|
+
const req: any = {
|
|
48
|
+
url: '/api/health',
|
|
49
|
+
method: 'GET',
|
|
50
|
+
headers: {},
|
|
51
|
+
on: (event: string, cb: any) => {
|
|
52
|
+
if (event === 'end') { cb(); }
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const res: any = {
|
|
56
|
+
writeHead: () => { },
|
|
57
|
+
end: () => { },
|
|
58
|
+
statusCode: 200
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
await serveGib.handleRequest(req, res);
|
|
62
|
+
iReckon(sir, res.statusCode).isGonnaBe(200);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await ifWe(sir, 'fall back to error handler when no handler matches', async () => {
|
|
66
|
+
const serveGib = new ServeGib_V1({
|
|
67
|
+
port: 3000,
|
|
68
|
+
dataDir: './test-data',
|
|
69
|
+
handlers: [],
|
|
70
|
+
errorHandler: new ErrorHandler()
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const req: any = {
|
|
74
|
+
url: '/not/found',
|
|
75
|
+
method: 'GET',
|
|
76
|
+
headers: {},
|
|
77
|
+
on: (event: string, cb: any) => {
|
|
78
|
+
if (event === 'end') { cb(); }
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const res: any = {
|
|
82
|
+
writeHead: () => { },
|
|
83
|
+
end: () => { },
|
|
84
|
+
statusCode: 404
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
await serveGib.handleRequest(req, res);
|
|
88
|
+
iReckon(sir, res.statusCode).isGonnaBe(404);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module serve-gib/types
|
|
3
|
+
*
|
|
4
|
+
* Foundational types for the serve-gib microframework.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Gib, Ib, IbGibAddr } from '@ibgib/ts-gib/dist/types.mjs';
|
|
8
|
+
import { GibInfo } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
9
|
+
import { MetaspaceService } from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Standard HTTP methods supported by the serve-gib framework, plus 'ALL' for catch-all handlers.
|
|
13
|
+
*/
|
|
14
|
+
export type ServeGibHttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'ALL';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @see {@link RequestContext.domainInfo}
|
|
18
|
+
*/
|
|
19
|
+
export interface DomainInfo {
|
|
20
|
+
ib: Ib;
|
|
21
|
+
gib: Gib;
|
|
22
|
+
addr: IbGibAddr;
|
|
23
|
+
gibInfo: GibInfo;
|
|
24
|
+
/** The physical root directory for the current domain. */
|
|
25
|
+
// rootPath?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ParamsWithDomain {
|
|
29
|
+
domainInfo: DomainInfo;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Encapsulates the context of an incoming HTTP request.
|
|
34
|
+
*/
|
|
35
|
+
export interface RequestContext<TParams = any, TQueryParams = any> {
|
|
36
|
+
/** The native Node.js/Web URL object. */
|
|
37
|
+
url: URL;
|
|
38
|
+
/** The root data directory for spaces. */
|
|
39
|
+
dataDir: string;
|
|
40
|
+
/** The original request URL string. */
|
|
41
|
+
rawUrl: string;
|
|
42
|
+
/** The pathname part of the URL (e.g. /api/health). */
|
|
43
|
+
pathname: string;
|
|
44
|
+
/** HTTP method (GET, POST, etc.). */
|
|
45
|
+
method: ServeGibHttpMethod;
|
|
46
|
+
/** Request protocol (http or https). */
|
|
47
|
+
protocol: string;
|
|
48
|
+
/** The raw request body as a string. */
|
|
49
|
+
body: string;
|
|
50
|
+
/** Array of raw path segments split by '/'. */
|
|
51
|
+
rawPathSegments: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Same as {@link rawPathSegments}, but with any ibgib addr(s) URL decoded.
|
|
54
|
+
*/
|
|
55
|
+
decodedPathSegments: string[];
|
|
56
|
+
params?: TParams;
|
|
57
|
+
/** Parsed query parameters. */
|
|
58
|
+
queryParams?: TQueryParams;
|
|
59
|
+
/** Request headers. */
|
|
60
|
+
headers: Record<string, string | string[] | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* The bootstrapped MetaspaceService for the current domain.
|
|
63
|
+
*
|
|
64
|
+
* Note that not all routes require this, hence it's optional.
|
|
65
|
+
*/
|
|
66
|
+
metaspace?: MetaspaceService;
|
|
67
|
+
/**
|
|
68
|
+
* keystones define "domains" to enable multitenancy. This is info about the
|
|
69
|
+
* keystone domain.
|
|
70
|
+
*
|
|
71
|
+
* ## notes
|
|
72
|
+
*
|
|
73
|
+
* I would call this "KeystoneInfo", and right now, this is only one keystone's
|
|
74
|
+
* info. but ultimately I _think_ that we will have nested domains.
|
|
75
|
+
*/
|
|
76
|
+
domainInfo?: DomainInfo;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Standardized response shape returned by handlers.
|
|
81
|
+
*/
|
|
82
|
+
export interface ResponseResult {
|
|
83
|
+
/** HTTP status code (200, 404, etc.). */
|
|
84
|
+
status: number;
|
|
85
|
+
/** Optional response headers. */
|
|
86
|
+
headers?: Record<string, string>;
|
|
87
|
+
/** Response body content. */
|
|
88
|
+
body: any;
|
|
89
|
+
/** If true, body will be stringified as JSON and Content-Type set accordingly. */
|
|
90
|
+
isJson?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Interface for a serve-gib route handler.
|
|
95
|
+
*/
|
|
96
|
+
export interface ServeGibHandler<TParams, TQueryParams> {
|
|
97
|
+
/**
|
|
98
|
+
* Attempts to handle the request.
|
|
99
|
+
* @returns A ResponseResult if handled, or undefined to pass to the next handler.
|
|
100
|
+
*/
|
|
101
|
+
handleRoute(reqCtx: RequestContext<TParams, TQueryParams>): Promise<ResponseResult | undefined>;
|
|
102
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module server
|
|
3
|
+
*
|
|
4
|
+
* Native Node.js HTTP server for space-gib — using the serve-gib microframework.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createServer } from 'node:http';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
12
|
+
|
|
13
|
+
import { ServeGib_V1 } from './serve-gib/serve-gib-v1.mjs';
|
|
14
|
+
import { HealthHandler } from './serve-gib/handlers/api/health.handler.mjs';
|
|
15
|
+
import { IbGibHandler } from './serve-gib/handlers/api/ibgib/ibgib.handler.mjs';
|
|
16
|
+
import { KeystonePostHandler } from './serve-gib/handlers/api/keystone/keystone-post.handler.mjs';
|
|
17
|
+
import { KeystoneGetHandler } from './serve-gib/handlers/api/keystone/keystone-get.handler.mjs';
|
|
18
|
+
import { KeystoneGenesisHandler } from './serve-gib/handlers/api/keystone/keystone-genesis.handler.mjs';
|
|
19
|
+
import { KeystoneEvolveHandler } from './serve-gib/handlers/api/keystone/keystone-evolve.handler.mjs';
|
|
20
|
+
import { StaticFileHandler } from './serve-gib/handlers/static-handler.mjs';
|
|
21
|
+
import { ErrorHandler } from './serve-gib/handlers/error-handler.mjs';
|
|
22
|
+
import { canHandleWsUpgrade, handleWsEchoUpgrade } from './serve-gib/handlers/api/debug/ws-echo.handler.mjs';
|
|
23
|
+
import { SyncUpgradeHandler } from './serve-gib/handlers/ws/sync-upgrade.handler.mjs';
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Config
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
const PORT = parseInt(process.env.PORT ?? '3000', 10);
|
|
32
|
+
const DATA_DIR = process.env.DATA_DIR ?? join(__dirname, '../../.data/ibgib-space');
|
|
33
|
+
const CLIENT_DIR = join(__dirname, '../client');
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Main
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
async function main(): Promise<void> {
|
|
40
|
+
const lc = '[space-gib server]';
|
|
41
|
+
try {
|
|
42
|
+
console.log(`${lc} starting...`);
|
|
43
|
+
|
|
44
|
+
console.log(`${lc} dataDir: ${DATA_DIR}`);
|
|
45
|
+
|
|
46
|
+
const syncUpgradeHandler = new SyncUpgradeHandler();
|
|
47
|
+
|
|
48
|
+
// Initialize serve-gib
|
|
49
|
+
const serveGib = new ServeGib_V1({
|
|
50
|
+
port: PORT,
|
|
51
|
+
dataDir: DATA_DIR,
|
|
52
|
+
handlers: [
|
|
53
|
+
new HealthHandler(),
|
|
54
|
+
new IbGibHandler(),
|
|
55
|
+
new KeystoneGenesisHandler(),
|
|
56
|
+
new KeystoneEvolveHandler(),
|
|
57
|
+
new KeystonePostHandler(),
|
|
58
|
+
new KeystoneGetHandler(),
|
|
59
|
+
new StaticFileHandler(CLIENT_DIR),
|
|
60
|
+
],
|
|
61
|
+
errorHandler: new ErrorHandler()
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const server = createServer(async (req, res) => {
|
|
65
|
+
await serveGib.handleRequest(req, res);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// WebSocket upgrade handling — separate from the HTTP request pipeline
|
|
69
|
+
// since Node.js fires `upgrade` events independently of `request` events.
|
|
70
|
+
server.on('upgrade', async (req, socket, head) => {
|
|
71
|
+
const reqCtx = await serveGib.prepareContext(req);
|
|
72
|
+
|
|
73
|
+
// Check sync upgrade handler first
|
|
74
|
+
const isSync = await syncUpgradeHandler.handleUpgrade(reqCtx, socket as any, head);
|
|
75
|
+
|
|
76
|
+
// Fallback to debug echo if not handled by sync
|
|
77
|
+
if (!isSync && canHandleWsUpgrade(req)) {
|
|
78
|
+
handleWsEchoUpgrade(req, socket as any, head);
|
|
79
|
+
} else if (!isSync) {
|
|
80
|
+
// Reject unrecognized upgrade requests
|
|
81
|
+
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
|
|
82
|
+
socket.destroy();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
server.listen(PORT, '0.0.0.0', () => {
|
|
87
|
+
console.log(`${lc} listening on http://0.0.0.0:${PORT}`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(`${lc} fatal: ${extractErrorMsg(error)}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"declaration": false,
|
|
5
|
+
"declarationMap": false,
|
|
6
|
+
"sourceMap": false,
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"composite": false,
|
|
9
|
+
"outDir": "./dist/client",
|
|
10
|
+
"module": "ESNext",
|
|
11
|
+
"lib": [
|
|
12
|
+
"DOM",
|
|
13
|
+
"ES2022",
|
|
14
|
+
"ES2023"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"src/client/**/*.ts",
|
|
19
|
+
"src/client/**/*.mts",
|
|
20
|
+
"src/client/**/*.d.ts",
|
|
21
|
+
"src/common/**/*.ts",
|
|
22
|
+
"src/common/**/*.mts"
|
|
23
|
+
],
|
|
24
|
+
"exclude": [
|
|
25
|
+
"node_modules",
|
|
26
|
+
"**/*.respec.mts",
|
|
27
|
+
"./dist/**/*"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"declaration": false,
|
|
5
|
+
"declarationMap": false,
|
|
6
|
+
"sourceMap": false,
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"composite": false,
|
|
9
|
+
"outDir": "./dist/server",
|
|
10
|
+
"module": "ESNext",
|
|
11
|
+
"lib": [
|
|
12
|
+
"ES2022",
|
|
13
|
+
"ES2023"
|
|
14
|
+
],
|
|
15
|
+
"types": ["node"]
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"src/server/**/*.ts",
|
|
19
|
+
"src/server/**/*.mts",
|
|
20
|
+
"src/server/**/*.d.ts",
|
|
21
|
+
"src/common/**/*.ts",
|
|
22
|
+
"src/common/**/*.mts"
|
|
23
|
+
],
|
|
24
|
+
"exclude": [
|
|
25
|
+
"node_modules",
|
|
26
|
+
"**/*.respec.mts",
|
|
27
|
+
"./dist/**/*"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"declaration": false,
|
|
5
|
+
"declarationMap": false,
|
|
6
|
+
"sourceMap": false,
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"composite": false,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"module": "ESNext",
|
|
11
|
+
"lib": [
|
|
12
|
+
"ES2022",
|
|
13
|
+
"ES2023"
|
|
14
|
+
],
|
|
15
|
+
"types": ["node"]
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"src/server/**/*.ts",
|
|
19
|
+
"src/server/**/*.mts",
|
|
20
|
+
"src/server/**/*.d.ts",
|
|
21
|
+
"src/respec-gib.node.mts"
|
|
22
|
+
],
|
|
23
|
+
"exclude": [
|
|
24
|
+
"node_modules",
|
|
25
|
+
"./dist/**/*"
|
|
26
|
+
]
|
|
27
|
+
}
|