From 7ff648ba3a966cf4f92ea2e2a7361a456cb24cef Mon Sep 17 00:00:00 2001 From: olcxja Date: Mon, 11 May 2026 19:22:30 +0200 Subject: [PATCH] Compatibility tweaks - Fix (i hope all) electron issues - Add complete blah support - Add userscript --- .../src/main/assets/public/blah/en-us.json | 40 +++ android/app/src/main/assets/public/index.html | 41 +++- .../src/main/assets/public/login/index.html | 43 ++-- android/app/src/main/assets/public/main.js | 197 +++++++++++---- android/app/src/main/assets/public/style.css | 7 +- .../app/src/main/assets/public/userscript.js | 4 + electron/build/src/preload.js | 2 +- electron/build/src/setup.js | 15 +- electron/src/index.ts | 70 ------ electron/src/preload.ts | 4 - electron/src/rt/electron-plugins.js | 4 - electron/src/rt/electron-rt.ts | 88 ------- electron/src/setup.ts | 231 ------------------ webroot/blah/en-us.json | 40 +++ webroot/index.html | 24 +- webroot/login/index.html | 44 ++-- webroot/main.js | 204 +++++++++++----- webroot/style.css | 4 + webroot/userscript.js | 4 + 19 files changed, 484 insertions(+), 582 deletions(-) create mode 100644 android/app/src/main/assets/public/blah/en-us.json create mode 100644 android/app/src/main/assets/public/userscript.js delete mode 100644 electron/src/index.ts delete mode 100644 electron/src/preload.ts delete mode 100644 electron/src/rt/electron-plugins.js delete mode 100644 electron/src/rt/electron-rt.ts delete mode 100644 electron/src/setup.ts create mode 100644 webroot/blah/en-us.json create mode 100644 webroot/userscript.js diff --git a/android/app/src/main/assets/public/blah/en-us.json b/android/app/src/main/assets/public/blah/en-us.json new file mode 100644 index 00000000..33f4e976 --- /dev/null +++ b/android/app/src/main/assets/public/blah/en-us.json @@ -0,0 +1,40 @@ +{ + "user.not.found": "User not found", + "cant.invite.urself": "You can't invite yourself", + "user.already.invited": "You have already invited this user", + "user.invited": "User invited successfully", + "cant.create.dm.without.invitation": "You can't create a dm without invitation", + "dm.begin.notice": "At the beginning of a DM, you should always verify that the person you're talking to is the intended recipient", + "failed.accept.dm": "Failed to accept DM", + "dm.accepted": "DM accepted", + "no.invite.found": "You can't create a dm without invitation", + "unknown.error": "Unknown error", + "account.creation.request.expired": "Account creation request expired. Try again", + "invalid.username": "Invalid username: {0}", + "invalid.password": "Invalid password: {0}", + "incorrect.captcha": "Incorrect captcha. Try again", + "username.taken": "This username is already taken", + "accounts.slots.full": "You can't create a new account because all ids are used. Try again later", + "registration.disabled": "Registration disabled", + "account.created": "Account created successfully", + "password.changed": "Password changed successfully", + "keys.updated": "Keys updated successfully", + "unknown.request": "Unknown request: {0}", + "login.successful": "Login successful", + "username.length": "Username must be {0} characters long", + "username.conditions.allowed": "Username can only include {all}", + "password.not.hashed.properly": "Password is not hashed properly", + "invalid.username.or.password": "Invalid username or password", + "account.not.exist": "Account with this name doesn't exist", + "invalid.nonce": "Invalid nonce. Try again", + "username.changed": "Username changed successfully", + "auth.failed.redirect.to.login": "Failed to auth. Redirecting to login...", + "dm.refresh.failed": "Failed to refresh dms", + "chat.add.failed": "Failed to add chat", + "something.wrong.mayb.pass": "Something went wrong... (probably wrong password)", + "something.wrong": "Something went wrong...", + "passwords.not.match": "Passwords do not match", + "password.cant.empty": "Password cannot be empty", + "username.cant.empty": "Username cannot be empty", + "bad.request": "Bad request" +} \ No newline at end of file diff --git a/android/app/src/main/assets/public/index.html b/android/app/src/main/assets/public/index.html index 125026b0..41dc0412 100644 --- a/android/app/src/main/assets/public/index.html +++ b/android/app/src/main/assets/public/index.html @@ -84,14 +84,14 @@ Splash -
+
Welcome to Miarven

First Larpix client. v1.0

