.editorconfig000066400000000000000000000006371520166327500135500ustar00rootroot00000000000000# EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] # Change these settings to your own preference indent_style = space indent_size = 4 # We recommend you to keep these unchanged end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false .gitignore000066400000000000000000000003521520166327500130550ustar00rootroot00000000000000/build /node_modules /*.log /*.iws .idea/workspace.xml .idea/tasks.xml .idea/profiles_settings.xml .idea/inspectionProfiles/Project_Default.xml .idea/inspectionProfiles/profiles_settings.xml node_modules/.yarn-integrity /p3xrs.json.npmignore000066400000000000000000000002151520166327500130620ustar00rootroot00000000000000/.idea /build /test /node_modules /*.iml /*.ipr /*.iws /.travis.yml /.scrutinizer.yml /Gruntfile.js /*.lock *.log /corifeus-boot.json /secure.travis.yml000066400000000000000000000014571520166327500132050ustar00rootroot00000000000000language: node_js cache: npm: false node_js: - lts/* before_script: - npm install -g grunt-cli npm env: global: secure: r4bMeHdS9vdDWuL3ax+va5YoaZKmn5MxsXNxMgnT7lrk4AFkb2wr+17+4zDalQFyt7zyqM0HJzGXRYGTiG4kow6Jk2P55wv3LeiNJxpTBOGYfKTldjLZKIxxaqJLYJ40JhlfuA+TaTj0JWVsohz5CQJxO1O7Z+/xq/0ZwHVIhDNwW5XdFDdCdBZRQMV7XKSynGAB7L0pdhASSdl4laQTxjL/6A3B0Sr9rwO5Nd8YbzZpUPQ5opTLXfln2JSMTiAEtxgLS2ARu5Aa+9qfIA5VTDXzcyYAE+uErjIXeUfNRbTCJuizNQQZOoeMR36tam0cOu+B0qt0FWqCECOlmN12tL0TpY54vqsJfS4AIUT4dm2LpjPKCKZn4jIZxVJe3uxTRKZrWiDSaKUmnuoRIoTCPh4JMYc7t6pxP/4kKcAEpw4OxYS0eERG4RXKSJx9I2szqvJ+9nS/eAO3AC/5KWn+o+x1ebnjNgmGk/7BQbgdEOm1wsUazpEAg/nkL4b5EW6IEzUJm8V31NO1OxoAs+U0NLgOu3OKys0KxjwpzTFtQ0bvQoNcy715Y6JfLwZQFOTOUM6OjMXhAE4AF6mt/SYHE7iTY1I+TZxt71RLN5+t0DQSyNHQ/VStx1nTg9HOjs35ZxxJYG2y5fbsBBmeV8wGY1iX9GAsTO2oGgfAzUIuMQs= Gruntfile.js000066400000000000000000000004471520166327500133670ustar00rootroot00000000000000const utils = require('corifeus-utils'); module.exports = (grunt) => { const _ = require('lodash'); const builder = require(`corifeus-builder`); const loader = new builder.loader(grunt); loader.js({ }); grunt.registerTask('default', builder.config.task.build.js); } LICENSE000066400000000000000000000024321520166327500120730ustar00rootroot00000000000000 @license p3x-redis-ui-server v2020.10.178 🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface https://corifeus.com/redis-ui-server Copyright (c) 2020 Patrik Laszlo / P3X / Corifeus and contributors. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md000066400000000000000000000115341520166327500123500ustar00rootroot00000000000000# This is a development package For the full-blown package, please follow: https://github.com/patrikx3/redis-ui https://www.npmjs.com/package/p3x-redis-ui https://corifeus.com/redis-ui [//]: #@corifeus-header [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://paypal.me/patrikx3) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Corifeus @ Facebook](https://img.shields.io/badge/Facebook-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) [![Build Status](https://api.travis-ci.com/patrikx3/redis-ui-server.svg?branch=master)](https://travis-ci.com/patrikx3/redis-ui-server) [![Uptime Robot ratio (30 days)](https://img.shields.io/uptimerobot/ratio/m780749701-41bcade28c1ea8154eda7cca.svg)](https://uptimerobot.patrikx3.com/) --- # 🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface v2020.10.181 **Bugs are evident™ - MATRIX️** ### NodeJs LTS Version Requirement ```txt >=12.13.0 ``` ### Built on NodeJs ```txt v12.19.0 ``` The ```async``` and ```await``` keywords are required. Only the latest LTS variant is supported. Install NodeJs: https://nodejs.org/en/download/package-manager/ # Description [//]: #@corifeus-header:end This is part of a composable `p3x-redis-ui` package. This is the server based on Socket.IO (no rest at all). The server will be using the `p3x-redis-ui-material` web client package based on built with Webpack, Socket.IO and AngularJs Material. This package is named as `p3x-redis-ui-server`. ## Configuration For now, there are 2 configuration files: ```bash p3xrs --config ./p3xrs.json ``` The 2nd configuration is the list of the connections if found in `p3xrs.json` it either in the config: ```text p3xrs.json/p3xrs.connections['home-dir'] = undefined|home|absolute|relative ``` The best is to keep it undefined and it will be in your home dir, but you can choose any place as well. You may also set connections file name which overrides default .p3xrs-conns.json ```text p3xrs --connections-file-name .p3xrs-conns.json ``` ### Verbose CLI help ```text patrikx3@workstation:~/Projects/patrikx3/redis-ui-workspace/redis-ui-server$ p3xrs.js --help Usage: p3xrs [options] Options: -V, --version output the version number -c, --config [config] Set the p3xr.json p3x-redis-ui-server configuration, see more help in https://github.com/patrikx3/redis-ui-server -r, --readonly-connections Set the connections to be readonly, no adding, saving or delete a connection -n, --connections-file-name [filename] Set the connections file name, overrides default .p3xrs-conns.json -h, --help output usage information ``` # For development standalone For file names do not use camelCase, but use kebab-case. Folder should be named as kebab-case as well. As you can see, all code filenames are using it like that, please do not change that. Please apply the `.editorconfig` settings in your IDE. Copy from `./artifacts/boot/p3xrs.json` to the root folder (`./p3xrs.json`). ```bash npm install npm run dev ``` It uses `nodemon` and when any file is changed, it will re-load it. The server app is available @ http://localhost:7843 [//]: #@corifeus-footer --- 🙏 This is an open-source project. Star this repository, if you like it, or even donate to maintain the servers and the development. Thank you so much! Possible, this server, rarely, is down, please, hang on for 15-30 minutes and the server will be back up. All my domains ([patrikx3.com](https://patrikx3.com) and [corifeus.com](https://corifeus.com)) could have minor errors, since I am developing in my free time. However, it is usually stable. **Note about versioning:** Versions are cut in Major.Minor.Patch schema. Major is always the current year. Minor is either 4 (January - June) or 10 (July - December). Patch is incremental by every build. If there is a breaking change, it should be noted in the readme. --- [**P3X-REDIS-UI-SERVER**](https://corifeus.com/redis-ui-server) Build v2020.10.181 [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) ## P3X Sponsor [IntelliJ - The most intelligent Java IDE](https://www.jetbrains.com/?from=patrikx3) [![JetBrains](https://cdn.corifeus.com/assets/svg/jetbrains-logo.svg)](https://www.jetbrains.com/?from=patrikx3) [//]: #@corifeus-footer:end artifacts/000077500000000000000000000000001520166327500130455ustar00rootroot00000000000000artifacts/boot/000077500000000000000000000000001520166327500140105ustar00rootroot00000000000000artifacts/boot/p3xrs.json000066400000000000000000000012731520166327500157650ustar00rootroot00000000000000{ "p3xrs": { "http": { "port-info": "this is ommitted, it will be default 7843", "port": 7843 }, "connections": { "home-dir-info": "if the dir config is empty or home, the connections are saved in the home folder, otherwise it will resolve the directory set as it is, either relative ./ or absolute starting with /. NodeJs will resolve this directory in p3xrs.connections.dir", "home-dir": "home" }, "static-info": "This is the best configuration, if it starts with ~, then it is in resolve the path in the node_modules, otherwise it resolves to the current process current working directory.", "static-disabled": "~p3x-redis-ui-material/dist" } }artifacts/cluster.md000066400000000000000000000035321520166327500150530ustar00rootroot00000000000000[//]: #@corifeus-header # 🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface [//]: #@corifeus-header:end # Run a Docker Cluster ```bash docker run -e "IP=0.0.0.0" -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 grokzen/redis-cluster:latest ``` [//]: #@corifeus-footer --- 🙏 This is an open-source project. Star this repository, if you like it, or even donate to maintain the servers and the development. Thank you so much! Possible, this server, rarely, is down, please, hang on for 15-30 minutes and the server will be back up. All my domains ([patrikx3.com](https://patrikx3.com) and [corifeus.com](https://corifeus.com)) could have minor errors, since I am developing in my free time. However, it is usually stable. **Note about versioning:** Versions are cut in Major.Minor.Patch schema. Major is always the current year. Minor is either 4 (January - June) or 10 (July - December). Patch is incremental by every build. If there is a breaking change, it should be noted in the readme. --- [**P3X-REDIS-UI-SERVER**](https://corifeus.com/redis-ui-server) Build v2020.10.181 [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) ## P3X Sponsor [IntelliJ - The most intelligent Java IDE](https://www.jetbrains.com/?from=patrikx3) [![JetBrains](https://cdn.corifeus.com/assets/svg/jetbrains-logo.svg)](https://www.jetbrains.com/?from=patrikx3) [//]: #@corifeus-footer:end bin/000077500000000000000000000000001520166327500116355ustar00rootroot00000000000000bin/p3xrs.js000077500000000000000000000001031520166327500132470ustar00rootroot00000000000000#!/usr/bin/env node const boot = require('../src/lib/boot') boot() package.json000066400000000000000000000037011520166327500133540ustar00rootroot00000000000000{ "name": "p3x-redis-ui-server", "version": "2020.10.181", "description": "🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface", "corifeus": { "icon": "fas fa-flag-checkered", "code": "Reverse", "opencollective": false, "build": true, "nodejs": "v12.19.0", "reponame": "redis-ui-server", "publish": true, "prefix": "p3x-", "type": "p3x" }, "main": "src/indexjs", "bin": { "p3xrs": "./bin/p3xrs.js" }, "scripts": { "test": "grunt", "start": "node ./bin/p3xrs --config ./p3xrs.json", "dev": "nodemon --watch src --watch package.json --watch bin ./bin/p3xrs", "dev-readonly-connections": "nodemon --watch src --watch package.json --watch bin ./bin/p3xrs --readonly-connections" }, "watch": { "run": "src/**/*.js" }, "repository": { "type": "git", "url": "https://github.com/patrikx3/redis-ui-server.git" }, "keywords": [ "redis", "ui", "gui", "web", "electron", "desktop", "server", "angularjs", "javascript", "material", "dark", "light" ], "author": "Patrik Laszlo ", "license": "MIT", "devDependencies": { "corifeus-builder": "^2020.10.116", "nodemon": "^2.0.4" }, "dependencies-saved": { "koa-body": "^4.0.4", "koa-router": "^7.4.0" }, "dependencies": { "chalk": "^4.1.0", "commander": "^6.1.0", "corifeus-utils": "^2020.10.112", "ioredis": "^4.17.3", "koa": "^2.13.0", "koa-send": "^5.0.1", "koa-static": "^5.0.0", "lodash": "^4.17.20", "socket.io": "^2.3.0" }, "engines": { "node": ">=12.13.0" }, "homepage": "https://corifeus.com/redis-ui-server" } redis-ui-server.iml000066400000000000000000000011551520166327500146170ustar00rootroot00000000000000 scripts/000077500000000000000000000000001520166327500125545ustar00rootroot00000000000000scripts/redis-bulk.lua000066400000000000000000000003541520166327500153220ustar00rootroot00000000000000-- -- Created by IntelliJ IDEA. -- User: patrikx3 -- Date: 7/9/20 -- Time: 4:59 PM -- To change this template use File | Settings | File Templates. -- for i = 1, 100000, 1 do redis.call("SET", "bulk-key-"..i, i) end return "Ok!" scripts/redis-cluster.sh000077500000000000000000000002311520166327500156740ustar00rootroot00000000000000#!/usr/bin/env bash docker run -e "IP=0.0.0.0" -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 grokzen/redis-cluster:latestsrc/000077500000000000000000000000001520166327500116545ustar00rootroot00000000000000src/index.js000066400000000000000000000001251520166327500133170ustar00rootroot00000000000000module.exports = { lib: require('./lib'), services: require('./service'), } src/lib/000077500000000000000000000000001520166327500124225ustar00rootroot00000000000000src/lib/boot.js000066400000000000000000000020531520166327500137230ustar00rootroot00000000000000require('corifeus-utils'); const boot = async () => { global.p3xrs = {} p3xrs.cfg = undefined const cli = require('./cli'); if (!cli()) { return; } const consoleStamp = require('./console-stamp') consoleStamp() const koaService = require('../service/koa') p3xrs.koa = new koaService() await p3xrs.koa.boot() const socketIoService = require('../service/socket.io') p3xrs.socketIo = new socketIoService(); await p3xrs.socketIo.boot({ koaService: p3xrs.koa }) const checkLicense = require('./check-license') checkLicense({ socket: p3xrs.socketIo.socketio, payload: { license: p3xrs.connections.license } }) setInterval(() => { checkLicense({ socket: p3xrs.socketIo.socketio, payload: { license: p3xrs.connections.license } }) }, 1000 * 60 /* 1 minute */ * 60) p3xrs.redisConnections = {} p3xrs.redisConnectionsSubscriber = {} } module.exports = boot src/lib/check-license.js000066400000000000000000000043171520166327500154620ustar00rootroot00000000000000const utils = require('corifeus-utils') module.exports = async (options) => { const { socket } = options; p3xrs.cfg.donated = true socket.emit(options.responseEvent || 'info-interval', { donated: true, info: 'ok', status: 'ok', }) /* console.log(new Date().toLocaleString(), 'check license') let license = options.payload.license || ''; let donated = false try { let serverError = false let disableDonated = false if (typeof license === 'string' && license.trim().length === 0) { disableDonated = true } if (disableDonated === false && (typeof license !== 'string' || license.length !== 128)) { throw new Error('invalid_license') } if (!disableDonated) { const response = await utils.http.request({ url: `https://server.patrikx3.com/api/patrikx3/redis-ui/status/${license}` // url: `https://server.patrikx3.com/api/patrikx3/test/521` }) if (response.statusCode !== 200) { license = '' serverError = true } else if (response.body.isValid === false) { license = '' } else { donated = true } } if (typeof license === 'string' && license.length === 128) { console.log(new Date().toLocaleString(), 'valid license') } else { console.log(new Date().toLocaleString(), 'in-valid license') } const fs = require('fs') p3xrs.connections.license = license fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify(p3xrs.connections, null, 4)) p3xrs.cfg.donated = donated socket.emit(options.responseEvent || 'info-interval', { donated: donated, info: disableDonated ? 'cleared_license' : (serverError ? 'server_error': 'ok'), status: 'ok', }) } catch (e) { p3xrs.cfg.donated = false console.error(e) socket.emit(options.responseEvent || 'info-interval', { donated: false, status: 'error', error: e.message }) } */ } src/lib/cli.js000066400000000000000000000100641520166327500135300ustar00rootroot00000000000000const path = require('path') const fs = require('fs') const cli = () => { const pkg = require('../../package') if (!process.versions.hasOwnProperty('electron') && !process.env.hasOwnProperty('P3XRS_DOCKER_HOME')) { const program = require('commander') program .version(pkg.version) .option('-c, --config [config]', 'Set the p3xr.json p3x-redis-ui-server configuration, see more help in https://github.com/patrikx3/redis-ui-server') .option('-r, --readonly-connections', 'Set the connections to be readonly, no adding, saving or delete a connection') .option('-n, --connections-file-name [filename]', 'Set the connections file name, overrides default .p3xrs-conns.json') .parse(process.argv); if (!program.config) { program.config = path.resolve(path.dirname(require.main.filename) + path.sep + '..', `.${path.sep}p3xrs.json`) // program.outputHelp() // return false } const configPath = path.resolve(process.cwd(), program.config) //console.log(configPath) p3xrs.cfg = require(configPath).p3xrs if (program.readonlyConnections) { // console.warn(program.readonlyConnections) p3xrs.cfg.readonlyConnections = true //console.warn(p3xrs.cfg.readonlyConnections === true) } if (typeof program.connectionsFileName !== 'undefined' && program.connectionsFileName) { // console.warn(program.connectionsFileName) p3xrs.cfg.connectionsFileName = program.connectionsFileName //console.warn(p3xrs.cfg.readonlyConnections === true) } } else { p3xrs.cfg = { "http": { "port-info": "this is ommitted, it will be default 7843", "port": process.env.hasOwnProperty('P3XRS_DOCKER_HOME') ? 7843 : 7844 }, "connections": { "home-dir-info": "if the dir config is empty or home, the connections are saved in the home folder, otherwise it will resolve the directory set as it is, either relative ./ or absolute starting with /. NodeJs will resolve this directory in p3xrs.connections.dir", "home-dir": "home" }, "static-info": "This is the best configuration, if it starts with ~, then it is in resolve the path in the node_modules, otherwise it resolves to the current process current working directory.", "static": "~p3x-redis-ui-material/dist" } p3xrs.cfg.readonlyConnections = false } if (p3xrs.cfg.connectionsFileName === undefined) { p3xrs.cfg.connectionsFileName = '.p3xrs-conns.json' } if (!p3xrs.cfg.hasOwnProperty('static')) { } if (!p3xrs.cfg.hasOwnProperty('connections')) { p3xrs.cfg.connections = {} } if (!p3xrs.cfg.connections.hasOwnProperty('home-dir')) { p3xrs.cfg.connections = 'home' } if (p3xrs.cfg.connections['home-dir'] === 'home') { p3xrs.cfg.connections['home-dir'] = require('os').homedir(); } if (process.env.hasOwnProperty('P3XRS_DOCKER_HOME')) { p3xrs.cfg.connections['home-dir'] = process.env.P3XRS_DOCKER_HOME } p3xrs.cfg.connections['home'] = path.resolve(p3xrs.cfg.connections['home-dir'], p3xrs.cfg.connectionsFileName) console.info('using home config is', p3xrs.cfg.connections['home']) if (!fs.existsSync(p3xrs.cfg.connections.home)) { fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify({ update: new Date(), list: [], }, null, 4)) } p3xrs.connections = require(p3xrs.cfg.connections.home) //console.log(p3xrs.cfg.connections.home, p3xrs.connections) //console.log(p3xrs.connections) /* p3xrs.redis = {} let keyStreamPaging = 10000 Object.defineProperty(p3xrs.redis, 'key-stream-paging', { get: () => { return keyStreamPaging }, set: (value) => { keyStreamPaging = value } }) */ return true; } module.exports = cli; src/lib/console-stamp.js000066400000000000000000000025721520166327500155520ustar00rootroot00000000000000const chalk = require('chalk'); const consoleStamp = () => { // overriding the console should be after this!!! const methods = ['log', 'info', 'warn', 'error', 'debug'] const originalMethods = {} for(let method of methods) { originalMethods[method] = console[method] console[method] = function() { if (arguments[0]) { let label switch(method) { case 'error': label = chalk`{bold.red ${method.toUpperCase()}}`; break; case 'warn': label = chalk`{bold.blue ${method.toUpperCase()}}`; break; default: label = chalk`{green ${method.toUpperCase()}}`; } let data = '' //chalk`${moment().format(`YYYY/MM/DD HH:mm:ss.SSS`)} ` data += chalk`{black.grey [P3XRS]}` + ` [PID: ${(String(process.pid).padStart(6, 0))}] ` //arguments[0] = data + arguments[0] const mainArguments = Array.prototype.slice.call(arguments); mainArguments.unshift(data); originalMethods[method].apply(null, mainArguments) } else { originalMethods[method].apply(null, arguments) } } } } module.exports = consoleStamp src/lib/index.js000066400000000000000000000001721520166327500140670ustar00rootroot00000000000000module.exports = { boot: require('./boot'), cli: require('./cli'), consoleStamp: require('./console-stamp'), }src/lib/ioredis-cluster/000077500000000000000000000000001520166327500155375ustar00rootroot00000000000000src/lib/ioredis-cluster/cluster.js000066400000000000000000000125121520166327500175570ustar00rootroot00000000000000const Redis = require('ioredis') const {EventEmitter} = require('events') const redisInfo = require('./redis-info') const setDefaultPasswordOptionFromServer = require('./set-default-password-option-from-server') module.exports = class Cluster extends Redis.Cluster { constructor(server, options = {}) { server = Array.isArray(server) ? server : [server] options = setDefaultPasswordOptionFromServer(options, server) super(server, options) } /* async infoObject(...args) { const [key] = args if (key === 'keyspace') { return { databases: [await this._getClusterInfoKeyspace()] } } const info = await this.info(...args) const infoObject = redisInfo.parse(info) if (!key) { infoObject.databases[0] = await this._getClusterInfoKeyspace() } return infoObject } */ async _getClusterInfoKeyspace(info) { const keyspaceList = await Promise.all(this.nodes('master').map(node => { return node.info('keyspace') })) let keys = 0 let expires = 0 let avg_ttl = 0 for (const nodeKeyspace of keyspaceList) { if (!nodeKeyspace) { continue } const parsed = redisInfo.parse(nodeKeyspace) const db0 = parsed.databases[0] if (!db0) { continue } const { keys: nodeKeys = 0, expires: nodeExpires = 0, avg_ttl: nodeAvgTtl = 0, } = db0 keys += nodeKeys expires += nodeExpires avg_ttl += nodeAvgTtl } avg_ttl = avg_ttl ? Math.round(avg_ttl / expires) : 0 const clusterKeyspace = { keys, expires, avg_ttl, } // console.log({clusterKeyspace}) return clusterKeyspace } async originalDbsize(...args) { return super.dbsize(...args) } async dbsize() { const nodeCounts = await Promise.all(this.nodes('master').map(node => { return node.dbsize() })) const count = nodeCounts.reduce((tt, c) => tt + c, 0) return count } originalRename(...args) { return super.rename(...args) } async rename(key, keyNew, callback) { let res = null let err = null try { let [value, ttl] = await Promise.all([ this.dumpBuffer(key), this.ttl(key), ]) ttl = parseInt(ttl) if (ttl < 0) { ttl = 0 } await this.del(keyNew) await this.restore(keyNew, ttl, value) await this.del(key) res = 'OK' } catch (e) { err = e } if (typeof callback === 'function') { callback(err, res) } else if (err) { throw err } return res } originalPipeline(...args) { return super.pipeline(...args) } pipeline(...pipelineArgs) { const calls = [] async function exec(calls) { const results = [] for (let c of calls) { const result = await c() results.push(result) } // console.log({results}) return results } const proxy = new Proxy(calls, { get: (calls, method) => { return (...callArgs) => { switch (method) { case 'exec': return exec(calls) break } const callback = async () => { let err = null let result = null try { result = await this[method](...callArgs) } catch (e) { err = e } return [err, result] } calls.push(callback) return proxy } } }) return proxy } scanStream(...args) { const stream = new EventEmitter() this._streamNodes({ stream, method: 'scanStream', params: args, }) return stream } async _streamNodes(options = {}) { let { nodes = this.nodes('master'), stream = new EventEmitter(), method, params = [], } = options try { const promises = [] for (let node of nodes) { promises.push( new Promise((resolve, reject) => { const nodeStream = node[method](...params) nodeStream.on('data', (resultKeys) => { // console.log({resultKeys}) stream.emit('data', resultKeys) }) nodeStream.on('end', () => { resolve() }) }) ) } await Promise.all(promises) } finally { stream.emit('end') } } } src/lib/ioredis-cluster/create-with-cluster-auto-detect.js000066400000000000000000000013361520166327500242070ustar00rootroot00000000000000const Redis = require('ioredis') const isClusterEnabled = require('./is-cluster-enabled') const getClusterNodes = require('./get-cluster-nodes') const Cluster = require('./cluster') const setDefaultPasswordOptionFromServer = require('./set-default-password-option-from-server') module.exports = async function createWithClusterAutoDetect(server, options = {}) { let isCluster if (Array.isArray(server)) { isCluster = true } else { isCluster = await isClusterEnabled(server, true) } if (!isCluster) { return new Redis(server) } // server = await getClusterNodes(server) options = setDefaultPasswordOptionFromServer(options, server) return new Cluster(server, options) } src/lib/ioredis-cluster/get-cluster-nodes.js000066400000000000000000000036271520166327500214510ustar00rootroot00000000000000const Redis = require('ioredis') module.exports = async function getClusterNodes(servers, options = {}) { if (!Array.isArray(servers)) { servers = [servers] } const errors = [] let nodes for (const server of servers) { try { const redis = new Redis({...server, retryStrategy: () => false}) const rawNodes = await new Promise((resolve, reject) => { redis.sendCommand( new Redis.Command( 'CLUSTER', ['NODES'], 'utf-8', function (err, value) { if (err) reject(err) else resolve(value.toString()) } ) ) }) const lines = rawNodes.trim().split("\n") nodes = lines.reduce((arr, line) => { if (!line) { return arr } const row = line.split(' ') const [ node_id, server, flags, ] = row const [target, slots] = server.split('@') const [host, port] = target.split(':') const node = { host, port, } arr.push(node) return arr }, []) return nodes } catch (error) { console.error(error) errors.push(error) } finally { redis.disconnect() } if (nodes) { break } } if (nodes) { return nodes } const errorsMsg = errors.map(e => e.toString()).join(', ') throw new Error('getClusterNodes errors: ' + errorsMsg) } src/lib/ioredis-cluster/get-default-password-from-server.js000066400000000000000000000003431520166327500244030ustar00rootroot00000000000000module.exports = function getDefaultPasswordFromServer(server) { const server1 = Array.isArray(server) ? server[0] : server if (typeof server1 === 'object' && server1 !== null) { return server1.password } } src/lib/ioredis-cluster/get-info.js000066400000000000000000000004541520166327500176100ustar00rootroot00000000000000const Redis = require('ioredis') const redisInfo = require('./redis-info') module.exports = async function getInfo(server, options = {}) { const redis = new Redis(server) const rawInfo = await redis.info() redis.disconnect() const info = redisInfo.parse(rawInfo) return info } src/lib/ioredis-cluster/index.js000066400000000000000000000000701520166327500172010ustar00rootroot00000000000000const Redis = require('./redis') module.exports = Redis src/lib/ioredis-cluster/is-cluster-enabled.js000066400000000000000000000003351520166327500215600ustar00rootroot00000000000000const getInfo = require('./get-info') module.exports = async function isClusterEnabled(server, cache = false) { const {cluster_enabled} = await getInfo(server, {cache}) return Boolean(parseInt(cluster_enabled)) } src/lib/ioredis-cluster/redis-info.js000066400000000000000000000056171520166327500201450ustar00rootroot00000000000000/* from npm's redis-info, adding missing avg_ttl */ const { fromPairs, find, has, } = require('lodash') module.exports = { parse: function (info) { return parseFields(splitStr(info)) } } function startWith(pattern) { return function (value) { return value.indexOf(pattern) === 0 } } function split(s) { return function (v) { return v.split(s) } } function orEmptyStr(v) { return v || '' } function takeN(func, n) { return function (v) { return func(v[n]) } } function takeFirst(func) { return takeN(func, 0) } /** * Split the info string by \n and : * @param {String} str the returned redis info * @return {Array} Array of [key, value] */ function splitStr(str) { return str.split('\n') .filter(function (line) { return line.length > 0 && line.indexOf('#') !== 0 }) .map(function (line) { return line.trim().split(':') }) } function parseDatabases(info) { return info .filter(takeFirst(startWith('db'))) .map(function _parseDatabaseInfo(args) { var dbName = args[0] var value = args[1] var values = orEmptyStr(value).split(',') // console.log({values}) function extract(param) { return parseInt(orEmptyStr(find(values, startWith(param))).split('=')[1] || 0, 10) } return { index: parseInt(dbName.substr(2), 10), keys: extract('keys'), expires: extract('expires'), avg_ttl: extract('avg_ttl'), } }) .reduce(function (m, v) { m[v.index] = { keys: v.keys, expires: v.expires, avg_ttl: v.avg_ttl, } return m }, {}) } function parseCommands(info) { return fromPairs(info.filter(function (a) { return orEmptyStr(a[0]).indexOf('cmdstat_') === 0 }) .map(function _parseCommands(args) { var v = args[0] var a = args[1] var val = fromPairs(orEmptyStr(a).split(',').map(split('='))) if (has(val, 'calls')) { val.calls = parseInt(val.calls, 10) } if (has(val, 'usec')) { val.usec = parseInt(val.usec, 10) } if (has(val, 'usec_per_call')) { val.usec_per_call = parseFloat(val.usec_per_call, 10) } return [orEmptyStr(v).split('_')[1], val] })) } function parseFields(info) { var fields = info.reduce(function (m, v) { if (!v[0].trim() || v[0].indexOf('db') === 0 || v[0].indexOf('cmdstat_') === 0) { return m } m[v[0]] = v[1] return m }, { databases: parseDatabases(info), commands: parseCommands(info) }) return fields } src/lib/ioredis-cluster/redis.js000066400000000000000000000017371520166327500172130ustar00rootroot00000000000000const IORedis = require('ioredis') const redisInfo = require('./redis-info') const Cluster = require('./cluster') const createWithClusterAutoDetect = require('./create-with-cluster-auto-detect') const getInfo = require('./get-info') const getClusterNodes = require('./get-cluster-nodes') const isClusterEnabled = require('./is-cluster-enabled') class Redis extends IORedis { constructor(server, {autoDetectCluster, ...options} = {}) { if (autoDetectCluster && !Array.isArray(server)) { return createWithClusterAutoDetect(server, options) } if (Array.isArray(server)) { return new Cluster(server, options) } super(server) } /* async infoObject(...args) { const info = await this.info(...args) return redisInfo.parse(info) } */ } Redis.Cluster = Cluster Redis.isClusterEnabled = isClusterEnabled Redis.getClusterNodes = getClusterNodes Redis.getInfo = getInfo module.exports = Redis src/lib/ioredis-cluster/set-default-password-option-from-server.js000066400000000000000000000007611520166327500257310ustar00rootroot00000000000000const getDefaultPasswordFromServer = require('./get-default-password-from-server') module.exports = function setDefaultPasswordOptionFromServer(options, server) { const defaultPassword = getDefaultPasswordFromServer(server) let {redisOptions} = options if (redisOptions === undefined) { redisOptions = {} options.redisOptions = redisOptions } if (redisOptions.password === undefined) { redisOptions.password = defaultPassword } return options } src/service/000077500000000000000000000000001520166327500133145ustar00rootroot00000000000000src/service/index.js000066400000000000000000000000571520166327500147630ustar00rootroot00000000000000module.exports = { koa: require('./koa'), }src/service/koa/000077500000000000000000000000001520166327500140665ustar00rootroot00000000000000src/service/koa/index.js000066400000000000000000000062341520166327500155400ustar00rootroot00000000000000const Koa = require('koa'); //const Router = require('koa-router') const fs = require('fs') //const koaBody = require('koa-body') const path = require('path') const koaService = function () { const self = this; self.boot = async () => { const app = new Koa(); this.app = app; // const router = new Router(); // this.router = router; // app.use(koaBody()); const resolvePath = (inputPath) => { let resolvedPath if (inputPath.startsWith('~')) { const inputPathFromNodeModules = inputPath.substring(1) resolvedPath = path.resolve(path.dirname(require.main.filename) + path.sep + '..', `node_modules${path.sep}${inputPathFromNodeModules}`) } else { resolvedPath = path.resolve(process.cwd(), inputPath) } return resolvedPath } let hasStatic = false let staticPath if (typeof p3xrs.cfg.static === 'string') { hasStatic = true staticPath = resolvePath(p3xrs.cfg.static) const serve = require('koa-static'); app.use(serve(staticPath)); } app.on('error', err => { console.error('koa server error', err) }); /* app.context.p3x = { status: { 404: () => { const error = new Error('not-found'); error.status = 404; throw error; } } } */ /* app.use(async (ctx) => { ctx.body = { status: 'operational' }; }); */ if (hasStatic) { const send = require('koa-send') app.use(async (ctx) => { await send(ctx, 'index.html', {root: staticPath}); }); } else { app.use(async (ctx) => { ctx.response.body = { status: 'operational' } }); } // app.use(router.routes()) // app.use(router.allowedMethods()); /* const keyFilename = resolvePath(p3xrs.cfg.https2.key) const certFilename = resolvePath(p3xrs.cfg.https2.cert) const certs = [ // key fs.readFileSync(keyFilename), // cert fs.readFileSync(certFilename), ] const options = { key: certs[0].toString(), cert: certs[1].toString(), }; */ //console.warn('keyFilename', keyFilename, options.key) //console.warn('certFilename', certFilename, options.cert) //const spdy = require('spdy'); //const server = spdy.createServer(options, app.callback()) // not working with websocket-s native node http2 //const http2 = require('http2'); //const server = http2.createSecureServer(options, app.callback()); const http = require('http') const server = http.createServer(app.callback()) this.server = server; server.listen(p3xrs.cfg.http.port || 7843, '0.0.0.0'); } } module.exports = koaService src/service/socket.io/000077500000000000000000000000001520166327500152125ustar00rootroot00000000000000src/service/socket.io/index.js000066400000000000000000000006551520166327500166650ustar00rootroot00000000000000const socketIo = require('socket.io') const socketIoService = function () { const self = this; self.boot = async (options) => { const {koaService} = options const socketio = require('socket.io')(koaService.server, { secure: true, path: '/socket.io', }); require('./socket')(socketio); this.socketio = socketio } } module.exports = socketIoService src/service/socket.io/request/000077500000000000000000000000001520166327500167025ustar00rootroot00000000000000src/service/socket.io/request/connection-connect.js000066400000000000000000000206251520166327500230330ustar00rootroot00000000000000const donationWareFeatureError = new Error('donation-ware-feature') donationWareFeatureError.code = 'donation-ware-feature' const consolePrefix = 'socket.io connection-connect'; const Redis = require('../../../lib/ioredis-cluster') const sharedIoRedis = require('../shared') const generateConnectInfo = async (options) => { const {socket, redis, payload} = options const { db} = payload // console.warn('generateConnectInfo', options.payload) let databases let results let commands const probeDatabaseCount = async() => { let tryUntilSelectDatabaseIsNotOk = true let currentDb = 0 let totalDb = 0 let maxDb = 512 while(tryUntilSelectDatabaseIsNotOk) { try { currentDb++ await redis.call('select', currentDb) //console.info('found correct database index', currentDb) if (currentDb > maxDb) { console.warn(`limiting to max ${maxDb} database index, as it could crash with a big db index number`) tryUntilSelectDatabaseIsNotOk = false } } catch(e) { console.warn('found wrong current db index', currentDb) tryUntilSelectDatabaseIsNotOk = false } } totalDb = currentDb if (db <= totalDb) { await redis.call('select', db) } console.log('calculated max databases index', totalDb) return totalDb } if (options.payload.connection.awsElastiCache === true || options.payload.connection.azure === true) { databases = await probeDatabaseCount() commands = await redis.command() } else { results = await Promise.all([ redis.config('get', 'databases'), redis.command(), ]) databases = parseInt(results[0][1]) commands = results[1] } //socket.p3xrs.commands = commands.map(e => e[0].toLowerCase()) //console.log('databases', databases) await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, extend: { databases: databases, commands: commands }, payload: payload, }) } module.exports = async (options) => { const {socket, payload} = options; const {connection, db} = payload try { if (!p3xrs.cfg.donated) { if (payload.connection.awsElastiCache === true) { throw donationWareFeatureError } else if (payload.connection.azure === true) { throw donationWareFeatureError } else if (payload.connection.cluster === true) { throw donationWareFeatureError } } if (socket.p3xrs.connectionId !== connection.id) { sharedIoRedis.disconnectRedis({ socket: socket, }) } if (!p3xrs.redisConnections.hasOwnProperty(connection.id)) { p3xrs.redisConnections[connection.id] = { connection: connection, clients: [] } } if (!p3xrs.redisConnections[connection.id].clients.includes(socket.id)) { console.info(consolePrefix, 'added new socket.id', socket.id, 'to', connection.id, 'name with', connection.name) p3xrs.redisConnections[connection.id].clients.push(socket.id) } if (socket.p3xrs.ioredis !== undefined) { console.info(consolePrefix, 'redis was already connected') socket.p3xrs.connectionId = connection.id await generateConnectInfo({ redis: socket.p3xrs.ioredis, socket: socket, responseEvent: options.responseEvent, payload: payload }) sharedIoRedis.sendStatus({ socket: socket, }) } else { const actualConnection = p3xrs.connections.list.find(con => options.payload.connection.id === con.id) let redisConfig = Object.assign({}, actualConnection); delete redisConfig.name delete redisConfig.id redisConfig.retryStrategy = () => { return false } if (db !== undefined) { redisConfig.db = db } if (redisConfig.cluster === true) { redisConfig = [redisConfig].concat(actualConnection.nodes) } let redis = new Redis(redisConfig) let redisSubscriber = new Redis(redisConfig) // let redis = await new Redis(redisConfig, {autoDetectCluster: true}) // let redisSubscriber = await new Redis(redisConfig, {autoDetectCluster: true}) socket.p3xrs.connectionId = connection.id socket.p3xrs.ioredis = redis socket.p3xrs.ioredisSubscriber = redisSubscriber let didConnected = false const redisErrorFun = async function (error) { const consolePrefix = 'socket.io connection-connect redis error fun' console.warn(consolePrefix, connection.id, connection.name, 'error') console.error(error) console.warn(consolePrefix, 'didConnected', didConnected) if (!didConnected) { socket.emit(options.responseEvent, { status: 'error', error: error.message }) } const disconnectedData = { connectionId: socket.p3xrs.connectionId, error: error.message, status: 'error', } console.warn(consolePrefix, 'disconnectedData', disconnectedData) socket.p3xrs.io.emit('redis-disconnected', disconnectedData) try { await sharedIoRedis.disconnectRedis({ socket: socket, }) } catch (e) { console.warn(consolePrefix, 'disconnectRedis') console.error(e) } delete p3xrs.redisConnections[socket.connectionId] socket.p3xrs.connectionId = undefined socket.p3xrs.ioredis = undefined socket.p3xrs.ioredisSubscriber = undefined sharedIoRedis.sendStatus({ socket: socket, }) } redis.on('error', redisErrorFun) redisSubscriber.on('error', redisErrorFun) //console.warn('create psubscribe', actualConnection.id) redisSubscriber.psubscribe('*', function (error, count) { if (error) { console.error(error) } }) //console.warn('create pmessage', actualConnection.id) redisSubscriber.on('pmessage', function (channel, pattern, message) { //console.log(`receive pmessage channel: ${channel} - pattern: ${pattern}, message: ${message}`); //console.log('list clients', actualConnection.id, JSON.stringify(p3xrs.redisConnections[actualConnection.id].clients, null, 4)) socket.emit('pubsub-message', { channel: pattern, message: message, }) }); redis.on('connect', async function () { try { console.info(consolePrefix, options.payload.connection.id, options.payload.connection.name, 'connected') didConnected = true await generateConnectInfo({ redis: redis, socket: socket, responseEvent: options.responseEvent, payload: options.payload, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } finally { sharedIoRedis.sendStatus({ socket: socket, }) } }) } } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/connection-delete.js000066400000000000000000000030161520166327500226370ustar00rootroot00000000000000const sharedIoRedis = require('../shared') module.exports = async (options) => { const {socket} = options; const connectionSaveId = options.payload.id; let connectionIndexExisting; let disableReadonlyConnections = true try { sharedIoRedis.ensureReadonlyConnections() disableReadonlyConnections = false for (let connectionIndex in p3xrs.connections.list) { const connection = p3xrs.connections.list[connectionIndex] if (connection.id === connectionSaveId) { connectionIndexExisting = connectionIndex break; } } if (connectionIndexExisting !== undefined) { p3xrs.connections.list.splice(connectionIndexExisting, 1) p3xrs.connections.update = new Date() const fs = require('fs') fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify(p3xrs.connections, null, 4)) } socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } finally { if (!disableReadonlyConnections) { sharedIoRedis.sendConnections({ socket: socket, }) sharedIoRedis.triggerDisconnect({ connectionId: connectionSaveId, code: 'delete-connection', socket: socket, }) } } } src/service/socket.io/request/connection-disconnect.js000066400000000000000000000016201520166327500235250ustar00rootroot00000000000000const sharedIoRedis = require('../shared') const consolePrefix = 'socket.io connection disconnect' module.exports = async (options) => { const {socket, payload} = options; const {connectionId} = payload; console.warn(consolePrefix, 'connectionId', connectionId, 'socket.p3xrs.connectionId', socket.p3xrs.connectionId) try { if (socket.p3xrs.connectionId === connectionId) { console.warn(consolePrefix, 'will disconnect from redis') sharedIoRedis.disconnectRedis({ socket: socket, }) } socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } finally { sharedIoRedis.sendStatus({ socket: socket, }) } } src/service/socket.io/request/connection-save.js000066400000000000000000000045231520166327500223370ustar00rootroot00000000000000const sharedIoRedis = require('../shared') module.exports = async (options) => { const {socket} = options; const connectionSave = options.payload.model; let disableReadonlyConnections = true try { sharedIoRedis.ensureReadonlyConnections() disableReadonlyConnections = false let connectionIndexExisting; for (let connectionIndex in p3xrs.connections.list) { const connection = p3xrs.connections.list[connectionIndex] if (connection.id === connectionSave.id) { connectionIndexExisting = connectionIndex break; } } p3xrs.connections.update = new Date() if (connectionIndexExisting !== undefined) { if (p3xrs.connections.list[connectionIndexExisting].id === connectionSave.password) { connectionSave.password = p3xrs.connections.list[connectionIndexExisting].password; } //TODO fix secured nodes password if (Array.isArray(connectionSave.nodes)) { for (let node of connectionSave.nodes) { const findNode = p3xrs.connections.list[connectionIndexExisting].nodes.find((findNode) => { return findNode.id === node.id && node.password === findNode.id }) if (findNode !== undefined) { node.password = findNode.password } } } p3xrs.connections.list[connectionIndexExisting] = connectionSave } else { p3xrs.connections.list.push(connectionSave) } const fs = require('fs') fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify(p3xrs.connections, null, 4)) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } finally { if (!disableReadonlyConnections) { sharedIoRedis.sendConnections({ socket: socket, }) sharedIoRedis.triggerDisconnect({ connectionId: connectionSave.id, code: 'save-connection', socket: socket, }) } } } src/service/socket.io/request/console.js000066400000000000000000000057371520166327500207160ustar00rootroot00000000000000const parser = (input, sep, keepQuotes) => { var separator = sep || /\s/g; var singleQuoteOpen = false; var doubleQuoteOpen = false; var tokenBuffer = []; var ret = []; var arr = input.split(''); for (var i = 0; i < arr.length; ++i) { var element = arr[i]; var matches = element.match(separator); if (element === "'" && !doubleQuoteOpen) { if (keepQuotes === true) { tokenBuffer.push(element); } singleQuoteOpen = !singleQuoteOpen; continue; } else if (element === '"' && !singleQuoteOpen) { if (keepQuotes === true) { tokenBuffer.push(element); } doubleQuoteOpen = !doubleQuoteOpen; continue; } if (!singleQuoteOpen && !doubleQuoteOpen && matches) { if (tokenBuffer.length > 0) { ret.push(tokenBuffer.join('')); tokenBuffer = []; } else if (!!sep) { ret.push(element); } } else { tokenBuffer.push(element); } } if (tokenBuffer.length > 0) { ret.push(tokenBuffer.join('')); } else if (!!sep) { ret.push(''); } return ret; } const consolePrefix = 'socket.io console call' module.exports = async (options) => { const {socket, payload} = options; const {command} = payload try { let redis = socket.p3xrs.ioredis const commands = parser( command); let mainCommand = commands.shift() mainCommand = mainCommand.toLowerCase(); console.info(consolePrefix, mainCommand, commands) /* if (!socket.p3xrs.commands.includes(mainCommand)) { throw new Error(`ERR Unknown command '${mainCommand}'.`) } */ let result = await redis.call(mainCommand, commands) const defaultEmit = {} let generatedCommand = mainCommand if (commands.length > 0) { generatedCommand += ' ' + commands.join(' ') } switch (mainCommand) { case 'select': defaultEmit.database = parseInt(commands[0]) break; } /* switch (generatedCommand) { case 'client list': //result = result.split(' ') break; } */ //console.warn(consolePrefix, typeof result, result) /* try { const clone = JSON.parse(JSON.stringify(result)) console.warn(consolePrefix, typeof clone, clone) } catch(e) { console.warn(e) } */ socket.emit(options.responseEvent, Object.assign(defaultEmit, { status: 'ok', result: result, generatedCommand: generatedCommand, })) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/delete.js000066400000000000000000000012421520166327500205010ustar00rootroot00000000000000const consolePrefix = 'socket.io del key' const sharedIoRedis = require('../shared') module.exports = async (options) => { const {socket, payload} = options; try { let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.key) await redis.del(payload.key) await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, payload: payload, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/expire.js000066400000000000000000000010471520166327500205360ustar00rootroot00000000000000const consolePrefix = 'socket.io expire' module.exports = async (options) => { const {socket, payload} = options; try { let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.key, payload.ttl) await redis.expire(payload.key, parseInt(payload.ttl)) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key-del-tree.js000066400000000000000000000021301520166327500215230ustar00rootroot00000000000000const consolePrefix = 'socket.io key del tree' const sharedIoRedis = require('../shared') module.exports = async (options) => { const {socket, payload} = options; try { let redis = socket.p3xrs.ioredis const deleteTree = `${payload.key}${payload.redisTreeDivider}*`; console.info(consolePrefix, deleteTree) const keys = await sharedIoRedis.getStreamKeys({ redis: redis, match: deleteTree, maxKeys: payload.maxKeys, }) const pipelineDeleteTree = redis.pipeline() for (let key of keys) { console.info(consolePrefix, 'delete key ', key) pipelineDeleteTree.del(key) } await pipelineDeleteTree.exec(); await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, payload: payload, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key-get.js000066400000000000000000000042721520166327500206120ustar00rootroot00000000000000const consolePrefix = 'socket.io key get full' module.exports = async (options) => { const {socket, payload} = options; try { let redis = socket.p3xrs.ioredis const key = payload.key; //const type = payload.type; const type = await redis.type(key) //console.info(consolePrefix, payload, type, key) const viewPipeline = redis.pipeline() switch (type) { case 'string': viewPipeline.get(key) break; case 'list': viewPipeline.lrange(key, 0, -1) break; case 'hash': viewPipeline.hgetall(key) break; case 'set': viewPipeline.smembers(key) break; case 'zset': viewPipeline.zrange(key, 0, -1, 'WITHSCORES') break; } viewPipeline.ttl(key) viewPipeline.object('encoding', key) switch (type) { case 'hash': viewPipeline.hlen(key) break; case 'list': viewPipeline.llen(key) break; case 'set': viewPipeline.scard(key) break; case 'zset': viewPipeline.zcard(key) break; } const viewPipelineResult = await viewPipeline.exec() // console.log(viewPipelineResult) const value = viewPipelineResult[0][1] const ttl = viewPipelineResult[1][1] const encoding = viewPipelineResult[2][1] let length if (type !== 'string') { length = viewPipelineResult[3][1] } const socketResult = { length: length, key: key, status: 'ok', type: type, value: value, ttl: ttl, encoding: encoding, }; // console.warn('socketResult', socketResult) socket.emit(options.responseEvent, socketResult) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/key-hash-delete-field.js000066400000000000000000000010531520166327500232710ustar00rootroot00000000000000const consolePrefix = 'socket.io key hash delete key' const utils = require('corifeus-utils') module.exports = async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { const {hashKey, key} = payload; await redis.hdel(key, hashKey) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key-list-delete-index.js000066400000000000000000000013301520166327500233430ustar00rootroot00000000000000const consolePrefix = 'socket.io key list delete index' const utils = require('corifeus-utils') module.exports = async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { const {index, key} = payload; const uniqueValue = utils.random.complexUuid() console.log(consolePrefix, key, index, uniqueValue) await redis.lset(key, index, uniqueValue) await redis.lrem(key, 1, uniqueValue) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key-new-or-set.js000066400000000000000000000052761520166327500220400ustar00rootroot00000000000000const consolePrefix = 'socket.io key new' const sharedIoRedis = require('../shared') module.exports = async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { const {model} = payload; model.score = model.score === null ? undefined : model.score model.index = model.index === null ? undefined : model.index model.hashKey = model.hashKey === null ? undefined : model.hashKey //console.warn(consolePrefix, payload) switch (model.type) { case 'string': await redis.set(model.key, model.value) break; case 'list': if (model.index === undefined) { await redis.rpush(model.key, model.value) } else { if (model.index === -1) { await redis.lpush(model.key, model.value) } else { const size = await redis.llen(model.key); if (model.index > -1 && model.index < size) { await redis.lset(model.key, model.index, model.value) } else { const listOutOBoundsError = new Error('list-out-of-bounds') listOutOBoundsError.code = 'list-out-of-bounds' throw listOutOBoundsError } } } break; case 'hash': if (payload.hasOwnProperty('originalHashKey')) { await redis.hdel(model.key, payload.originalHashKey) } await redis.hset(model.key, model.hashKey, model.value) break; case 'set': if (payload.hasOwnProperty('originalValue')) { await redis.srem(model.key, payload.originalValue) } await redis.sadd(model.key, model.value) break; case 'zset': if (payload.hasOwnProperty('originalValue')) { await redis.zrem(model.key, payload.originalValue) } await redis.zadd(model.key, model.score, model.value) break; } await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, extend: { key: model.key }, payload: payload, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key-set-delete-member.js000066400000000000000000000010511520166327500233230ustar00rootroot00000000000000const consolePrefix = 'socket.io key list delete index' const utils = require('corifeus-utils') module.exports = async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { const {key, value} = payload; await redis.srem(key, value) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key-set.js000066400000000000000000000010651520166327500206230ustar00rootroot00000000000000module.exports = async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { const ttl = await redis.ttl(payload.key) await redis.set(payload.key, payload.value) if (ttl !== -1) { await redis.expire(payload.key, ttl) } socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/key-zset-delete-member.js000066400000000000000000000010551520166327500235210ustar00rootroot00000000000000const consolePrefix = 'socket.io key zsit delete member' module.exports = async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { const {key, value} = payload; console.log(consolePrefix, payload) await redis.zrem(key, value) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/persist.js000066400000000000000000000007201520166327500207300ustar00rootroot00000000000000const consolePrefix = 'socket.io persists' module.exports = async (options) => { const {socket, payload} = options; try { let redis = socket.p3xrs.ioredis await redis.persist(payload.key) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/redis-test-connection.js000066400000000000000000000037701520166327500234670ustar00rootroot00000000000000const Redis = require('../../../lib/ioredis-cluster') module.exports = async (options) => { const {socket} = options; try { let redisConfig = options.payload.model; const actualConnection = p3xrs.connections.list.find(con => redisConfig.id === con.id) if (actualConnection !== undefined) { if (redisConfig.password === actualConnection.id) { redisConfig.password = actualConnection.password; } } //TODO fix secured nodes password delete redisConfig.name delete redisConfig.id if (redisConfig.cluster === true) { redisConfig.nodes = redisConfig.nodes.map((node) => { if (node.password === node.id) { const foundNode = actualConnection.nodes.find((findNode) => findNode.id === node.password) node.password = foundNode.password } return node }) redisConfig = [redisConfig].concat(redisConfig.nodes) } let redis = new Redis(redisConfig) redis.on('error', function (error) { console.error(error) socket.emit(options.responseEvent, { status: 'error', error: error.message }) redis.disconnect() }) redis.on('connect', async function () { try { //await redis.call('client', 'list') socket.emit(options.responseEvent, { status: 'ok', }) } catch (error) { console.error(error) socket.emit(options.responseEvent, { status: 'error', error: error.message }) } finally { redis.disconnect() } }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/refresh.js000066400000000000000000000011201520166327500206700ustar00rootroot00000000000000const sharedIoRedis = require('../shared') //const consolePrefix = 'socket.io refresh redis' module.exports = async (options) => { const {socket, payload} = options; const redis = socket.p3xrs.ioredis try { await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, payload: payload, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/rename.js000066400000000000000000000012711520166327500205100ustar00rootroot00000000000000const consolePrefix = 'socket.io rename key' const sharedIoRedis = require('../shared') module.exports = async (options) => { const {socket, payload} = options; try { let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.key) await redis.rename(payload.key, payload.keyNew) await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, payload: payload, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message, }) } } src/service/socket.io/request/save.js000066400000000000000000000006621520166327500202020ustar00rootroot00000000000000module.exports = async (options) => { const {socket} = options; const redis = socket.p3xrs.ioredis try { await redis.save() socket.emit(options.responseEvent, { status: 'ok', info: await redis.info(), }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/set-language.js000066400000000000000000000006751520166327500216240ustar00rootroot00000000000000module.exports = async (options) => { const { socket, payload } = options; try { global.p3xre.setLanguage({ key: payload.key }) socket.emit(options.responseEvent, { status: 'ok', key: payload.key, }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/request/set-license.js000066400000000000000000000002761520166327500214600ustar00rootroot00000000000000const utils = require('corifeus-utils') const checkLicense = require('../../../lib/check-license') module.exports = async (options) => { options.save = true checkLicense(options) } src/service/socket.io/request/trigger-redis-disconnect.js000066400000000000000000000011351520166327500241360ustar00rootroot00000000000000const sharedIoRedis = require('../shared') const consolePrefix = 'socket.io trigger redis disconnect' module.exports = async (options) => { const {socket} = options; try { console.warn(consolePrefix, 'socket.p3xrs.connectionId', socket.p3xrs.connectionId) sharedIoRedis.disconnectRedis({ socket: socket, }) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e.message }) } } src/service/socket.io/shared.js000066400000000000000000000255631520166327500170310ustar00rootroot00000000000000const triggerDisconnect = (options) => { const {connectionId, code, socket} = options if (p3xrs.redisConnections.hasOwnProperty(connectionId)) { delete p3xrs.redisConnections[connectionId] socket.p3xrs.io.emit('redis-disconnected', { connectionId: connectionId, status: 'code', code: code }) sendStatus({ socket: socket }) } } const sendStatus = (options) => { const {socket} = options const redisConnections = {} Object.keys(p3xrs.redisConnections).forEach((redisConnectionKey) => { redisConnections[redisConnectionKey] = {} Object.keys(p3xrs.redisConnections[redisConnectionKey]).forEach(redisConnectionKey2 => { redisConnections[redisConnectionKey][redisConnectionKey2] = p3xrs.redisConnections[redisConnectionKey][redisConnectionKey2] }) }) socket.p3xrs.io.emit('redis-status', { redisConnections: redisConnections, }) } const consolePrefixDisconnectRedis = 'socket.io shared disconnect redis' const disconnectRedis = (options) => { const {socket} = options //console.warn(consolePrefixDisconnectRedis, `${socket.p3xrs.connectionId} !== ${connection.id}`) if (p3xrs.redisConnections.hasOwnProperty(socket.p3xrs.connectionId)) { console.warn(consolePrefixDisconnectRedis, `includes ${p3xrs.redisConnections[socket.p3xrs.connectionId].clients.includes(socket.id)} length === 1 ${p3xrs.redisConnections[socket.p3xrs.connectionId].clients.length}`) if (p3xrs.redisConnections[socket.p3xrs.connectionId].clients.includes(socket.id) && p3xrs.redisConnections[socket.p3xrs.connectionId].clients.length === 1) { //console.warn(consolePrefixDisconnectRedis, p3xrs.redisConnections[socket.p3xrs.connectionId]) //p3xrs.redisConnections[socket.p3xrs.connectionId].ioredis.disconnect() delete p3xrs.redisConnections[socket.p3xrs.connectionId] } else { let connectionIndexExisting = p3xrs.redisConnections[socket.p3xrs.connectionId].clients.indexOf(socket.id); console.warn(consolePrefixDisconnectRedis, socket.p3xrs.connectionId, p3xrs.redisConnections[socket.p3xrs.connectionId].clients, socket.id, connectionIndexExisting) if (connectionIndexExisting > -1) { p3xrs.redisConnections[socket.p3xrs.connectionId].clients.splice(connectionIndexExisting, 1) } } } if (p3xrs.redisConnections.hasOwnProperty(socket.p3xrs.connectionId) && p3xrs.redisConnections[socket.p3xrs.connectionId].hasOwnProperty('clients') && p3xrs.redisConnections[socket.p3xrs.connectionId].clients.length === 0) { delete p3xrs.redisConnections[socket.p3xrs.connectionId] } module.exports.disconnectRedisIo(options) socket.p3xrs.connectionId = undefined } const cloneDeep = require('lodash/cloneDeep') const sendConnections = (options) => { const {socket} = options const connections = cloneDeep(p3xrs.connections); let connectionsList = connections.list.map(connection => { delete connection.password //TODO fix secured nodes password if (Array.isArray(connection.nodes)) { connection.nodes = connection.nodes.map(node => { delete node.password return node }) } return connection }) connections.list = connectionsList socket.p3xrs.io.emit('connections', { status: 'ok', connections: connections }) } const disconnectRedisIo = (options) => { const {socket} = options console.warn('shared disconnectRedisIo', 'try') if (socket.p3xrs.ioredis !== undefined) { console.warn('shared disconnectRedisIo', 'executed') socket.p3xrs.ioredis.disconnect() socket.p3xrs.ioredisSubscriber.disconnect() socket.p3xrs.ioredis = undefined socket.p3xrs.ioredisSubscriber = undefined } } const getStreamKeys = (options) => { const {redis, } = options let {dbsize, maxKeys} = options return new Promise(async (resolve, reject) => { try { /* if (dbsize === undefined) { dbsize = await redis.dbsize() } */ if (isNaN(maxKeys) || maxKeys < 5 || maxKeys > 100000) { maxKeys = 10000 } //console.warn('check if received max keys', maxKeys, typeof maxKeys, !isNaN(maxKeys), maxKeys < 5, maxKeys > 100000) /* let count = 100 if (dbsize > 110000) { count = 10000 } else if (dbsize > 11000) { count = 1000 } */ let count = Math.round(maxKeys / 10) if (count < 5) { count = 5 } //console.warn('socket.io getStreamKeys dbsize', dbsize, 'count', count, 'maxKeys', maxKeys) const stream = redis.scanStream({ match: options.match, count: count }); let keys = []; let ended = false stream.on('data', (resultKeys) => { /* keys = keys.concat(resultKeys); if (maxKeys && keys.length >= maxKeys && !ended) { ended = true console.warn('reached max key count', maxKeys, 'found', keys.length, 'keys our of unknown total') //stream.pause() //stream.destroy() stream.emit('end') } */ if (maxKeys && keys.length < maxKeys) { keys = keys.concat(resultKeys); if (keys.length >= maxKeys) { ended = true resolve(keys) //stream.emit('end') } } else if (!ended) { ended = true resolve(keys) } }); stream.on('end', () => { if (ended) { return } resolve(keys); }); /* stream.on('error', (error) => { console.error('getStreamKeys stream', error) reject(error) }) */ } catch (e) { reject(e) } }) } /* const getStreamTypedKeys = (options) => { const { redis, key, match } = options let { scan } = options if (scan === undefined) { scan = 'scanStream' } return new Promise((resolve, reject) => { let stream; if (scan === 'scanStream') { stream = redis[scan]({ match: match }); } else { stream = redis[scan](key, { match: match }); } let keys = []; stream.on('data', (resultKeys) => { keys = keys.concat(resultKeys); }); stream.on('end', async () => { try { resolve(keys); } catch (e) { console.error(e); reject(e) } }); }) } */ const getKeysInfo = async (options) => { const {redis, keys} = options; const keyTypePipeline = redis.pipeline() // const promises = []; for (let key of keys) { keyTypePipeline.type(key) // promises.push(redis.type(key)) } // const keysType = await Promise.all(promises); const keysType = await keyTypePipeline.exec(); const result = {} const complexLengthPipeline = redis.pipeline() for (let keysIndex in keys) { const keyType = keysType[keysIndex] const key = keys[keysIndex] const obj = { type: keyType[1] } switch (obj.type) { case 'hash': complexLengthPipeline.hlen(key) break; case 'list': complexLengthPipeline.llen(key) break; case 'set': complexLengthPipeline.scard(key) break; case 'zset': complexLengthPipeline.zcard(key) break; } result[key] = obj } const lengthsPipeline = await complexLengthPipeline.exec() for (let keysIndex in keys) { const key = keys[keysIndex] const obj = result[key] if (obj.type === 'string' || obj.type === 'none') { continue } const lengthPipelineElement = lengthsPipeline.shift() if (lengthPipelineElement === undefined) { continue } obj.length = lengthPipelineElement[1] } return result; } const ensureReadonlyConnections = () => { if (p3xrs.cfg.readonlyConnections === true) { const errorCode = new Error('Connections add/save/delete are readonly only') errorCode.code = 'readonly-connections' throw errorCode; } } const getFullInfo = async (options) => { const {redis} = options; let {payload} = options if (payload === undefined) { payload = {} } const dbsize = await redis.dbsize() const results = await Promise.all([ redis.info(), getStreamKeys({ dbsize: dbsize, redis: redis, match: payload.match, maxKeys: payload.maxKeys, }), redis.pubsub('channels', '*'), // redis.infoObject(), ]) const keys = results[1] let keysInfo = {} if (keys.length < 110000) { keysInfo = await getKeysInfo({ redis: redis, keys: keys, }) } // const keysInfo = [] const result = { info: results[0], // infoObject: results[3], keys: keys, keysInfo: keysInfo, dbsize: dbsize, channels: results[2] } //console.log('get full info', result) return result } const getFullInfoAndSendSocket = async (options) => { const {redis, socket, payload} = options const result = await getFullInfo({ redis: redis, payload: payload, }) let {extend} = options if (extend === undefined) { extend = {} } socket.emit(options.responseEvent, Object.assign(extend, { status: 'ok', info: result.info, // infoObject: result.infoObject, keys: result.keys, keysInfo: result.keysInfo, dbsize: result.dbsize, })) } module.exports.ensureReadonlyConnections = ensureReadonlyConnections module.exports.triggerDisconnect = triggerDisconnect module.exports.getStreamKeys = getStreamKeys module.exports.disconnectRedisIo = disconnectRedisIo module.exports.sendConnections = sendConnections module.exports.sendStatus = sendStatus module.exports.disconnectRedis = disconnectRedis module.exports.getKeysInfo = getKeysInfo module.exports.getFullInfo = getFullInfo module.exports.getFullInfoAndSendSocket = getFullInfoAndSendSocket src/service/socket.io/socket.js000066400000000000000000000044671520166327500170530ustar00rootroot00000000000000const socketIoShared = require('./shared') module.exports = (io) => { io.on('connect', function (socket) { //const token = socket.handshake.query.token; socket.p3xrs = { address: socket.handshake.headers.origin, connectedAt: new Date(), connectionId: undefined, io: io, ioredis: undefined, ioredisSubscriber: undefined, // commands: undefined, } console.info('socket.io connected %s', socket.id); socket.emit('info-interval', { status: 'ok', donated: p3xrs.cfg.donated, }) socket.on('disconnect', function () { console.warn('socket.p3xrs.connectionId', socket.p3xrs.connectionId) if (socket.p3xrs.connectionId !== undefined) { const connectionId = socket.p3xrs.connectionId; if (p3xrs.redisConnections.hasOwnProperty(connectionId)) { const redisConnectionIndex = p3xrs.redisConnections[connectionId].clients.indexOf(socket.id); if (redisConnectionIndex !== -1) { p3xrs.redisConnections[connectionId].clients.splice(redisConnectionIndex, 1); } if (p3xrs.redisConnections[connectionId].clients.length === 0) { delete p3xrs.redisConnections[connectionId] } socketIoShared.disconnectRedisIo({ socket: socket, }) } } // Call on disconnect. console.info('socket.io disconnected %s', socket.id); socketIoShared.sendStatus({ socket: socket, }) }); socket.on('p3xr-request', (options) => { options.socket = socket; options.responseEvent = `p3xr-response-${options.requestId}` require(`./request/${options.action}`)(options) }) socket.emit('configuration', { readonlyConnections: p3xrs.cfg.readonlyConnections === true, snapshot: p3xrs.cfg.snapshot === true, }) socketIoShared.sendStatus({ socket: socket, }) socketIoShared.sendConnections({ socket: socket, }) }); }