@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,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module serve-gib/handler
|
|
3
|
+
*
|
|
4
|
+
* Base class and utilities for serve-gib handlers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
8
|
+
import { IbGibAddr } from '@ibgib/ts-gib/dist/types.mjs';
|
|
9
|
+
import { getIbAndGib } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
10
|
+
import { getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
|
|
11
|
+
|
|
12
|
+
import { GLOBAL_LOG_A_LOT } from '../constants.mjs';
|
|
13
|
+
import { DomainInfo, ParamsWithDomain, RequestContext, ResponseResult, ServeGibHandler } from '../types.mjs';
|
|
14
|
+
import { ServeGibHttpMethod } from '../types.mjs';
|
|
15
|
+
import { bootstrapDomainMetaspace, getDomainRootPath } from '../../bootstrap-helper.mjs';
|
|
16
|
+
|
|
17
|
+
const logalot = GLOBAL_LOG_A_LOT;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Abstract base class for serve-gib handlers.
|
|
21
|
+
* Provides a "pit of success" plumbing for routing, error handling, and parameter parsing.
|
|
22
|
+
*
|
|
23
|
+
* @template TParams Type for the parsed query parameters.
|
|
24
|
+
* @template TQueryParams Type for the parsed query parameters.
|
|
25
|
+
*/
|
|
26
|
+
export abstract class ServeGibHandlerBase<TParams = any, TQueryParams = any>
|
|
27
|
+
implements ServeGibHandler<TParams, TQueryParams> {
|
|
28
|
+
protected lc: string = `[${ServeGibHandlerBase.name}]`;
|
|
29
|
+
|
|
30
|
+
/** The HTTP method this handler supports, or 'ALL' for catch-all. */
|
|
31
|
+
protected abstract method: ServeGibHttpMethod;
|
|
32
|
+
|
|
33
|
+
/** Regex for quick route matching via the default `canHandleRoute`. */
|
|
34
|
+
protected abstract regex: RegExp;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The main entry point processing incoming requests.
|
|
38
|
+
* Implements standard plumbing for matching, error handling, and param parsing.
|
|
39
|
+
*/
|
|
40
|
+
async handleRoute(reqCtx: RequestContext<TParams, TQueryParams>): Promise<ResponseResult | undefined> {
|
|
41
|
+
const lc = `${this.lc}[${this.handleRoute.name}]`;
|
|
42
|
+
try {
|
|
43
|
+
if (logalot) { console.log(`${lc} starting... (I: ad1a523a7bbd222db2e6ffab54f59c26)`); }
|
|
44
|
+
|
|
45
|
+
// First check if we can handle it
|
|
46
|
+
if (!this.canHandleRoute(reqCtx)) {
|
|
47
|
+
if (logalot) { console.log(`${lc} cannot handle route. returning undefined. (I: 359bd4a9d44acb2408e3ad9570cfa526)`); }
|
|
48
|
+
return undefined; /* <<<< returns early */
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// prepare handling params, e.g., keystone/domain addr(s), ibgib
|
|
52
|
+
// addr(s), not sure what else
|
|
53
|
+
reqCtx.params = await this.parseParams(reqCtx);
|
|
54
|
+
|
|
55
|
+
// prepare queryParams
|
|
56
|
+
reqCtx.queryParams = await this.parseQueryParams(reqCtx);
|
|
57
|
+
|
|
58
|
+
// hook to initialize contextual things like metaspace
|
|
59
|
+
await this.initConcreteContext(reqCtx);
|
|
60
|
+
|
|
61
|
+
// Delegate to implementation
|
|
62
|
+
return await this.handleRouteImpl(reqCtx);
|
|
63
|
+
|
|
64
|
+
} catch (error) {
|
|
65
|
+
const emsg = `${lc} Error handling route: ${extractErrorMsg(error)}`;
|
|
66
|
+
console.error(emsg);
|
|
67
|
+
// Standard fallback error. Individual handlers can override with more specific catches in handleRouteImpl.
|
|
68
|
+
return this.error(500, 'Internal Server Error', {
|
|
69
|
+
errorMsg: emsg,
|
|
70
|
+
method: this.method,
|
|
71
|
+
regex: this.regex?.source,
|
|
72
|
+
rawUrl: reqCtx?.rawUrl,
|
|
73
|
+
});
|
|
74
|
+
} finally {
|
|
75
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Concrete implementation of the handler logic.
|
|
80
|
+
*
|
|
81
|
+
* This is where the "meat" of the code goes, as there is some
|
|
82
|
+
* pre-processing in the parent wrapper.
|
|
83
|
+
*/
|
|
84
|
+
protected abstract handleRouteImpl(reqCtx: RequestContext<TParams, TQueryParams>): Promise<ResponseResult | undefined>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Extracts per-handler/route params from a request context.
|
|
88
|
+
*
|
|
89
|
+
* @see {@link RequestContext.params}
|
|
90
|
+
*/
|
|
91
|
+
parseParams(reqCtx: RequestContext<TParams, TQueryParams>): Promise<TParams | undefined> {
|
|
92
|
+
const lc = `${this.lc}[${this.parseParams.name}]`;
|
|
93
|
+
try {
|
|
94
|
+
if (logalot) { console.log(`${lc} starting... (I: c08d98768f449317b8e4b56925d3b826)`); }
|
|
95
|
+
return this.parseParamsImpl(reqCtx);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
98
|
+
throw error;
|
|
99
|
+
} finally {
|
|
100
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Concrete implementation for extracting params from a path.
|
|
105
|
+
*
|
|
106
|
+
* @see {@link RequestContext.params}
|
|
107
|
+
*/
|
|
108
|
+
protected async parseParamsImpl(reqCtx: RequestContext<TParams, TQueryParams>): Promise<TParams | undefined> {
|
|
109
|
+
const lc = `${this.lc}[${this.parseParamsImpl.name}]`;
|
|
110
|
+
try {
|
|
111
|
+
if (logalot) { console.log(`${lc} starting... (I: 849e491b436860d6788b76c898b0a826)`); }
|
|
112
|
+
|
|
113
|
+
if (logalot) { console.log(`${lc} default implementation in base class is a no-op (I: 88d128a11e1c66030839dc582486e826)`); }
|
|
114
|
+
|
|
115
|
+
return undefined;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
118
|
+
throw error;
|
|
119
|
+
} finally {
|
|
120
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Hook to initialize concrete context, e.g., initializing a metaspace for the
|
|
126
|
+
* specific domain. The base implementation is a no-op.
|
|
127
|
+
*/
|
|
128
|
+
protected async initConcreteContext(reqCtx: RequestContext<TParams, TQueryParams>): Promise<void> {
|
|
129
|
+
const lc = `${this.lc}[${this.initConcreteContext.name}]`;
|
|
130
|
+
try {
|
|
131
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
132
|
+
|
|
133
|
+
if (logalot) { console.log(`${lc} default implementation in base class is a no-op`); }
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
136
|
+
throw error;
|
|
137
|
+
} finally {
|
|
138
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Determines if this handler should attempt to handle the given request.
|
|
144
|
+
*
|
|
145
|
+
* Default implementation checks {@link method} and {@link regex} matching
|
|
146
|
+
* against the pathname. Override for more advanced logic for edge cases.
|
|
147
|
+
*/
|
|
148
|
+
protected canHandleRoute(reqCtx: RequestContext<TParams, TQueryParams>): boolean {
|
|
149
|
+
const lc = `${this.lc}[${this.canHandleRoute.name}]`;
|
|
150
|
+
try {
|
|
151
|
+
if (logalot) { console.log(`${lc} starting... (I: d790f637074feee63c4bfcb158f47826)`); }
|
|
152
|
+
|
|
153
|
+
const isMethodMatch = this.method === 'ALL' || reqCtx.method === this.method;
|
|
154
|
+
const isRegexMatch = this.regex.test(reqCtx.pathname);
|
|
155
|
+
return isMethodMatch && isRegexMatch;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
158
|
+
throw error;
|
|
159
|
+
} finally {
|
|
160
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Parses query parameters from the URL.
|
|
166
|
+
* Defaults to a basic key-value mapping of the URL search params.
|
|
167
|
+
*/
|
|
168
|
+
protected async parseQueryParams(reqCtx: RequestContext<TParams, TQueryParams>): Promise<TQueryParams | undefined> {
|
|
169
|
+
const lc = `${this.lc}[${this.parseQueryParams.name}]`;
|
|
170
|
+
try {
|
|
171
|
+
if (logalot) { console.log(`${lc} starting... (I: b5d985a4a58b8f3edff9f5ab500e5c26)`); }
|
|
172
|
+
|
|
173
|
+
const queryParams: any = {};
|
|
174
|
+
reqCtx.url.searchParams.forEach((value, key) => {
|
|
175
|
+
queryParams[key] = value;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (Object.keys(queryParams).length > 0) {
|
|
179
|
+
// query params exist, validate and return if valid, throw if not
|
|
180
|
+
const validationErrors = await this.validateQueryParams({ queryParams });
|
|
181
|
+
if (validationErrors.length === 0) {
|
|
182
|
+
return queryParams as TQueryParams;
|
|
183
|
+
} else {
|
|
184
|
+
throw new Error(`invalid query params. validationErrors: ${validationErrors.join(', ')} (E: 9355f8a9643803f52f2241f1c7f39826)`);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
// no query params given
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
192
|
+
throw error;
|
|
193
|
+
} finally {
|
|
194
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* base class implementation is a no-op. override this in descendant class
|
|
200
|
+
* who expect the possibility of query params
|
|
201
|
+
* @returns array of validation errors, empty if valid
|
|
202
|
+
*/
|
|
203
|
+
protected async validateQueryParams({ queryParams }: { queryParams: any }): Promise<string[]> {
|
|
204
|
+
const lc = `${this.lc}[${this.validateQueryParams.name}]`;
|
|
205
|
+
try {
|
|
206
|
+
if (logalot) { console.log(`${lc} starting... (I: 33b448e223c8682bd8b8b055cab86f26)`); }
|
|
207
|
+
|
|
208
|
+
if (logalot) { console.log(`${lc} base class implementation is a no-op. returning empty array (i.e., valid) (I: 393e8db53d5a9e6ca8eb69ae25810826)`); }
|
|
209
|
+
|
|
210
|
+
return [];
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
213
|
+
throw error;
|
|
214
|
+
} finally {
|
|
215
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Standard JSON success response.
|
|
221
|
+
*/
|
|
222
|
+
protected ok(body: any, status: number = 200): ResponseResult {
|
|
223
|
+
return { status, body, isJson: true };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Standard JSON error response.
|
|
228
|
+
*/
|
|
229
|
+
protected error(status: number, message: string, details?: any): ResponseResult {
|
|
230
|
+
return { status, body: { error: message, details }, isJson: true };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 404 Not Found response.
|
|
235
|
+
*/
|
|
236
|
+
protected notFound(message: string = 'Not found'): ResponseResult {
|
|
237
|
+
return this.error(404, message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Base class for handlers that have to first initialize a metaspace in order to
|
|
243
|
+
* process the incoming request.
|
|
244
|
+
*
|
|
245
|
+
* ## notes
|
|
246
|
+
*
|
|
247
|
+
* Some handlers, like those who do error handling or serving static files, do
|
|
248
|
+
* not need to initialize a metaspace or do any get/put of any ibgibs. So those
|
|
249
|
+
* don't need to descend from this class.
|
|
250
|
+
*
|
|
251
|
+
* But if the handler is one that has to put/get an ibgib, then a metaspace will
|
|
252
|
+
* need to be init. Those handlers should descend from this class.
|
|
253
|
+
*/
|
|
254
|
+
export abstract class ServeGibHandlerWithMetaspaceBase<TParams extends ParamsWithDomain = ParamsWithDomain, TQueryParams = any>
|
|
255
|
+
extends ServeGibHandlerBase<TParams, TQueryParams> {
|
|
256
|
+
protected override lc: string = `[${ServeGibHandlerWithMetaspaceBase.name}]`;
|
|
257
|
+
|
|
258
|
+
protected override async initConcreteContext(reqCtx: RequestContext<TParams, TQueryParams>): Promise<void> {
|
|
259
|
+
const lc = `${this.lc}[${this.initConcreteContext.name}]`;
|
|
260
|
+
try {
|
|
261
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
262
|
+
|
|
263
|
+
await this.initAndPopulateDomainContext(reqCtx);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
266
|
+
throw error;
|
|
267
|
+
} finally {
|
|
268
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
protected override parseParamsImpl(reqCtx: RequestContext<TParams, TQueryParams>): Promise<TParams | undefined> {
|
|
273
|
+
const lc = `${this.lc}[${this.parseParamsImpl.name}]`;
|
|
274
|
+
try {
|
|
275
|
+
if (logalot) { console.log(`${lc} starting... (I: f3ae6e662fe8d28db8c230e80a700826)`); }
|
|
276
|
+
|
|
277
|
+
throw new Error(`(UNEXPECTED) not implemented. this function must be implemented in handlers that initialize metaspace. in that concrete handler class, you must parse out the raw path and build up the params object. That params object must at least include domain info, because that drives the multitenancy. (E: bd6a584b8f988f9ed81c77eedd36b826)`);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
280
|
+
throw error;
|
|
281
|
+
} finally {
|
|
282
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
protected override validateQueryParams({ queryParams }: { queryParams: any; }): Promise<string[]> {
|
|
287
|
+
const lc = `${this.lc}[${this.validateQueryParams.name}]`;
|
|
288
|
+
try {
|
|
289
|
+
if (logalot) { console.log(`${lc} starting... (I: 14bb4659f9587d18786105c8e4966826)`); }
|
|
290
|
+
|
|
291
|
+
throw new Error(`(UNEXPECTED) not implemented. this function must be implemented in handlers that initialize metaspace. in that concrete handler class, you must provide a function that validates query params expectations, even if you expect no query params. (E: 804c98a724ddf59998199ec87a9ee126)`);
|
|
292
|
+
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
295
|
+
throw error;
|
|
296
|
+
} finally {
|
|
297
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* helper that should extract the "domain" (driven by the correctly scoped
|
|
303
|
+
* keystone) and set related props on the context.
|
|
304
|
+
*/
|
|
305
|
+
protected async initAndPopulateDomainContext(reqCtx: RequestContext<TParams, TQueryParams>): Promise<void> {
|
|
306
|
+
const lc = `${this.lc}[${this.initAndPopulateDomainContext.name}]`;
|
|
307
|
+
try {
|
|
308
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
309
|
+
|
|
310
|
+
if (!reqCtx.params) { throw new Error(`invalid RequestContext. params is falsy but we expected params.domainInfo at the minimum. (E: 433c15116e2e821768847d23f7853a26)`); }
|
|
311
|
+
if (!reqCtx.params.domainInfo) { throw new Error(`invalid RequestContext. reqCtx.params is truthy but params.domainInfo is falsy. at this point, we should have parsed domain info from reqCtx.params (E: ae40882910aa6f45a810e268fd126f26)`); }
|
|
312
|
+
|
|
313
|
+
const { domainInfo } = reqCtx.params as TParams
|
|
314
|
+
const { addr: domainAddr, } = domainInfo;
|
|
315
|
+
if (!domainAddr) {
|
|
316
|
+
throw new Error(`(UNEXPECTED) domainAddr falsy? reqCtx.params does not contain domainIb and domainGib for a handler extending ServeGibHandlerWithMetaspaceBase. If extending this metaspace base, we are expected to have a domain addr with ib and gib. (E: 06bae6908ba8f200e8c50b28ed8df626)`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// since we have access to reqCtx.dataDir, not sure if we need rootPath in DomainInfo?
|
|
320
|
+
reqCtx.metaspace = await bootstrapDomainMetaspace(domainAddr, reqCtx.dataDir);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
323
|
+
throw error;
|
|
324
|
+
} finally {
|
|
325
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Helper to build the domain info from a {@link domainAddr}.
|
|
331
|
+
*
|
|
332
|
+
* ## usage
|
|
333
|
+
*
|
|
334
|
+
* Use this helper function once your concrete handler has parsed the
|
|
335
|
+
* domainAddr from the request url.
|
|
336
|
+
*
|
|
337
|
+
* For example, say the incoming path is /api/ibgib/:domainAddr/:ibgibAddr (GET)
|
|
338
|
+
* Then the concrete handler for this path would pull out domainAddr, call
|
|
339
|
+
* this function to build the domain info, and then put that domain info on
|
|
340
|
+
* the `reqCtx`.
|
|
341
|
+
*/
|
|
342
|
+
protected getDomainInfo({
|
|
343
|
+
domainAddr,
|
|
344
|
+
// dataDir,
|
|
345
|
+
}: {
|
|
346
|
+
/**
|
|
347
|
+
* addr extracted from incoming url by concrete handler
|
|
348
|
+
*/
|
|
349
|
+
domainAddr: IbGibAddr,
|
|
350
|
+
// /**
|
|
351
|
+
// * root dir of multitenancy on server. get from reqCtx.dataDir
|
|
352
|
+
// */
|
|
353
|
+
// dataDir: string,
|
|
354
|
+
}): DomainInfo {
|
|
355
|
+
const lc = `${this.lc}[${this.getDomainInfo.name}]`;
|
|
356
|
+
try {
|
|
357
|
+
if (logalot) { console.log(`${lc} starting... (I: 2d8bc8f40fd8ae3c98b84f6aaf6e0f26)`); }
|
|
358
|
+
|
|
359
|
+
if (!domainAddr) { throw new Error(`(UNEXPECTED) domainAddr falsy? (E: 7009f98fead9712cb5d4c8985162c826)`); }
|
|
360
|
+
|
|
361
|
+
const domainGibInfo = getGibInfo({ ibGibAddr: domainAddr });
|
|
362
|
+
const { tjpGib, punctiliarHash } = domainGibInfo;
|
|
363
|
+
const { ib: domainIb, gib: domainGib } = getIbAndGib({ ibGibAddr: domainAddr });
|
|
364
|
+
const finalDomainGib = tjpGib || punctiliarHash;
|
|
365
|
+
if (!finalDomainGib) { throw new Error(`(UNEXPECTED) finalDomainGib falsy? we have a domainAddr (${domainAddr}) so we should have a domain gib. (E: de1e18078678f3b4887ebfae3c893626)`); }
|
|
366
|
+
|
|
367
|
+
const domainInfo: DomainInfo = {
|
|
368
|
+
addr: domainAddr,
|
|
369
|
+
gib: domainGib,
|
|
370
|
+
ib: domainIb,
|
|
371
|
+
gibInfo: domainGibInfo,
|
|
372
|
+
// rootPath: getDomainRootPath(finalDomainGib, dataDir),
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return domainInfo;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
378
|
+
throw error;
|
|
379
|
+
} finally {
|
|
380
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module serve-gib/handlers/static-handler
|
|
3
|
+
*
|
|
4
|
+
* Handler for serving static client assets and SPA fallback.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFile } from 'node:fs/promises';
|
|
8
|
+
import { existsSync, statSync } from 'node:fs';
|
|
9
|
+
import { join, extname } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { ServeGibHandlerBase } from './handler-base.mjs';
|
|
12
|
+
import { RequestContext, ResponseResult, ServeGibHttpMethod } from '../types.mjs';
|
|
13
|
+
import { isValidStaticPath } from '../../path-helper.mjs';
|
|
14
|
+
import { GLOBAL_LOG_A_LOT } from '../constants.mjs';
|
|
15
|
+
|
|
16
|
+
const logalot = GLOBAL_LOG_A_LOT;
|
|
17
|
+
|
|
18
|
+
export class StaticFileHandler extends ServeGibHandlerBase {
|
|
19
|
+
protected override lc: string = `[${StaticFileHandler.name}]`;
|
|
20
|
+
protected override method: ServeGibHttpMethod = 'GET';
|
|
21
|
+
protected override regex = /.*/;
|
|
22
|
+
|
|
23
|
+
private MIME_TYPES: Record<string, string> = {
|
|
24
|
+
'.html': 'text/html; charset=utf-8',
|
|
25
|
+
'.css': 'text/css; charset=utf-8',
|
|
26
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
27
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
28
|
+
'.json': 'application/json; charset=utf-8',
|
|
29
|
+
'.png': 'image/png',
|
|
30
|
+
'.jpg': 'image/jpeg',
|
|
31
|
+
'.jpeg': 'image/jpeg',
|
|
32
|
+
'.svg': 'image/svg+xml',
|
|
33
|
+
'.ico': 'image/x-icon',
|
|
34
|
+
'.woff': 'font/woff',
|
|
35
|
+
'.woff2': 'font/woff2',
|
|
36
|
+
'.ttf': 'font/ttf',
|
|
37
|
+
'.webp': 'image/webp',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
constructor(private clientDir: string) { super(); }
|
|
41
|
+
|
|
42
|
+
protected async handleRouteImpl(reqCtx: RequestContext): Promise<ResponseResult | undefined> {
|
|
43
|
+
const lc = `${this.lc}[${this.handleRouteImpl.name}]`;
|
|
44
|
+
try {
|
|
45
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
46
|
+
|
|
47
|
+
// Hardened path validation
|
|
48
|
+
const { pathname } = reqCtx;
|
|
49
|
+
if (!isValidStaticPath(pathname)) { return undefined; }
|
|
50
|
+
|
|
51
|
+
let filePath = join(this.clientDir, pathname);
|
|
52
|
+
|
|
53
|
+
// Directory → index.html
|
|
54
|
+
if (existsSync(filePath) && statSync(filePath).isDirectory()) {
|
|
55
|
+
filePath = join(filePath, 'index.html');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// File not found → SPA fallback
|
|
59
|
+
if (!existsSync(filePath)) {
|
|
60
|
+
filePath = join(this.clientDir, 'index.html');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Final check just in case the fallback doesn't exist
|
|
64
|
+
if (!existsSync(filePath)) { return undefined; }
|
|
65
|
+
|
|
66
|
+
const content = await readFile(filePath);
|
|
67
|
+
const ext = extname(filePath).toLowerCase();
|
|
68
|
+
const contentType = this.MIME_TYPES[ext] ?? 'application/octet-stream';
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
status: 200,
|
|
72
|
+
headers: { 'Content-Type': contentType },
|
|
73
|
+
body: content
|
|
74
|
+
};
|
|
75
|
+
} catch (error: any) {
|
|
76
|
+
console.error(`${lc} ${error.message}`);
|
|
77
|
+
throw error;
|
|
78
|
+
} finally {
|
|
79
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|