diff --git a/package-lock.json b/package-lock.json index 79c32b0f0..b4862777e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,8 +92,10 @@ "@types/better-sqlite3": "^7.6.9", "@types/escape-html": "^1.0.4", "@types/express": "^4.17.21", + "@types/ini": "^4.1.0", "@types/mime-types": "^2.1.4", "@types/node": "^20.11.19", + "@types/ws": "^8.5.10", "cross-env": "7.0.3", "electron": "25.9.8", "electron-builder": "24.6.4", @@ -1444,6 +1446,12 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "node_modules/@types/ini": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.0.tgz", + "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -1589,6 +1597,15 @@ "dev": true, "optional": true }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", @@ -16086,6 +16103,12 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "@types/ini": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.0.tgz", + "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", + "dev": true + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -16231,6 +16254,15 @@ "dev": true, "optional": true }, + "@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", diff --git a/package.json b/package.json index 608d4c3a4..1c9c869f6 100644 --- a/package.json +++ b/package.json @@ -115,8 +115,10 @@ "@types/better-sqlite3": "^7.6.9", "@types/escape-html": "^1.0.4", "@types/express": "^4.17.21", + "@types/ini": "^4.1.0", "@types/mime-types": "^2.1.4", "@types/node": "^20.11.19", + "@types/ws": "^8.5.10", "cross-env": "7.0.3", "electron": "25.9.8", "electron-builder": "24.6.4", diff --git a/src/becca/entities/abstract_becca_entity.ts b/src/becca/entities/abstract_becca_entity.ts index 35d0d2979..0f9769fb6 100644 --- a/src/becca/entities/abstract_becca_entity.ts +++ b/src/becca/entities/abstract_becca_entity.ts @@ -67,7 +67,7 @@ abstract class AbstractBeccaEntity { return this.getPojo(); } - protected abstract getPojo(): {}; + abstract getPojo(): {}; /** * Saves entity - executes SQL, but doesn't commit the transaction on its own diff --git a/src/services/config.js b/src/services/config.ts similarity index 65% rename from src/services/config.js rename to src/services/config.ts index 8bc0723a7..f0437a2d1 100644 --- a/src/services/config.js +++ b/src/services/config.ts @@ -1,10 +1,10 @@ "use strict"; -const ini = require('ini'); -const fs = require('fs'); -const dataDir = require('./data_dir'); -const path = require('path'); -const resourceDir = require('./resource_dir'); +import ini = require('ini'); +import fs = require('fs'); +import dataDir = require('./data_dir'); +import path = require('path'); +import resourceDir = require('./resource_dir'); const configSampleFilePath = path.resolve(resourceDir.RESOURCE_DIR, "config-sample.ini"); @@ -16,4 +16,4 @@ if (!fs.existsSync(dataDir.CONFIG_INI_PATH)) { const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, 'utf-8')); -module.exports = config; +export = config; diff --git a/src/services/entity_changes_interface.ts b/src/services/entity_changes_interface.ts index 0a6409a80..2252f27fb 100644 --- a/src/services/entity_changes_interface.ts +++ b/src/services/entity_changes_interface.ts @@ -1,7 +1,10 @@ export interface EntityChange { id?: number | null; + noteId?: string; entityName: string; entityId: string; + entity?: any; + positions?: Record; hash: string; utcDateChanged: string; isSynced: boolean | 1 | 0; diff --git a/src/services/env.js b/src/services/env.js deleted file mode 100644 index e7fa6caf8..000000000 --- a/src/services/env.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - isDev: function () { - return !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === 'dev'); - } -}; \ No newline at end of file diff --git a/src/services/env.ts b/src/services/env.ts new file mode 100644 index 000000000..23c94ee0f --- /dev/null +++ b/src/services/env.ts @@ -0,0 +1,7 @@ +function isDev() { + return !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === 'dev'); +} + +export = { + isDev +}; \ No newline at end of file diff --git a/src/services/resource_dir.ts b/src/services/resource_dir.ts index 5bd21b31d..cba351ac8 100644 --- a/src/services/resource_dir.ts +++ b/src/services/resource_dir.ts @@ -20,7 +20,7 @@ if (!fs.existsSync(MIGRATIONS_DIR)) { process.exit(1); } -module.exports = { +export = { RESOURCE_DIR, MIGRATIONS_DIR, DB_INIT_DIR, diff --git a/src/services/sql.ts b/src/services/sql.ts index f31d51728..e1c3f5776 100644 --- a/src/services/sql.ts +++ b/src/services/sql.ts @@ -110,7 +110,7 @@ function getValue(query: string, params: Params = []): T { // smaller values can result in better performance due to better usage of statement cache const PARAM_LIMIT = 100; -function getManyRows(query: string, params: Params) { +function getManyRows(query: string, params: Params): T[] | null { let results: unknown[] = []; while (params.length > 0) { @@ -136,7 +136,7 @@ function getManyRows(query: string, params: Params) { results = results.concat(subResults); } - return results; + return results as (T[] | null); } function getRows(query: string, params: Params = []): T[] { diff --git a/src/services/sync_mutex.js b/src/services/sync_mutex.ts similarity index 87% rename from src/services/sync_mutex.js rename to src/services/sync_mutex.ts index fb95d03c4..655af4d59 100644 --- a/src/services/sync_mutex.js +++ b/src/services/sync_mutex.ts @@ -6,7 +6,7 @@ const Mutex = require('async-mutex').Mutex; const instance = new Mutex(); -async function doExclusively(func) { +async function doExclusively(func: () => void) { const releaseMutex = await instance.acquire(); try { @@ -17,6 +17,6 @@ async function doExclusively(func) { } } -module.exports = { +export = { doExclusively }; diff --git a/src/services/ws.js b/src/services/ws.ts similarity index 74% rename from src/services/ws.js rename to src/services/ws.ts index 73caa8d26..9e76868e4 100644 --- a/src/services/ws.js +++ b/src/services/ws.ts @@ -1,15 +1,17 @@ -const WebSocket = require('ws'); -const utils = require('./utils'); -const log = require('./log'); -const sql = require('./sql'); -const cls = require('./cls'); -const config = require('./config.js'); -const syncMutexService = require('./sync_mutex.js'); -const protectedSessionService = require('./protected_session'); -const becca = require('../becca/becca.js'); -const AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity.js'); +import WebSocket = require('ws'); +import utils = require('./utils'); +import log = require('./log'); +import sql = require('./sql'); +import cls = require('./cls'); +import config = require('./config'); +import syncMutexService = require('./sync_mutex.js'); +import protectedSessionService = require('./protected_session'); +import becca = require('../becca/becca.js'); +import AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity.js'); -const env = require('./env.js'); +import env = require('./env'); +import { IncomingMessage, Server } from 'http'; +import { EntityChange } from './entity_changes_interface'; if (env.isDev()) { const chokidar = require('chokidar'); const debounce = require('debounce'); @@ -21,15 +23,26 @@ if (env.isDev()) { .on('unlink', debouncedReloadFrontend); } -let webSocketServer; -let lastSyncedPush = null; +let webSocketServer!: WebSocket.Server; +let lastSyncedPush: number | null = null; -function init(httpServer, sessionParser) { +interface Message { + type: string; + reason?: string; + data?: { + lastSyncedPush?: number, + entityChanges?: any[] + }, + lastSyncedPush?: number +} + +type SessionParser = (req: IncomingMessage, params: {}, cb: () => void) => void; +function init(httpServer: Server, sessionParser: SessionParser) { webSocketServer = new WebSocket.Server({ verifyClient: (info, done) => { sessionParser(info.req, {}, () => { const allowed = utils.isElectron() - || info.req.session.loggedIn + || (info.req as any).session.loggedIn || (config.General && config.General.noAuthentication); if (!allowed) { @@ -43,12 +56,12 @@ function init(httpServer, sessionParser) { }); webSocketServer.on('connection', (ws, req) => { - ws.id = utils.randomString(10); + (ws as any).id = utils.randomString(10); console.log(`websocket client connected`); ws.on('message', async messageJson => { - const message = JSON.parse(messageJson); + const message = JSON.parse(messageJson as any); if (message.type === 'log-error') { log.info(`JS Error: ${message.error}\r @@ -73,7 +86,7 @@ Stack: ${message.stack}`); }); } -function sendMessage(client, message) { +function sendMessage(client: WebSocket, message: Message) { const jsonStr = JSON.stringify(message); if (client.readyState === WebSocket.OPEN) { @@ -81,7 +94,7 @@ function sendMessage(client, message) { } } -function sendMessageToAllClients(message) { +function sendMessageToAllClients(message: Message) { const jsonStr = JSON.stringify(message); if (webSocketServer) { @@ -97,7 +110,7 @@ function sendMessageToAllClients(message) { } } -function fillInAdditionalProperties(entityChange) { +function fillInAdditionalProperties(entityChange: EntityChange) { if (entityChange.isErased) { return; } @@ -123,14 +136,14 @@ function fillInAdditionalProperties(entityChange) { if (!entityChange.entity) { entityChange.entity = sql.getRow(`SELECT * FROM notes WHERE noteId = ?`, [entityChange.entityId]); - if (entityChange.entity.isProtected) { - entityChange.entity.title = protectedSessionService.decryptString(entityChange.entity.title); + if (entityChange.entity && entityChange.entity.isProtected) { + entityChange.entity.title = protectedSessionService.decryptString(entityChange.entity.title || ""); } } } else if (entityChange.entityName === 'revisions') { - entityChange.noteId = sql.getValue(`SELECT noteId - FROM revisions - WHERE revisionId = ?`, [entityChange.entityId]); + entityChange.noteId = sql.getValue(`SELECT noteId + FROM revisions + WHERE revisionId = ?`, [entityChange.entityId]); } else if (entityChange.entityName === 'note_reordering') { entityChange.positions = {}; @@ -160,7 +173,7 @@ function fillInAdditionalProperties(entityChange) { } // entities with higher number can reference the entities with lower number -const ORDERING = { +const ORDERING: Record = { "etapi_tokens": 0, "attributes": 2, "branches": 2, @@ -172,14 +185,17 @@ const ORDERING = { "options": 0 }; -function sendPing(client, entityChangeIds = []) { +function sendPing(client: WebSocket, entityChangeIds = []) { if (entityChangeIds.length === 0) { sendMessage(client, { type: 'ping' }); return; } - const entityChanges = sql.getManyRows(`SELECT * FROM entity_changes WHERE id IN (???)`, entityChangeIds); + const entityChanges = sql.getManyRows(`SELECT * FROM entity_changes WHERE id IN (???)`, entityChangeIds); + if (!entityChanges) { + return; + } // sort entity changes since froca expects "referential order", i.e. referenced entities should already exist // in froca. @@ -190,7 +206,7 @@ function sendPing(client, entityChangeIds = []) { try { fillInAdditionalProperties(entityChange); } - catch (e) { + catch (e: any) { log.error(`Could not fill additional properties for entity change ${JSON.stringify(entityChange)} because of error: ${e.message}: ${e.stack}`); } } @@ -198,7 +214,7 @@ function sendPing(client, entityChangeIds = []) { sendMessage(client, { type: 'frontend-update', data: { - lastSyncedPush, + lastSyncedPush: lastSyncedPush || undefined, entityChanges } }); @@ -213,26 +229,26 @@ function sendTransactionEntityChangesToAllClients() { } function syncPullInProgress() { - sendMessageToAllClients({ type: 'sync-pull-in-progress', lastSyncedPush }); + sendMessageToAllClients({ type: 'sync-pull-in-progress', lastSyncedPush: lastSyncedPush || undefined }); } function syncPushInProgress() { - sendMessageToAllClients({ type: 'sync-push-in-progress', lastSyncedPush }); + sendMessageToAllClients({ type: 'sync-push-in-progress', lastSyncedPush: lastSyncedPush || undefined }); } function syncFinished() { - sendMessageToAllClients({ type: 'sync-finished', lastSyncedPush }); + sendMessageToAllClients({ type: 'sync-finished', lastSyncedPush: lastSyncedPush || undefined }); } function syncFailed() { - sendMessageToAllClients({ type: 'sync-failed', lastSyncedPush }); + sendMessageToAllClients({ type: 'sync-failed', lastSyncedPush: lastSyncedPush || undefined }); } -function reloadFrontend(reason) { +function reloadFrontend(reason: string) { sendMessageToAllClients({ type: 'reload-frontend', reason }); } -function setLastSyncedPush(entityChangeId) { +function setLastSyncedPush(entityChangeId: number) { lastSyncedPush = entityChangeId; }