@_linked/server 2.0.1 → 2.0.3
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 +12 -0
- package/lib/cjs/backend.d.ts +6 -0
- package/lib/cjs/backend.js +58 -0
- package/lib/cjs/backend.js.map +1 -0
- package/lib/cjs/data/lincd-server.json +47 -0
- package/lib/cjs/index.d.ts +9 -0
- package/lib/cjs/index.js +13 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/cjs/ontologies/lincd-server.d.ts +31 -0
- package/lib/cjs/ontologies/lincd-server.js +86 -0
- package/lib/cjs/ontologies/lincd-server.js.map +1 -0
- package/lib/cjs/package.d.ts +4 -0
- package/lib/cjs/package.js +7 -0
- package/lib/cjs/package.js.map +1 -0
- package/lib/cjs/package.json +90 -0
- package/lib/cjs/shapes/LincdAPI.d.ts +41 -0
- package/lib/cjs/shapes/LincdAPI.js +124 -0
- package/lib/cjs/shapes/LincdAPI.js.map +1 -0
- package/lib/cjs/shapes/LincdServer.d.ts +79 -0
- package/lib/cjs/shapes/LincdServer.js +1339 -0
- package/lib/cjs/shapes/LincdServer.js.map +1 -0
- package/lib/cjs/shapes/LincdWebApp.d.ts +7 -0
- package/lib/cjs/shapes/LincdWebApp.js +43 -0
- package/lib/cjs/shapes/LincdWebApp.js.map +1 -0
- package/lib/cjs/shapes/filestores/LocalFileStore.d.ts +42 -0
- package/lib/cjs/shapes/filestores/LocalFileStore.js +146 -0
- package/lib/cjs/shapes/filestores/LocalFileStore.js.map +1 -0
- package/lib/cjs/shapes/quadstores/BackendAPIStore.d.ts +33 -0
- package/lib/cjs/shapes/quadstores/BackendAPIStore.js +79 -0
- package/lib/cjs/shapes/quadstores/BackendAPIStore.js.map +1 -0
- package/lib/cjs/shapes/quadstores/BackendAPIStoreProvider.d.ts +13 -0
- package/lib/cjs/shapes/quadstores/BackendAPIStoreProvider.js +30 -0
- package/lib/cjs/shapes/quadstores/BackendAPIStoreProvider.js.map +1 -0
- package/lib/cjs/types/RouteConfig.d.ts +56 -0
- package/lib/cjs/types/RouteConfig.js +3 -0
- package/lib/cjs/types/RouteConfig.js.map +1 -0
- package/lib/cjs/types.d.ts +12 -0
- package/lib/cjs/types.js +1 -0
- package/lib/cjs/types.js.map +1 -0
- package/lib/cjs/utils/Shapes.d.ts +7 -0
- package/lib/cjs/utils/Shapes.js +76 -0
- package/lib/cjs/utils/Shapes.js.map +1 -0
- package/lib/cjs/utils/accessUrl.d.ts +6 -0
- package/lib/cjs/utils/accessUrl.js +13 -0
- package/lib/cjs/utils/accessUrl.js.map +1 -0
- package/lib/esm/backend.d.ts +6 -0
- package/lib/esm/backend.js +27 -0
- package/lib/esm/backend.js.map +1 -0
- package/lib/esm/data/lincd-server.json +47 -0
- package/lib/esm/index.d.ts +9 -0
- package/lib/esm/index.js +11 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/ontologies/lincd-server.d.ts +31 -0
- package/lib/esm/ontologies/lincd-server.js +42 -0
- package/lib/esm/ontologies/lincd-server.js.map +1 -0
- package/lib/esm/package.d.ts +4 -0
- package/lib/esm/package.js +3 -0
- package/lib/esm/package.js.map +1 -0
- package/lib/esm/package.json +90 -0
- package/lib/esm/shapes/LincdAPI.d.ts +41 -0
- package/lib/esm/shapes/LincdAPI.js +113 -0
- package/lib/esm/shapes/LincdAPI.js.map +1 -0
- package/lib/esm/shapes/LincdServer.d.ts +79 -0
- package/lib/esm/shapes/LincdServer.js +1257 -0
- package/lib/esm/shapes/LincdServer.js.map +1 -0
- package/lib/esm/shapes/LincdWebApp.d.ts +7 -0
- package/lib/esm/shapes/LincdWebApp.js +40 -0
- package/lib/esm/shapes/LincdWebApp.js.map +1 -0
- package/lib/esm/shapes/filestores/LocalFileStore.d.ts +42 -0
- package/lib/esm/shapes/filestores/LocalFileStore.js +93 -0
- package/lib/esm/shapes/filestores/LocalFileStore.js.map +1 -0
- package/lib/esm/shapes/quadstores/BackendAPIStore.d.ts +33 -0
- package/lib/esm/shapes/quadstores/BackendAPIStore.js +65 -0
- package/lib/esm/shapes/quadstores/BackendAPIStore.js.map +1 -0
- package/lib/esm/shapes/quadstores/BackendAPIStoreProvider.d.ts +13 -0
- package/lib/esm/shapes/quadstores/BackendAPIStoreProvider.js +26 -0
- package/lib/esm/shapes/quadstores/BackendAPIStoreProvider.js.map +1 -0
- package/lib/esm/types/RouteConfig.d.ts +56 -0
- package/lib/esm/types/RouteConfig.js +2 -0
- package/lib/esm/types/RouteConfig.js.map +1 -0
- package/lib/esm/types.d.ts +12 -0
- package/lib/esm/types.js +1 -0
- package/lib/esm/types.js.map +1 -0
- package/lib/esm/utils/Shapes.d.ts +7 -0
- package/lib/esm/utils/Shapes.js +59 -0
- package/lib/esm/utils/Shapes.js.map +1 -0
- package/lib/esm/utils/accessUrl.d.ts +6 -0
- package/lib/esm/utils/accessUrl.js +9 -0
- package/lib/esm/utils/accessUrl.js.map +1 -0
- package/package.json +7 -4
|
@@ -0,0 +1,1339 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
45
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
46
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
47
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
48
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
49
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
50
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
54
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
55
|
+
};
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
exports.LincdServer = void 0;
|
|
58
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
59
|
+
const events_1 = __importDefault(require("events"));
|
|
60
|
+
const express_1 = __importDefault(require("express"));
|
|
61
|
+
const fetch_cookie_1 = __importDefault(require("fetch-cookie"));
|
|
62
|
+
const fsNative = __importStar(require("fs"));
|
|
63
|
+
const fs = __importStar(require("fs/promises"));
|
|
64
|
+
const config_webpack_app_1 = require("@_linked/cli/config-webpack-app");
|
|
65
|
+
const cli_methods_1 = require("@_linked/cli/cli-methods");
|
|
66
|
+
const utils_1 = require("@_linked/cli/utils");
|
|
67
|
+
const AppContext_1 = require("@_linked/server-utils/components/AppContext");
|
|
68
|
+
const JSONParser_1 = require("@_linked/server-utils/utils/JSONParser");
|
|
69
|
+
const JSONWriter_1 = require("@_linked/server-utils/utils/JSONWriter");
|
|
70
|
+
const Server_1 = require("@_linked/server-utils/utils/Server");
|
|
71
|
+
const ShapeProvider_1 = require("@_linked/server-utils/utils/ShapeProvider");
|
|
72
|
+
const CoreMap_1 = require("@_linked/core/collections/CoreMap");
|
|
73
|
+
const Shape_1 = require("@_linked/core/shapes/Shape");
|
|
74
|
+
const LinkedErrorLogging_1 = require("@_linked/core/utils/LinkedErrorLogging");
|
|
75
|
+
const LinkedFileStorage_1 = require("@_linked/core/utils/LinkedFileStorage");
|
|
76
|
+
const LinkedStorage_1 = require("@_linked/core/utils/LinkedStorage");
|
|
77
|
+
const Package_1 = require("@_linked/core/utils/Package");
|
|
78
|
+
const ShapeClass_1 = require("@_linked/core/utils/ShapeClass");
|
|
79
|
+
const path_1 = __importDefault(require("path"));
|
|
80
|
+
const process_1 = __importDefault(require("process"));
|
|
81
|
+
const React = __importStar(require("react"));
|
|
82
|
+
const server_1 = require("react-dom/server");
|
|
83
|
+
const server_js_1 = require("react-router-dom/server.js");
|
|
84
|
+
const rimraf_1 = require("rimraf");
|
|
85
|
+
const sharp_1 = __importDefault(require("sharp"));
|
|
86
|
+
const stream_1 = require("stream");
|
|
87
|
+
const tough_cookie_1 = require("tough-cookie");
|
|
88
|
+
const webpack_1 = __importDefault(require("webpack"));
|
|
89
|
+
const webpack_dev_middleware_1 = __importDefault(require("webpack-dev-middleware"));
|
|
90
|
+
const webpack_hot_middleware_1 = __importDefault(require("webpack-hot-middleware"));
|
|
91
|
+
const lincd_server_js_1 = require("../ontologies/lincd-server.js");
|
|
92
|
+
const package_js_1 = require("../package.js");
|
|
93
|
+
const Shapes_js_1 = require("../utils/Shapes.js");
|
|
94
|
+
const LincdAPI_js_1 = require("./LincdAPI.js");
|
|
95
|
+
//prevent errors in node.js when (s)css files are imported in js
|
|
96
|
+
const isProduction = process_1.default.env.NODE_ENV === 'production';
|
|
97
|
+
const isDevelopment = process_1.default.env.NODE_ENV === 'development';
|
|
98
|
+
// Install a global fetch that persists cookies across redirects using a CookieJar.
|
|
99
|
+
// This ensures server-side requests behave more like browsers when handling Set-Cookie + redirects.
|
|
100
|
+
const __lincdCookieJar = new tough_cookie_1.CookieJar();
|
|
101
|
+
// Wrap native fetch with fetch-cookie so it reads/writes cookies in the jar.
|
|
102
|
+
const __lincdFetchWithCookies = (0, fetch_cookie_1.default)(fetch, __lincdCookieJar);
|
|
103
|
+
// Expose (optionally) for debugging/tests; not required by app logic.
|
|
104
|
+
globalThis.__lincdCookieJar = __lincdCookieJar;
|
|
105
|
+
// Set the global fetch used throughout the server code path
|
|
106
|
+
globalThis.fetch = __lincdFetchWithCookies;
|
|
107
|
+
process_1.default.on('uncaughtException', (err) => {
|
|
108
|
+
console.warn(chalk_1.default.red('Asynchronous error caught.'));
|
|
109
|
+
console.error(err);
|
|
110
|
+
// error logging
|
|
111
|
+
LinkedErrorLogging_1.LinkedErrorLogging.log(err);
|
|
112
|
+
});
|
|
113
|
+
process_1.default.on('unhandledRejection', (err) => {
|
|
114
|
+
console.warn(chalk_1.default.red('Unhandled rejection caught.'));
|
|
115
|
+
console.error(err);
|
|
116
|
+
// error logging
|
|
117
|
+
LinkedErrorLogging_1.LinkedErrorLogging.log(err);
|
|
118
|
+
});
|
|
119
|
+
process_1.default.on('warning', (e) => console.warn(e.stack));
|
|
120
|
+
//allow more listeners for when we have many concurrent users
|
|
121
|
+
events_1.default.EventEmitter.prototype.setMaxListeners(500);
|
|
122
|
+
// const jsdom = require("jsdom");
|
|
123
|
+
// const { JSDOM } = jsdom;
|
|
124
|
+
// const { document } = (new JSDOM(`...`)).window;
|
|
125
|
+
//
|
|
126
|
+
// global['document'] = document;
|
|
127
|
+
global['reactStaticRenderer'] = server_1.renderToStaticMarkup;
|
|
128
|
+
(0, Package_1.autoLoadOntologyData)(true);
|
|
129
|
+
let LincdServer = class LincdServer extends Shape_1.Shape {
|
|
130
|
+
/**
|
|
131
|
+
* yarn linked start sends the contents of linked.config.js as an object to this constructor
|
|
132
|
+
* @param n
|
|
133
|
+
*/
|
|
134
|
+
constructor(config) {
|
|
135
|
+
super(typeof config === 'string' || (config && 'id' in config)
|
|
136
|
+
? config
|
|
137
|
+
: undefined);
|
|
138
|
+
this.cachedPaths = new Map();
|
|
139
|
+
this.latestManifest = null;
|
|
140
|
+
this.cssMode = 'scss-modules';
|
|
141
|
+
this.analyse = false;
|
|
142
|
+
this.shapeProviders = new CoreMap_1.CoreMap();
|
|
143
|
+
this.genericProviders = new CoreMap_1.CoreMap();
|
|
144
|
+
//from resizedFileName to full resized path (CDN or similar)
|
|
145
|
+
this.resizePathsMap = new Map();
|
|
146
|
+
if (config && typeof config !== 'string' && !('id' in config)) {
|
|
147
|
+
this.config = config;
|
|
148
|
+
}
|
|
149
|
+
this.api = new LincdAPI_js_1.LincdAPI({ id: process_1.default.env.SITE_ROOT + '/api' });
|
|
150
|
+
//Ensure the Server utility (for Server.call()) directly accesses
|
|
151
|
+
// this server on the backend instead of going through a network call
|
|
152
|
+
Server_1.Server.setLocalServer(this);
|
|
153
|
+
}
|
|
154
|
+
/*async getLincdDependencies(): Promise<string[]> {
|
|
155
|
+
let lincdDependencies = [];
|
|
156
|
+
let dependencies = this.package.dependencies;
|
|
157
|
+
|
|
158
|
+
await Promise.all(
|
|
159
|
+
Object.keys(dependencies).map((dependencyPkgName) => {
|
|
160
|
+
let modulePackageJson = getModulePackageJSON(dependencyPkgName);
|
|
161
|
+
if (modulePackageJson['lincd']) {
|
|
162
|
+
lincdDependencies.push(modulePackageJson.name);
|
|
163
|
+
}
|
|
164
|
+
// //TODO: also iteratively look into dependencies of this dependency
|
|
165
|
+
|
|
166
|
+
// let packagePath;
|
|
167
|
+
// try {
|
|
168
|
+
// packagePath = require.resolve(`${dependencyPkgName}`);
|
|
169
|
+
// } catch (err) {
|
|
170
|
+
// console.warn('Could not find package ' + dependencyPkgName+'. Error: '+err.toString());
|
|
171
|
+
// return;
|
|
172
|
+
// }
|
|
173
|
+
// packagePath = path.dirname(packagePath) + '/package.json'
|
|
174
|
+
// return fs
|
|
175
|
+
// .readFile(packagePath, 'utf-8')
|
|
176
|
+
// .then((res) => {
|
|
177
|
+
// let pkg = JSON.parse(res);
|
|
178
|
+
// if (pkg['lincd']) {
|
|
179
|
+
// lincdDependencies.push(pkg.name);
|
|
180
|
+
// }
|
|
181
|
+
// //TODO: also iteratively look into dependencies of this dependency
|
|
182
|
+
// })
|
|
183
|
+
// .catch((err) => {
|
|
184
|
+
// console.log('Could not read package.json file: '+err);
|
|
185
|
+
// });
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
return lincdDependencies;
|
|
189
|
+
}*/
|
|
190
|
+
get app() {
|
|
191
|
+
return this.server;
|
|
192
|
+
}
|
|
193
|
+
initPackage() {
|
|
194
|
+
this.package = JSON.parse(fsNative.readFileSync(path_1.default.resolve(process_1.default.cwd(), 'package.json'), 'utf-8'));
|
|
195
|
+
}
|
|
196
|
+
initOnly() {
|
|
197
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
198
|
+
yield this.initOntologies();
|
|
199
|
+
yield this.initStores();
|
|
200
|
+
this.initPackage();
|
|
201
|
+
this.server = (0, express_1.default)();
|
|
202
|
+
yield this.initBackendProviders();
|
|
203
|
+
(0, Shapes_js_1.syncShapes)();
|
|
204
|
+
return this;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// async serveData(req,res) {
|
|
208
|
+
// let nodeURI = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
|
|
209
|
+
//
|
|
210
|
+
// let store = LinkedStorage.getDatasets().find(store => {
|
|
211
|
+
// return nodeURI.includes(store.namedNode.uri)
|
|
212
|
+
// })
|
|
213
|
+
// }
|
|
214
|
+
start() {
|
|
215
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
216
|
+
this.initPackage();
|
|
217
|
+
// Static assets should come from the static store URL (versioned path),
|
|
218
|
+
// not the upload store URL. storage-config sets STATIC_ACCESS_URL accordingly.
|
|
219
|
+
// In development we deliberately use a relative path so bundle URLs work
|
|
220
|
+
// regardless of which PORT the dev server bound to (browsers resolve
|
|
221
|
+
// relative URLs against window.location.origin). Hardcoding SITE_ROOT
|
|
222
|
+
// baked :4000 into every SSR'd HTML page.
|
|
223
|
+
const isDevAssets = process_1.default.env.NODE_ENV === 'development';
|
|
224
|
+
const staticAccessURL = isDevAssets
|
|
225
|
+
? ''
|
|
226
|
+
: (process_1.default.env.STATIC_ACCESS_URL ||
|
|
227
|
+
LinkedFileStorage_1.LinkedFileStorage.accessURL ||
|
|
228
|
+
'').replace(/\/$/, '');
|
|
229
|
+
const staticAsset = (assetPath) => `${staticAccessURL}/public${assetPath}`;
|
|
230
|
+
//for apps with multiple bundles this should be read from the webpack build manifest
|
|
231
|
+
this.assets = {
|
|
232
|
+
'main.js': staticAsset('/bundles/main.bundle.js') + '?v=' + this.package.version, //output js bundle from Webpack
|
|
233
|
+
'main.css': staticAsset('/bundles/main.css'), //output css from Webpack
|
|
234
|
+
};
|
|
235
|
+
try {
|
|
236
|
+
const manifestPath = path_1.default.resolve(process_1.default.cwd(), 'public/bundles/manifest.json');
|
|
237
|
+
if (fsNative.existsSync(manifestPath)) {
|
|
238
|
+
const manifestRaw = fsNative.readFileSync(manifestPath, 'utf-8');
|
|
239
|
+
this.assets.manifest = JSON.parse(manifestRaw);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
console.warn('Could not load bundle manifest:', err);
|
|
244
|
+
}
|
|
245
|
+
const isProduction = process_1.default.env.NODE_ENV === 'production';
|
|
246
|
+
if (!isProduction && this.config.cssMode === 'tailwind') {
|
|
247
|
+
this.assets['tailwind-cdn'] = 'https://cdn.tailwindcss.com';
|
|
248
|
+
}
|
|
249
|
+
//for multicore we use PM2, and each instance needs to listen to port 0.
|
|
250
|
+
// whilst the main thread will listen to env.PORT automatically
|
|
251
|
+
// const PORT = this.config.multiCore ? 0 : process.env.PORT || 3000;
|
|
252
|
+
// const publicPort = process.env.PORT || 3000;
|
|
253
|
+
//update: back to original setup. multicore handles itself inside @semantu/multicore
|
|
254
|
+
const PORT = parseInt(process_1.default.env.PORT) || 4000;
|
|
255
|
+
this.server = (0, express_1.default)();
|
|
256
|
+
const dirName = path_1.default.resolve(process_1.default.cwd(), 'frontend');
|
|
257
|
+
yield this.initOntologies();
|
|
258
|
+
//TODO: when we do not keep all data in memory (and thus do not need to rely on in memory data for page requests), we can possibly remove await here, since all stores handle their own initialisation before executing commands
|
|
259
|
+
yield this.initStores();
|
|
260
|
+
this.initGarbageCollection();
|
|
261
|
+
// this.app.use((req, res, next) => {
|
|
262
|
+
// console.log('start request');
|
|
263
|
+
// next();
|
|
264
|
+
// });
|
|
265
|
+
// before controllers
|
|
266
|
+
yield this.initBackendProviders();
|
|
267
|
+
(0, Shapes_js_1.syncShapes)();
|
|
268
|
+
//START OF EXPRESS ROUTES AND MIDDLEWARE
|
|
269
|
+
//use cors
|
|
270
|
+
// var corsOptions = {
|
|
271
|
+
// origin: ['http://localhost:4001', 'https://www.mynd.site'],
|
|
272
|
+
// optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
|
|
273
|
+
// };
|
|
274
|
+
// //accept JSON bodies
|
|
275
|
+
// this.server.use(bodyParser.json({limit: '50mb'}));
|
|
276
|
+
this.server.use(express_1.default.json({ limit: '50mb' }));
|
|
277
|
+
// this.server.use(cors(corsOptions));
|
|
278
|
+
//
|
|
279
|
+
// //compress server output with gzip,
|
|
280
|
+
//UPDATE ive set level to 2 (low compression fast speed) because responses were taking too long (98% of server time was used by compress)
|
|
281
|
+
// this.server.use(compress({level: 2}));
|
|
282
|
+
yield this.callGenericBackendProvidersMethod('setupBeforeControllers');
|
|
283
|
+
// if development, run webpack from the server
|
|
284
|
+
// for production you need to build the bundles before starting the server
|
|
285
|
+
const skipBuild = process_1.default.env.NO_WEBPACK === 'true';
|
|
286
|
+
if (isDevelopment && !skipBuild) {
|
|
287
|
+
//three levels up because of lib/esm (2) instead of src (1)
|
|
288
|
+
//@ts-ignore
|
|
289
|
+
// const getWebpackConfig = (await import('../../../site.webpack.config.cjs')).default;
|
|
290
|
+
// let webpackConfig = await getWebpackConfig();
|
|
291
|
+
let webpackConfig = yield (0, config_webpack_app_1.getWebpackAppConfig)();
|
|
292
|
+
// const compare = (c1,c2,path?='') => {
|
|
293
|
+
// for(let key1 of Object.keys(c1)) {
|
|
294
|
+
// if(typeof c1[key1] === 'object') {
|
|
295
|
+
// compare(c1[key1],c2[key1],path+='.'+key1);
|
|
296
|
+
// } else {
|
|
297
|
+
// if(c1[key1] !== c2[key1]) {
|
|
298
|
+
// console.log("Difference "+path+": ");
|
|
299
|
+
// console.log(c1[key1]);
|
|
300
|
+
// console.log(c2[key1]);
|
|
301
|
+
// }
|
|
302
|
+
// }
|
|
303
|
+
// }
|
|
304
|
+
// }
|
|
305
|
+
// compare(webpackConfig,webpackConfig2);
|
|
306
|
+
//default to having webpack cache on and filesystem, unless explicitly set to false
|
|
307
|
+
if (this.cacheWebpack === false) {
|
|
308
|
+
webpackConfig.cache.type = 'memory';
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
webpackConfig.cache.type = 'filesystem';
|
|
312
|
+
}
|
|
313
|
+
//clean dist/build folder
|
|
314
|
+
yield (0, rimraf_1.rimraf)(webpackConfig.output.path).catch((err) => {
|
|
315
|
+
if (err) {
|
|
316
|
+
console.warn(err);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
const compiler = (0, webpack_1.default)(webpackConfig, (err, stats) => {
|
|
320
|
+
//watch build completed
|
|
321
|
+
if (err) {
|
|
322
|
+
console.error(err);
|
|
323
|
+
}
|
|
324
|
+
// Output stats JSON for analysis
|
|
325
|
+
if (this.analyse) {
|
|
326
|
+
fs.writeFile('./data/webpack-stats.json', JSON.stringify(stats.toJson({ all: true }), null, 2)).then(() => {
|
|
327
|
+
console.log('Webpack stats written to ./data/webpack-stats.json');
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
if (!compiler) {
|
|
332
|
+
//something went wrong with webpack config, error will be logged above
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
compiler.hooks.afterEmit.tap('cleanup-the-require-cache', () => {
|
|
336
|
+
// After webpack rebuild, clear the files from the require cache,
|
|
337
|
+
// so that next server side render wil be in sync
|
|
338
|
+
// console.log(Object.keys(require.cache).filter(k => k.includes(dirName)).join("\n"));
|
|
339
|
+
// if (typeof require !== 'undefined') {
|
|
340
|
+
// Object.keys(require.cache)
|
|
341
|
+
// .filter((key) => key.includes(dirName))
|
|
342
|
+
// .forEach((key) => delete require.cache[key]);
|
|
343
|
+
// }
|
|
344
|
+
});
|
|
345
|
+
//after the first emit (which means bundles are ready and the site is running) also rebuild the index files of the site
|
|
346
|
+
let updatedMetadata = false;
|
|
347
|
+
compiler.hooks.afterEmit.tap('update-metadata', () => {
|
|
348
|
+
if (!updatedMetadata) {
|
|
349
|
+
updatedMetadata = true;
|
|
350
|
+
//log updated paths
|
|
351
|
+
// buildMetadata()
|
|
352
|
+
// .then((updatedPaths) => {
|
|
353
|
+
// // if(updatedPaths && updatedPaths.length)
|
|
354
|
+
// // {
|
|
355
|
+
// // console.log(chalk.blueBright('Updated metadata:\n - '+updatedPaths.join('\n - ')));
|
|
356
|
+
// // }
|
|
357
|
+
// })
|
|
358
|
+
// .catch((err) => {
|
|
359
|
+
// console.warn('Could not update metadata: ' + err);
|
|
360
|
+
// });
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
this.server.use((0, webpack_dev_middleware_1.default)(compiler, {
|
|
364
|
+
serverSideRender: true,
|
|
365
|
+
publicPath: webpackConfig.output.publicPath,
|
|
366
|
+
stats: {
|
|
367
|
+
children: true,
|
|
368
|
+
version: false,
|
|
369
|
+
chunks: false,
|
|
370
|
+
assets: false,
|
|
371
|
+
entrypoints: false,
|
|
372
|
+
modules: false,
|
|
373
|
+
},
|
|
374
|
+
writeToDisk: true,
|
|
375
|
+
}));
|
|
376
|
+
compiler.hooks.afterEmit.tap('store-manifest', (compilation) => {
|
|
377
|
+
try {
|
|
378
|
+
// Read manifest from the filesystem after webpack writes it
|
|
379
|
+
const manifestPath = path_1.default.resolve(compilation.options.output.path, 'manifest.json');
|
|
380
|
+
if (fsNative.existsSync(manifestPath)) {
|
|
381
|
+
const manifestContent = fsNative.readFileSync(manifestPath, 'utf-8');
|
|
382
|
+
this.latestManifest = JSON.parse(manifestContent);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
console.warn('Failed to parse manifest from webpack emit:', err);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
this.server.use((0, webpack_hot_middleware_1.default)(compiler, {
|
|
390
|
+
log: false,
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
// //map URL routes to file paths
|
|
394
|
+
const oneYear = 1000 * 60 * 60 * 24 * 365; // in milliseconds
|
|
395
|
+
const oneMonth = 1000 * 60 * 60 * 24 * 30; // in milliseconds
|
|
396
|
+
this.server.use('/public', express_1.default.static('./public', {
|
|
397
|
+
maxAge: oneYear, // Tell browser to cache for 1 year
|
|
398
|
+
immutable: true, // Suggest that the content won't change
|
|
399
|
+
}));
|
|
400
|
+
this.server.use('/uploads', express_1.default.static('./data/uploads', {
|
|
401
|
+
maxAge: oneYear, // Tell browser to cache for 1 year
|
|
402
|
+
immutable: true, // Suggest that the content won't change
|
|
403
|
+
}));
|
|
404
|
+
this.server.use('/', express_1.default.static('./public/root'));
|
|
405
|
+
this.server.use('/favicon.ico', express_1.default.static('./public/favicon.ico', {
|
|
406
|
+
maxAge: oneMonth, // Tell browser to cache for 1 year
|
|
407
|
+
immutable: true, // Suggest that the content won't change
|
|
408
|
+
}));
|
|
409
|
+
this.server.use('/.well-known', express_1.default.static('./public/.well-known'));
|
|
410
|
+
// this.server.post('/data',this.handleErrorsJson(async (req,res) => this.serveData(req, res)));
|
|
411
|
+
this.server.get('/resized/*', (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
412
|
+
this.resizeImage(req, res);
|
|
413
|
+
}));
|
|
414
|
+
// Scoped-package variants (@scope/pkg). Register BEFORE the unscoped
|
|
415
|
+
// routes — Express `:pkg` won't consume slashes, so a call to
|
|
416
|
+
// `/call/@_linked/auth/signinDev` would otherwise fall through to the
|
|
417
|
+
// 3-segment `:pkg/:shape/:method` route and be misinterpreted as a
|
|
418
|
+
// shape-method call (pkg=`@_linked`, shape=`auth`). These handlers
|
|
419
|
+
// recognise the `@scope/pkg` prefix and rebuild the full package name
|
|
420
|
+
// before dispatching to the same handler used for unscoped packages.
|
|
421
|
+
this.server.post('/call/@:scope/:pkg/:method', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
422
|
+
req.params.pkg = `@${req.params.scope}/${req.params.pkg}`;
|
|
423
|
+
return this.processBackendMethodCall(req, res);
|
|
424
|
+
})));
|
|
425
|
+
this.server.post('/call/@:scope/:pkg/:shape/:method', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
426
|
+
req.params.pkg = `@${req.params.scope}/${req.params.pkg}`;
|
|
427
|
+
return this.processShapeMethodCall(req, res);
|
|
428
|
+
})));
|
|
429
|
+
this.server.post('/call/:pkg/:method', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () { return this.processBackendMethodCall(req, res); })));
|
|
430
|
+
this.server.post('/call/:pkg/:shape/:method', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () { return this.processShapeMethodCall(req, res); })));
|
|
431
|
+
this.server.post('/api/:method/:action?', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () { return this.processAPICall(req, res, 'post'); })));
|
|
432
|
+
this.server.get('/api/:method/:action?', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () { return this.processAPICall(req, res, 'get'); })));
|
|
433
|
+
// this.server.post(
|
|
434
|
+
// '/api/query/:method',
|
|
435
|
+
// this.handleErrorsJson(async (req, res) => this.processQuery(req, res)),
|
|
436
|
+
// );
|
|
437
|
+
// now that all the middleware is defined, we initialise the providers, before we define a catch-all route
|
|
438
|
+
// before catch all
|
|
439
|
+
// await this.initBackendProviders();
|
|
440
|
+
yield this.callGenericBackendProvidersMethod('setupBeforeCatchAllControllers');
|
|
441
|
+
// HEAD catch-all for maintenance/health check (client fetches SITE_ROOT with method: HEAD)
|
|
442
|
+
this.server.head('__health', (_req, res) => {
|
|
443
|
+
res.sendStatus(200);
|
|
444
|
+
});
|
|
445
|
+
this.server.get('*', this.handleErrors((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
446
|
+
//make sure the frontend bundle has finished building
|
|
447
|
+
// await this.waitForWebpack();
|
|
448
|
+
this.render(req, res);
|
|
449
|
+
})));
|
|
450
|
+
// after controller
|
|
451
|
+
yield this.callGenericBackendProvidersMethod('setupAfterControllers');
|
|
452
|
+
//remove http(s):// and remove port :[port]
|
|
453
|
+
const HOST = process_1.default.env.SITE_ROOT.replace(/https?:\/\//, '').replace(/:\d+$/, '');
|
|
454
|
+
//backlog of 1024 means maximum of 1024 connections in the queue (higher than default)
|
|
455
|
+
this.httpServer = this.server.listen({ port: PORT, backlog: 1024 }, () => {
|
|
456
|
+
console.log(`Up and running at http://localhost:${PORT}`);
|
|
457
|
+
// open(`http://localhost:${PORT}`)
|
|
458
|
+
});
|
|
459
|
+
// Ensure all inactive connections are terminated by the ALB, by setting this a few seconds higher than the ALB idle timeout
|
|
460
|
+
this.httpServer.keepAliveTimeout = 60000;
|
|
461
|
+
// Ensure the headersTimeout is set higher than the keepAliveTimeout due to this nodejs regression bug: https://github.com/nodejs/node/issues/27363
|
|
462
|
+
this.httpServer.headersTimeout = 61000;
|
|
463
|
+
this.httpServer.on('error', function (error) {
|
|
464
|
+
if (error['syscall'] !== 'listen') {
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
const isPipe = (portOrPipe) => Number.isNaN(portOrPipe);
|
|
468
|
+
const bind = isPipe(PORT) ? 'Pipe ' + PORT : 'Port ' + PORT;
|
|
469
|
+
switch (error['code']) {
|
|
470
|
+
case 'EACCES':
|
|
471
|
+
console.error(bind + ' requires elevated privileges');
|
|
472
|
+
process_1.default.exit(1);
|
|
473
|
+
case 'EADDRINUSE':
|
|
474
|
+
console.error(bind + ' is already in use');
|
|
475
|
+
process_1.default.exit(1);
|
|
476
|
+
default:
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
return this;
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
initOntologies() {
|
|
484
|
+
return __awaiter(this, void 0, void 0, function* () { });
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
*
|
|
488
|
+
* @returns
|
|
489
|
+
* @todo Check if default file store is set, if not, set it
|
|
490
|
+
*/
|
|
491
|
+
initStores() {
|
|
492
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
493
|
+
return Promise.all(LinkedStorage_1.LinkedStorage.getDatasets().map((store) => {
|
|
494
|
+
return store.init ? store.init() : Promise.resolve();
|
|
495
|
+
}));
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
// get generic backend providers for handle controller methods
|
|
499
|
+
callGenericBackendProvidersMethod(method, ...args) {
|
|
500
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
501
|
+
for (let genericProvider of this.genericProviders.values()) {
|
|
502
|
+
if (!genericProvider || !genericProvider[method]) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (typeof genericProvider[method] == 'function') {
|
|
506
|
+
yield Promise.resolve(genericProvider[method](...args));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Removes temporary nodes from memory if they've been seen twice
|
|
513
|
+
* Current interval is once per hour.
|
|
514
|
+
* So after 2 hours temporary nodes are removed.
|
|
515
|
+
*/
|
|
516
|
+
initGarbageCollection() {
|
|
517
|
+
// let seenLastTime:NodeSet<NamedNode> = null;
|
|
518
|
+
// setInterval(() => {
|
|
519
|
+
//
|
|
520
|
+
// let tempNodes = new NodeSet<NamedNode>();
|
|
521
|
+
// NamedNode.getAllNamedNodes().forEach(n => {
|
|
522
|
+
// if(n.isTemporaryNode) {
|
|
523
|
+
// if(seenLastTime && seenLastTime.has(n))
|
|
524
|
+
// {
|
|
525
|
+
// n.remove();
|
|
526
|
+
// } else {
|
|
527
|
+
// tempNodes.add(n);
|
|
528
|
+
// }
|
|
529
|
+
// }
|
|
530
|
+
// })
|
|
531
|
+
// seenLastTime = tempNodes;
|
|
532
|
+
// }, 1000 * 10); //every 10 sec
|
|
533
|
+
// // }, 1000 * 60 * 60); //every hour
|
|
534
|
+
}
|
|
535
|
+
initBackendProviders() {
|
|
536
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
537
|
+
//get all local workspace lincd packages, then filter to only those
|
|
538
|
+
//in this app's dependency tree. This avoids loading npm-installed
|
|
539
|
+
//legacy packages that may use an old version of the core.
|
|
540
|
+
const allLocalPackages = (0, cli_methods_1.getLincdPackages)();
|
|
541
|
+
const localPackageMap = new Map(allLocalPackages.map((pkg) => [pkg.packageName, pkg]));
|
|
542
|
+
const relevantPackages = this.filterPackagesByDependencyTree(localPackageMap, this.package);
|
|
543
|
+
for (let [pkgName, pkg] of relevantPackages) {
|
|
544
|
+
yield this.indexPackageBackendProviders(pkgName);
|
|
545
|
+
yield this.indexLincdPackage(pkgName);
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
yield fs
|
|
549
|
+
.readFile(path_1.default.join(process_1.default.cwd(), 'package.json'), 'utf-8')
|
|
550
|
+
.then((contents) => __awaiter(this, void 0, void 0, function* () {
|
|
551
|
+
let pkg = JSON.parse(contents);
|
|
552
|
+
yield this.indexPackageBackendProviders(pkg.name, false, path_1.default.join(process_1.default.cwd(), 'src', 'backend.ts'));
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
555
|
+
catch (err) {
|
|
556
|
+
console.warn(err);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Filters local workspace packages to only those reachable from the app's
|
|
562
|
+
* dependency tree. Mirrors the logic used by `buildAll` in lincd-cli.
|
|
563
|
+
*/
|
|
564
|
+
filterPackagesByDependencyTree(allPackages, appPackageJson) {
|
|
565
|
+
const relevantPackages = new Map();
|
|
566
|
+
const packagesToCheck = new Set();
|
|
567
|
+
const processedPackages = new Set();
|
|
568
|
+
// Start with direct dependencies from the app
|
|
569
|
+
if (appPackageJson.dependencies) {
|
|
570
|
+
for (const dep of Object.keys(appPackageJson.dependencies)) {
|
|
571
|
+
if (allPackages.has(dep)) {
|
|
572
|
+
packagesToCheck.add(dep);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// Recursively follow each package's dependencies
|
|
577
|
+
while (packagesToCheck.size > 0) {
|
|
578
|
+
const packageName = packagesToCheck.values().next().value;
|
|
579
|
+
packagesToCheck.delete(packageName);
|
|
580
|
+
if (processedPackages.has(packageName)) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
processedPackages.add(packageName);
|
|
584
|
+
const packageDetails = allPackages.get(packageName);
|
|
585
|
+
if (packageDetails) {
|
|
586
|
+
relevantPackages.set(packageName, packageDetails);
|
|
587
|
+
const pkg = (0, utils_1.getPackageJSON)(packageDetails.path);
|
|
588
|
+
if (pkg === null || pkg === void 0 ? void 0 : pkg.dependencies) {
|
|
589
|
+
for (const dep of Object.keys(pkg.dependencies)) {
|
|
590
|
+
if (allPackages.has(dep) && !processedPackages.has(dep)) {
|
|
591
|
+
packagesToCheck.add(dep);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return relevantPackages;
|
|
598
|
+
}
|
|
599
|
+
resizeImage(req, res) {
|
|
600
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
601
|
+
var _a;
|
|
602
|
+
//check if the w or h query parameters are set
|
|
603
|
+
let width = req.query.w;
|
|
604
|
+
let height = req.query.h;
|
|
605
|
+
//if not
|
|
606
|
+
if (!width && !height) {
|
|
607
|
+
//redirect to the original image
|
|
608
|
+
res.redirect(req.originalUrl.replace('/resized', '/uploads'));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
let imageFileName = req.query.src; // example: https://cdnurl.com/uploads/1710814765388-935b511c9_cropped.jpeg
|
|
612
|
+
const accessURL = LinkedFileStorage_1.LinkedFileStorage.accessURL;
|
|
613
|
+
if (imageFileName) {
|
|
614
|
+
// TODO: restrict resizing to images that are stored by LinkedFileStorage.fileExists()
|
|
615
|
+
// if (imageFileName.startsWith(accessURL)) {
|
|
616
|
+
// const exists = await LinkedFileStorage.fileExists(
|
|
617
|
+
// imageFileName.replace(`${accessURL}/`, ''),
|
|
618
|
+
// );
|
|
619
|
+
// extract the base name and extension from the imageFileName
|
|
620
|
+
// example: /uploads/resized/935b511c9_cropped.jpeg
|
|
621
|
+
const url = new URL(imageFileName);
|
|
622
|
+
const { name, ext } = path_1.default.parse(url.pathname);
|
|
623
|
+
// append the width and height parameters to the base name
|
|
624
|
+
// example: 935b511c9_cropped_w190.jpeg or 935b511c9_cropped_w190h190.jpeg
|
|
625
|
+
const newName = `${name}${width ? '_w' + width : ''}${height ? 'h' + height : ''}`;
|
|
626
|
+
// create a new pathname with dimensions
|
|
627
|
+
// example: /uploads/resized/935b511c9_cropped.jpeg -> /uploads/resized/935b511c9_cropped_w190.jpeg
|
|
628
|
+
const newPathname = path_1.default.join(path_1.default.dirname(url.pathname), 'resized', `${newName}${ext}`);
|
|
629
|
+
// remove the leading slash from the pathname
|
|
630
|
+
// example: /uploads/resized/935b511c9_cropped_w190.jpeg -> uploads/resized/935b511c9_cropped_w190.jpeg
|
|
631
|
+
const resizedImageFileName = newPathname.startsWith('/')
|
|
632
|
+
? newPathname.slice(1)
|
|
633
|
+
: newPathname;
|
|
634
|
+
if (this.resizePathsMap.has(resizedImageFileName)) {
|
|
635
|
+
res.redirect(this.resizePathsMap.get(resizedImageFileName));
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
// check if the resized image already exists in the CDN
|
|
639
|
+
const imageExists = yield LinkedFileStorage_1.LinkedFileStorage.fileExists(resizedImageFileName);
|
|
640
|
+
// if exists, redirect to the resized image
|
|
641
|
+
if (imageExists) {
|
|
642
|
+
const resizedPathOnCdn = accessURL + '/' + resizedImageFileName;
|
|
643
|
+
//save to cache
|
|
644
|
+
this.resizePathsMap.set(resizedImageFileName, resizedPathOnCdn);
|
|
645
|
+
//redirect this request to the resized image
|
|
646
|
+
res.redirect(resizedPathOnCdn);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
console.log(`${process_1.default.pid} - ${process_1.default.env.PORT}: Resizing image: ${imageFileName} to ${width} x ${height || ''}`);
|
|
651
|
+
//in multicore development, we need to access the site from 127.0.0.1, and so all requests need to go there
|
|
652
|
+
// // if (process.env.NODE_ENV === 'development' && process.env.NUM_WORKER_PROCESSES) {
|
|
653
|
+
// // imageFileName = imageFileName.replace('localhost', '127.0.0.1');
|
|
654
|
+
// // }
|
|
655
|
+
// get the image from imageFileName
|
|
656
|
+
const image = yield globalThis
|
|
657
|
+
.fetch(imageFileName)
|
|
658
|
+
.then((res) => res.arrayBuffer())
|
|
659
|
+
.then((arrayBuffer) => Buffer.from(arrayBuffer))
|
|
660
|
+
.catch((err) => {
|
|
661
|
+
console.warn('Could not fetch image from URL: ' + err);
|
|
662
|
+
return null;
|
|
663
|
+
});
|
|
664
|
+
// if image is null, return 404
|
|
665
|
+
if (!image) {
|
|
666
|
+
res.status(404).send({ error: 'Could not fetch image from URL' });
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
// get the format of the image
|
|
670
|
+
let format;
|
|
671
|
+
try {
|
|
672
|
+
format = yield (0, sharp_1.default)(image)
|
|
673
|
+
.metadata()
|
|
674
|
+
.then((meta) => meta.format);
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
console.warn('Unsupported image format: ' + err);
|
|
678
|
+
res.status(400).send({ error: 'Unsupported image format' });
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
// set the output options based on the format for quality and compression
|
|
682
|
+
let outputOptions;
|
|
683
|
+
switch (format) {
|
|
684
|
+
case 'jpeg':
|
|
685
|
+
outputOptions = { quality: 90 };
|
|
686
|
+
break;
|
|
687
|
+
case 'png':
|
|
688
|
+
outputOptions = { compressionLevel: 9 };
|
|
689
|
+
break;
|
|
690
|
+
case 'webp':
|
|
691
|
+
outputOptions = { quality: 90 };
|
|
692
|
+
break;
|
|
693
|
+
// add more formats here
|
|
694
|
+
default:
|
|
695
|
+
outputOptions = {};
|
|
696
|
+
}
|
|
697
|
+
// resize the image
|
|
698
|
+
const resizedImage = yield (0, sharp_1.default)(image)
|
|
699
|
+
.resize(width ? parseInt(width) : null, height ? parseInt(height) : null)
|
|
700
|
+
.toFormat(format, outputOptions)
|
|
701
|
+
.toBuffer()
|
|
702
|
+
.catch((err) => {
|
|
703
|
+
console.warn('Could not resize image: ' + err);
|
|
704
|
+
return null;
|
|
705
|
+
});
|
|
706
|
+
// if resizedImage is null, return 500
|
|
707
|
+
if (!resizedImage) {
|
|
708
|
+
res.status(500).send({ error: 'Could not resize image' });
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
// upload the resized image to the CDN
|
|
712
|
+
const resizedPathOnCdn = yield LinkedFileStorage_1.LinkedFileStorage.saveFile(newPathname, resizedImage);
|
|
713
|
+
//save to cache
|
|
714
|
+
this.resizePathsMap.set(resizedImageFileName, resizedPathOnCdn);
|
|
715
|
+
//redirect this request to the resized image
|
|
716
|
+
res.redirect(resizedPathOnCdn);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
imageFileName = (_a = req.originalUrl.split('/resized/')[1]) === null || _a === void 0 ? void 0 : _a.split('?')[0];
|
|
722
|
+
}
|
|
723
|
+
//if this request has not been made (and stored on the HD) before
|
|
724
|
+
let [trueFileName, ...extensions] = imageFileName.split('.');
|
|
725
|
+
let extension = extensions.join('.');
|
|
726
|
+
let resizedImageFileName = trueFileName +
|
|
727
|
+
'_' +
|
|
728
|
+
(width ? 'w' + width : '') +
|
|
729
|
+
(height ? 'h' + height : '') +
|
|
730
|
+
'.' +
|
|
731
|
+
extension;
|
|
732
|
+
let resizedFilePath = path_1.default.join(process_1.default.cwd(), 'data', 'uploads', 'resized', resizedImageFileName);
|
|
733
|
+
if (!fsNative.existsSync(resizedFilePath)) {
|
|
734
|
+
//ensure the resized folder exists
|
|
735
|
+
// if (!fsNative.existsSync(path.join(process.cwd(), 'data', 'uploads', 'resized'))) {
|
|
736
|
+
// fsNative.mkdirSync(path.join(process.cwd(), 'data', 'uploads', 'resized'), {recursive: true});
|
|
737
|
+
// }
|
|
738
|
+
//then lets resize and store the image:
|
|
739
|
+
// resize the image with sharp and return it
|
|
740
|
+
let originalImagePath = path_1.default.join(process_1.default.cwd(), 'data', 'uploads', imageFileName);
|
|
741
|
+
if (!fsNative.existsSync(originalImagePath)) {
|
|
742
|
+
console.warn('Could not find original image at ' + originalImagePath);
|
|
743
|
+
return res.status(404).send({ error: 'Could not find original image' });
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
let image = (0, sharp_1.default)(originalImagePath);
|
|
747
|
+
image.resize(width ? parseInt(width) : null, height ? parseInt(height) : null);
|
|
748
|
+
//write image to disk
|
|
749
|
+
yield image
|
|
750
|
+
.toFile(resizedFilePath)
|
|
751
|
+
.then(() => {
|
|
752
|
+
// console.log('resized image written to disk: ' + resizedFilePath);
|
|
753
|
+
})
|
|
754
|
+
.catch((err) => {
|
|
755
|
+
console.warn('Could not write resized image to disk at ' +
|
|
756
|
+
resizedFilePath +
|
|
757
|
+
': ' +
|
|
758
|
+
err);
|
|
759
|
+
});
|
|
760
|
+
//
|
|
761
|
+
// //get content type from the file extension
|
|
762
|
+
// let contentType;
|
|
763
|
+
// let extension = imageFileName.split('.').pop();
|
|
764
|
+
// switch (extension) {
|
|
765
|
+
// case 'jpg':
|
|
766
|
+
// case 'jpeg':
|
|
767
|
+
// contentType = 'image/jpeg';
|
|
768
|
+
// break;
|
|
769
|
+
// case 'png':
|
|
770
|
+
// contentType = 'image/png';
|
|
771
|
+
// break;
|
|
772
|
+
// case 'gif':
|
|
773
|
+
// contentType = 'image/gif';
|
|
774
|
+
// break;
|
|
775
|
+
// default:
|
|
776
|
+
// contentType = 'image/jpeg';
|
|
777
|
+
// }
|
|
778
|
+
// res.setHeader('Content-Type', contentType);
|
|
779
|
+
// image.pipe(res);
|
|
780
|
+
}
|
|
781
|
+
catch (err) {
|
|
782
|
+
console.warn(err);
|
|
783
|
+
res.status(500).send({ error: 'Could not resize image' });
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
//send the resized image
|
|
787
|
+
res.sendFile(resizedFilePath);
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
indexLincdPackage(pkg_1) {
|
|
791
|
+
return __awaiter(this, arguments, void 0, function* (pkg, warnIfNotFound = false) {
|
|
792
|
+
if (pkg === this.package.name) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
try {
|
|
796
|
+
// console.log(`🔍 Loading package: ${pkg}`);
|
|
797
|
+
// console.log(
|
|
798
|
+
// `🔍 Module resolution for ${pkg}:`,
|
|
799
|
+
//@ts-ignore
|
|
800
|
+
// await import.meta.resolve(pkg)
|
|
801
|
+
// );
|
|
802
|
+
yield Promise.resolve(`${pkg}`).then(s => __importStar(require(s)));
|
|
803
|
+
// console.log(`✅ Successfully loaded: ${pkg}`);
|
|
804
|
+
}
|
|
805
|
+
catch (e) {
|
|
806
|
+
let providerNotFound = e.code === 'MODULE_NOT_FOUND' &&
|
|
807
|
+
e.message.indexOf(`Cannot find package '${pkg}'`) !== -1;
|
|
808
|
+
if (providerNotFound) {
|
|
809
|
+
// console.warn('Error loading ' + providerPath + ': ' + e.stack);
|
|
810
|
+
if (warnIfNotFound) {
|
|
811
|
+
console.warn(chalk_1.default.magenta(`Could not load package ${pkg}`, typeof module !== 'undefined' && typeof exports !== 'undefined'
|
|
812
|
+
? //@ts-ignore
|
|
813
|
+
' at ' + (yield import.meta.resolve(pkg))
|
|
814
|
+
: ''));
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
console.warn(chalk_1.default.red(`Error loading '${pkg}' ${typeof module !== 'undefined' && typeof exports !== 'undefined'
|
|
819
|
+
? //@ts-ignore
|
|
820
|
+
' at ' + (yield import.meta.resolve(pkg))
|
|
821
|
+
: ''}: ${e.message}\n`), e.stack);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
indexPackageBackendProviders(pkg_1) {
|
|
827
|
+
return __awaiter(this, arguments, void 0, function* (pkg, warnIfNotFound = false, backendIndexFilePath) {
|
|
828
|
+
if (!backendIndexFilePath) {
|
|
829
|
+
backendIndexFilePath = `${pkg}/backend`;
|
|
830
|
+
}
|
|
831
|
+
let backendProviderExports;
|
|
832
|
+
let genericBackendProvider;
|
|
833
|
+
let shapeProviders = [];
|
|
834
|
+
yield Promise.resolve(`${backendIndexFilePath}`).then(s => __importStar(require(s))).then((backendProviderExports) => {
|
|
835
|
+
//instantiate the exported provider classes and add them to the right place
|
|
836
|
+
Object.keys(backendProviderExports).forEach((key) => {
|
|
837
|
+
let providerClass = backendProviderExports[key];
|
|
838
|
+
//always send an instance of the express server
|
|
839
|
+
//TODO: do not create an instance, just save the class and instantiate it when needed
|
|
840
|
+
let provider = new providerClass(this.server, this);
|
|
841
|
+
if (provider instanceof ShapeProvider_1.ShapeProvider) {
|
|
842
|
+
shapeProviders.push(provider);
|
|
843
|
+
if (!Object.getOwnPropertyNames(provider).includes('shape')) {
|
|
844
|
+
console.warn(chalk_1.default.red(`${Object.getPrototypeOf(provider).constructor.name} in package ${pkg}
|
|
845
|
+
is not properly linked to a shape. Use public shape = SomeShape.`));
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
if (genericBackendProvider) {
|
|
850
|
+
console.warn(`Package ${pkg} exports two generic backend providers. Only one will work`);
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
genericBackendProvider = provider;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
})
|
|
858
|
+
.catch((e) => {
|
|
859
|
+
const match = e.message.match(/module \'([^\']+)'/);
|
|
860
|
+
//check that the imported /backend path is not found (and only that path, not an import IN that file that is not found, that should still throw an error)
|
|
861
|
+
let providerNotFound = e.code === 'ERR_MODULE_NOT_FOUND' &&
|
|
862
|
+
e.message.indexOf(`Cannot find module`) !== -1 &&
|
|
863
|
+
match &&
|
|
864
|
+
match[1] &&
|
|
865
|
+
match[1].includes('/backend');
|
|
866
|
+
if (providerNotFound) {
|
|
867
|
+
// console.warn('Error loading ' + providerPath + ': ' + e.stack);
|
|
868
|
+
if (warnIfNotFound) {
|
|
869
|
+
console.warn(chalk_1.default.magenta(`Could not find backend file of package ${pkg}.
|
|
870
|
+
Check:\n
|
|
871
|
+
- Make sure backend.ts exists and is included in tsconfig.json\n
|
|
872
|
+
- Make sure the package name in src/package.ts matches the package name in package.json`));
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
console.warn(chalk_1.default.red(`Could not load backend file of module '${pkg}' from ${process_1.default.cwd()}:\n`), e.stack);
|
|
877
|
+
}
|
|
878
|
+
genericBackendProvider = null;
|
|
879
|
+
});
|
|
880
|
+
this.genericProviders.set(pkg, genericBackendProvider);
|
|
881
|
+
this.shapeProviders.set(pkg, shapeProviders);
|
|
882
|
+
return { backendProviderExports, shapeProviders };
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
processBackendMethodCall(request, response) {
|
|
886
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
887
|
+
this.noCache(response);
|
|
888
|
+
yield this.initRequest(request, response);
|
|
889
|
+
let { pkg, method } = request.params;
|
|
890
|
+
let { args } = JSONParser_1.JSONParser.parseObject(request.body);
|
|
891
|
+
return this.callBackendMethod(pkg, method, args, request, response).then((result) => {
|
|
892
|
+
//some methods of backend providers may choose to work with request/response directly and will not return anything
|
|
893
|
+
//so only if a result is returned
|
|
894
|
+
if (typeof result !== 'undefined') {
|
|
895
|
+
//do we convert it to JSON and send it to the frontend
|
|
896
|
+
this.sendJson(response, result);
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
//in other cases, we still need to close the request and send an empty response
|
|
900
|
+
if (!response.headersSent) {
|
|
901
|
+
this.sendJson(response, null);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
noCache(response) {
|
|
908
|
+
response.setHeader('Surrogate-Control', 'no-store');
|
|
909
|
+
response.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
910
|
+
response.setHeader('Expires', '0');
|
|
911
|
+
}
|
|
912
|
+
processAPICall(request, response, method) {
|
|
913
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
914
|
+
this.noCache(response);
|
|
915
|
+
const result = yield this.api.process(request, response, method);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
// async processQuery(request, response) {
|
|
919
|
+
//
|
|
920
|
+
// this.api.process(request, response);
|
|
921
|
+
// }
|
|
922
|
+
processShapeMethodCall(request, response) {
|
|
923
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
924
|
+
var _a, _b;
|
|
925
|
+
this.noCache(response);
|
|
926
|
+
// console.log('process shape call');
|
|
927
|
+
// response.on('finish', function() {
|
|
928
|
+
// console.log('shape method call request finish');
|
|
929
|
+
// });
|
|
930
|
+
//
|
|
931
|
+
// response.on('close', function() {
|
|
932
|
+
// console.log('close');
|
|
933
|
+
// });
|
|
934
|
+
//
|
|
935
|
+
// response.on('end', function() {
|
|
936
|
+
// console.log('end');
|
|
937
|
+
// });
|
|
938
|
+
//
|
|
939
|
+
// response.on('header', function() {
|
|
940
|
+
// console.log('header');
|
|
941
|
+
// console.log(response.statusCode);
|
|
942
|
+
// });
|
|
943
|
+
yield this.initRequest(request, response);
|
|
944
|
+
let { pkg, shape, method } = request.params;
|
|
945
|
+
let { shapeURI, instanceNode, args } = JSONParser_1.JSONParser.parseObject(request.body);
|
|
946
|
+
if (!shapeURI) {
|
|
947
|
+
if ((_a = request.query) === null || _a === void 0 ? void 0 : _a.shapeURI) {
|
|
948
|
+
shapeURI = (_b = request.query) === null || _b === void 0 ? void 0 : _b.shapeURI;
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
response.status(500).send({
|
|
952
|
+
error: 'Invalid server call request: ' + request.originalUrl,
|
|
953
|
+
});
|
|
954
|
+
console.warn(chalk_1.default.red('Invalid server call request: ' + request.originalUrl));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return this.callShapeMethod(pkg, method, shapeURI, instanceNode, args, request, response).then((result) => {
|
|
959
|
+
//we return json if something was returned or, if nothing was returned, we still close the request if the method has not accessed response itself already to send things over
|
|
960
|
+
if (typeof result !== 'undefined' || !response.headersSent) {
|
|
961
|
+
this.sendJson(response, result);
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
callShapeMethod(pkg, method, shapeURI, instanceNode, args, request, response) {
|
|
967
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
968
|
+
var _a;
|
|
969
|
+
//- index module providers if not done yet
|
|
970
|
+
if (!this.shapeProviders.has(pkg)) {
|
|
971
|
+
yield this.indexPackageBackendProviders(pkg, true);
|
|
972
|
+
}
|
|
973
|
+
let packageShapeProviders = this.shapeProviders.get(pkg);
|
|
974
|
+
let findProviderForShape = (nodeShapeId) => {
|
|
975
|
+
return packageShapeProviders.find((provider) => {
|
|
976
|
+
var _a, _b;
|
|
977
|
+
//access the static shape (which is a linkedShape() / Shape class)
|
|
978
|
+
//then access the SHACL NodeShape of that shape class, and its id
|
|
979
|
+
return (provider instanceof ShapeProvider_1.ShapeProvider &&
|
|
980
|
+
((_b = (_a = provider.shape) === null || _a === void 0 ? void 0 : _a.shape) === null || _b === void 0 ? void 0 : _b.id) === nodeShapeId);
|
|
981
|
+
});
|
|
982
|
+
};
|
|
983
|
+
try {
|
|
984
|
+
//- find matching provider
|
|
985
|
+
let shapeClass = (0, ShapeClass_1.getShapeClass)(shapeURI);
|
|
986
|
+
let shapeProvider = findProviderForShape(shapeClass.shape.id);
|
|
987
|
+
if (!shapeProvider) {
|
|
988
|
+
let superShapeClasses = (0, ShapeClass_1.getSuperShapesClasses)(shapeClass);
|
|
989
|
+
for (let superShapeClass of superShapeClasses) {
|
|
990
|
+
let superShapeProvider = findProviderForShape((_a = superShapeClass.shape) === null || _a === void 0 ? void 0 : _a.id);
|
|
991
|
+
if (superShapeProvider) {
|
|
992
|
+
shapeProvider = superShapeProvider;
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
if (shapeProvider) {
|
|
998
|
+
//NOTE: if this is a direct call from backend to backend, we won't know the request & response here because those don't get passed to the Server utility
|
|
999
|
+
//if this is an issue, we need to see how we can get those back
|
|
1000
|
+
if (request && response) {
|
|
1001
|
+
//give the provider a chance to prepare for this request
|
|
1002
|
+
//wrap the call in a promise and wait for it, because providers MAY return a promise
|
|
1003
|
+
yield Promise.resolve(shapeProvider.initRequest(request, response));
|
|
1004
|
+
}
|
|
1005
|
+
//see if the shapeProvider implements the called method
|
|
1006
|
+
if (shapeProvider[method]) {
|
|
1007
|
+
//prepare the first argument
|
|
1008
|
+
//we want to convert the instance node into an instance of the shape that this shapeProvider provides for
|
|
1009
|
+
//let's find the shape class
|
|
1010
|
+
if (shapeProvider.shape) {
|
|
1011
|
+
//instance nodes are not always sent. A shape can also call a shape provider from a static method without a shape instance.
|
|
1012
|
+
if (instanceNode) {
|
|
1013
|
+
let providerShapeClass = shapeProvider.shape;
|
|
1014
|
+
let instance = new providerShapeClass({
|
|
1015
|
+
id: instanceNode.id,
|
|
1016
|
+
});
|
|
1017
|
+
//always send instanceNode as first argument to static provider methods
|
|
1018
|
+
args.unshift(instance);
|
|
1019
|
+
}
|
|
1020
|
+
try {
|
|
1021
|
+
//call the method with the given arguments
|
|
1022
|
+
let result = yield Promise.resolve(shapeProvider[method].apply(shapeProvider, args));
|
|
1023
|
+
return result;
|
|
1024
|
+
}
|
|
1025
|
+
catch (e) {
|
|
1026
|
+
console.warn(`Error whilst calling ${Object.getPrototypeOf(shapeProvider).constructor.name}.${method}(): `, e);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1030
|
+
console.warn(`${Object.getPrototypeOf(shapeProvider).constructor.name} does not define its own 'static shape' property. Please connect the provider to a shape.`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
return this.sendError(response, 501, `${Object.getPrototypeOf(shapeProvider).constructor.name} does not have a method called ${method}`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
return this.sendError(response, 501, "Could not find provider for shape '" + shapeURI + "'");
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
catch (err) {
|
|
1042
|
+
console.warn(`Error whilst trying to access provider of ${pkg}: `, err);
|
|
1043
|
+
}
|
|
1044
|
+
return null;
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
handleErrors(fn) {
|
|
1048
|
+
return function (req, res, next) {
|
|
1049
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1050
|
+
try {
|
|
1051
|
+
return yield fn(req, res);
|
|
1052
|
+
}
|
|
1053
|
+
catch (x) {
|
|
1054
|
+
console.log(x);
|
|
1055
|
+
next(x);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
handleErrorsJson(fn) {
|
|
1061
|
+
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
1062
|
+
try {
|
|
1063
|
+
return yield fn(req, res);
|
|
1064
|
+
}
|
|
1065
|
+
catch (err) {
|
|
1066
|
+
this.sendError(res, 500, 'internal server error' + (isDevelopment ? ': ' + (err === null || err === void 0 ? void 0 : err.stack) : ''), 'internal server error: ' + (err === null || err === void 0 ? void 0 : err.stack));
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
sendError(res, statusCode = 500, message, logMessage) {
|
|
1071
|
+
res === null || res === void 0 ? void 0 : res.status(statusCode);
|
|
1072
|
+
if (message) {
|
|
1073
|
+
res === null || res === void 0 ? void 0 : res.send({ error: message });
|
|
1074
|
+
console.warn(chalk_1.default.red(logMessage || message));
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
render(req, res) {
|
|
1078
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1079
|
+
var _a, _b, _c, _d;
|
|
1080
|
+
res.socket.on('error', (error) => {
|
|
1081
|
+
console.error('Fatal socket error', error);
|
|
1082
|
+
});
|
|
1083
|
+
//when render() is called, the request is always an 'initial page request', and the server which will return HTML (so this is a SSR request)
|
|
1084
|
+
//in that case we send data back to the frontend in a <script> tag.
|
|
1085
|
+
//We initiate that object here
|
|
1086
|
+
if (req['frontendData'] == null) {
|
|
1087
|
+
req['frontendData'] = {};
|
|
1088
|
+
}
|
|
1089
|
+
//if we are caching this page
|
|
1090
|
+
if (((_a = this.config.server) === null || _a === void 0 ? void 0 : _a.cachePaths) &&
|
|
1091
|
+
this.config.server.cachePaths.includes(req.url)) {
|
|
1092
|
+
//if there is a cach for this path, send it
|
|
1093
|
+
if (this.cachedPaths.has(req.url)) {
|
|
1094
|
+
// console.log(req.url + ': Returning cached path');
|
|
1095
|
+
const html = this.cachedPaths.get(req.url);
|
|
1096
|
+
// res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
1097
|
+
// res.setHeader('Content-Length', Buffer.byteLength(html, 'utf8').toString());
|
|
1098
|
+
// res.status(200).end(html);
|
|
1099
|
+
res.send(html);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
let didError = false;
|
|
1104
|
+
let App = ((_b = this.config.server) === null || _b === void 0 ? void 0 : _b.loadAppComponent)
|
|
1105
|
+
? yield this.config.server.loadAppComponent()
|
|
1106
|
+
: null;
|
|
1107
|
+
yield this.initRequest(req, res);
|
|
1108
|
+
let { requestLD, requestObject } = yield this.getRequestData(req, res);
|
|
1109
|
+
//on the backend we need to inform the hook of the request-data value
|
|
1110
|
+
//on the frontend it will be read from the HTML
|
|
1111
|
+
// setRequestData(requestObject);
|
|
1112
|
+
// Abandon and switch to client rendering if enough time passes.
|
|
1113
|
+
// Try lowering this to see the client recover.
|
|
1114
|
+
// Abandon and switch to client rendering if enough time passes.
|
|
1115
|
+
// Try lowering this to see the client recover.
|
|
1116
|
+
let stream;
|
|
1117
|
+
let timedout = false;
|
|
1118
|
+
// const timeout = setTimeout(() => {
|
|
1119
|
+
// stream.abort(`${req.url}: ⏱ SSR stream took too long — force abort`);
|
|
1120
|
+
// if (!res.headersSent) {
|
|
1121
|
+
// res.statusCode = 500;
|
|
1122
|
+
// res.end('SSR timed out');
|
|
1123
|
+
// }
|
|
1124
|
+
// timedout = true;
|
|
1125
|
+
// }, 8_000);
|
|
1126
|
+
let manifest = this.latestManifest || this.assets.manifest || {};
|
|
1127
|
+
let preloadScripts = [];
|
|
1128
|
+
let preloadStyles = [];
|
|
1129
|
+
let matchedRouteKey = null;
|
|
1130
|
+
// Load routes config if available and extract preload chunks for the current route
|
|
1131
|
+
if ((_c = this.config.server) === null || _c === void 0 ? void 0 : _c.loadRoutes) {
|
|
1132
|
+
try {
|
|
1133
|
+
const routesModule = yield this.config.server.loadRoutes();
|
|
1134
|
+
const ROUTES = routesModule.ROUTES ||
|
|
1135
|
+
((_d = routesModule.default) === null || _d === void 0 ? void 0 : _d.ROUTES) ||
|
|
1136
|
+
routesModule;
|
|
1137
|
+
// Match the current request path to a route
|
|
1138
|
+
let matchedRoute = null;
|
|
1139
|
+
for (const [key, route] of Object.entries(ROUTES)) {
|
|
1140
|
+
if (!(route === null || route === void 0 ? void 0 : route.path))
|
|
1141
|
+
continue;
|
|
1142
|
+
const pathPattern = route.path
|
|
1143
|
+
.replace(/:\w+\??/g, '([^/]+)')
|
|
1144
|
+
.replace(/\*/g, '.*');
|
|
1145
|
+
const regex = new RegExp('^' + pathPattern + '$');
|
|
1146
|
+
if (regex.test(req.path)) {
|
|
1147
|
+
matchedRoute = route;
|
|
1148
|
+
matchedRouteKey = key;
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
// If we found a matching route with preloadChunks, resolve them to URLs (both JS and CSS)
|
|
1153
|
+
if ((matchedRoute === null || matchedRoute === void 0 ? void 0 : matchedRoute.preloadChunks) &&
|
|
1154
|
+
Array.isArray(matchedRoute.preloadChunks)) {
|
|
1155
|
+
preloadScripts = matchedRoute.preloadChunks
|
|
1156
|
+
.map((chunkName) => manifest[`${chunkName}.js`] ||
|
|
1157
|
+
manifest[`${chunkName}.bundle.js`] ||
|
|
1158
|
+
manifest[`${chunkName}.mjs`])
|
|
1159
|
+
.filter(Boolean);
|
|
1160
|
+
// Also collect CSS chunks for the same routes
|
|
1161
|
+
preloadStyles = matchedRoute.preloadChunks
|
|
1162
|
+
.map((chunkName) => manifest[`${chunkName}.css`])
|
|
1163
|
+
.filter(Boolean);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
catch (err) {
|
|
1167
|
+
console.warn('Failed to resolve preload chunks:', err);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
// Add matched route key to request object for Html component
|
|
1171
|
+
req['matchedRouteKey'] = matchedRouteKey;
|
|
1172
|
+
stream = (0, server_1.renderToPipeableStream)(React.createElement(React.StrictMode, null,
|
|
1173
|
+
React.createElement(server_js_1.StaticRouter, { location: req.url },
|
|
1174
|
+
React.createElement(AppContext_1.AppContextProvider, { assets: this.assets, requestLD: requestLD, requestObject: requestObject, preloadScripts: preloadScripts, preloadStyles: preloadStyles, expressRequest: req, expressResponse: res },
|
|
1175
|
+
React.createElement(App, null)))), {
|
|
1176
|
+
bootstrapScripts: [
|
|
1177
|
+
this.assets['main.js'], //generated webpack bundle from frontend/src
|
|
1178
|
+
],
|
|
1179
|
+
onShellReady: function () {
|
|
1180
|
+
var _a;
|
|
1181
|
+
res.statusCode = didError ? 500 : 200;
|
|
1182
|
+
res.setHeader('Content-type', 'text/html');
|
|
1183
|
+
// Create a caching transform stream à la mxstbr.com/thoughts/streaming-ssr
|
|
1184
|
+
if (((_a = this.config.server) === null || _a === void 0 ? void 0 : _a.cachePaths) &&
|
|
1185
|
+
this.config.server.cachePaths.includes(req.url)) {
|
|
1186
|
+
const bufferedChunks = [];
|
|
1187
|
+
const cacheStream = new stream_1.Transform({
|
|
1188
|
+
transform(chunk, _enc, cb) {
|
|
1189
|
+
bufferedChunks.push(chunk); // keep a copy
|
|
1190
|
+
cb(null, chunk); // forward unchanged
|
|
1191
|
+
},
|
|
1192
|
+
flush: (cb) => {
|
|
1193
|
+
const html = Buffer.concat(bufferedChunks).toString('utf8');
|
|
1194
|
+
this.cachedPaths.set(req.url, html);
|
|
1195
|
+
if (this.config.server.cacheTimeout) {
|
|
1196
|
+
setTimeout(() => this.cachedPaths.delete(req.url), this.config.server.cacheTimeout);
|
|
1197
|
+
}
|
|
1198
|
+
clearTimeout(timeout); // rendering finished → stop timer
|
|
1199
|
+
cb();
|
|
1200
|
+
},
|
|
1201
|
+
});
|
|
1202
|
+
// Pipe the caching stream into the real response
|
|
1203
|
+
cacheStream.pipe(res);
|
|
1204
|
+
// React may **only be piped once**, so pipe it to the cacheStream
|
|
1205
|
+
stream.pipe(cacheStream);
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
stream.pipe(res);
|
|
1209
|
+
}
|
|
1210
|
+
}.bind(this),
|
|
1211
|
+
onShellError(x) {
|
|
1212
|
+
didError = true;
|
|
1213
|
+
console.error(x);
|
|
1214
|
+
},
|
|
1215
|
+
});
|
|
1216
|
+
// Abandon and switch to client rendering if enough time passes.
|
|
1217
|
+
// Try lowering this to see the client recover.
|
|
1218
|
+
const timeout = setTimeout(() => {
|
|
1219
|
+
stream.abort('⏱ SSR stream took too long — force abort');
|
|
1220
|
+
if (!res.headersSent) {
|
|
1221
|
+
res.statusCode = 500;
|
|
1222
|
+
res.end('SSR timed out');
|
|
1223
|
+
}
|
|
1224
|
+
}, 10000);
|
|
1225
|
+
res.on('close', () => {
|
|
1226
|
+
clearTimeout(timeout); // if you're using a safety timeout
|
|
1227
|
+
});
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
//If we ever need to use multiple webpack entry-points and different bundle names, then uncomment this
|
|
1231
|
+
/*getWebpackAssets()
|
|
1232
|
+
{
|
|
1233
|
+
// const { devMiddleware } = res.locals.webpack;
|
|
1234
|
+
// const outputFileSystem = devMiddleware.outputFileSystem;
|
|
1235
|
+
// const jsonWebpackStats = devMiddleware.stats.toJson();
|
|
1236
|
+
// const { assetsByChunkName, outputPath } = jsonWebpackStats;
|
|
1237
|
+
//
|
|
1238
|
+
// let normalizedAssets = normalizeAssets(assetsByChunkName.main);
|
|
1239
|
+
// let cssAssets = normalizedAssets
|
|
1240
|
+
// .filter((path) => path.endsWith(".css"))
|
|
1241
|
+
// .map((filePath) => outputFileSystem.readFileSync(path.join(outputPath, filePath)))
|
|
1242
|
+
// .join("\n");
|
|
1243
|
+
//
|
|
1244
|
+
// let jsAssets = normalizeAssets(assetsByChunkName.main)
|
|
1245
|
+
// .filter((path) => path.endsWith(".js"))
|
|
1246
|
+
// .map((path) => `<script src="${path}"></script>`)
|
|
1247
|
+
// .join("\n")}
|
|
1248
|
+
}*/
|
|
1249
|
+
sendJson(res, obj) {
|
|
1250
|
+
let jsonObject = JSONWriter_1.JSONWriter.toJsObject(obj);
|
|
1251
|
+
res.json(jsonObject);
|
|
1252
|
+
}
|
|
1253
|
+
initRequest(request, response) {
|
|
1254
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1255
|
+
// initialise the request for all providers, do it synchroniously, one after the other
|
|
1256
|
+
let p = Promise.resolve();
|
|
1257
|
+
[...this.genericProviders.values()]
|
|
1258
|
+
.filter(Boolean)
|
|
1259
|
+
.forEach((backendProvider) => {
|
|
1260
|
+
p = p
|
|
1261
|
+
.then(() => {
|
|
1262
|
+
return backendProvider.initRequest(request, response);
|
|
1263
|
+
})
|
|
1264
|
+
.catch((err) => {
|
|
1265
|
+
console.warn(`Error during initRequest for provider ${Object.getPrototypeOf(backendProvider).constructor.name}: `, err);
|
|
1266
|
+
});
|
|
1267
|
+
});
|
|
1268
|
+
return p;
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
getRequestData(request, response) {
|
|
1272
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1273
|
+
// Phase 1: providers return plain JSON data via supplyDataForRequest.
|
|
1274
|
+
// requestLD is kept as empty string for now — SSR data seeding will be
|
|
1275
|
+
// reworked in Phase 2/3 to inject query results instead of graph data.
|
|
1276
|
+
let requestData = {};
|
|
1277
|
+
yield Promise.all([...this.genericProviders.values()].map((backendProvider) => {
|
|
1278
|
+
if (backendProvider) {
|
|
1279
|
+
return Promise.resolve(backendProvider.supplyDataForRequest(request, response, requestData)).catch((err) => {
|
|
1280
|
+
console.warn(`Error requesting page-request data from ${Object.getPrototypeOf(backendProvider).constructor.name}: `, err);
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}));
|
|
1284
|
+
let requestLD = '';
|
|
1285
|
+
let requestObject = JSONWriter_1.JSONWriter.stringify(request['frontendData']);
|
|
1286
|
+
return { requestLD, requestObject };
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
callBackendMethod(pkg, method, args, request, response) {
|
|
1290
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1291
|
+
if (!this.genericProviders.has(pkg)) {
|
|
1292
|
+
yield this.indexPackageBackendProviders(pkg, true);
|
|
1293
|
+
}
|
|
1294
|
+
//retrieve the indexed provider class and create a new instance for this request
|
|
1295
|
+
let genericBackendProvider = this.genericProviders.get(pkg);
|
|
1296
|
+
let result;
|
|
1297
|
+
if (!genericBackendProvider) {
|
|
1298
|
+
console.warn(`${chalk_1.default.magenta(pkg)} does not have a generic backend provider. If you can edit this package, make sure 'backend.ts' is included in 'tsconfig.json' and that it exports a provider.`);
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
//test if there is a matching method in the backend provider
|
|
1302
|
+
if (!genericBackendProvider[method]) {
|
|
1303
|
+
console.warn(`Generic provider '${Object.getPrototypeOf(genericBackendProvider).constructor.name}' of ${pkg} does not have a method called ${method}`);
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
try {
|
|
1307
|
+
//TODO: remove init request.
|
|
1308
|
+
//TODO: refactor this.request and this.response to a request parameter
|
|
1309
|
+
//NOTE: if this is a direct call from backend to backend, we won't know the request & response here because those don't get passed to the Server utility
|
|
1310
|
+
//if this is an issue, we need to see how we can get those back
|
|
1311
|
+
if (request && response) {
|
|
1312
|
+
//initialise the request for this specific provider
|
|
1313
|
+
//wrap the call in a promise and wait for it, because providers MAY return a promise
|
|
1314
|
+
yield Promise.resolve(genericBackendProvider.initRequest(request, response));
|
|
1315
|
+
}
|
|
1316
|
+
// args.push(request);
|
|
1317
|
+
// args.push(response);
|
|
1318
|
+
//call the method with the given arguments and return the result as json
|
|
1319
|
+
result = yield Promise.resolve(genericBackendProvider[method].apply(genericBackendProvider, args));
|
|
1320
|
+
}
|
|
1321
|
+
catch (e) {
|
|
1322
|
+
console.warn(`Error whilst calling ${method}() in provider ${Object.getPrototypeOf(genericBackendProvider).constructor.name} of package ${pkg}:\n`, e);
|
|
1323
|
+
// error logging
|
|
1324
|
+
LinkedErrorLogging_1.LinkedErrorLogging.log(e);
|
|
1325
|
+
}
|
|
1326
|
+
return result;
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
exports.LincdServer = LincdServer;
|
|
1331
|
+
/**
|
|
1332
|
+
* indicates that instances of this shape need to have this rdf.type
|
|
1333
|
+
*/
|
|
1334
|
+
LincdServer.targetClass = lincd_server_js_1.lincdServer.LincdServer;
|
|
1335
|
+
exports.LincdServer = LincdServer = __decorate([
|
|
1336
|
+
package_js_1.linkedShape,
|
|
1337
|
+
__metadata("design:paramtypes", [Object])
|
|
1338
|
+
], LincdServer);
|
|
1339
|
+
//# sourceMappingURL=LincdServer.js.map
|