-
- + + - - \ No newline at end of file + \ No newline at end of file diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js index 7f2b728d..f046bf40 100644 --- a/android/app/src/main/assets/public/main.js +++ b/android/app/src/main/assets/public/main.js @@ -1,49 +1,74 @@ -console.log(window.location.protocol); +var prot = window.location.protocol; -var url = `${window.location.protocol}//${window.location.hostname}/_larpix`; +async function updateProtocolAndUrl(host) +{ + prot = window.location.protocol == "miarven:" ? "http:" : window.location.protocol; + if (prot == "http:") { + try { + JSON.parse(await fetchAsync(`${prot}//${host}/_larpix/serverinfo`)); + } + catch (error) { + try { + JSON.parse(await fetchAsync(`https://${host}/_larpix/serverinfo`)); + prot = "https:"; + } + catch (error) {} + } + } + url = `${prot}//${host}/_larpix`; +} + +console.log(prot); + +var url = `${prot}//${window.location.hostname}/_larpix`; var params = new URLSearchParams(window.location.search); -const collapseDmsBtn = document.getElementById("collapse-dms"); -const collapseGroupsBtn = document.getElementById("collapse-groups"); +try { -const addDmBtn = document.getElementById("add-dm-btn"); -const addGroupBtn = document.getElementById("add-group-btn"); -const sidebarHome = document.getElementById("sidebar-home"); -const sidebarHomeButton = sidebarHome.children.item(1); -const sidebarHomeIndicator = sidebarHome.children.item(0); + var collapseDmsBtn = document.getElementById("collapse-dms"); + var collapseGroupsBtn = document.getElementById("collapse-groups"); -const sidebarAdd = document.getElementById("sidebar-add"); -const sidebarAddButton = sidebarAdd.children.item(1); -const sidebarAddIndicator = sidebarAdd.children.item(0); + var addDmBtn = document.getElementById("add-dm-btn"); + var addGroupBtn = document.getElementById("add-group-btn"); -const roomDetailsBar = document.getElementById("roomdetailsbar"); -const roomContent = document.getElementsByTagName("roomcontent")[0]; -const roomsBar = document.getElementById("roomsbar"); -const sideBar = document.getElementsByTagName("sidebar")[0]; + var sidebarHome = document.getElementById("sidebar-home"); + var sidebarHomeButton = sidebarHome.children.item(1); + var sidebarHomeIndicator = sidebarHome.children.item(0); -const roomContentMain = document.getElementsByTagName("roomcontent2")[0]; + var sidebarAdd = document.getElementById("sidebar-add"); + var sidebarAddButton = sidebarAdd.children.item(1); + var sidebarAddIndicator = sidebarAdd.children.item(0); -const roomContentBar = roomContent.children[0]; + var roomDetailsBar = document.getElementById("roomdetailsbar"); + var roomContent = document.getElementsByTagName("roomcontent")[0]; + var roomsBar = document.getElementById("roomsbar"); + var sideBar = document.getElementsByTagName("sidebar")[0]; + var roomContentMain = document.getElementsByTagName("roomcontent2")[0]; + + var roomContentBar = roomContent.children[0]; + +} catch (e) { +} function delay(time) { return new Promise(resolve => setTimeout(resolve, time)); } + async function packetEncPass(pass, key, username) { return await encryptWithNonce(pass, key, getNonce(username, key)); } async function getNonce(username, key) { - + let nonce; let fetchRes = await (await fetch(`${url}/nextnonce?u=${username}`)).text(); try { nonce = await decryptString(fetchRes, key); - } - catch(err) { + } catch (err) { nonce = await decryptString(fetchRes, ""); } return nonce; @@ -83,12 +108,13 @@ function keyDataFromServerJson(jsonFromServer) { return [p, g, pubServer, data.idKey] } -function base64ToUint8(base64) { +function base64ToUint8(base64) { return Uint8Array.from(atob(base64), c => c.charCodeAt(0)); } + function uint8ToBase64(uint8) { return fixBase64Padding(btoa(String.fromCharCode(...uint8))); -} +} async function encrypt(plainText, keyBytes) { const iv = window.crypto.getRandomValues(new Uint8Array(16)); @@ -241,10 +267,10 @@ async function fetchPostEnc(url, value) { async function fetchAsync(url) { let response = await fetch(url, { - method: "GET", + method: "GET", credentials: "omit" }); - + let data = await response.text(); return data; } @@ -262,15 +288,13 @@ async function fetchAsyncWAuth(url) { return data; } -async function getServerInfo(host){ - console.log(`${window.location.protocol}//${host}/_larpix/serverinfo`) - return JSON.parse(await fetchAsync(`${window.location.protocol}//${host}/_larpix/serverinfo`)); +async function getServerInfo(host) { + console.log(`${prot}//${host}/_larpix/serverinfo`) + return JSON.parse(await fetchAsync(`${prot}//${host}/_larpix/serverinfo`)); } async function Auth(username, password) { - - - let passwordHash = await hashSHA3_512(password); + let passwordHash = await hashSHA3_512(password); let response = await fetch(`${url}/auth?u=${username}`, { method: "GET", credentials: "omit", @@ -285,16 +309,14 @@ async function Auth(username, password) { } -async function fetchEncrypted(request, body) -{ +async function fetchEncrypted(request, body) { let nonce = await getNonce(username, passwordHash); - + let response = await fetch(`${url}/encryptedrequest?u=${username}`, { method: "POST", credentials: "omit", body: await encryptWithNonce( - JSON.stringify({ string1: request, string2: body @@ -334,6 +356,15 @@ function fixBase64Padding(base64String) { return str; } +function showBlahNotification(blahmessage, duration = 3500) { + try { + let split = blahmessage.split(":"); + showNotification(processBlah(blahmessage), split[0], duration); + } catch (e) { + showNotification(blahmessage, "info", duration); + } +} + function showNotification(message, type = 'info', duration = 3500) { let container = document.getElementById('notification-container'); if (!container) { @@ -385,40 +416,99 @@ function showAction(message, actionid) { }); }); } + function clearAction(actionid) { + const notifications = document.querySelectorAll(`[id="notification-${actionid}"]`); - let notif = document.getElementById(`notification-${actionid}`); - notif.classList.remove('show'); + notifications.forEach(notif => { + notif.classList.remove('show'); - notif.addEventListener('transitionend', () => { - notif.remove(); + notif.addEventListener('transitionend', () => { + notif.remove(); + }, { once: true }); }); } async function hashSHA3_512(input) { const encoder = new TextEncoder(); - const data = encoder.encode(input); + const data = encoder.encode(input); const hashBuffer = await crypto.subtle.digest('SHA-512', data); //-3 kiedys xddddddddddddddddd - const hashArray = Array.from(new Uint8Array(hashBuffer)); - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - return hashHex; + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + return hashHex; } +var blah; +async function initBlahs() { + lang = lang.toLowerCase(); + let res; + + let path = window.location.pathname.replace("/login/index.html", "/"); + + try { //try user lang first + res = await fetchAsync(`${path}blah/${lang}.json`); + } catch (e) { //fallback to en-us + res = await fetchAsync(`${path}blah/en-us.json`); + } + blah = JSON.parse(res); +} + +function processBlah(blahmessage) { + try { + let split = blahmessage.split(":"); + let values = []; + try { + values = split[2].split(";"); + } catch (e) { + } + + let message = blah[split[1]]; + + let valueslist = ""; + for (let i = 0; i < values.length; i++) { + let value = values[i]; + valueslist+=`${values[i]}, `; + processBlah(value); + + message = message.replaceAll(`{${i}}`, value); + } + valueslist = valueslist.slice(0, -2); + if (message.includes('{all}')) { + + message = message.replaceAll('{all}', valueslist); + } + + return message; + } + catch (e) + { + return blahmessage; + } +} + +function getLang() { + return (navigator.language || navigator.languages[0]); +} var password = ""; var username = ""; var passwordHash = ""; var host = ""; -async function mainJS() -{ - username = localStorage.getItem('username'); - password = localStorage.getItem('password'); - host = localStorage.getItem('host'); +var lang = getLang(); + +async function mainJS() { + await initBlahs(); + passwordHash = await hashSHA3_512(password); - url = `${window.location.protocol}//${host}/_larpix`; + if (localStorage.getItem('lang') != null) { + lang = localStorage.getItem('lang'); + } } +username = localStorage.getItem('username'); +password = localStorage.getItem('password'); +host = localStorage.getItem('host'); mainJS(); collapseDmsBtn.addEventListener("click", () => { @@ -429,9 +519,9 @@ collapseGroupsBtn.addEventListener("click", () => { }); addDmBtn.addEventListener("click", () => { - roomContentMain.innerHTML = + roomContentMain.innerHTML = ` -
+
Add Chat

Add a private, encrypted chat by entering a username

@@ -441,7 +531,7 @@ addDmBtn.addEventListener("click", () => {
-
@@ -449,9 +539,8 @@ addDmBtn.addEventListener("click", () => { ` }); addGroupBtn.addEventListener("click", () => { - -}); +}); sidebarHomeButton.addEventListener("mouseenter", () => { diff --git a/android/app/src/main/assets/public/style.css b/android/app/src/main/assets/public/style.css index 6a571f5f..663eea28 100644 --- a/android/app/src/main/assets/public/style.css +++ b/android/app/src/main/assets/public/style.css @@ -17,6 +17,10 @@ --big-red: rgb(202 0 0); --big-green: rgb(25 189 0); } +*:focus { + outline: none; + box-shadow: none; +} .red { color: var(--big-red); } @@ -303,11 +307,12 @@ herotitle { .input-group { + text-align: left; display: flex; flex-direction: column; gap: 0.2rem; - width: 80%; + width: 90%; max-width: 30rem; } diff --git a/android/app/src/main/assets/public/userscript.js b/android/app/src/main/assets/public/userscript.js new file mode 100644 index 00000000..a41b1768 --- /dev/null +++ b/android/app/src/main/assets/public/userscript.js @@ -0,0 +1,4 @@ +//write your custom deployment scripts here + +loginHost.value = "olcxja.ovh"; +loginHostChanged(); \ No newline at end of file diff --git a/electron/build/src/preload.js b/electron/build/src/preload.js index c817d3b7..02904818 100644 --- a/electron/build/src/preload.js +++ b/electron/build/src/preload.js @@ -1,4 +1,4 @@ require('./rt/electron-rt'); ////////////////////////////// // User Defined Preload scripts below -console.log('User Preload!'); +//console.log('User Preload!'); diff --git a/electron/build/src/setup.js b/electron/build/src/setup.js index 286d2d14..344c3e28 100644 --- a/electron/build/src/setup.js +++ b/electron/build/src/setup.js @@ -56,7 +56,7 @@ class ElectronCapacitorApp { { role: 'viewMenu' }, ]; this.CapacitorFileConfig = capacitorFileConfig; - this.customScheme = (_b = (_a = this.CapacitorFileConfig.electron) === null || _a === void 0 ? void 0 : _a.customUrlScheme) !== null && _b !== void 0 ? _b : 'capacitor-electron'; + this.customScheme = (_b = (_a = this.CapacitorFileConfig.electron) === null || _a === void 0 ? void 0 : _a.customUrlScheme) !== null && _b !== void 0 ? _b : 'miarven'; if (trayMenuTemplate) { this.TrayMenuTemplate = trayMenuTemplate; } @@ -101,8 +101,10 @@ class ElectronCapacitorApp { height: this.mainWindowState.height, autoHideMenuBar: true, webPreferences: { - nodeIntegration: true, + nodeIntegration: false, contextIsolation: true, + webSecurity: true, + allowRunningInsecureContent: true, // Use preload to inject the electron varriant overrides for capacitor plugins. // preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"), preload: preloadPath, @@ -175,6 +177,11 @@ class ElectronCapacitorApp { electron_1.CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', ''); }, 400); }); + this.MainWindow.webContents.on('before-input-event', (event, input) => { + if (input.control && input.shift && input.key.toLowerCase() === 'i') { + this.MainWindow.webContents.openDevTools(); + } + }); this.MainWindow.setMenu(null); this.MainWindow.setAutoHideMenuBar(true); this.MainWindow.removeMenu(); @@ -186,8 +193,8 @@ function setupContentSecurityPolicy(customScheme) { electron_2.session.defaultSession.webRequest.onHeadersReceived((details, callback) => { callback({ responseHeaders: Object.assign(Object.assign({}, details.responseHeaders), { 'Content-Security-Policy': [ - electronIsDev - ? `default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data:` + true + ? `default-src ${customScheme}://* 'unsafe-inline' 'unsafe-eval' devtools://* http://* https://* ws://* wss://* * data: blob:; frame-src *; style-src * 'unsafe-inline';` : `default-src ${customScheme}://* 'unsafe-inline' data:`, ] }), }); diff --git a/electron/src/index.ts b/electron/src/index.ts deleted file mode 100644 index 8d70f291..00000000 --- a/electron/src/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { CapacitorElectronConfig } from '@capacitor-community/electron'; -import { getCapacitorElectronConfig, setupElectronDeepLinking } from '@capacitor-community/electron'; -import type { MenuItemConstructorOptions } from 'electron'; -import { app, MenuItem } from 'electron'; -var electronIsDev = false; -import unhandled from 'electron-unhandled'; -//import { autoUpdater } from 'electron-updater'; - -import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup'; - -// Graceful handling of unhandled errors. -unhandled(); - -// Define our menu templates (these are optional) -const trayMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [new MenuItem({ label: 'Quit App', role: 'quit' })]; -const appMenuBarMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [ - { role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' }, - { role: 'viewMenu' }, -]; - -// Get Config options from capacitor.config -const capacitorFileConfig: CapacitorElectronConfig = getCapacitorElectronConfig(); - -// Initialize our app. You can pass menu templates into the app here. -// const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig); -const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate); - -// If deeplinking is enabled then we will set it up here. -if (capacitorFileConfig.electron?.deepLinkingEnabled) { - setupElectronDeepLinking(myCapacitorApp, { - customProtocol: capacitorFileConfig.electron.deepLinkingCustomProtocol ?? 'mycapacitorapp', - }); -} - -// If we are in Dev mode, use the file watcher components. -if (electronIsDev) { - setupReloadWatcher(myCapacitorApp); -} - -// Run Application -(async () => { - // Wait for electron app to be ready. - await app.whenReady(); - // Security - Set Content-Security-Policy based on whether or not we are in dev mode. - setupContentSecurityPolicy(myCapacitorApp.getCustomURLScheme()); - // Initialize our app, build windows, and load content. - await myCapacitorApp.init(); - // Check for updates if we are in a packaged app. - //autoUpdater.checkForUpdatesAndNotify(); -})(); - -// Handle when all of our windows are close (platforms have their own expectations). -app.on('window-all-closed', function () { - // On OS X it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit(); - } -}); - -// When the dock icon is clicked. -app.on('activate', async function () { - // On OS X it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (myCapacitorApp.getMainWindow().isDestroyed()) { - await myCapacitorApp.init(); - } -}); - -// Place all ipc or other electron api calls and custom functionality under this line diff --git a/electron/src/preload.ts b/electron/src/preload.ts deleted file mode 100644 index c817d3b7..00000000 --- a/electron/src/preload.ts +++ /dev/null @@ -1,4 +0,0 @@ -require('./rt/electron-rt'); -////////////////////////////// -// User Defined Preload scripts below -console.log('User Preload!'); diff --git a/electron/src/rt/electron-plugins.js b/electron/src/rt/electron-plugins.js deleted file mode 100644 index b33b2826..00000000 --- a/electron/src/rt/electron-plugins.js +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - -module.exports = { -} \ No newline at end of file diff --git a/electron/src/rt/electron-rt.ts b/electron/src/rt/electron-rt.ts deleted file mode 100644 index 55d67c30..00000000 --- a/electron/src/rt/electron-rt.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { randomBytes } from 'crypto'; -import { ipcRenderer, contextBridge } from 'electron'; -import { EventEmitter } from 'events'; - -//////////////////////////////////////////////////////// -// eslint-disable-next-line @typescript-eslint/no-var-requires -const plugins = require('./electron-plugins'); - -const randomId = (length = 5) => randomBytes(length).toString('hex'); - -const contextApi: { - [plugin: string]: { [functionName: string]: () => Promise }; -} = {}; - -Object.keys(plugins).forEach((pluginKey) => { - Object.keys(plugins[pluginKey]) - .filter((className) => className !== 'default') - .forEach((classKey) => { - const functionList = Object.getOwnPropertyNames(plugins[pluginKey][classKey].prototype).filter( - (v) => v !== 'constructor' - ); - - if (!contextApi[classKey]) { - contextApi[classKey] = {}; - } - - functionList.forEach((functionName) => { - if (!contextApi[classKey][functionName]) { - contextApi[classKey][functionName] = (...args) => ipcRenderer.invoke(`${classKey}-${functionName}`, ...args); - } - }); - - // Events - if (plugins[pluginKey][classKey].prototype instanceof EventEmitter) { - const listeners: { [key: string]: { type: string; listener: (...args: any[]) => void } } = {}; - const listenersOfTypeExist = (type) => - !!Object.values(listeners).find((listenerObj) => listenerObj.type === type); - - Object.assign(contextApi[classKey], { - addListener(type: string, callback: (...args) => void) { - const id = randomId(); - - // Deduplicate events - if (!listenersOfTypeExist(type)) { - ipcRenderer.send(`event-add-${classKey}`, type); - } - - const eventHandler = (_, ...args) => callback(...args); - - ipcRenderer.addListener(`event-${classKey}-${type}`, eventHandler); - listeners[id] = { type, listener: eventHandler }; - - return id; - }, - removeListener(id: string) { - if (!listeners[id]) { - throw new Error('Invalid id'); - } - - const { type, listener } = listeners[id]; - - ipcRenderer.removeListener(`event-${classKey}-${type}`, listener); - - delete listeners[id]; - - if (!listenersOfTypeExist(type)) { - ipcRenderer.send(`event-remove-${classKey}-${type}`); - } - }, - removeAllListeners(type: string) { - Object.entries(listeners).forEach(([id, listenerObj]) => { - if (!type || listenerObj.type === type) { - ipcRenderer.removeListener(`event-${classKey}-${listenerObj.type}`, listenerObj.listener); - ipcRenderer.send(`event-remove-${classKey}-${listenerObj.type}`); - delete listeners[id]; - } - }); - }, - }); - } - }); -}); - -contextBridge.exposeInMainWorld('CapacitorCustomPlatform', { - name: 'electron', - plugins: contextApi, -}); -//////////////////////////////////////////////////////// diff --git a/electron/src/setup.ts b/electron/src/setup.ts deleted file mode 100644 index de092b19..00000000 --- a/electron/src/setup.ts +++ /dev/null @@ -1,231 +0,0 @@ -import type { CapacitorElectronConfig } from '@capacitor-community/electron'; -import { - CapElectronEventEmitter, - CapacitorSplashScreen, - setupCapacitorElectronPlugins, -} from '@capacitor-community/electron'; -import chokidar from 'chokidar'; -import type { MenuItemConstructorOptions } from 'electron'; -import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session } from 'electron'; -//import electronIsDev from 'electron-is-dev'; -var electronIsDev = false; -import electronServe from 'electron-serve'; -import windowStateKeeper from 'electron-window-state'; -import { join } from 'path'; - -// Define components for a watcher to detect when the webapp is changed so we can reload in Dev mode. -const reloadWatcher = { - debouncer: null, - ready: false, - watcher: null, -}; -export function setupReloadWatcher(electronCapacitorApp: ElectronCapacitorApp): void { - reloadWatcher.watcher = chokidar - .watch(join(app.getAppPath(), 'app'), { - ignored: /[/\\]\./, - persistent: true, - }) - .on('ready', () => { - reloadWatcher.ready = true; - }) - .on('all', (_event, _path) => { - if (reloadWatcher.ready) { - clearTimeout(reloadWatcher.debouncer); - reloadWatcher.debouncer = setTimeout(async () => { - electronCapacitorApp.getMainWindow().webContents.reload(); - reloadWatcher.ready = false; - clearTimeout(reloadWatcher.debouncer); - reloadWatcher.debouncer = null; - reloadWatcher.watcher = null; - setupReloadWatcher(electronCapacitorApp); - }, 1500); - } - }); -} - -// Define our class to manage our app. -export class ElectronCapacitorApp { - private MainWindow: BrowserWindow | null = null; - private SplashScreen: CapacitorSplashScreen | null = null; - private TrayIcon: Tray | null = null; - private CapacitorFileConfig: CapacitorElectronConfig; - private TrayMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [ - new MenuItem({ label: 'Quit App', role: 'quit' }), - ]; - private AppMenuBarMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [ - { role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' }, - { role: 'viewMenu' }, - ]; - private mainWindowState; - private loadWebApp; - private customScheme: string; - - constructor( - capacitorFileConfig: CapacitorElectronConfig, - trayMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[], - appMenuBarMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[] - ) { - this.CapacitorFileConfig = capacitorFileConfig; - - this.customScheme = this.CapacitorFileConfig.electron?.customUrlScheme ?? 'capacitor-electron'; - - if (trayMenuTemplate) { - this.TrayMenuTemplate = trayMenuTemplate; - } - - if (appMenuBarMenuTemplate) { - this.AppMenuBarMenuTemplate = appMenuBarMenuTemplate; - } - - // Setup our web app loader, this lets us load apps like react, vue, and angular without changing their build chains. - this.loadWebApp = electronServe({ - directory: join(app.getAppPath(), 'app'), - scheme: this.customScheme, - }); - } - - // Helper function to load in the app. - private async loadMainWindow(thisRef: any) { - await thisRef.loadWebApp(thisRef.MainWindow); - } - - // Expose the mainWindow ref for use outside of the class. - getMainWindow(): BrowserWindow { - return this.MainWindow; - } - - getCustomURLScheme(): string { - return this.customScheme; - } - - async init(): Promise { - const icon = nativeImage.createFromPath( - join(app.getAppPath(), 'assets', process.platform === 'win32' ? 'icon.ico' : 'icon.png') - ); - - const appName = "olcxja.miarven" - app.setName(appName); - - app.setAppUserModelId(appName); - - this.mainWindowState = windowStateKeeper({ - defaultWidth: 1000, - defaultHeight: 800, - }); - // Setup preload script path and construct our main window. - const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js'); - this.MainWindow = new BrowserWindow({ - icon, - show: false, - x: this.mainWindowState.x, - y: this.mainWindowState.y, - width: this.mainWindowState.width, - height: this.mainWindowState.height, - autoHideMenuBar: true, - webPreferences: { - nodeIntegration: true, - contextIsolation: true, - // Use preload to inject the electron varriant overrides for capacitor plugins. - // preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"), - preload: preloadPath, - }, - }); - - this.mainWindowState.manage(this.MainWindow); - - if (this.CapacitorFileConfig.backgroundColor) { - this.MainWindow.setBackgroundColor(this.CapacitorFileConfig.electron.backgroundColor); - } - - // If we close the main window with the splashscreen enabled we need to destory the ref. - this.MainWindow.on('closed', () => { - if (this.SplashScreen?.getSplashWindow() && !this.SplashScreen.getSplashWindow().isDestroyed()) { - this.SplashScreen.getSplashWindow().close(); - } - }); - - // When the tray icon is enabled, setup the options. - if (this.CapacitorFileConfig.electron?.trayIconAndMenuEnabled) { - this.TrayIcon = new Tray(icon); - this.TrayIcon.on('double-click', () => { - if (this.MainWindow) { - if (this.MainWindow.isVisible()) { - this.MainWindow.hide(); - } else { - this.MainWindow.show(); - this.MainWindow.focus(); - } - } - }); - this.TrayIcon.on('click', () => { - if (this.MainWindow) { - if (this.MainWindow.isVisible()) { - this.MainWindow.hide(); - } else { - this.MainWindow.show(); - this.MainWindow.focus(); - } - } - }); - this.TrayIcon.setToolTip(app.getName()); - this.TrayIcon.setContextMenu(Menu.buildFromTemplate(this.TrayMenuTemplate)); - } - - // Setup the main manu bar at the top of our window. - //Menu.setApplicationMenu(Menu.buildFromTemplate(this.AppMenuBarMenuTemplate)); - Menu.setApplicationMenu(null); - - - this.loadMainWindow(this); - - - // Security - this.MainWindow.webContents.setWindowOpenHandler((details) => { - - return { action: 'allow' }; - - }); - this.MainWindow.webContents.on('will-navigate', (event, _newURL) => { - if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) { - event.preventDefault(); - } - }); - - // Link electron plugins into the system. - setupCapacitorElectronPlugins(); - - // When the web app is loaded we hide the splashscreen if needed and show the mainwindow. - this.MainWindow.webContents.on('dom-ready', () => { - if (this.CapacitorFileConfig.electron?.splashScreenEnabled) { - this.SplashScreen.getSplashWindow().hide(); - } - if (!this.CapacitorFileConfig.electron?.hideMainWindowOnLaunch) { - this.MainWindow.show(); - } - setTimeout(() => { - - CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', ''); - }, 400); - }); - - this.MainWindow.setMenu(null); - this.MainWindow.setAutoHideMenuBar(true); - this.MainWindow.removeMenu(); - } -} - -// Set a CSP up for our application based on the custom scheme -export function setupContentSecurityPolicy(customScheme: string): void { - session.defaultSession.webRequest.onHeadersReceived((details, callback) => { - callback({ - responseHeaders: { - ...details.responseHeaders, - 'Content-Security-Policy': [ - electronIsDev - ? `default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data:` - : `default-src ${customScheme}://* 'unsafe-inline' data:`, - ], - }, - }); - }); -} diff --git a/webroot/blah/en-us.json b/webroot/blah/en-us.json new file mode 100644 index 00000000..33f4e976 --- /dev/null +++ b/webroot/blah/en-us.json @@ -0,0 +1,40 @@ +{ + "user.not.found": "User not found", + "cant.invite.urself": "You can't invite yourself", + "user.already.invited": "You have already invited this user", + "user.invited": "User invited successfully", + "cant.create.dm.without.invitation": "You can't create a dm without invitation", + "dm.begin.notice": "At the beginning of a DM, you should always verify that the person you're talking to is the intended recipient", + "failed.accept.dm": "Failed to accept DM", + "dm.accepted": "DM accepted", + "no.invite.found": "You can't create a dm without invitation", + "unknown.error": "Unknown error", + "account.creation.request.expired": "Account creation request expired. Try again", + "invalid.username": "Invalid username: {0}", + "invalid.password": "Invalid password: {0}", + "incorrect.captcha": "Incorrect captcha. Try again", + "username.taken": "This username is already taken", + "accounts.slots.full": "You can't create a new account because all ids are used. Try again later", + "registration.disabled": "Registration disabled", + "account.created": "Account created successfully", + "password.changed": "Password changed successfully", + "keys.updated": "Keys updated successfully", + "unknown.request": "Unknown request: {0}", + "login.successful": "Login successful", + "username.length": "Username must be {0} characters long", + "username.conditions.allowed": "Username can only include {all}", + "password.not.hashed.properly": "Password is not hashed properly", + "invalid.username.or.password": "Invalid username or password", + "account.not.exist": "Account with this name doesn't exist", + "invalid.nonce": "Invalid nonce. Try again", + "username.changed": "Username changed successfully", + "auth.failed.redirect.to.login": "Failed to auth. Redirecting to login...", + "dm.refresh.failed": "Failed to refresh dms", + "chat.add.failed": "Failed to add chat", + "something.wrong.mayb.pass": "Something went wrong... (probably wrong password)", + "something.wrong": "Something went wrong...", + "passwords.not.match": "Passwords do not match", + "password.cant.empty": "Password cannot be empty", + "username.cant.empty": "Username cannot be empty", + "bad.request": "Bad request" +} \ No newline at end of file diff --git a/webroot/index.html b/webroot/index.html index 441f2e55..41dc0412 100644 --- a/webroot/index.html +++ b/webroot/index.html @@ -100,7 +100,14 @@ \ No newline at end of file + + \ No newline at end of file diff --git a/webroot/login/index.html b/webroot/login/index.html index b05299de..c844e976 100644 --- a/webroot/login/index.html +++ b/webroot/login/index.html @@ -264,11 +264,10 @@
- + + - - \ No newline at end of file + \ No newline at end of file diff --git a/webroot/main.js b/webroot/main.js index f09833fa..f046bf40 100644 --- a/webroot/main.js +++ b/webroot/main.js @@ -1,50 +1,74 @@ -console.log(window.location.protocol); +var prot = window.location.protocol; -var url = `${window.location.protocol}//${window.location.hostname}/_larpix`; +async function updateProtocolAndUrl(host) +{ + prot = window.location.protocol == "miarven:" ? "http:" : window.location.protocol; + if (prot == "http:") { + try { + JSON.parse(await fetchAsync(`${prot}//${host}/_larpix/serverinfo`)); + } + catch (error) { + try { + JSON.parse(await fetchAsync(`https://${host}/_larpix/serverinfo`)); + prot = "https:"; + } + catch (error) {} + } + } + url = `${prot}//${host}/_larpix`; +} + +console.log(prot); + +var url = `${prot}//${window.location.hostname}/_larpix`; var params = new URLSearchParams(window.location.search); -const collapseDmsBtn = document.getElementById("collapse-dms"); -const collapseGroupsBtn = document.getElementById("collapse-groups"); - -const addDmBtn = document.getElementById("add-dm-btn"); -const addGroupBtn = document.getElementById("add-group-btn"); - -const sidebarHome = document.getElementById("sidebar-home"); -const sidebarHomeButton = sidebarHome.children.item(1); -const sidebarHomeIndicator = sidebarHome.children.item(0); - -const sidebarAdd = document.getElementById("sidebar-add"); -const sidebarAddButton = sidebarAdd.children.item(1); -const sidebarAddIndicator = sidebarAdd.children.item(0); - -const roomDetailsBar = document.getElementById("roomdetailsbar"); -const roomContent = document.getElementsByTagName("roomcontent")[0]; -const roomsBar = document.getElementById("roomsbar"); -const sideBar = document.getElementsByTagName("sidebar")[0]; - -const roomContentMain = document.getElementsByTagName("roomcontent2")[0]; - -const roomContentBar = roomContent.children[0]; +try { + var collapseDmsBtn = document.getElementById("collapse-dms"); + var collapseGroupsBtn = document.getElementById("collapse-groups"); + + var addDmBtn = document.getElementById("add-dm-btn"); + var addGroupBtn = document.getElementById("add-group-btn"); + + var sidebarHome = document.getElementById("sidebar-home"); + var sidebarHomeButton = sidebarHome.children.item(1); + var sidebarHomeIndicator = sidebarHome.children.item(0); + + var sidebarAdd = document.getElementById("sidebar-add"); + var sidebarAddButton = sidebarAdd.children.item(1); + var sidebarAddIndicator = sidebarAdd.children.item(0); + + var roomDetailsBar = document.getElementById("roomdetailsbar"); + var roomContent = document.getElementsByTagName("roomcontent")[0]; + var roomsBar = document.getElementById("roomsbar"); + var sideBar = document.getElementsByTagName("sidebar")[0]; + + var roomContentMain = document.getElementsByTagName("roomcontent2")[0]; + + var roomContentBar = roomContent.children[0]; + +} catch (e) { +} function delay(time) { return new Promise(resolve => setTimeout(resolve, time)); } + async function packetEncPass(pass, key, username) { return await encryptWithNonce(pass, key, getNonce(username, key)); } async function getNonce(username, key) { - + let nonce; let fetchRes = await (await fetch(`${url}/nextnonce?u=${username}`)).text(); try { nonce = await decryptString(fetchRes, key); - } - catch(err) { + } catch (err) { nonce = await decryptString(fetchRes, ""); } return nonce; @@ -84,12 +108,13 @@ function keyDataFromServerJson(jsonFromServer) { return [p, g, pubServer, data.idKey] } -function base64ToUint8(base64) { +function base64ToUint8(base64) { return Uint8Array.from(atob(base64), c => c.charCodeAt(0)); } + function uint8ToBase64(uint8) { return fixBase64Padding(btoa(String.fromCharCode(...uint8))); -} +} async function encrypt(plainText, keyBytes) { const iv = window.crypto.getRandomValues(new Uint8Array(16)); @@ -242,10 +267,10 @@ async function fetchPostEnc(url, value) { async function fetchAsync(url) { let response = await fetch(url, { - method: "GET", + method: "GET", credentials: "omit" }); - + let data = await response.text(); return data; } @@ -263,15 +288,13 @@ async function fetchAsyncWAuth(url) { return data; } -async function getServerInfo(host){ - console.log(`${window.location.protocol}//${host}/_larpix/serverinfo`) - return JSON.parse(await fetchAsync(`${window.location.protocol}//${host}/_larpix/serverinfo`)); +async function getServerInfo(host) { + console.log(`${prot}//${host}/_larpix/serverinfo`) + return JSON.parse(await fetchAsync(`${prot}//${host}/_larpix/serverinfo`)); } async function Auth(username, password) { - - - let passwordHash = await hashSHA3_512(password); + let passwordHash = await hashSHA3_512(password); let response = await fetch(`${url}/auth?u=${username}`, { method: "GET", credentials: "omit", @@ -286,16 +309,14 @@ async function Auth(username, password) { } -async function fetchEncrypted(request, body) -{ +async function fetchEncrypted(request, body) { let nonce = await getNonce(username, passwordHash); - + let response = await fetch(`${url}/encryptedrequest?u=${username}`, { method: "POST", credentials: "omit", body: await encryptWithNonce( - JSON.stringify({ string1: request, string2: body @@ -335,6 +356,15 @@ function fixBase64Padding(base64String) { return str; } +function showBlahNotification(blahmessage, duration = 3500) { + try { + let split = blahmessage.split(":"); + showNotification(processBlah(blahmessage), split[0], duration); + } catch (e) { + showNotification(blahmessage, "info", duration); + } +} + function showNotification(message, type = 'info', duration = 3500) { let container = document.getElementById('notification-container'); if (!container) { @@ -386,40 +416,99 @@ function showAction(message, actionid) { }); }); } + function clearAction(actionid) { + const notifications = document.querySelectorAll(`[id="notification-${actionid}"]`); - let notif = document.getElementById(`notification-${actionid}`); - notif.classList.remove('show'); + notifications.forEach(notif => { + notif.classList.remove('show'); - notif.addEventListener('transitionend', () => { - notif.remove(); + notif.addEventListener('transitionend', () => { + notif.remove(); + }, { once: true }); }); } async function hashSHA3_512(input) { const encoder = new TextEncoder(); - const data = encoder.encode(input); + const data = encoder.encode(input); const hashBuffer = await crypto.subtle.digest('SHA-512', data); //-3 kiedys xddddddddddddddddd - const hashArray = Array.from(new Uint8Array(hashBuffer)); - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - return hashHex; + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + return hashHex; } +var blah; +async function initBlahs() { + lang = lang.toLowerCase(); + let res; + + let path = window.location.pathname.replace("/login/index.html", "/"); + + try { //try user lang first + res = await fetchAsync(`${path}blah/${lang}.json`); + } catch (e) { //fallback to en-us + res = await fetchAsync(`${path}blah/en-us.json`); + } + blah = JSON.parse(res); +} + +function processBlah(blahmessage) { + try { + let split = blahmessage.split(":"); + let values = []; + try { + values = split[2].split(";"); + } catch (e) { + } + + let message = blah[split[1]]; + + let valueslist = ""; + for (let i = 0; i < values.length; i++) { + let value = values[i]; + valueslist+=`${values[i]}, `; + processBlah(value); + + message = message.replaceAll(`{${i}}`, value); + } + valueslist = valueslist.slice(0, -2); + if (message.includes('{all}')) { + + message = message.replaceAll('{all}', valueslist); + } + + return message; + } + catch (e) + { + return blahmessage; + } +} + +function getLang() { + return (navigator.language || navigator.languages[0]); +} var password = ""; var username = ""; var passwordHash = ""; var host = ""; -async function mainJS() -{ - username = localStorage.getItem('username'); - password = localStorage.getItem('password'); - host = localStorage.getItem('host'); +var lang = getLang(); + +async function mainJS() { + await initBlahs(); + passwordHash = await hashSHA3_512(password); - url = `${window.location.protocol}//${host}/_larpix`; + if (localStorage.getItem('lang') != null) { + lang = localStorage.getItem('lang'); + } } +username = localStorage.getItem('username'); +password = localStorage.getItem('password'); +host = localStorage.getItem('host'); mainJS(); collapseDmsBtn.addEventListener("click", () => { @@ -430,7 +519,7 @@ collapseGroupsBtn.addEventListener("click", () => { }); addDmBtn.addEventListener("click", () => { - roomContentMain.innerHTML = + roomContentMain.innerHTML = `
@@ -450,9 +539,8 @@ addDmBtn.addEventListener("click", () => { ` }); addGroupBtn.addEventListener("click", () => { - -}); +}); sidebarHomeButton.addEventListener("mouseenter", () => { diff --git a/webroot/style.css b/webroot/style.css index 54d8f7dd..663eea28 100644 --- a/webroot/style.css +++ b/webroot/style.css @@ -17,6 +17,10 @@ --big-red: rgb(202 0 0); --big-green: rgb(25 189 0); } +*:focus { + outline: none; + box-shadow: none; +} .red { color: var(--big-red); } diff --git a/webroot/userscript.js b/webroot/userscript.js new file mode 100644 index 00000000..a41b1768 --- /dev/null +++ b/webroot/userscript.js @@ -0,0 +1,4 @@ +//write your custom deployment scripts here + +loginHost.value = "olcxja.ovh"; +loginHostChanged(); \ No newline at end of file