@awesomeness-js/server 1.0.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.
Files changed (51) hide show
  1. package/.editorconfig +4 -0
  2. package/.gitattributes +2 -0
  3. package/.jshintrc +3 -0
  4. package/.vscode/settings.json +17 -0
  5. package/CLA.md +9 -0
  6. package/CONTRIBUTING.md +18 -0
  7. package/LICENSE +13 -0
  8. package/NOTICE +15 -0
  9. package/README.md +15 -0
  10. package/SECURITY.md +7 -0
  11. package/config.js +29 -0
  12. package/eslint.config.js +101 -0
  13. package/example-.awesomeness/applicationMap.js +4 -0
  14. package/example-.awesomeness/beforeRouteMiddleware.js +15 -0
  15. package/example-.awesomeness/checkSession.js +15 -0
  16. package/example-.awesomeness/config.js +40 -0
  17. package/example-.awesomeness/hostMap.js +44 -0
  18. package/example-.awesomeness/initDB.js +65 -0
  19. package/example-.awesomeness/setupDB/applications.js +47 -0
  20. package/example-.awesomeness/setupDB/users.js +65 -0
  21. package/example-.awesomeness/setupDB/websites.js +49 -0
  22. package/example-.awesomeness/specialRoutes.js +7 -0
  23. package/example-.awesomeness/wsHandler.js +13 -0
  24. package/index.js +22 -0
  25. package/package.json +34 -0
  26. package/server/applicationMap.js +33 -0
  27. package/server/awesomenessNormalizeRequest.js +131 -0
  28. package/server/brotliJsonResponse.js +28 -0
  29. package/server/checkAccess.js +34 -0
  30. package/server/componentDependencies.js +301 -0
  31. package/server/errors.js +11 -0
  32. package/server/fetchPage.js +269 -0
  33. package/server/getMD.js +22 -0
  34. package/server/koa/attachAwesomenessRequest.js +24 -0
  35. package/server/koa/cors.js +22 -0
  36. package/server/koa/errorHandler.js +32 -0
  37. package/server/koa/finalFormat.js +34 -0
  38. package/server/koa/jsonBodyParser.js +172 -0
  39. package/server/koa/routeRequest.js +288 -0
  40. package/server/koa/serverUp.js +7 -0
  41. package/server/koa/staticFiles.js +97 -0
  42. package/server/koa/timeout.js +42 -0
  43. package/server/pageInfo.js +121 -0
  44. package/server/reRoute.js +54 -0
  45. package/server/resolveRealCasePath.js +56 -0
  46. package/server/specialPaths.js +107 -0
  47. package/server/validateRequest.js +127 -0
  48. package/server/ws/handlers.js +67 -0
  49. package/server/ws/index.js +50 -0
  50. package/start.js +122 -0
  51. package/vitest.config.js +15 -0
