├── screen1.jpg ├── screen2.jpg ├── screen3.jpg ├── source ├── modules │ ├── manifest.js │ ├── io-fs.js │ ├── context.js │ ├── fp-api.js │ └── app.js ├── styles │ ├── themes │ │ ├── light.css │ │ ├── dark.css │ │ ├── hacker.css │ │ └── cherry.css │ └── global.css └── index.htm ├── README.md ├── .github └── FUNDING.yml └── LICENSE /screen1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DosX-dev/braux/HEAD/screen1.jpg -------------------------------------------------------------------------------- /screen2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DosX-dev/braux/HEAD/screen2.jpg -------------------------------------------------------------------------------- /screen3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DosX-dev/braux/HEAD/screen3.jpg -------------------------------------------------------------------------------- /source/modules/manifest.js: -------------------------------------------------------------------------------- 1 | // Github: https://github.com/DosX-dev/braux 2 | 3 | const config = { 4 | version: '3.20.25' 5 | }, 6 | version = { 7 | os: 'braux (client)', 8 | kernel: 'VM-' + config.version, 9 | shell: 'bash-js' 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is Braux? 2 | Braux is a unique console system built on browser-based client tools. 3 | It combines ease of use with powerful features, giving you a Unix-like 4 | command line with a choice of different themes and color schemes. 5 | 6 | # Installed? 7 | ### https://dosx.su/terminal 8 | 9 | # To do 10 | * make command control the current user 11 | 12 | ![](screen1.jpg) 13 | ![](screen2.jpg) 14 | ![](screen3.jpg) 15 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://github.com/DosX-dev/DosX-dev/blob/main/donate.md'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /source/styles/themes/light.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | color: black; 4 | } 5 | 6 | hr { 7 | background-color: rgb(151, 151, 151); 8 | } 9 | 10 | ::-webkit-scrollbar-track { 11 | background-color: white; 12 | } 13 | 14 | ::-webkit-scrollbar-thumb { 15 | background-color: black; 16 | } 17 | 18 | ::selection { 19 | background-color: black; 20 | color: white; 21 | } 22 | 23 | input[type="text"] { 24 | color: black; 25 | caret-color: black; 26 | } 27 | 28 | .error { 29 | background-color: rgb(116, 0, 0); 30 | color: white; 31 | } 32 | 33 | a { 34 | background-color: rgb(0, 63, 238); 35 | color: white; 36 | } 37 | 38 | a:hover { 39 | background-color: rgb(0, 0, 0); 40 | } 41 | 42 | .pointer { 43 | color: black; 44 | } 45 | 46 | #context-menu { 47 | background: #eee; 48 | border-color: rgb(151, 151, 151); 49 | } 50 | 51 | #context-menu .item { 52 | color: #000000; 53 | } 54 | 55 | #context-menu .item:hover { 56 | background: #dbdbdb; 57 | } -------------------------------------------------------------------------------- /source/styles/themes/dark.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: black; 3 | color: white; 4 | } 5 | 6 | hr { 7 | background-color: rgb(66, 66, 66); 8 | } 9 | 10 | ::-webkit-scrollbar-track { 11 | background-color: black; 12 | } 13 | 14 | ::-webkit-scrollbar-thumb { 15 | background-color: white; 16 | } 17 | 18 | ::selection { 19 | background-color: white; 20 | color: black; 21 | } 22 | 23 | input[type="text"] { 24 | color: white; 25 | caret-color: white; 26 | } 27 | 28 | .error { 29 | background-color: rgb(116, 0, 0); 30 | color: white; 31 | } 32 | 33 | a { 34 | background-color: rgb(43, 100, 255); 35 | color: white; 36 | } 37 | 38 | a:hover { 39 | background-color: white; 40 | color: rgb(57, 43, 255); 41 | } 42 | 43 | .pointer { 44 | color: white; 45 | } 46 | 47 | #context-menu { 48 | background: #1b1a1a; 49 | border-color: rgb(66, 66, 66); 50 | } 51 | 52 | #context-menu .item { 53 | color: #eee; 54 | } 55 | 56 | #context-menu .item:hover { 57 | background: #343434; 58 | } -------------------------------------------------------------------------------- /source/styles/themes/hacker.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(0, 18, 19); 3 | color: white; 4 | } 5 | 6 | hr { 7 | background-color: rgb(0, 138, 0); 8 | } 9 | 10 | ::-webkit-scrollbar-track { 11 | background-color: black; 12 | } 13 | 14 | ::-webkit-scrollbar-thumb { 15 | background-color: lime; 16 | } 17 | 18 | ::selection { 19 | background-color: rgb(0, 138, 0); 20 | color: white; 21 | } 22 | 23 | input[type="text"] { 24 | color: white; 25 | caret-color: lime; 26 | } 27 | 28 | .error { 29 | background-color: rgb(255, 155, 155); 30 | color: black; 31 | } 32 | 33 | a { 34 | background-color: rgb(23, 80, 0); 35 | color: white; 36 | } 37 | 38 | a:hover { 39 | background-color: white; 40 | color: rgb(57, 43, 255); 41 | } 42 | 43 | .pointer { 44 | color: lime; 45 | } 46 | 47 | #context-menu { 48 | background: #1b1a1a; 49 | border-color: rgb(23, 80, 0); 50 | } 51 | 52 | #context-menu .item { 53 | color: #eee; 54 | } 55 | 56 | #context-menu .item:hover { 57 | background: #003612; 58 | } -------------------------------------------------------------------------------- /source/styles/themes/cherry.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(41, 0, 35); 3 | color: white; 4 | } 5 | 6 | hr { 7 | background-color: rgb(108, 0, 158); 8 | } 9 | 10 | ::-webkit-scrollbar-track { 11 | background-color: black; 12 | } 13 | 14 | ::-webkit-scrollbar-thumb { 15 | background-color: white; 16 | } 17 | 18 | ::selection { 19 | background-color: rgb(255, 0, 43); 20 | color: white; 21 | } 22 | 23 | input[type="text"] { 24 | color: white; 25 | caret-color: rgb(255, 210, 247); 26 | } 27 | 28 | .error { 29 | background-color: rgb(190, 44, 44); 30 | color: white; 31 | } 32 | 33 | a { 34 | background-color: rgb(173, 0, 130); 35 | color: white; 36 | } 37 | 38 | a:hover { 39 | background-color: white; 40 | color: rgb(255, 0, 191); 41 | } 42 | 43 | .pointer { 44 | color: white; 45 | } 46 | 47 | #context-menu { 48 | background: #1b1a1a; 49 | border-color: rgb(108, 0, 158); 50 | } 51 | 52 | #context-menu .item { 53 | color: #eee; 54 | } 55 | 56 | #context-menu .item:hover { 57 | background: #4e004e; 58 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 DosX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /source/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | Braux: Terminal 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
Clear
19 |
About
20 |
21 |
22 |
23 |
24 |
25 |
26 | > 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /source/styles/global.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | transition: background-color 0.8s, color 0.8s; 4 | cursor: default; 5 | font-family: monospace; 6 | margin: 0; 7 | padding: 0; 8 | zoom: 100%; 9 | width: 100%; 10 | height: 100%; 11 | } 12 | 13 | input .container { 14 | margin-left: 10px; 15 | } 16 | 17 | input, 18 | .prompt { 19 | user-select: none; 20 | } 21 | 22 | .command { 23 | white-space: pre-wrap; 24 | } 25 | 26 | .first { 27 | font-style: italic; 28 | } 29 | 30 | .log { 31 | margin: 10px; 32 | margin-bottom: 10px; 33 | white-space: pre-wrap; 34 | } 35 | 36 | hr { 37 | height: 1px; 38 | border: none; 39 | } 40 | 41 | #console { 42 | overflow-y: auto; 43 | word-wrap: break-word; 44 | } 45 | 46 | ::-webkit-scrollbar { 47 | width: 10px; 48 | } 49 | 50 | input[type="text"] { 51 | cursor: default; 52 | background-color: transparent; 53 | border: none; 54 | font-family: monospace; 55 | font-size: inherit; 56 | width: 94vw; 57 | outline: none; 58 | } 59 | 60 | .error { 61 | transition: background-color 0.9s, color 0.9s; 62 | border-radius: 4px; 63 | height: 100%; 64 | } 65 | 66 | a { 67 | transition: background-color 0.3s, color 0.3s; 68 | text-decoration-line: none; 69 | border-bottom: 1px dotted; 70 | } 71 | 72 | .insert-cmd { 73 | cursor: help; 74 | } 75 | 76 | #context-menu { 77 | border: 1px solid; 78 | position: fixed; 79 | z-index: 10000; 80 | width: 150px; 81 | border-radius: 5px; 82 | transform: scale(0); 83 | transform-origin: top left; 84 | user-select: none; 85 | } 86 | 87 | #context-menu.visible { 88 | transform: scale(1); 89 | transition: transform 200ms ease-in-out; 90 | } 91 | 92 | #context-menu .item { 93 | transition: background-color 0.3s, color 0.3s; 94 | padding: 8px 10px; 95 | font-size: 15px; 96 | cursor: pointer; 97 | border-radius: inherit; 98 | } -------------------------------------------------------------------------------- /source/modules/io-fs.js: -------------------------------------------------------------------------------- 1 | // Github: https://github.com/DosX-dev/braux 2 | 3 | class FileSystem { 4 | constructor() { 5 | this.storage = window.localStorage; 6 | this.files = JSON.parse(this.storage.getItem('files')) || {}; 7 | this.serializedFiles = JSON.stringify(this.files); 8 | this.fileList = Object.keys(this.files).join('\n'); 9 | } 10 | 11 | createFile(name, content = '') { 12 | if (!this.isValidFileName(name)) { 13 | throw new Error('Invalid file name'); 14 | } 15 | 16 | if (this.fileExists(name)) { 17 | throw new Error('File already exists'); 18 | } 19 | 20 | this.files[name] = content; 21 | this.serializedFiles = JSON.stringify(this.files); 22 | this.saveFiles(); 23 | } 24 | 25 | deleteFile(name) { 26 | if (!this.isValidFileName(name)) { 27 | throw new Error('Invalid file name'); 28 | } 29 | 30 | if (!this.fileExists(name)) { 31 | throw new Error('File not found'); 32 | } 33 | 34 | delete this.files[name]; 35 | this.serializedFiles = JSON.stringify(this.files); 36 | this.saveFiles(); 37 | } 38 | 39 | readFile(name) { 40 | if (!this.isValidFileName(name)) { 41 | throw new Error('Invalid file name'); 42 | } 43 | 44 | if (!this.fileExists(name)) { 45 | throw new Error('File not found'); 46 | } 47 | 48 | return this.files[name]; 49 | } 50 | 51 | writeFile(name, content) { 52 | if (!this.isValidFileName(name)) { 53 | throw new Error('Invalid file name'); 54 | } 55 | 56 | if (!this.fileExists(name)) { 57 | throw new Error('File not found'); 58 | } 59 | 60 | this.files[name] = content; 61 | this.serializedFiles = JSON.stringify(this.files); 62 | this.saveFiles(); 63 | } 64 | 65 | saveFiles() { 66 | this.storage.setItem('files', this.serializedFiles); 67 | } 68 | 69 | getFileList() { 70 | return this.fileList; 71 | } 72 | 73 | isValidFileName(name) { 74 | const forbiddenChars = /[^\w\d-_]/; 75 | return !forbiddenChars.test(name); 76 | } 77 | 78 | fileExists(name) { 79 | return name in this.files; 80 | } 81 | } 82 | 83 | const IO = new FileSystem(); -------------------------------------------------------------------------------- /source/modules/context.js: -------------------------------------------------------------------------------- 1 | // Github: https://github.com/DosX-dev/braux 2 | 3 | const contextMenu = document.getElementById("context-menu"); 4 | const scope = document.querySelector("body"); 5 | 6 | const normalizePozition = (mouseX, mouseY) => { 7 | // ? compute what is the mouse position relative to the container element (scope) 8 | let { 9 | left: scopeOffsetX, 10 | top: scopeOffsetY, 11 | } = scope.getBoundingClientRect(); 12 | 13 | scopeOffsetX = scopeOffsetX < 0 ? 0 : scopeOffsetX; 14 | scopeOffsetY = scopeOffsetY < 0 ? 0 : scopeOffsetY; 15 | 16 | const scopeX = mouseX - scopeOffsetX, 17 | scopeY = mouseY - scopeOffsetY; 18 | 19 | // ? check if the element will go out of bounds 20 | const outOfBoundsOnX = 21 | scopeX + contextMenu.clientWidth > scope.clientWidth; 22 | 23 | const outOfBoundsOnY = 24 | scopeY + contextMenu.clientHeight > scope.clientHeight; 25 | 26 | let normalizedX = mouseX, 27 | normalizedY = mouseY; 28 | 29 | // ? normalize on X 30 | if (outOfBoundsOnX) { 31 | normalizedX = 32 | scopeOffsetX + scope.clientWidth - contextMenu.clientWidth; 33 | } 34 | 35 | // ? normalize on Y 36 | if (outOfBoundsOnY) { 37 | normalizedY = 38 | scopeOffsetY + scope.clientHeight - contextMenu.clientHeight; 39 | } 40 | 41 | return { 42 | normalizedX, 43 | normalizedY 44 | }; 45 | }; 46 | 47 | scope.addEventListener("contextmenu", (event) => { 48 | event.preventDefault(); 49 | 50 | const { 51 | clientX: mouseX, 52 | clientY: mouseY 53 | } = event, { 54 | normalizedX, 55 | normalizedY 56 | } = normalizePozition(mouseX, mouseY); 57 | 58 | contextMenu.classList.remove("visible"); 59 | 60 | contextMenu.style.top = `${normalizedY}px`; 61 | contextMenu.style.left = `${normalizedX}px`; 62 | 63 | setTimeout(() => { 64 | contextMenu.classList.add("visible"); 65 | }); 66 | }); 67 | 68 | scope.addEventListener("click", (event) => { 69 | // ? close the menu if the user clicks outside of it 70 | if (event.target.offsetParent != contextMenu) { 71 | contextMenu.classList.remove("visible"); 72 | } 73 | }); 74 | 75 | const items = document.getElementsByClassName("item"); 76 | 77 | Array.from(items).forEach(item => { 78 | item.addEventListener("click", (event) => { 79 | contextMenu.classList.remove('visible'); 80 | }); 81 | }); -------------------------------------------------------------------------------- /source/modules/fp-api.js: -------------------------------------------------------------------------------- 1 | // Github: https://github.com/DosX-dev/braux 2 | 3 | function fingerprint() { 4 | var fingerprintData = { 5 | 'User Agent': navigator.userAgent, 6 | 'Browser Language': navigator.language, 7 | 'Cookies Enabled': navigator.cookieEnabled, 8 | 'Screen Resolution': screen.width + 'x' + screen.height, 9 | 'Available Screen Resolution': screen.availWidth + 'x' + screen.availHeight, 10 | 'Color Depth': screen.colorDepth, 11 | 'Timezone': Intl.DateTimeFormat().resolvedOptions().timeZone, 12 | 'Local Storage Enabled': typeof Storage !== 'undefined', 13 | 'Session Storage Enabled': typeof sessionStorage !== 'undefined', 14 | 'Do Not Track': Boolean(navigator.doNotTrack), 15 | 'Plugins': Array.from(navigator.plugins).map(plugin => plugin.name).join(', '), 16 | 'WebGL Vendor': getWebGLVendor(), 17 | 'WebGL Renderer': getWebGLRenderer(), 18 | 'WebGL Version': getWebGLVersion(), 19 | 'Web Audio API': isWebAudioAPISupported(), 20 | 'MIDI API': isMIDIAPIAvailable(), 21 | 'WebSockets Supported': isWebSocketsSupported(), 22 | 'Battery API Supported': isBatteryAPISupported(), 23 | 'WebVR API Supported': isWebVRAPIAvailable(), 24 | 'AudioContext Max Channels': getMaxAudioContextChannels(), 25 | 'Is Chromium based': isChromium() 26 | }; 27 | 28 | var fingerprintString = Object.entries(fingerprintData) 29 | .map(entry => `${entry[0]}` + ': ' + entry[1]) 30 | .join('\n'); 31 | 32 | return fingerprintString; 33 | } 34 | 35 | function getWebGLVendor() { 36 | var canvas = document.createElement('canvas'), 37 | gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); 38 | if (gl && gl.getExtension('WEBGL_debug_renderer_info')) { return gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info').UNMASKED_VENDOR_WEBGL); } 39 | return 'N/A'; 40 | } 41 | 42 | function getWebGLRenderer() { 43 | var canvas = document.createElement('canvas'), 44 | gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); 45 | if (gl && gl.getExtension('WEBGL_debug_renderer_info')) { return gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL); } 46 | return 'N/A'; 47 | } 48 | 49 | function getWebGLVersion() { 50 | var canvas = document.createElement('canvas'), 51 | gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); 52 | if (gl) { return gl.getParameter(gl.VERSION); } 53 | return 'N/A'; 54 | } 55 | 56 | function isWebAudioAPISupported() { return typeof window.AudioContext !== 'undefined' || typeof window.webkitAudioContext !== 'undefined'; } 57 | 58 | function isMIDIAPIAvailable() { return typeof navigator.requestMIDIAccess !== 'undefined'; } 59 | 60 | function isWebSocketsSupported() { return 'WebSocket' in window ? 'Supported' : 'Not supported'; } 61 | 62 | function isBatteryAPISupported() { return navigator.getBattery ? 'Supported' : 'Not supported'; } 63 | 64 | function isWebVRAPIAvailable() { return 'getVRDisplays' in navigator ? 'Supported' : 'Not supported'; } 65 | 66 | function isWebXRAPIAvailable() { return 'xr' in navigator ? 'Supported' : 'Not supported'; } 67 | 68 | function getMaxAudioContextChannels() { 69 | try { 70 | var audioContext = new(window.AudioContext || window.webkitAudioContext)(), 71 | maxChannels = audioContext.destination.maxChannelCount; 72 | audioContext.close(); 73 | return maxChannels; 74 | } catch (exc) { 75 | return 'N/A' 76 | } 77 | } 78 | 79 | function isChromium() { return typeof window.chrome == 'object'; } 80 | 81 | function isOpera() { return typeof window.opera == 'object'; } -------------------------------------------------------------------------------- /source/modules/app.js: -------------------------------------------------------------------------------- 1 | // Github: https://github.com/DosX-dev/braux 2 | 3 | const visual = { 4 | themes: { 5 | dark: 'styles/themes/dark.css', 6 | light: 'styles/themes/light.css', 7 | cherry: 'styles/themes/cherry.css', 8 | hacker: 'styles/themes/hacker.css' 9 | }, 10 | installTheme(theme) { 11 | visual.setTheme(theme); 12 | localStorage.setItem('console-theme', theme); 13 | }, 14 | setTheme(theme) { 15 | const linkId = 'theme-link', 16 | link = document.getElementById(linkId); 17 | 18 | if (link) { 19 | link.href = theme; 20 | } else { 21 | const newLink = document.createElement('link'); 22 | newLink.id = linkId; 23 | newLink.rel = 'stylesheet'; 24 | newLink.href = theme; 25 | document.head.appendChild(newLink); 26 | } 27 | }, 28 | loadTheme() { 29 | const theme = localStorage.getItem('console-theme'); 30 | if (theme) { 31 | visual.setTheme(theme); 32 | } else { 33 | const preferredTheme = window.matchMedia && 34 | window.matchMedia('(prefers-color-scheme: dark)').matches ? visual.themes.dark : visual.themes.light; 35 | visual.installTheme(preferredTheme); 36 | } 37 | } 38 | } 39 | 40 | visual.loadTheme(); 41 | document.getElementById('version-info').innerText = `version ${config.version}`; 42 | 43 | Object.entries({ 44 | "app-default-config": "0", 45 | "app-prompt": navigator.userAgent 46 | }).forEach(([key, value]) => { 47 | setDefaultPromptValue(key, value); 48 | }); 49 | 50 | const commandInput = document.getElementById("commandInput"); 51 | const isWelcomeHiddenKey = 'isWelcomeHidden'; 52 | 53 | function isWelcomeHidden() { 54 | return localStorage.getItem(isWelcomeHiddenKey); 55 | } 56 | 57 | var commandHistory = [], 58 | currentCommandIndex = -1; 59 | 60 | 61 | 62 | const userProfile = { 63 | name: { 64 | key: 'user', 65 | defaultName: 'user', 66 | get() { 67 | const userNameValue = localStorage.getItem(userProfile.name.key); 68 | 69 | if (userNameValue) { 70 | return userNameValue; 71 | } else { 72 | this.set(this.defaultName); 73 | return this.defaultName; 74 | } 75 | }, 76 | set(newUserName) { 77 | 78 | if (newUserName.length < 1) { 79 | console.error('Username cannot be empty.'); 80 | return; 81 | } else if (!/^[a-zA-Z0-9]+$/.test(newUserName)) { 82 | console.error('Invalid characters.'); 83 | return; 84 | } 85 | 86 | localStorage.setItem(userProfile.name.key, newUserName.toLowerCase()); 87 | } 88 | } 89 | }; 90 | 91 | function replaceTagsWithEntities(text) { 92 | return replacedText = text.replace(//g, '>'); 93 | } 94 | 95 | function setFocus() { 96 | if (window.getSelection().toString() == '' && commandInput !== document.activeElement) { 97 | commandInput.focus(); 98 | } 99 | } 100 | 101 | async function getIpInfo(ip = '') { 102 | const response = await fetch('https://freeipapi.com/api/json/' + ip, { 103 | method: 'GET' 104 | }); 105 | 106 | const data = await response.json(); 107 | 108 | const processedData = Object.entries(data) 109 | .map(([key, value]) => `${key}: ${value}`) 110 | .join('\n'); 111 | 112 | return processedData + '\napiUsed: freeipapi.com'; 113 | } 114 | 115 | function wrapFirstWord(sentence) { 116 | var words = sentence.split(' '); 117 | 118 | if (words.length === 0) { 119 | return ''; 120 | } 121 | 122 | words[0] = `${words[0]}`; 123 | 124 | return words.join(' '); 125 | } 126 | 127 | window.onerror = function(message, source, lineno, colno, error) { 128 | console.error(`[APP]: ${message}`); 129 | }; 130 | 131 | function autoScroll() { 132 | window.scrollTo(0, document.body.scrollHeight); 133 | } 134 | 135 | function pushCommand(command, displayCommand = true, sudo = false) { 136 | var consoleDiv = document.getElementById('console'), 137 | output = document.createElement('div'); 138 | if (displayCommand) { 139 | commandInput.placeholder = ''; 140 | output.classList.add('out'); 141 | output.innerHTML = '> ' + wrapFirstWord(replaceTagsWithEntities(command)) + ''; 142 | consoleDiv.appendChild(output); 143 | } 144 | 145 | function executeAsRoot(callback) { 146 | if (sudo) { 147 | callback(); 148 | } else { 149 | error('You do not have permission to execute this command.'); 150 | out(`Try 'sudo ${command}' (click to insert)`); 151 | } 152 | } 153 | 154 | function getAllCommandArguments(array) { 155 | let out = ''; 156 | for (let i = 1; i < array.length; i++) { 157 | out += array[i] + (i == array.length - 1 ? '' : ' '); 158 | } 159 | return out; 160 | } 161 | 162 | function out(text) { 163 | let outStd = document.createElement('div'); 164 | ['out', 'log'].forEach(outClass => { 165 | outStd.classList.add(outClass); 166 | }); 167 | outStd.innerHTML = text; 168 | consoleDiv.appendChild(outStd); 169 | autoScroll(); 170 | } 171 | 172 | function error(text) { 173 | out(`${text}`); 174 | let lastCommands = document.getElementsByClassName("command"), 175 | lastCommand = lastCommands[lastCommands.length - 1]; 176 | lastCommand.style = 'color: rgba(255, 79, 79);'; 177 | lastCommand.innerHTML += ' (!)'; 178 | autoScroll(); 179 | } 180 | 181 | console.error = error; 182 | console.log = console.info = console.warn = out; 183 | 184 | console.clear = () => pushCommand("clear"); 185 | 186 | const commandArgs = command.trim().split(' '); 187 | 188 | switch (commandArgs[0]) { 189 | case 'help': 190 | out(`help - show this message 191 | clear - clear console 192 | theme [name] - change theme 193 | echo [html] - format and write text in console 194 | ipinfo [ip] - get info about IP (no domains support) 195 | history - get a log of commands entered 196 | fingerprint - get client information 197 | clear - clear console 198 | 199 | sudo [command] - execute command as root 200 | about - get info about application 201 | reboot - restart the application 202 | exit - exit from the application 203 | 204 | * js [code] - execute JavaScript (unsafe) 205 | * factory-reset - reset all application data settings 206 | `); 207 | break; 208 | 209 | 210 | 211 | case 'clear': 212 | consoleDiv.innerText = ''; 213 | out("Console cleared."); 214 | break; 215 | 216 | 217 | 218 | case 'exit': 219 | out('Goodbye!'); 220 | setTimeout(function() { 221 | window.location.href = 'about:blank'; 222 | }, 300); 223 | break; 224 | 225 | 226 | 227 | case 'echo': 228 | out(getAllCommandArguments(commandArgs)) 229 | break; 230 | 231 | 232 | 233 | case 'fingerprint': 234 | out(fingerprint()); 235 | break; 236 | 237 | 238 | 239 | case 'factory-reset': 240 | executeAsRoot(() => { 241 | localStorage.clear(); 242 | out(`All data of '${document.domain}' erased`); 243 | setTimeout(() => { 244 | pushCommand('reboot', false); 245 | }, 750); 246 | }); 247 | break; 248 | 249 | 250 | 251 | case 'history': 252 | switch (commandArgs[1]) { 253 | case 'clear': 254 | clearCommandHistory(); 255 | out('Command history cleared.'); 256 | break; 257 | case 'list': 258 | out(getCommandHistory()); 259 | break; 260 | default: 261 | out(`Arguments: 262 | * list (get history as numbered list) 263 | * clear (clear history) 264 | 265 | Usage: history list`) 266 | } 267 | break; 268 | 269 | 270 | 271 | case 'theme': 272 | let isSeccuss = true; 273 | switch (commandArgs[1]) { 274 | case 'dark': 275 | visual.installTheme(visual.themes.dark); 276 | break; 277 | case 'light': 278 | visual.installTheme(visual.themes.light); 279 | break; 280 | case 'cherry': 281 | visual.installTheme(visual.themes.cherry); 282 | break; 283 | case 'hacker': 284 | visual.installTheme(visual.themes.hacker); 285 | break; 286 | default: 287 | isSeccuss = false; 288 | out(`Themes: 289 | * dark 290 | * light 291 | * cherry 292 | * hacker 293 | 294 | Usage: theme dark`); 295 | } 296 | if (isSeccuss) { 297 | out(`Theme installed: ${commandArgs[1]}`); 298 | } 299 | break; 300 | 301 | 302 | 303 | case 'ipinfo': 304 | out('Requesting...'); 305 | getIpInfo(commandArgs[1]) 306 | .then(dataString => { 307 | out(dataString); 308 | }) 309 | .catch(error => { 310 | error('No API access'); 311 | }); 312 | break; 313 | 314 | 315 | 316 | case 'js': 317 | executeAsRoot(() => { 318 | let codeToExec = getAllCommandArguments(commandArgs); 319 | 320 | if (codeToExec.trim() == '') { 321 | error('Empty source!'); 322 | } else { 323 | try { 324 | out(`< ${eval(codeToExec)}`); 325 | } catch (exc) { 326 | error(`[VM]: ${exc}`); 327 | } 328 | } 329 | }); 330 | break; 331 | 332 | 333 | 334 | case 'about': 335 | out(` OS: ${version.os} 336 | Kernel: ${version.kernel} 337 | Shell: ${version.shell}`); 338 | break; 339 | 340 | 341 | 342 | case 'reboot': 343 | out('Rebooting...'); 344 | setTimeout(() => { 345 | location.reload(); 346 | }, 300); 347 | break; 348 | 349 | 350 | 351 | case 'su': 352 | error(`You can only use 'sudo'`); 353 | break; 354 | 355 | 356 | 357 | case 'sudo': 358 | let commandToExecute = getAllCommandArguments(commandArgs); 359 | if (commandToExecute.trim() == '') { 360 | out(`Usage: sudo [command]`) 361 | } 362 | pushCommand(commandToExecute, false, true); 363 | break; 364 | 365 | 366 | 367 | case 'remove-intro': 368 | localStorage.setItem(isWelcomeHiddenKey, String(true)) 369 | break; 370 | 371 | 372 | 373 | case 'python': // Yes, I hate python 374 | error('Do not embarrass yourself.'); 375 | break; 376 | 377 | 378 | 379 | case '': // Empty prompt 380 | break; 381 | case '#': // Comment 382 | break; 383 | default: 384 | error(`Command or packet '${commandArgs[0]}' not found!`); 385 | } 386 | autoScroll(); 387 | } 388 | 389 | function pushCommandScript(command, sudo = false) { 390 | command.split('\n').forEach(element => { 391 | pushCommand(element, false, sudo); 392 | }); 393 | } 394 | 395 | pushCommand(`echo Welcome, ${userProfile.name.get()}!`, false); 396 | 397 | if (isWelcomeHidden() !== String(true)) { 398 | pushCommand('echo ' + 399 | `+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 400 | Braux is a unique console system built on browser-based client tools. 401 | It combines ease of use with powerful features, giving you a Unix-like 402 | command line with a choice of different themes and color schemes. 403 | 404 | GitHub -> https://github.com/DosX-dev/braux 405 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 406 | Use 'remove-intro' to remove this message`, false); 407 | } 408 | 409 | function setDefaultPromptValue(name, defaultValue) { 410 | const urlParams = new URLSearchParams(window.location.search); 411 | if (!urlParams.has(name)) { 412 | let locationBarSections = document.URL.split('/'), 413 | locationBarLastSection = locationBarSections[locationBarSections.length - 1], 414 | separator = locationBarLastSection.includes('?') ? '&' : '?', 415 | newUrl = `${window.location.href + separator + name}=${defaultValue}`; 416 | history.pushState({ path: newUrl }, '', newUrl); 417 | } 418 | } 419 | 420 | // load history form localStorage 421 | const storedHistory = localStorage.getItem("history"); 422 | if (storedHistory) { 423 | commandHistory = JSON.parse(storedHistory); 424 | currentCommandIndex = commandHistory.length; 425 | } 426 | 427 | commandInput.addEventListener("keydown", (event) => { 428 | switch (event.keyCode) { 429 | case 13: // Enter 430 | let command = commandInput.value.trim(); 431 | if (command !== '') { 432 | if (commandHistory.length === 0 || command !== commandHistory[commandHistory.length - 1]) { 433 | commandHistory.push(command); 434 | } 435 | currentCommandIndex = commandHistory.length; 436 | 437 | // save history in localStorage 438 | localStorage.setItem("history", JSON.stringify(commandHistory)); 439 | } 440 | break; 441 | case 38: // Up 442 | event.preventDefault(); 443 | if (currentCommandIndex > 0) { 444 | currentCommandIndex--; 445 | commandInput.value = commandHistory[currentCommandIndex]; 446 | } 447 | break; 448 | case 40: // Down 449 | event.preventDefault(); 450 | if (currentCommandIndex < commandHistory.length - 1) { 451 | currentCommandIndex++; 452 | commandInput.value = commandHistory[currentCommandIndex]; 453 | } else { 454 | currentCommandIndex = commandHistory.length; 455 | commandInput.value = ''; 456 | } 457 | break; 458 | default: 459 | break; 460 | } 461 | }); 462 | 463 | 464 | function getCommandHistory() { 465 | return commandHistory.map((command, index) => `${index + 1}. ${command}`).join("\n"); 466 | } 467 | 468 | function clearCommandHistory() { 469 | commandHistory = []; 470 | currentCommandIndex = 0; 471 | localStorage.removeItem("history"); 472 | } 473 | 474 | document.addEventListener('DOMContentLoaded', () => { 475 | commandInput.addEventListener('keypress', function(event) { 476 | if (event.keyCode === 13) { // Enter 477 | event.preventDefault(); 478 | pushCommand(commandInput.value.trim()); 479 | commandInput.value = ''; 480 | } 481 | }); 482 | }); 483 | 484 | setInterval(() => { 485 | setFocus() 486 | }, 500); 487 | 488 | document.addEventListener('click', (event) => { 489 | if (event.target.classList.contains('insert-cmd')) { 490 | commandInput.value = event.target.textContent; 491 | } 492 | }); --------------------------------------------------------------------------------