diff --git a/back/src/App.ts b/back/src/App.ts index c13b6fdc..5853f4d6 100644 --- a/back/src/App.ts +++ b/back/src/App.ts @@ -1,10 +1,6 @@ // lib/app.ts import {IoSocketController} from "./Controller/IoSocketController"; //TODO fix import by "_Controller/..." import {AuthenticateController} from "./Controller/AuthenticateController"; //TODO fix import by "_Controller/..." -import express from "express"; -import {Application, Request, Response} from 'express'; -import bodyParser = require('body-parser'); -import * as http from "http"; import {MapController} from "./Controller/MapController"; import {PrometheusController} from "./Controller/PrometheusController"; import {AdminController} from "./Controller/AdminController"; diff --git a/back/src/Controller/AdminController.ts b/back/src/Controller/AdminController.ts index c4905a8a..78280523 100644 --- a/back/src/Controller/AdminController.ts +++ b/back/src/Controller/AdminController.ts @@ -1,24 +1,26 @@ -import {Application, Request, Response} from "express"; import {OK} from "http-status-codes"; import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; import Axios from "axios"; +import {HttpRequest, HttpResponse} from "uWebSockets.js"; +import {parse} from "query-string"; +import {App} from "../Server/sifrr.server"; export class AdminController { - App : Application; - - constructor(App : Application) { - this.App = App; + constructor(private App : App) { this.getLoginUrlByToken(); } - + getLoginUrlByToken(){ - this.App.get("/register/:token", async (req: Request, res: Response) => { + this.App.get("/register/:token", async (res: HttpResponse, req: HttpRequest) => { if (!ADMIN_API_URL) { - return res.status(500).send('No admin backoffice set!'); + return res.writeStatus("500 Internal Server Error").end('No admin backoffice set!'); } - const token:string = req.params.token; - + + const query = parse(req.getQuery()); + + const token:string = query.token as string; + let response = null try { response = await Axios.get(ADMIN_API_URL+'/api/login-url/'+token, { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }) @@ -30,7 +32,7 @@ export class AdminController { const organizationSlug = response.data.organizationSlug; const worldSlug = response.data.worldSlug; const roomSlug = response.data.roomSlug; - return res.status(OK).send({organizationSlug, worldSlug, roomSlug}); + return res.writeStatus("200 OK").end(JSON.stringify({organizationSlug, worldSlug, roomSlug})); }); } } diff --git a/back/src/Controller/AuthenticateController.ts b/back/src/Controller/AuthenticateController.ts index a65255a2..b7fd093c 100644 --- a/back/src/Controller/AuthenticateController.ts +++ b/back/src/Controller/AuthenticateController.ts @@ -1,6 +1,4 @@ -import {Application, Request, Response} from "express"; import Jwt from "jsonwebtoken"; -import {BAD_REQUEST, OK} from "http-status-codes"; import {SECRET_KEY, URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." import { uuid } from 'uuidv4'; import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js"; diff --git a/back/src/Controller/DebugController.ts b/back/src/Controller/DebugController.ts index 54544f6c..e77b28f3 100644 --- a/back/src/Controller/DebugController.ts +++ b/back/src/Controller/DebugController.ts @@ -1,22 +1,25 @@ -import {Application, Request, Response} from "express"; -import {OK} from "http-status-codes"; import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; import {IoSocketController} from "_Controller/IoSocketController"; import {stringify} from "circular-json"; +import {HttpRequest, HttpResponse} from "uWebSockets.js"; +import { parse } from 'query-string'; +import {App} from "../Server/sifrr.server"; export class DebugController { - constructor(private App : Application, private ioSocketController: IoSocketController) { + constructor(private App : App, private ioSocketController: IoSocketController) { this.getDump(); } getDump(){ - this.App.get("/dump", (req: Request, res: Response) => { - if (req.query.token !== ADMIN_API_TOKEN) { + this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => { + const query = parse(req.getQuery()); + + if (query.token !== ADMIN_API_TOKEN) { return res.status(401).send('Invalid token sent!'); } - return res.status(OK).contentType('application/json').send(stringify( + return res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(stringify( this.ioSocketController.getWorlds(), (key: unknown, value: unknown) => { if(value instanceof Map) { diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index d9bdc950..cd3d5e52 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -121,7 +121,7 @@ export class IoSocketController { * * @param token */ - searchClientByToken(token: string): ExSocketInterface | null { +/* searchClientByToken(token: string): ExSocketInterface | null { const clients: ExSocketInterface[] = Object.values(this.Io.sockets.sockets) as ExSocketInterface[]; for (let i = 0; i < clients.length; i++) { const client = clients[i]; @@ -131,7 +131,7 @@ export class IoSocketController { return client; } return null; - } + }*/ private authenticate(ws: WebSocket) { //console.log(socket.handshake.query.token); @@ -657,25 +657,23 @@ export class IoSocketController { //socket.emit(SocketIoEvent.GROUP_CREATE_UPDATE, groupUpdateMessage.serializeBinary().buffer); } - private emitDeleteGroupEvent(socket: Socket, groupId: number): void { + private emitDeleteGroupEvent(client: ExSocketInterface, groupId: number): void { const groupDeleteMessage = new GroupDeleteMessage(); groupDeleteMessage.setGroupid(groupId); const subMessage = new SubMessage(); subMessage.setGroupdeletemessage(groupDeleteMessage); - const client : ExSocketInterface = socket as ExSocketInterface; emitInBatch(client, subMessage); } - private emitUserLeftEvent(socket: Socket, userId: number): void { + private emitUserLeftEvent(client: ExSocketInterface, userId: number): void { const userLeftMessage = new UserLeftMessage(); userLeftMessage.setUserid(userId); const subMessage = new SubMessage(); subMessage.setUserleftmessage(userLeftMessage); - const client : ExSocketInterface = socket as ExSocketInterface; emitInBatch(client, subMessage); } diff --git a/back/src/Server/server/baseapp.ts b/back/src/Server/server/baseapp.ts index dbac5929..d723c33d 100644 --- a/back/src/Server/server/baseapp.ts +++ b/back/src/Server/server/baseapp.ts @@ -1,18 +1,9 @@ -import { readdirSync, statSync } from 'fs'; -import { join, relative } from 'path'; import { Readable } from 'stream'; import { us_listen_socket_close, TemplatedApp, HttpResponse, HttpRequest } from 'uWebSockets.js'; -//import { watch } from 'chokidar'; -import { wsConfig } from './livereload'; -import sendFile from './sendfile'; -import formData from './formdata'; -import loadroutes from './loadroutes'; -import { graphqlPost, graphqlWs } from './graphql'; import { stob } from './utils'; -import { SendFileOptions, Handler } from './types'; +import { Handler } from './types'; -const contTypes = ['application/x-www-form-urlencoded', 'multipart/form-data']; const noOp = () => true; const handleBody = (res: HttpResponse, req: HttpRequest) => { @@ -24,7 +15,7 @@ const handleBody = (res: HttpResponse, req: HttpRequest) => { this.onData((ab, isLast) => { // uint and then slicing is bit faster than slice and then uint - stream.push(new Uint8Array(ab.slice(ab.byteOffset, ab.byteLength))); + stream.push(new Uint8Array(ab.slice((ab as any).byteOffset, ab.byteLength))); if (isLast) { stream.push(null); } @@ -37,15 +28,10 @@ const handleBody = (res: HttpResponse, req: HttpRequest) => { if (contType.indexOf('application/json') > -1) res.json = async () => JSON.parse(await res.body()); - if (contTypes.map(t => contType.indexOf(t) > -1).indexOf(true) > -1) - res.formData = formData.bind(res, contType); }; class BaseApp { - _staticPaths = new Map(); - //_watched = new Map(); _sockets = new Map(); - __livereloadenabled = false; ws!: TemplatedApp['ws']; get!: TemplatedApp['get']; _post!: TemplatedApp['post']; @@ -53,84 +39,6 @@ class BaseApp { _patch!: TemplatedApp['patch']; _listen!: TemplatedApp['listen']; - file(pattern: string, filePath: string, options: SendFileOptions = {}) { - pattern=pattern.replace(/\\/g,'/'); - if (this._staticPaths.has(pattern)) { - if (options.failOnDuplicateRoute) - throw Error( - `Error serving '${filePath}' for '${pattern}', already serving '${ - this._staticPaths.get(pattern)[0] - }' file for this pattern.` - ); - else if (!options.overwriteRoute) return this; - } - - if (options.livereload && !this.__livereloadenabled) { - this.ws('/__sifrrLiveReload', wsConfig); - this.file('/livereload.js', join(__dirname, './livereloadjs.js')); - this.__livereloadenabled = true; - } - - this._staticPaths.set(pattern, [filePath, options]); - this.get(pattern, this._serveStatic); - return this; - } - - folder(prefix: string, folder: string, options: SendFileOptions, base: string = folder) { - // not a folder - if (!statSync(folder).isDirectory()) { - throw Error('Given path is not a directory: ' + folder); - } - - // ensure slash in beginning and no trailing slash for prefix - if (prefix[0] !== '/') prefix = '/' + prefix; - if (prefix[prefix.length - 1] === '/') prefix = prefix.slice(0, -1); - - // serve folder - const filter = options ? options.filter || noOp : noOp; - readdirSync(folder).forEach(file => { - // Absolute path - const filePath = join(folder, file); - // Return if filtered - if (!filter(filePath)) return; - - if (statSync(filePath).isDirectory()) { - // Recursive if directory - this.folder(prefix, filePath, options, base); - } else { - this.file(prefix + '/' + relative(base, filePath), filePath, options); - } - }); - - /*if (options && options.watch) { - if (!this._watched.has(folder)) { - const w = watch(folder); - - w.on('unlink', filePath => { - const url = '/' + relative(base, filePath); - this._staticPaths.delete(prefix + url); - }); - - w.on('add', filePath => { - const url = '/' + relative(base, filePath); - this.file(prefix + url, filePath, options); - }); - - this._watched.set(folder, w); - } - }*/ - return this; - } - - _serveStatic(res: HttpResponse, req: HttpRequest) { - res.onAborted(noOp); - const options = this._staticPaths.get(req.getUrl()); - if (typeof options === 'undefined') { - res.writeStatus('404 Not Found'); - res.end(); - } else sendFile(res, req, options[0], options[1]); - } - post(pattern: string, handler: Handler) { if (typeof handler !== 'function') throw Error(`handler should be a function, given ${typeof handler}.`); @@ -163,21 +71,6 @@ class BaseApp { return this; } - graphql(route: string, schema, graphqlOptions: any = {}, uwsOptions = {}, graphql) { - const handler = graphqlPost(schema, graphqlOptions, graphql); - this.post(route, handler); - this.ws(route, graphqlWs(schema, graphqlOptions, uwsOptions, graphql)); - // this.get(route, handler); - if (graphqlOptions && graphqlOptions.graphiqlPath) - this.file(graphqlOptions.graphiqlPath, join(__dirname, './graphiql.html')); - return this; - } - - load(dir: string, options) { - loadroutes.call(this, dir, options); - return this; - } - listen(h: string | number, p: Function | number = noOp, cb?: Function) { if (typeof p === 'number' && typeof h === 'string') { this._listen(h, p, socket => { @@ -202,8 +95,6 @@ class BaseApp { } close(port: null | number = null) { - //this._watched.forEach(v => v.close()); - //this._watched.clear(); if (port) { this._sockets.has(port) && us_listen_socket_close(this._sockets.get(port)); this._sockets.delete(port); diff --git a/back/src/Server/server/cluster.ts b/back/src/Server/server/cluster.ts deleted file mode 100644 index 5b875327..00000000 --- a/back/src/Server/server/cluster.ts +++ /dev/null @@ -1,48 +0,0 @@ -const noop = (a, b) => {}; - -export default class Cluster { - apps: any[]; - listens = {}; - // apps = [ { app: SifrrServerApp, port/ports: int } ] - constructor(apps) { - if (!Array.isArray(apps)) apps = [apps]; - this.apps = apps; - } - - listen(onListen = noop) { - for (let i = 0; i < this.apps.length; i++) { - const config = this.apps[i]; - let { app, port, ports } = config; - if (!Array.isArray(ports) || ports.length === 0) { - ports = [port]; - } - ports.forEach(p => { - if (typeof p !== 'number') throw Error(`Port should be a number, given ${p}`); - if (this.listens[p]) return; - - app.listen(p, socket => { - onListen.call(app, socket, p); - }); - this.listens[p] = app; - }); - } - return this; - } - - closeAll() { - Object.keys(this.listens).forEach(port => { - this.close(port); - }); - return this; - } - - close(port = null) { - if (port) { - this.listens[port] && this.listens[port].close(port); - delete this.listens[port]; - } else { - this.closeAll(); - } - return this; - } -} diff --git a/back/src/Server/server/formdata.ts b/back/src/Server/server/formdata.ts deleted file mode 100644 index 419e6c6b..00000000 --- a/back/src/Server/server/formdata.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { createWriteStream } from 'fs'; -import { join, dirname } from 'path'; -import Busboy from 'busboy'; -import mkdirp from 'mkdirp'; - -function formData( - contType: string, - options: busboy.BusboyConfig & { - abortOnLimit?: boolean; - tmpDir?: string; - onFile?: ( - fieldname: string, - file: NodeJS.ReadableStream, - filename: string, - encoding: string, - mimetype: string - ) => string; - onField?: (fieldname: string, value: any) => void; - filename?: (oldName: string) => string; - } = {} -) { - options.headers = { - 'content-type': contType - }; - - return new Promise((resolve, reject) => { - const busb = new Busboy(options); - const ret = {}; - - this.bodyStream().pipe(busb); - - busb.on('limit', () => { - if (options.abortOnLimit) { - reject(Error('limit')); - } - }); - - busb.on('file', function(fieldname, file, filename, encoding, mimetype) { - const value = { - filename, - encoding, - mimetype, - filePath: undefined - }; - - if (typeof options.tmpDir === 'string') { - if (typeof options.filename === 'function') filename = options.filename(filename); - const fileToSave = join(options.tmpDir, filename); - mkdirp(dirname(fileToSave)); - - file.pipe(createWriteStream(fileToSave)); - value.filePath = fileToSave; - } - if (typeof options.onFile === 'function') { - value.filePath = - options.onFile(fieldname, file, filename, encoding, mimetype) || value.filePath; - } - - setRetValue(ret, fieldname, value); - }); - - busb.on('field', function(fieldname, value) { - if (typeof options.onField === 'function') options.onField(fieldname, value); - - setRetValue(ret, fieldname, value); - }); - - busb.on('finish', function() { - resolve(ret); - }); - - busb.on('error', reject); - }); -} - -function setRetValue( - ret: { [x: string]: any }, - fieldname: string, - value: { filename: string; encoding: string; mimetype: string; filePath?: string } | any -) { - if (fieldname.slice(-2) === '[]') { - fieldname = fieldname.slice(0, fieldname.length - 2); - if (Array.isArray(ret[fieldname])) { - ret[fieldname].push(value); - } else { - ret[fieldname] = [value]; - } - } else { - if (Array.isArray(ret[fieldname])) { - ret[fieldname].push(value); - } else if (ret[fieldname]) { - ret[fieldname] = [ret[fieldname], value]; - } else { - ret[fieldname] = value; - } - } -} - -export default formData; diff --git a/back/src/Server/server/graphiql.html b/back/src/Server/server/graphiql.html deleted file mode 100644 index 7ce03921..00000000 --- a/back/src/Server/server/graphiql.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - - - - - - - - - -
Loading...
- - - diff --git a/back/src/Server/server/graphql.ts b/back/src/Server/server/graphql.ts deleted file mode 100644 index 63a2bdaa..00000000 --- a/back/src/Server/server/graphql.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { parse } from 'query-string'; -import { createAsyncIterator, forAwaitEach, isAsyncIterable } from 'iterall'; -import { HttpResponse, HttpRequest } from 'uWebSockets.js'; -// client -> server -const GQL_START = 'start'; -const GQL_STOP = 'stop'; -// server -> client -const GQL_DATA = 'data'; -const GQL_QUERY = 'query'; - -async function getGraphqlParams(res: HttpResponse, req: HttpRequest) { - // query and variables - const queryParams = parse(req.getQuery()); - let { query, variables, operationName } = queryParams; - if (typeof variables === 'string') variables = JSON.parse(variables); - - // body - if (res && typeof res.json === 'function') { - const data = await res.json(); - query = data.query || query; - variables = data.variables || variables; - operationName = data.operationName || operationName; - } - return { - source: query, - variableValues: variables, - operationName - }; -} - -function graphqlPost(schema, graphqlOptions: any = {}, graphql: any = {}) { - const execute = graphql.graphql || require('graphql').graphql; - - return async (res: HttpResponse, req: HttpRequest) => { - res.onAborted(console.error); - - res.writeHeader('content-type', 'application/json'); - res.end( - JSON.stringify( - await execute({ - schema, - ...(await getGraphqlParams(res, req)), - ...graphqlOptions, - contextValue: { - res, - req, - ...(graphqlOptions && - (graphqlOptions.contextValue || - (graphqlOptions.contextFxn && (await graphqlOptions.contextFxn(res, req))))) - } - }) - ) - ); - }; -} - -function stopGqsSubscription(operations, reqOpId) { - if (!reqOpId) return; - operations[reqOpId] && operations[reqOpId].return && operations[reqOpId].return(); - delete operations[reqOpId]; -} - -function graphqlWs(schema, graphqlOptions: any = {}, uwsOptions: any = {}, graphql: any = {}) { - const subscribe = graphql.subscribe || require('graphql').subscribe; - const execute = graphql.graphql || require('graphql').graphql; - - return { - open: (ws, req) => { - ws.req = req; - ws.operations = {}; - ws.opId = 1; - }, - message: async (ws, message) => { - const { type, payload = {}, id: reqOpId } = JSON.parse(Buffer.from(message).toString('utf8')); - let opId; - if (reqOpId) { - opId = reqOpId; - } else { - opId = ws.opId++; - } - - const params = { - schema, - source: payload.query, - variableValues: payload.variables, - operationName: payload.operationName, - contextValue: { - ws, - ...(graphqlOptions && - (graphqlOptions.contextValue || - (graphqlOptions.contextFxn && (await graphqlOptions.contextFxn(ws))))) - }, - ...graphqlOptions - }; - - switch (type) { - case GQL_START: - stopGqsSubscription(ws.operations, opId); - - // eslint-disable-next-line no-case-declarations - let asyncIterable = await subscribe( - params.schema, - graphql.parse(params.source), - params.rootValue, - params.contextValue, - params.variableValues, - params.operationName - ); - asyncIterable = isAsyncIterable(asyncIterable) - ? asyncIterable - : createAsyncIterator([asyncIterable]); - - forAwaitEach(asyncIterable, result => - ws.send( - JSON.stringify({ - id: opId, - type: GQL_DATA, - payload: result - }) - ) - ); - break; - - case GQL_STOP: - stopGqsSubscription(ws.operations, reqOpId); - break; - - default: - ws.send(JSON.stringify({ payload: await execute(params), type: GQL_QUERY, id: opId })); - break; - } - }, - idleTimeout: 24 * 60 * 60, - ...uwsOptions - }; -} - -export { graphqlPost, graphqlWs }; diff --git a/back/src/Server/server/livereload.ts b/back/src/Server/server/livereload.ts deleted file mode 100644 index 787c871b..00000000 --- a/back/src/Server/server/livereload.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { WebSocketBehavior, WebSocket } from 'uWebSockets.js'; - -const websockets = {}; -let id = 0; - -const wsConfig: WebSocketBehavior = { - open: (ws: WebSocket & { id: number }, req) => { - websockets[id] = { - dirty: false - }; - ws.id = id; - console.log('websocket connected: ', id); - id++; - }, - message: ws => { - ws.send(JSON.stringify(websockets[ws.id].dirty)); - websockets[ws.id].dirty = false; - }, - close: (ws, code, message) => { - delete websockets[ws.id]; - console.log( - `websocket disconnected with code ${code} and message ${message}:`, - ws.id, - websockets - ); - } -}; - -const sendSignal = (type: string, path: string) => { - console.log(type, 'signal for file: ', path); - for (let i in websockets) websockets[i].dirty = true; -}; - -export default { websockets, wsConfig, sendSignal }; -export { websockets, wsConfig, sendSignal }; diff --git a/back/src/Server/server/livereloadjs.js b/back/src/Server/server/livereloadjs.js deleted file mode 100644 index 04839578..00000000 --- a/back/src/Server/server/livereloadjs.js +++ /dev/null @@ -1,47 +0,0 @@ -const loc = window.location; -let path; -if (loc.protocol === 'https:') { - path = 'wss:'; -} else { - path = 'ws:'; -} -path += '//' + loc.host + '/__sifrrLiveReload'; - -let ws, - ttr = 500, - timeout; - -function newWsConnection() { - ws = new WebSocket(path); - ws.onopen = function() { - ttr = 500; - checkMessage(); - console.log('watching for file changes through sifrr-server livereload mode.'); - }; - ws.onmessage = function(event) { - if (JSON.parse(event.data)) { - console.log('Files changed, refreshing page.'); - location.reload(); - } - }; - ws.onerror = e => { - console.error('Webosocket error: ', e); - console.log('Retrying after ', ttr / 4, 'ms'); - ttr *= 4; - }; - ws.onclose = e => { - console.error(`Webosocket closed with code \${e.code} error \${e.message}`); - }; -} - -function checkMessage() { - if (!ws) return; - if (ws.readyState === WebSocket.OPEN) ws.send(''); - else if (ws.readyState === WebSocket.CLOSED) newWsConnection(); - - if (timeout) clearTimeout(timeout); - timeout = setTimeout(checkMessage, ttr); -} - -newWsConnection(); -setTimeout(checkMessage, ttr); diff --git a/back/src/Server/server/loadroutes.ts b/back/src/Server/server/loadroutes.ts deleted file mode 100644 index 3761d762..00000000 --- a/back/src/Server/server/loadroutes.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { statSync, readdirSync } from 'fs'; -import { join, extname } from 'path'; - -function loadRoutes(dir, { filter = () => true, basePath = '' } = {}) { - let files; - const paths = []; - - if (statSync(dir).isDirectory()) { - files = readdirSync(dir) - .filter(filter) - .map(file => join(dir, file)); - } else { - files = [dir]; - } - - files.forEach(file => { - if (statSync(file).isDirectory()) { - // Recursive if directory - paths.push(...loadRoutes.call(this, file, { filter, basePath })); - } else if (extname(file) === '.js') { - const routes = require(file); - let basePaths = routes.basePath || ['']; - delete routes.basePath; - if (typeof basePaths === 'string') basePaths = [basePaths]; - - basePaths.forEach(basep => { - for (const method in routes) { - const methodRoutes = routes[method]; - for (let r in methodRoutes) { - if (!Array.isArray(methodRoutes[r])) methodRoutes[r] = [methodRoutes[r]]; - this[method](basePath + basep + r, ...methodRoutes[r]); - paths.push(basePath + basep + r); - } - } - }); - } - }); - - return paths; -} - -export default loadRoutes; diff --git a/back/src/Server/server/mime.ts b/back/src/Server/server/mime.ts deleted file mode 100644 index 396073cc..00000000 --- a/back/src/Server/server/mime.ts +++ /dev/null @@ -1,176 +0,0 @@ -const mimes = { - '3gp': 'video/3gpp', - a: 'application/octet-stream', - ai: 'application/postscript', - aif: 'audio/x-aiff', - aiff: 'audio/x-aiff', - asc: 'application/pgp-signature', - asf: 'video/x-ms-asf', - asm: 'text/x-asm', - asx: 'video/x-ms-asf', - atom: 'application/atom+xml', - au: 'audio/basic', - avi: 'video/x-msvideo', - bat: 'application/x-msdownload', - bin: 'application/octet-stream', - bmp: 'image/bmp', - bz2: 'application/x-bzip2', - c: 'text/x-c', - cab: 'application/vnd.ms-cab-compressed', - cc: 'text/x-c', - chm: 'application/vnd.ms-htmlhelp', - class: 'application/octet-stream', - com: 'application/x-msdownload', - conf: 'text/plain', - cpp: 'text/x-c', - crt: 'application/x-x509-ca-cert', - css: 'text/css', - csv: 'text/csv', - cxx: 'text/x-c', - deb: 'application/x-debian-package', - der: 'application/x-x509-ca-cert', - diff: 'text/x-diff', - djv: 'image/vnd.djvu', - djvu: 'image/vnd.djvu', - dll: 'application/x-msdownload', - dmg: 'application/octet-stream', - doc: 'application/msword', - dot: 'application/msword', - dtd: 'application/xml-dtd', - dvi: 'application/x-dvi', - ear: 'application/java-archive', - eml: 'message/rfc822', - eps: 'application/postscript', - exe: 'application/x-msdownload', - f: 'text/x-fortran', - f77: 'text/x-fortran', - f90: 'text/x-fortran', - flv: 'video/x-flv', - for: 'text/x-fortran', - gem: 'application/octet-stream', - gemspec: 'text/x-script.ruby', - gif: 'image/gif', - gz: 'application/x-gzip', - h: 'text/x-c', - hh: 'text/x-c', - htm: 'text/html', - html: 'text/html', - ico: 'image/vnd.microsoft.icon', - ics: 'text/calendar', - ifb: 'text/calendar', - iso: 'application/octet-stream', - jar: 'application/java-archive', - java: 'text/x-java-source', - jnlp: 'application/x-java-jnlp-file', - jpeg: 'image/jpeg', - jpg: 'image/jpeg', - js: 'application/javascript', - json: 'application/json', - log: 'text/plain', - m3u: 'audio/x-mpegurl', - m4v: 'video/mp4', - man: 'text/troff', - mathml: 'application/mathml+xml', - mbox: 'application/mbox', - mdoc: 'text/troff', - me: 'text/troff', - mid: 'audio/midi', - midi: 'audio/midi', - mime: 'message/rfc822', - mjs: 'application/javascript', - mml: 'application/mathml+xml', - mng: 'video/x-mng', - mov: 'video/quicktime', - mp3: 'audio/mpeg', - mp4: 'video/mp4', - mp4v: 'video/mp4', - mpeg: 'video/mpeg', - mpg: 'video/mpeg', - ms: 'text/troff', - msi: 'application/x-msdownload', - odp: 'application/vnd.oasis.opendocument.presentation', - ods: 'application/vnd.oasis.opendocument.spreadsheet', - odt: 'application/vnd.oasis.opendocument.text', - ogg: 'application/ogg', - p: 'text/x-pascal', - pas: 'text/x-pascal', - pbm: 'image/x-portable-bitmap', - pdf: 'application/pdf', - pem: 'application/x-x509-ca-cert', - pgm: 'image/x-portable-graymap', - pgp: 'application/pgp-encrypted', - pkg: 'application/octet-stream', - pl: 'text/x-script.perl', - pm: 'text/x-script.perl-module', - png: 'image/png', - pnm: 'image/x-portable-anymap', - ppm: 'image/x-portable-pixmap', - pps: 'application/vnd.ms-powerpoint', - ppt: 'application/vnd.ms-powerpoint', - ps: 'application/postscript', - psd: 'image/vnd.adobe.photoshop', - py: 'text/x-script.python', - qt: 'video/quicktime', - ra: 'audio/x-pn-realaudio', - rake: 'text/x-script.ruby', - ram: 'audio/x-pn-realaudio', - rar: 'application/x-rar-compressed', - rb: 'text/x-script.ruby', - rdf: 'application/rdf+xml', - roff: 'text/troff', - rpm: 'application/x-redhat-package-manager', - rss: 'application/rss+xml', - rtf: 'application/rtf', - ru: 'text/x-script.ruby', - s: 'text/x-asm', - sgm: 'text/sgml', - sgml: 'text/sgml', - sh: 'application/x-sh', - sig: 'application/pgp-signature', - snd: 'audio/basic', - so: 'application/octet-stream', - svg: 'image/svg+xml', - svgz: 'image/svg+xml', - swf: 'application/x-shockwave-flash', - t: 'text/troff', - tar: 'application/x-tar', - tbz: 'application/x-bzip-compressed-tar', - tcl: 'application/x-tcl', - tex: 'application/x-tex', - texi: 'application/x-texinfo', - texinfo: 'application/x-texinfo', - text: 'text/plain', - tif: 'image/tiff', - tiff: 'image/tiff', - torrent: 'application/x-bittorrent', - tr: 'text/troff', - txt: 'text/plain', - vcf: 'text/x-vcard', - vcs: 'text/x-vcalendar', - vrml: 'model/vrml', - war: 'application/java-archive', - wav: 'audio/x-wav', - wma: 'audio/x-ms-wma', - wmv: 'video/x-ms-wmv', - wmx: 'video/x-ms-wmx', - wrl: 'model/vrml', - wsdl: 'application/wsdl+xml', - xbm: 'image/x-xbitmap', - xhtml: 'application/xhtml+xml', - xls: 'application/vnd.ms-excel', - xml: 'application/xml', - xpm: 'image/x-xpixmap', - xsl: 'application/xml', - xslt: 'application/xslt+xml', - yaml: 'text/yaml', - yml: 'text/yaml', - zip: 'application/zip', - default: 'text/html' -}; - -const getMime = (path: string): string => { - const i = path.lastIndexOf('.'); - return mimes[path.substr(i + 1).toLowerCase()] || mimes['default']; -}; - -export { getMime, mimes }; diff --git a/back/src/Server/server/sendfile.ts b/back/src/Server/server/sendfile.ts deleted file mode 100644 index 8310c4a7..00000000 --- a/back/src/Server/server/sendfile.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { watch, statSync, createReadStream } from 'fs'; -import { createBrotliCompress, createGzip, createDeflate } from 'zlib'; -const watchedPaths = new Set(); - -const compressions = { - br: createBrotliCompress, - gzip: createGzip, - deflate: createDeflate -}; -import { writeHeaders } from './utils'; -import { getMime } from './mime'; -const bytes = 'bytes='; -import { stob } from './utils'; -import { sendSignal } from './livereload'; -import { SendFileOptions } from './types'; -import { HttpResponse, HttpRequest } from 'uWebSockets.js'; - -function sendFile(res: HttpResponse, req: HttpRequest, path: string, options: SendFileOptions) { - if (options && options.livereload && !watchedPaths.has(path)) { - watchedPaths.add(path); - watch(path, sendSignal); - } - - sendFileToRes( - res, - { - 'if-modified-since': req.getHeader('if-modified-since'), - range: req.getHeader('range'), - 'accept-encoding': req.getHeader('accept-encoding') - }, - path, - options - ); -} - -function sendFileToRes( - res: HttpResponse, - reqHeaders: { [name: string]: string }, - path: string, - { - lastModified = true, - headers = {}, - compress = false, - compressionOptions = { - priority: ['gzip', 'br', 'deflate'] - }, - cache = false - }: { cache: any } & any = {} -) { - let { mtime, size } = statSync(path); - mtime.setMilliseconds(0); - const mtimeutc = mtime.toUTCString(); - - headers = Object.assign({}, headers); - // handling last modified - if (lastModified) { - // Return 304 if last-modified - if (reqHeaders['if-modified-since']) { - if (new Date(reqHeaders['if-modified-since']) >= mtime) { - res.writeStatus('304 Not Modified'); - return res.end(); - } - } - headers['last-modified'] = mtimeutc; - } - headers['content-type'] = getMime(path); - - // write data - let start = 0, - end = size - 1; - - if (reqHeaders.range) { - compress = false; - const parts = reqHeaders.range.replace(bytes, '').split('-'); - start = parseInt(parts[0], 10); - end = parts[1] ? parseInt(parts[1], 10) : end; - headers['accept-ranges'] = 'bytes'; - headers['content-range'] = `bytes ${start}-${end}/${size}`; - size = end - start + 1; - res.writeStatus('206 Partial Content'); - } - - // for size = 0 - if (end < 0) end = 0; - - let readStream = createReadStream(path, { start, end }); - // Compression; - let compressed: boolean | string = false; - if (compress) { - const l = compressionOptions.priority.length; - for (let i = 0; i < l; i++) { - const type = compressionOptions.priority[i]; - if (reqHeaders['accept-encoding'].indexOf(type) > -1) { - compressed = type; - const compressor = compressions[type](compressionOptions); - readStream.pipe(compressor); - readStream = compressor; - headers['content-encoding'] = compressionOptions.priority[i]; - break; - } - } - } - - res.onAborted(() => readStream.destroy()); - writeHeaders(res, headers); - // check cache - if (cache) { - return cache.wrap( - `${path}_${mtimeutc}_${start}_${end}_${compressed}`, - cb => { - stob(readStream) - .then(b => cb(null, b)) - .catch(cb); - }, - { ttl: 0 }, - (err, buffer) => { - if (err) { - res.writeStatus('500 Internal server error'); - res.end(); - throw err; - } - res.end(buffer); - } - ); - } else if (compressed) { - readStream.on('data', buffer => { - res.write(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)); - }); - } else { - readStream.on('data', buffer => { - const chunk = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), - lastOffset = res.getWriteOffset(); - - // First try - const [ok, done] = res.tryEnd(chunk, size); - - if (done) { - readStream.destroy(); - } else if (!ok) { - // pause because backpressure - readStream.pause(); - - // Save unsent chunk for later - res.ab = chunk; - res.abOffset = lastOffset; - - // Register async handlers for drainage - res.onWritable(offset => { - const [ok, done] = res.tryEnd(res.ab.slice(offset - res.abOffset), size); - if (done) { - readStream.destroy(); - } else if (ok) { - readStream.resume(); - } - return ok; - }); - } - }); - } - readStream - .on('error', e => { - res.writeStatus('500 Internal server error'); - res.end(); - readStream.destroy(); - throw e; - }) - .on('end', () => { - res.end(); - }); -} - -export default sendFile; diff --git a/back/src/Server/server/types.ts b/back/src/Server/server/types.ts index 09916b5f..3d0f48c7 100644 --- a/back/src/Server/server/types.ts +++ b/back/src/Server/server/types.ts @@ -6,21 +6,6 @@ export type UwsApp = { prototype: TemplatedApp; }; -export type SendFileOptions = { - failOnDuplicateRoute?: boolean; - overwriteRoute?: boolean; - watch?: boolean; - filter?: (path: string) => boolean; - livereload?: boolean; - lastModified?: boolean; - headers?: { [name: string]: string }; - compress?: boolean; - compressionOptions?: { - priority?: 'gzip' | 'br' | 'deflate'; - }; - cache?: boolean; -}; - export type Handler = (res: HttpResponse, req: HttpRequest) => void; export {}; diff --git a/back/src/Server/server/utils.ts b/back/src/Server/server/utils.ts index 8f6db886..f7f3e4b5 100644 --- a/back/src/Server/server/utils.ts +++ b/back/src/Server/server/utils.ts @@ -1,21 +1,6 @@ -import { HttpResponse } from 'uWebSockets.js'; import { ReadStream } from 'fs'; -function writeHeaders( - res: HttpResponse, - headers: { [name: string]: string } | string, - other?: string -) { - if (typeof headers === 'string') { - res.writeHeader(headers, other.toString()); - } else { - for (const n in headers) { - res.writeHeader(n, headers[n].toString()); - } - } -} - -function extend(who: object, from: object, overwrite = true) { +function extend(who: any, from: any, overwrite = true) { const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(from)).concat( Object.keys(from) ); @@ -31,7 +16,7 @@ function extend(who: object, from: object, overwrite = true) { function stob(stream: ReadStream): Promise { return new Promise(resolve => { - const buffers = []; + const buffers: Buffer[] = []; stream.on('data', buffers.push.bind(buffers)); stream.on('end', () => { @@ -49,4 +34,4 @@ function stob(stream: ReadStream): Promise { }); } -export { writeHeaders, extend, stob }; +export { extend, stob }; diff --git a/back/src/Server/sifrr.server.ts b/back/src/Server/sifrr.server.ts index 9a274378..47fba02c 100644 --- a/back/src/Server/sifrr.server.ts +++ b/back/src/Server/sifrr.server.ts @@ -2,29 +2,18 @@ import { parse } from 'query-string'; import { HttpRequest } from 'uWebSockets.js'; import App from './server/app'; import SSLApp from './server/sslapp'; -import { mimes, getMime } from './server/mime'; -import { writeHeaders } from './server/utils'; -import sendFile from './server/sendfile'; -import Cluster from './server/cluster'; -import livereload from './server/livereload'; import * as types from './server/types'; const getQuery = (req: HttpRequest) => { return parse(req.getQuery()); }; -export { App, SSLApp, mimes, getMime, writeHeaders, sendFile, Cluster, livereload, getQuery }; +export { App, SSLApp, getQuery }; export * from './server/types'; export default { App, SSLApp, - mimes, - getMime, - writeHeaders, - sendFile, - Cluster, - livereload, getQuery, ...types };