@@ -0,0 +1,33 @@
1
+ import { getConfig } from "../config.js";
2
+
3
+ export default function(host){
4
+
5
+ const awesomenessConfig = getConfig();
6
+
7
+ if (
8
+ awesomenessConfig.applicationMap
9
+ && awesomenessConfig.applicationMap[host]
10
+ ) {
11
+
12
+ return awesomenessConfig.applicationMap[host];
13
+
14
+ } else {
15
+
16
+ // use the root domain
17
+ const parts = host.split('.');
18
+
19
+
20
+ if(parts.length > 2){
21
+
22
+ return parts.slice(-2).join('.');
23
+
24
+ } else {
25
+
26
+ return host;
27
+
28
+ }
29
+
30
+ }
31
+
32
+
33
+ }
@@ -0,0 +1,131 @@
1
+ import { getConfig } from "../config.js";
2
+ import applicationMap from './applicationMap.js';
3
+ import reRoute from './reRoute.js';
4
+
5
+ async function awesomenessNormalizeRequest({
6
+ req = {}
7
+ } = {}) {
8
+
9
+ const awesomenessConfig = getConfig();
10
+
11
+
12
+ const domain = req.headers.host;
13
+
14
+ // If the request is from API Gateway, use the requestContext to get the method
15
+ const method = req.method ?? req.requestContext?.http?.method;
16
+ const ip = req.ip ?? req.requestContext?.http?.sourceIp;
17
+ const userAgent = req.headers.userAgent;
18
+
19
+ let host = domain.toLowerCase();
20
+
21
+ const domainParts = host.split('.');
22
+ const mainDomain = domainParts.slice(-2).join('.').toLowerCase();
23
+
24
+ let subDomain = '';
25
+
26
+
27
+ if (domainParts.length > 2) {
28
+
29
+ subDomain = domainParts.slice(0, -2).join('.');
30
+
31
+ }
32
+
33
+ host = awesomenessConfig.hostMap({
34
+ host,
35
+ mainDomain
36
+ });
37
+
38
+ const applicationName = applicationMap(host);
39
+
40
+ let path = req.path ?? req.requestContext?.http?.path ?? '/';
41
+ let pageRoute = path.replace('/', '');
42
+
43
+ const slug = pageRoute.split('/').pop().split('.')[0].split('?')[0];
44
+
45
+ // top level keys
46
+ const topLevelKeys = [ 'awesomenessType', 'meta', 'device' ];
47
+
48
+ const newData = {};
49
+ const parts = req?.request?.body ?? {};
50
+
51
+ for (const key in parts) {
52
+
53
+ if (!topLevelKeys.includes(key)) {
54
+
55
+ newData[key] = parts[key];
56
+
57
+ }
58
+
59
+ }
60
+
61
+ const urlParams = {};
62
+
63
+ const queryString = req?.request?.url || '';
64
+
65
+ if (queryString.includes('?')) {
66
+
67
+ const queryPart = queryString.split('?').slice(1).join('?').split('#')[0];
68
+ const queryPairs = queryPart.split('&');
69
+
70
+ for (const pair of queryPairs) {
71
+
72
+ const [ key, value ] = pair.split('=');
73
+
74
+ urlParams[decodeURIComponent(key)] = decodeURIComponent(value || '');
75
+
76
+ }
77
+
78
+ }
79
+
80
+ const awesomenessRequest = {
81
+
82
+ // request info
83
+ headers: { ... req.headers },
84
+ ip,
85
+ userAgent,
86
+ method,
87
+ // our site info - fucked with
88
+ host,
89
+ mainDomain,
90
+ domain,
91
+ subDomain,
92
+ site: host, // might not be host with special mapping
93
+ application: applicationName,
94
+ path,
95
+ pageRoute,
96
+ slug,
97
+
98
+ // stuff to return
99
+ meta: {},
100
+
101
+ // stuff from sender
102
+ testing: req?.request?.body?.testing ?? false,
103
+ meta: req?.request?.body?.meta ?? {
104
+ components: {},
105
+ pages: {}
106
+ },
107
+ device: req?.request?.body?.device ?? {},
108
+ data: newData,
109
+ awesomenessType: req?.request?.body?.awesomenessType ?? 'generic',
110
+
111
+ urlParams,
112
+
113
+ // just because
114
+ _RAW: req
115
+ };
116
+
117
+ awesomenessRequest.reRoute = (destination)=>{
118
+
119
+ return reRoute({
120
+ goToPage: destination,
121
+ awesomenessRequest
122
+ });
123
+
124
+ };
125
+
126
+ return awesomenessRequest;
127
+
128
+ }
129
+
130
+ export { awesomenessNormalizeRequest };
131
+ export default awesomenessNormalizeRequest;
@@ -0,0 +1,28 @@
1
+ import { brotliCompressSync, constants } from 'zlib';
2
+
3
+ function brotliJsonResponse(data, {
4
+
5
+ } = {}) {
6
+
7
+ const json = JSON.stringify(data);
8
+
9
+ const compressed = brotliCompressSync(Buffer.from(json), {
10
+ params: {
11
+ [constants.BROTLI_PARAM_QUALITY]: 5
12
+ }
13
+ });
14
+
15
+ return {
16
+ statusCode: 200,
17
+ isBase64Encoded: true,
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ 'Content-Encoding': 'br'
21
+ },
22
+ body: compressed.toString('base64')
23
+ };
24
+
25
+ }
26
+
27
+ export { brotliJsonResponse };
28
+ export default brotliJsonResponse;
@@ -0,0 +1,34 @@
1
+ export default async ({
2
+ permissionsAllowed,
3
+ awesomenessRequest,
4
+ }) => {
5
+
6
+ const userPermissions = awesomenessRequest.user?.permissions || [];
7
+
8
+ // has at lease one of the permissions
9
+ const hasPermission = permissionsAllowed.some((rp) => userPermissions.includes(rp) || rp === '*');
10
+
11
+ if(!hasPermission){
12
+
13
+ if(
14
+ process.env.NODE_ENV === 'development'
15
+ && awesomenessConfig.byPassAccessRequirementsInDev === true
16
+ ){
17
+
18
+ console.log('by passing access requirement. 2 - development env && byPassAccessRequirementsInDev is true');
19
+
20
+ } else {
21
+
22
+ throw {
23
+ message: 'user does not have permission',
24
+ permissionsAllowed,
25
+ userPermissions
26
+ };
27
+
28
+ }
29
+
30
+ }
31
+
32
+ return true;
33
+
34
+ };
@@ -0,0 +1,301 @@
1
+ import path from "path";
2
+ import { fileURLToPath } from "url";
3
+ import { each, getAllFiles } from "@awesomeness-js/utils";
4
+ import { readFileSync } from "fs";
5
+
6
+ function urlToFsPath(u) {
7
+
8
+ if (!(u instanceof URL)) {
9
+
10
+ throw new TypeError("componentLocations must be an array of URL objects");
11
+
12
+ }
13
+
14
+ return fileURLToPath(u);
15
+
16
+ }
17
+
18
+ function extractUiFirstParts(str) {
19
+
20
+ const regex = /ui\.([a-zA-Z0-9_]+)(?:\.[a-zA-Z0-9_.]*)?\(/g;
21
+ const matches = new Set();
22
+ let match;
23
+
24
+ while ((match = regex.exec(str)) !== null) {
25
+
26
+ matches.add(match[1]);
27
+
28
+ }
29
+
30
+ return [ ...matches ];
31
+
32
+ }
33
+
34
+ function componentDependencies(allComponents, {
35
+ componentLocations = [],
36
+ namespace = "ui",
37
+ showDetails = false,
38
+ ignore = [ "*.css.js", "*.css.php" ],
39
+ } = {}) {
40
+
41
+ if (!Array.isArray(componentLocations) || componentLocations.length === 0) {
42
+
43
+ throw new TypeError("componentLocations must be a non-empty array of URL objects");
44
+
45
+ }
46
+
47
+ let componentsToProcess = [ ...allComponents ];
48
+ const out = {};
49
+
50
+ while (componentsToProcess.length > 0) {
51
+
52
+ const newComponentsToProcess = [];
53
+
54
+ componentsToProcess.forEach((component) => {
55
+
56
+ // Build roots in priority order; last is default because it’s last
57
+ const candidateRoots = componentLocations.map((baseUrl) => {
58
+
59
+ // baseUrl should point at a directory; we resolve component under it
60
+ const componentUrl = new URL(`./${component}/`, baseUrl);
61
+
62
+
63
+ return path.resolve(urlToFsPath(componentUrl));
64
+
65
+ });
66
+
67
+ let allFiles;
68
+ let chosenRoot;
69
+
70
+ for (const root of candidateRoots) {
71
+
72
+ try {
73
+
74
+ allFiles = getAllFiles(".", {
75
+ dir: root,
76
+ ignore
77
+ });
78
+ chosenRoot = root;
79
+ break; // first match wins
80
+
81
+ } catch {
82
+ // try next
83
+ }
84
+
85
+ }
86
+
87
+ if (!allFiles) {
88
+
89
+ throw {
90
+ message: "component does not exist (no location matched)",
91
+ component,
92
+ tried: candidateRoots,
93
+ };
94
+
95
+ }
96
+
97
+ allFiles.forEach((file) => {
98
+
99
+ const normalizedPath = path.normalize(file);
100
+ const pathSegments = normalizedPath.split(path.sep);
101
+
102
+ const variableIndex = pathSegments.indexOf(component);
103
+
104
+ let fileNameFull = pathSegments[pathSegments.length - 1];
105
+ const fileTypeArr = fileNameFull.split(".");
106
+ const fileType = fileTypeArr[fileTypeArr.length - 1];
107
+ const fileName = fileTypeArr.slice(0, -1).join(".");
108
+
109
+ out[component] = out[component] || {};
110
+ out[component][fileType] = out[component][fileType] || {};
111
+
112
+ let arrAfterComponent = pathSegments.slice(variableIndex + 1);
113
+
114
+ arrAfterComponent.pop();
115
+
116
+ let tail = "";
117
+
118
+ if (fileType === "js" || fileType === "css") {
119
+
120
+ if (arrAfterComponent.length > 0) {
121
+
122
+ tail =
123
+ fileName === "index"
124
+ ? "." + arrAfterComponent.join(".")
125
+ : `.${arrAfterComponent.join(".")}.${fileName}`;
126
+
127
+ } else {
128
+
129
+ tail = fileName === "index" ? "" : `.${fileName}`;
130
+
131
+ }
132
+
133
+ }
134
+
135
+ const componentName = `${namespace}.${component}${tail}`;
136
+
137
+ try {
138
+
139
+ const fileContent = readFileSync(file, "utf-8");
140
+ const lines = fileContent.split("\n");
141
+ let fileWithImportsStripped = "";
142
+
143
+ try {
144
+
145
+ const newTest = extractUiFirstParts(fileContent);
146
+
147
+ if (newTest.length > 0) {
148
+
149
+ newTest.forEach((newComp) => {
150
+
151
+ if (!allComponents.includes(newComp)) {
152
+
153
+ allComponents.push(newComp);
154
+ newComponentsToProcess.push(newComp);
155
+
156
+ }
157
+
158
+ });
159
+
160
+ }
161
+
162
+ } catch (error) {
163
+
164
+ console.error("Error extracting UI parts:", error);
165
+
166
+ }
167
+
168
+ lines.forEach((line) => {
169
+
170
+ if (line.startsWith("import ui")) {
171
+
172
+ if (line.includes(`import ui from '#ui'; // `)) {
173
+
174
+ const imports = line.split(`import ui from '#ui'; // `);
175
+
176
+ if (imports.length > 1) {
177
+
178
+ const importComponents = imports[1].split(",").map((c) => c.trim());
179
+
180
+ importComponents.forEach((importComponent) => {
181
+
182
+ if (!allComponents.includes(importComponent)) {
183
+
184
+ allComponents.push(importComponent);
185
+ newComponentsToProcess.push(importComponent);
186
+
187
+ }
188
+
189
+ });
190
+
191
+ }
192
+
193
+ }
194
+
195
+ } else if (
196
+ line.startsWith("// awesomeness import")
197
+ || line.startsWith("/* awesomeness @import")
198
+ ) {
199
+
200
+ const importPathMatch = line.match(/['"]([^'"]+)['"]/);
201
+
202
+ if (importPathMatch) {
203
+
204
+ const importedComponentName = importPathMatch[1].replace(/;$/, "").trim();
205
+
206
+ if (!allComponents.includes(importedComponentName)) {
207
+
208
+ allComponents.push(importedComponentName);
209
+ newComponentsToProcess.push(importedComponentName);
210
+
211
+ }
212
+
213
+ }
214
+
215
+ } else {
216
+
217
+ fileWithImportsStripped += `${line}\n`;
218
+
219
+ }
220
+
221
+ });
222
+
223
+ if (fileType === "js") {
224
+
225
+ if (
226
+ fileWithImportsStripped.startsWith("(function")
227
+ || fileWithImportsStripped.startsWith("((")
228
+ ) {
229
+
230
+ fileWithImportsStripped = `;${fileWithImportsStripped}`;
231
+
232
+ } else {
233
+
234
+ fileWithImportsStripped = fileWithImportsStripped.replace(
235
+ "export default ",
236
+ `${componentName} = `
237
+ );
238
+
239
+ }
240
+
241
+ }
242
+
243
+ out[component][fileType][componentName] = fileWithImportsStripped;
244
+
245
+ } catch (err) {
246
+
247
+ console.log("Failed to get dependencies", { component });
248
+
249
+ }
250
+
251
+ });
252
+
253
+ if (out[component]) {
254
+
255
+ each(out[component], (files, type) => {
256
+
257
+ if (type === "js" && !files[`${namespace}.${component}`]) {
258
+
259
+ files[`${namespace}.${component}`] = `${namespace}.${component} = {}; `;
260
+
261
+ }
262
+
263
+ files = Object.keys(files)
264
+ .sort()
265
+ .reduce((obj, key) => {
266
+
267
+ obj[key] = files[key];
268
+
269
+ return obj;
270
+
271
+ }, {});
272
+
273
+ if (showDetails) {
274
+
275
+ out[component][type + "_details"] = files;
276
+
277
+ }
278
+
279
+ out[component][type] = ` ${Object.values(files).join("\n")} `;
280
+
281
+ });
282
+
283
+ }
284
+
285
+ componentsToProcess = componentsToProcess.filter((f) => f !== component);
286
+
287
+ });
288
+
289
+ if (newComponentsToProcess.length) {
290
+
291
+ componentsToProcess = componentsToProcess.concat(newComponentsToProcess);
292
+
293
+ }
294
+
295
+ }
296
+
297
+ return out;
298
+
299
+ }
300
+
301
+ export { componentDependencies };
@@ -0,0 +1,11 @@
1
+ process.on('uncaughtException', (err) => {
2
+
3
+ console.error('💥 Uncaught Exception:', err);
4
+
5
+ });
6
+
7
+ process.on('unhandledRejection', (reason, promise) => {
8
+
9
+ console.error('💥 Unhandled Rejection:', reason);
10
+
11
+ });