diff --git a/.editorconfig b/.editorconfig index 99cd0f5..ba92aaf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,6 @@ +root=true +[*] charset = utf-8 indent_size = 3 -indent_style = space \ No newline at end of file +indent_style = space +insert_final_newline = true \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 8281be2..ff410b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import Web from "./web"; import config from "./config"; import { DatabaseManager } from "./database/database"; import { createServer } from "http"; -import { ConnectionManager } from "./connection"; +import { WebsocketConnectionManager } from "./websocket"; import { LoggingTypes } from "@hibas123/logging"; import { readFileSync } from "fs"; @@ -13,12 +13,14 @@ const version = JSON.parse(readFileSync("./package.json").toString()).version; Logging.log("Starting Database version:", version); -DatabaseManager.init().then(() => { - const http = createServer(Web.callback()); - ConnectionManager.bind(http); - const port = config.port || 5000; - http.listen(port, () => Logging.log("Listening on port:", port)) -}).catch(err => { - Logging.error(err); - process.exit(-1); -}) \ No newline at end of file +DatabaseManager.init() + .then(() => { + const http = createServer(Web.callback()); + WebsocketConnectionManager.bind(http); + const port = config.port || 5000; + http.listen(port, () => Logging.log("WS: Listening on port:", port)); + }) + .catch((err) => { + Logging.error(err); + process.exit(-1); + }); diff --git a/src/websocket.ts b/src/websocket.ts new file mode 100644 index 0000000..b8aa52d --- /dev/null +++ b/src/websocket.ts @@ -0,0 +1,122 @@ +import Logging from "@hibas123/nodelogging"; +import { IncomingMessage, Server } from "http"; +import * as WebSocket from "ws"; +import { DatabaseManager } from "./database/database"; +import { CollectionQuery, DocumentQuery, IQuery, ITypedQuery } from "./database/query"; +import Session from "./database/session"; +import { verifyJWT } from "./helper/jwt"; +import nanoid = require("nanoid"); + +export class WebsocketConnectionManager { + static server: WebSocket.Server; + + static bind(server: Server) { + this.server = new WebSocket.Server({ server }); + this.server.on("connection", this.onConnection.bind(this)); + } + + private static async onConnection(socket: WebSocket, req: IncomingMessage) { + Logging.log("New Connection:"); + const sendError = (error: string) => socket.send(JSON.stringify({ ns: "error_msg", data: error })); + + const session = new Session(nanoid()); + + const query = new URL(req.url, "http://localhost").searchParams; + + const database = query.get("database"); + const db = DatabaseManager.getDatabase(database); + if (!db) { + sendError("Invalid Database!"); + socket.close(); + return; + } + + const accesskey = query.get("accesskey"); + if (db.accesskey) { + if (!accesskey || accesskey !== db.accesskey) { + sendError("Unauthorized!"); + socket.close(); + return; + } + } + + const authkey = query.get("authkey"); + if (authkey && db.publickey) { + let res = await verifyJWT(authkey, db.publickey); + if (!res || !res.uid) { + sendError("Invalid JWT"); + socket.close(); + return; + } else { + session.uid = res.uid; + } + } + + const rootkey = query.get("rootkey"); + if (rootkey && db.rootkey) { + if (rootkey === db.rootkey) { + session.root = true; + Logging.warning(`Somebody logged into ${database} via rootkey`); + } + } + + const answer = (id: string, data: any, error: boolean = false) => { + if (error) + Logging.error(error as any); + socket.send(JSON.stringify({ ns: "message", data: { id, error, data } })); + } + + const handler = new Map void)>(); + + handler.set("v2", async ({ id, query }) => db.run(Array.isArray(query) ? query : [query], session) + .then(res => answer(id, res)) + .catch(err => answer(id, undefined, err)) + ); + + // handler.set("bulk", async ({ id, query }) => db.run(query, session) + // .then(res => answer(id, res)) + // .catch(err => answer(id, undefined, err)) + // ); + + + const SnapshotMap = new Map(); + handler.set("snapshot", async ({ id, query }: { id: string, query: ITypedQuery<"snapshot"> }) => { + db.snapshot(query, session, (data => { + socket.send(JSON.stringify({ + ns: "snapshot", data: { id, data } + })); + })).then(s => { + answer(id, s.snaphot); + SnapshotMap.set(id, s.id); + }).catch(err => answer(id, undefined, err)); + }) + + handler.set("unsubscribe", async ({ id }) => { + let i = SnapshotMap.get(id); + if (i) { + db.unsubscribe(i, session); + SnapshotMap.delete(i); + } + }) + + socket.on("message", async (rawData: string) => { + try { + let message: { ns: string, data: any } = JSON.parse(rawData); + let h = handler.get(message.ns); + if (h) { + h(message.data); + } + } catch (err) { + Logging.errorMessage("Unknown Error:"); + Logging.error(err); + } + }) + + socket.on("close", () => { + Logging.log(`${session.id} has disconnected!`); + session.subscriptions.forEach(unsubscribe => unsubscribe()); + session.subscriptions.clear(); + socket.removeAllListeners(); + }) + } +} \ No newline at end of file diff --git a/views/admin.html b/views/admin.html index 9e5893b..44a585f 100644 --- a/views/admin.html +++ b/views/admin.html @@ -1,112 +1,117 @@ + + + + + Admin Interface + + - - - - - Admin Interface - - + - + - - - -
-
-

Navigation:

-
    -
  • Settings
  • -
  • Databases
  • -
  • New Database
  • -
- Databases: -
+ #content { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + } + + + +
+
+

Navigation:

+
    +
  • Settings
  • +
  • Databases
  • +
  • New Database
  • +
+ Databases: +
+
+
+ +
-
- -
-
- + - - - \ No newline at end of file + reloadDBs(); + setInterval(reloadDBs, 5000); + + + diff --git a/views/forms.hbs b/views/forms.hbs index 6f1a6a8..ab45bd8 100644 --- a/views/forms.hbs +++ b/views/forms.hbs @@ -6,8 +6,8 @@ {{title}} - - + +