@api.global/typedserver
Version:
A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.
207 lines • 17.8 kB
JavaScript
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import * as interfaces from '../dist_ts_interfaces/index.js';
import * as servertools from './servertools/index.js';
import {} from './servertools/classes.compressor.js';
export class TypedServer {
constructor(optionsArg) {
this.serveDirHashSubject = new plugins.smartrx.rxjs.ReplaySubject(1);
this.serveHash = '000000';
this.typedrouter = new plugins.typedrequest.TypedRouter();
this.lastReload = Date.now();
this.ended = false;
const standardOptions = {
port: 3000,
injectReload: false,
serveDir: null,
watch: false,
cors: true,
};
this.options = {
...standardOptions,
...optionsArg,
};
this.server = new servertools.Server(this.options);
// add routes to the smartexpress instance
this.server.addRoute('/typedserver/:request', new servertools.Handler('ALL', async (req, res) => {
switch (req.params.request) {
case 'devtools':
res.setHeader('Content-Type', 'text/javascript');
res.status(200);
res.write(plugins.smartfile.fs.toStringSync(paths.injectBundlePath));
res.end();
break;
case 'reloadcheck':
console.log('got request for reloadcheck');
res.setHeader('Content-Type', 'text/plain');
res.status(200);
if (this.ended) {
res.write('end');
res.end();
return;
}
res.write(this.lastReload.toString());
res.end();
break;
default:
res.status(404);
res.write('Unknown request type');
res.end();
break;
}
}));
this.server.addRoute('/typedrequest', new servertools.HandlerTypedRouter(this.typedrouter));
}
/**
* inits and starts the server
*/
async start() {
// Validate essential configuration before starting
if (this.options.injectReload && !this.options.serveDir) {
throw new Error('You set to inject the reload script without a serve dir. This is not supported at the moment.');
}
if (this.options.serveDir) {
this.server.addRoute('/*splat', new servertools.HandlerStatic(this.options.serveDir, {
responseModifier: async (responseArg) => {
if (plugins.path.parse(responseArg.path).ext === '.html') {
let fileString = responseArg.responseContent.toString();
const fileStringArray = fileString.split('<head>');
if (this.options.injectReload && fileStringArray.length === 2) {
fileStringArray[0] = `${fileStringArray[0]}<head>
<!-- injected by /typedserver start -->
<script async defer type="module" src="/typedserver/devtools"></script>
<script>
globalThis.typedserver = {
lastReload: ${this.lastReload},
versionInfo: ${JSON.stringify({}, null, 2)},
}
</script>
<!-- injected by /typedserver stop -->
`;
fileString = fileStringArray.join('');
console.log('injected typedserver script.');
responseArg.responseContent = Buffer.from(fileString);
}
else if (this.options.injectReload) {
console.log('Could not insert typedserver script - no <head> tag found');
}
}
const headers = responseArg.headers;
headers.appHash = this.serveHash;
headers['Cache-Control'] = 'no-cache, no-store, must-revalidate';
headers['Pragma'] = 'no-cache';
headers['Expires'] = '0';
return {
headers,
path: responseArg.path,
responseContent: responseArg.responseContent,
travelData: responseArg.travelData,
};
},
serveIndexHtmlDefault: true,
enableCompression: this.options.enableCompression,
preferredCompressionMethod: this.options.preferredCompressionMethod,
}));
}
if (this.options.watch && this.options.serveDir) {
try {
this.smartchokInstance = new plugins.smartchok.Smartchok([this.options.serveDir]);
await this.smartchokInstance.start();
(await this.smartchokInstance.getObservableFor('change')).subscribe(async () => {
await this.createServeDirHash();
this.reload();
});
await this.createServeDirHash();
}
catch (error) {
console.error('Failed to initialize file watching:', error);
// Continue without file watching rather than crashing
}
}
// lets start the server
await this.server.start();
try {
this.typedsocket = await plugins.typedsocket.TypedSocket.createServer(this.typedrouter, this.server);
// lets setup typedrouter
this.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler('getLatestServerChangeTime', async () => {
return {
time: this.lastReload,
};
}));
}
catch (error) {
console.error('Failed to initialize TypedSocket:', error);
// Continue without WebSocket support rather than crashing
}
}
/**
* reloads the page
*/
async reload() {
this.lastReload = Date.now();
if (!this.typedsocket) {
console.warn('TypedSocket not initialized, skipping client notifications');
return;
}
try {
const connections = await this.typedsocket.findAllTargetConnectionsByTag('typedserver_frontend');
for (const connection of connections) {
const pushTime = this.typedsocket.createTypedRequest('pushLatestServerChangeTime', connection);
pushTime.fire({
time: this.lastReload,
});
}
}
catch (error) {
console.error('Failed to notify clients about reload:', error);
}
}
/**
* Stops the server and cleans up resources
*/
async stop() {
this.ended = true;
const stopWithErrorHandling = async (stopFn, componentName) => {
try {
await stopFn();
}
catch (err) {
console.error(`Error stopping ${componentName}:`, err);
}
};
const tasks = [];
// Stop server
if (this.server) {
tasks.push(stopWithErrorHandling(() => this.server.stop(), 'server'));
}
// Stop TypedSocket
if (this.typedsocket) {
tasks.push(stopWithErrorHandling(() => this.typedsocket.stop(), 'TypedSocket'));
}
// Stop file watcher
if (this.smartchokInstance) {
tasks.push(stopWithErrorHandling(() => this.smartchokInstance.stop(), 'file watcher'));
}
await Promise.all(tasks);
}
/**
* Calculates a hash of the served directory for cache busting
*/
async createServeDirHash() {
try {
const serveDirHash = await plugins.smartfile.fs.fileTreeToHash(this.options.serveDir, '**/*');
this.serveHash = serveDirHash;
console.log('Current ServeDir hash: ' + serveDirHash);
this.serveDirHashSubject.next(serveDirHash);
}
catch (error) {
console.error('Failed to create serve directory hash:', error);
// Use a timestamp-based hash as fallback
const fallbackHash = Date.now().toString(16).slice(-6);
this.serveHash = fallbackHash;
console.log('Using fallback hash: ' + fallbackHash);
this.serveDirHashSubject.next(fallbackHash);
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy50eXBlZHNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMudHlwZWRzZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxLQUFLLEtBQUssTUFBTSxZQUFZLENBQUM7QUFDcEMsT0FBTyxLQUFLLFVBQVUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUM3RCxPQUFPLEtBQUssV0FBVyxNQUFNLHdCQUF3QixDQUFDO0FBQ3RELE9BQU8sRUFBMkIsTUFBTSxxQ0FBcUMsQ0FBQztBQWdFOUUsTUFBTSxPQUFPLFdBQVc7SUFnQnRCLFlBQVksVUFBMEI7UUFSL0Isd0JBQW1CLEdBQUcsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQVMsQ0FBQyxDQUFDLENBQUM7UUFDeEUsY0FBUyxHQUFXLFFBQVEsQ0FBQztRQUU3QixnQkFBVyxHQUFHLElBQUksT0FBTyxDQUFDLFlBQVksQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUVyRCxlQUFVLEdBQVcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ2hDLFVBQUssR0FBRyxLQUFLLENBQUM7UUFHbkIsTUFBTSxlQUFlLEdBQW1CO1lBQ3RDLElBQUksRUFBRSxJQUFJO1lBQ1YsWUFBWSxFQUFFLEtBQUs7WUFDbkIsUUFBUSxFQUFFLElBQUk7WUFDZCxLQUFLLEVBQUUsS0FBSztZQUNaLElBQUksRUFBRSxJQUFJO1NBQ1gsQ0FBQztRQUNGLElBQUksQ0FBQyxPQUFPLEdBQUc7WUFDYixHQUFHLGVBQWU7WUFDbEIsR0FBRyxVQUFVO1NBQ2QsQ0FBQztRQUVGLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxXQUFXLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNuRCwwQ0FBMEM7UUFDMUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQ2xC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