Refactor project structure: remove app.js, update config.json for environment-specific settings, enhance package.json with new scripts and dependencies, and convert websockets.js to ES module syntax with Keycloak authentication integration.
This commit is contained in:
parent
15cb1a03b6
commit
bf56234c4b
13
.eslintignore
Normal file
13
.eslintignore
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
coverage/
|
||||||
|
*.min.js
|
||||||
|
*.bundle.js
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
pnpm-lock.yaml
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
100
.eslintrc.json
Normal file
100
.eslintrc.json
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"es2022": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended", "prettier"],
|
||||||
|
"plugins": ["prettier"],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2022,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": "error",
|
||||||
|
"indent": ["error", 2],
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
"quotes": ["error", "single"],
|
||||||
|
"semi": ["error", "always"],
|
||||||
|
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
||||||
|
"no-console": "warn",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"no-var": "error",
|
||||||
|
"object-shorthand": "error",
|
||||||
|
"prefer-template": "error",
|
||||||
|
"template-curly-spacing": ["error", "never"],
|
||||||
|
"arrow-spacing": "error",
|
||||||
|
"no-duplicate-imports": "error",
|
||||||
|
"no-useless-rename": "error",
|
||||||
|
"prefer-destructuring": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"array": true,
|
||||||
|
"object": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"prefer-rest-params": "error",
|
||||||
|
"prefer-spread": "error",
|
||||||
|
"no-useless-constructor": "error",
|
||||||
|
"no-useless-computed-key": "error",
|
||||||
|
"no-useless-escape": "error",
|
||||||
|
"no-useless-return": "error",
|
||||||
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
|
"no-empty": ["error", { "allowEmptyCatch": true }],
|
||||||
|
"no-extra-boolean-cast": "error",
|
||||||
|
"no-extra-semi": "error",
|
||||||
|
"no-irregular-whitespace": "error",
|
||||||
|
"no-multiple-empty-lines": ["error", { "max": 2 }],
|
||||||
|
"no-trailing-spaces": "error",
|
||||||
|
"eol-last": "error",
|
||||||
|
"comma-dangle": ["error", "never"],
|
||||||
|
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||||
|
"comma-style": ["error", "last"],
|
||||||
|
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
|
||||||
|
"keyword-spacing": ["error", { "before": true, "after": true }],
|
||||||
|
"object-curly-spacing": ["error", "always"],
|
||||||
|
"array-bracket-spacing": ["error", "never"],
|
||||||
|
"space-before-blocks": "error",
|
||||||
|
"space-before-function-paren": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"anonymous": "always",
|
||||||
|
"named": "never",
|
||||||
|
"asyncArrow": "always"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"space-in-parens": ["error", "never"],
|
||||||
|
"space-infix-ops": "error",
|
||||||
|
"space-unary-ops": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"words": true,
|
||||||
|
"nonwords": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spaced-comment": ["error", "always"],
|
||||||
|
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||||
|
"camelcase": ["error", { "properties": "never" }],
|
||||||
|
"new-cap": ["error", { "newIsCap": true, "capIsNew": false }],
|
||||||
|
"new-parens": "error",
|
||||||
|
"no-array-constructor": "error",
|
||||||
|
"no-new-object": "error",
|
||||||
|
"no-new-require": "error",
|
||||||
|
"no-path-concat": "error",
|
||||||
|
"no-process-exit": "error",
|
||||||
|
"no-return-assign": "error",
|
||||||
|
"no-self-compare": "error",
|
||||||
|
"no-sequences": "error",
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-unmodified-loop-condition": "error",
|
||||||
|
"no-unused-expressions": "error",
|
||||||
|
"no-useless-call": "error",
|
||||||
|
"no-useless-concat": "error",
|
||||||
|
"no-useless-escape": "error",
|
||||||
|
"no-void": "error",
|
||||||
|
"no-warning-comments": "warn",
|
||||||
|
"prefer-promise-reject-errors": "error",
|
||||||
|
"require-await": "error",
|
||||||
|
"yoda": "error"
|
||||||
|
},
|
||||||
|
"ignorePatterns": ["node_modules/", "dist/", "build/", "*.min.js"]
|
||||||
|
}
|
||||||
7
.prettierignore
Normal file
7
.prettierignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.min.js
|
||||||
|
package-lock.json
|
||||||
|
.git/
|
||||||
|
.vscode/
|
||||||
15
.prettierrc
Normal file
15
.prettierrc
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"commaSpacing": true,
|
||||||
|
"objectCurlySpacing": true,
|
||||||
|
"arrayBracketSpacing": false,
|
||||||
|
"spaceBeforeFunctionParen": true
|
||||||
|
}
|
||||||
17
.vscode/settings.json
vendored
Normal file
17
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
|
},
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript"
|
||||||
|
],
|
||||||
|
"eslint.workingDirectories": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"prettier.requireConfig": true,
|
||||||
|
"files.eol": "\n",
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"files.trimTrailingWhitespace": true
|
||||||
|
}
|
||||||
178
README.md
Normal file
178
README.md
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
# FarmControl WebSocket Service
|
||||||
|
|
||||||
|
A WebSocket microservice for FarmControl that handles real-time communication and distributed locking using etcd.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Real-time WebSocket communication
|
||||||
|
- Distributed locking using etcd
|
||||||
|
- Keycloak authentication integration
|
||||||
|
- MongoDB integration for user management
|
||||||
|
- Event streaming and notifications
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js (v16 or higher)
|
||||||
|
- etcd server
|
||||||
|
- MongoDB server
|
||||||
|
- Keycloak server (for authentication)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The application uses `config.json` for configuration. Update the following sections:
|
||||||
|
|
||||||
|
### Etcd Configuration
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"database": {
|
||||||
|
"etcd": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 2379
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MongoDB Configuration
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"database": {
|
||||||
|
"mongo": {
|
||||||
|
"url": "mongodb://localhost:27017/farmcontrol"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication Configuration
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"enabled": true,
|
||||||
|
"keycloak": {
|
||||||
|
"url": "https://your-keycloak-server",
|
||||||
|
"realm": "your-realm",
|
||||||
|
"clientId": "your-client-id",
|
||||||
|
"clientSecret": "your-client-secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the Application
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Etcd Setup
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
#### Using Docker
|
||||||
|
```bash
|
||||||
|
docker run -d --name etcd \
|
||||||
|
-p 2379:2379 \
|
||||||
|
-p 2380:2380 \
|
||||||
|
quay.io/coreos/etcd:v3.5.0 \
|
||||||
|
/usr/local/bin/etcd \
|
||||||
|
--advertise-client-urls http://0.0.0.0:2379 \
|
||||||
|
--listen-client-urls http://0.0.0.0:2379
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Homebrew (macOS)
|
||||||
|
```bash
|
||||||
|
brew install etcd
|
||||||
|
etcd
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using apt (Ubuntu/Debian)
|
||||||
|
```bash
|
||||||
|
sudo apt-get install etcd
|
||||||
|
sudo systemctl start etcd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
Test that etcd is running:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:2379/version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration from Redis
|
||||||
|
|
||||||
|
This application was migrated from Redis to etcd. The main changes include:
|
||||||
|
|
||||||
|
1. **Stream-like functionality**: Redis streams are replaced with etcd key-value pairs using a prefix pattern
|
||||||
|
2. **Hash-like functionality**: Redis hashes are replaced with etcd key-value pairs using a prefix pattern
|
||||||
|
3. **Pub/Sub**: Redis pub/sub is replaced with etcd watchers
|
||||||
|
4. **Connection management**: Simplified connection handling with automatic reconnection
|
||||||
|
|
||||||
|
### Key Differences
|
||||||
|
|
||||||
|
- **Data structure**: etcd uses a flat key-value store, so we simulate Redis data structures using key prefixes
|
||||||
|
- **Streams**: Instead of Redis streams, we use etcd keys with timestamps and random suffixes
|
||||||
|
- **Watching**: etcd watchers provide real-time notifications for key changes
|
||||||
|
- **Transactions**: etcd supports atomic operations for distributed locking
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
The service exposes WebSocket endpoints for:
|
||||||
|
|
||||||
|
- **Lock management**: Lock/unlock objects with real-time notifications
|
||||||
|
- **Authentication**: Keycloak-based authentication
|
||||||
|
- **Real-time updates**: Stream-based event notifications
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── auth/ # Authentication logic
|
||||||
|
├── database/ # Database connections (etcd, mongo)
|
||||||
|
├── lock/ # Distributed locking
|
||||||
|
├── notification/ # Notification management
|
||||||
|
├── socket/ # WebSocket handling
|
||||||
|
└── index.js # Main application entry point
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding New Features
|
||||||
|
|
||||||
|
1. **Database operations**: Use the `etcdServer` instance for etcd operations
|
||||||
|
2. **WebSocket events**: Extend the `SocketClient` class
|
||||||
|
3. **Authentication**: Extend the `KeycloakAuth` class
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Etcd connection failed**: Ensure etcd is running on the configured host and port
|
||||||
|
2. **Authentication errors**: Verify Keycloak configuration and credentials
|
||||||
|
3. **MongoDB connection issues**: Check MongoDB server status and connection string
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
The application uses log4js for logging. Set the log level in the configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"logLevel": "debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Available log levels: `trace`, `debug`, `info`, `warn`, `error`
|
||||||
21
app.js
21
app.js
@ -1,21 +0,0 @@
|
|||||||
const log4js = require("log4js");
|
|
||||||
const os = require("os");
|
|
||||||
const config = require("./config.json");
|
|
||||||
|
|
||||||
const WebSocketServer = require("./src/websockets.js").WebSocketServer;
|
|
||||||
|
|
||||||
const logger = log4js.getLogger("App");
|
|
||||||
logger.level = config.logLevel;
|
|
||||||
|
|
||||||
function showSystemInfo() {
|
|
||||||
logger.info("=== System Info ===")
|
|
||||||
logger.info("Hostname:", os.hostname());
|
|
||||||
logger.info("Memory:", os.totalmem() / 1024 / 1024 + "mb");
|
|
||||||
console.log("");
|
|
||||||
}
|
|
||||||
|
|
||||||
showSystemInfo();
|
|
||||||
logger.info("Web Socket Server Starting...");
|
|
||||||
|
|
||||||
var server = new WebSocketServer();
|
|
||||||
server.start(5050);
|
|
||||||
55
config.json
55
config.json
@ -1,5 +1,52 @@
|
|||||||
{
|
{
|
||||||
"logLevel": "trace",
|
"development": {
|
||||||
"jwt_secret": "secret123",
|
"server": {
|
||||||
"mongo_uri": "mongodb://localhost:27017/socketio"
|
"port": 9090,
|
||||||
}
|
"logLevel": "trace"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"enabled": true,
|
||||||
|
"keycloak": {
|
||||||
|
"url": "https://auth.tombutcher.work",
|
||||||
|
"realm": "master",
|
||||||
|
"clientId": "farmcontrol-client",
|
||||||
|
"clientSecret": "GPyh59xctRX83yfKWb83ShK6VEwHIvLF"
|
||||||
|
},
|
||||||
|
"requiredRoles": []
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"etcd": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 2379
|
||||||
|
},
|
||||||
|
"mongo": {
|
||||||
|
"url": "mongodb://192.168.68.53:27017/farmcontrol"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"server": {
|
||||||
|
"port": 8081,
|
||||||
|
"logLevel": "info"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"enabled": true,
|
||||||
|
"keycloak": {
|
||||||
|
"url": "https://auth.tombutcher.work",
|
||||||
|
"realm": "master",
|
||||||
|
"clientId": "farmcontrol-client",
|
||||||
|
"clientSecret": "GPyh59xctRX83yfKWb83ShK6VEwHIvLF"
|
||||||
|
},
|
||||||
|
"requiredRoles": []
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"etcd": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 2379
|
||||||
|
},
|
||||||
|
"mongo": {
|
||||||
|
"url": "mongodb://farmcontrol.tombutcher.local:27017/farmcontrol"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
625
package-lock.json
generated
625
package-lock.json
generated
@ -9,6 +9,9 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.10.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"etcd3": "^1.1.2",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
@ -19,6 +22,10 @@
|
|||||||
"socketio-jwt": "^4.6.2"
|
"socketio-jwt": "^4.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-prettier": "^10.1.5",
|
||||||
|
"eslint-plugin-prettier": "^5.5.1",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"standard": "^17.1.0"
|
"standard": "^17.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -107,6 +114,37 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@grpc/grpc-js": {
|
||||||
|
"version": "1.13.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz",
|
||||||
|
"integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@grpc/proto-loader": "^0.7.13",
|
||||||
|
"@js-sdsl/ordered-map": "^4.4.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@grpc/proto-loader": {
|
||||||
|
"version": "0.7.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
|
||||||
|
"integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.camelcase": "^4.3.0",
|
||||||
|
"long": "^5.0.0",
|
||||||
|
"protobufjs": "^7.2.5",
|
||||||
|
"yargs": "^17.7.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.14",
|
"version": "0.11.14",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||||
@ -170,6 +208,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/@js-sdsl/ordered-map": {
|
||||||
|
"version": "4.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
|
||||||
|
"integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/js-sdsl"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mongodb-js/saslprep": {
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz",
|
||||||
@ -217,6 +265,83 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@pkgr/core": {
|
||||||
|
"version": "0.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
|
||||||
|
"integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/pkgr"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/aspromise": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/base64": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/codegen": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/eventemitter": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/fetch": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.1",
|
||||||
|
"@protobufjs/inquire": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/float": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/inquire": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/path": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/pool": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/utf8": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/@socket.io/component-emitter": {
|
"node_modules/@socket.io/component-emitter": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||||
@ -333,7 +458,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@ -343,7 +467,6 @@
|
|||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
@ -526,6 +649,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
@ -542,6 +671,17 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@ -566,6 +706,15 @@
|
|||||||
"node": "^4.5.0 || >= 5.9"
|
"node": "^4.5.0 || >= 5.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bignumber.js": {
|
||||||
|
"version": "9.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz",
|
||||||
|
"integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.2",
|
"version": "1.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||||
@ -670,6 +819,19 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
@ -697,11 +859,33 @@
|
|||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cockatiel": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
@ -714,9 +898,20 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@ -918,6 +1113,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@ -950,6 +1154,32 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||||
|
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ecdsa-sig-formatter": {
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||||
@ -965,6 +1195,12 @@
|
|||||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/encodeurl": {
|
"node_modules/encodeurl": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
@ -1118,13 +1354,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"get-intrinsic": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@ -1165,10 +1398,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-object-atoms": {
|
"node_modules/es-object-atoms": {
|
||||||
"version": "1.0.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
@ -1178,15 +1410,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-set-tostringtag": {
|
"node_modules/es-set-tostringtag": {
|
||||||
"version": "2.0.3",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
"integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"get-intrinsic": "^1.2.4",
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
"has-tostringtag": "^1.0.2",
|
"has-tostringtag": "^1.0.2",
|
||||||
"hasown": "^2.0.1"
|
"hasown": "^2.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -1226,6 +1458,15 @@
|
|||||||
"integrity": "sha512-oj4jOSXvWglTsc3wrw86iom3LDPOx1nbipQk+jaG3dy+sMRM6ReSgVr/VlmBuF6lXUrflN9DCcQHeSbAwGUl4g==",
|
"integrity": "sha512-oj4jOSXvWglTsc3wrw86iom3LDPOx1nbipQk+jaG3dy+sMRM6ReSgVr/VlmBuF6lXUrflN9DCcQHeSbAwGUl4g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/escalade": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escape-html": {
|
"node_modules/escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@ -1301,6 +1542,22 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-config-prettier": {
|
||||||
|
"version": "10.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
|
||||||
|
"integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"eslint-config-prettier": "bin/cli.js"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint-config-prettier"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-config-standard": {
|
"node_modules/eslint-config-standard": {
|
||||||
"version": "17.1.0",
|
"version": "17.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
|
||||||
@ -1564,6 +1821,37 @@
|
|||||||
"eslint": ">=7.0.0"
|
"eslint": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-prettier": {
|
||||||
|
"version": "5.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz",
|
||||||
|
"integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prettier-linter-helpers": "^1.0.0",
|
||||||
|
"synckit": "^0.11.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.18.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint-plugin-prettier"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/eslint": ">=8.0.0",
|
||||||
|
"eslint": ">=8.0.0",
|
||||||
|
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
|
||||||
|
"prettier": ">=3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/eslint": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"eslint-config-prettier": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-plugin-promise": {
|
"node_modules/eslint-plugin-promise": {
|
||||||
"version": "6.6.0",
|
"version": "6.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz",
|
||||||
@ -1811,6 +2099,21 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/etcd3": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/etcd3/-/etcd3-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-YIampCz1/OmrVo/tR3QltAVUtYCQQOSFoqmHKKeoHbalm+WdXe3l4rhLIylklu8EzR/I3PBiOF4dC847dDskKg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@grpc/grpc-js": "^1.8.20",
|
||||||
|
"@grpc/proto-loader": "^0.7.8",
|
||||||
|
"bignumber.js": "^9.1.1",
|
||||||
|
"cockatiel": "^3.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.19.2",
|
"version": "4.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
||||||
@ -1860,6 +2163,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-diff": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/fast-json-stable-stringify": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||||
@ -1953,6 +2263,26 @@
|
|||||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||||
@ -1963,6 +2293,22 @@
|
|||||||
"is-callable": "^1.1.3"
|
"is-callable": "^1.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
@ -2040,17 +2386,31 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.4",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
"function-bind": "^1.1.2",
|
"function-bind": "^1.1.2",
|
||||||
"has-proto": "^1.0.1",
|
"get-proto": "^1.0.1",
|
||||||
"has-symbols": "^1.0.3",
|
"gopd": "^1.2.0",
|
||||||
"hasown": "^2.0.0"
|
"has-symbols": "^1.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -2059,6 +2419,19 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dunder-proto": "^1.0.1",
|
||||||
|
"es-object-atoms": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-stdin": {
|
"node_modules/get-stdin": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
|
||||||
@ -2159,12 +2532,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"engines": {
|
||||||
"get-intrinsic": "^1.1.3"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@ -2219,6 +2592,7 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -2228,9 +2602,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/has-symbols": {
|
"node_modules/has-symbols": {
|
||||||
"version": "1.0.3",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -2243,7 +2617,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
@ -2538,6 +2911,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-generator-function": {
|
"node_modules/is-generator-function": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
|
||||||
@ -2994,6 +3376,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.camelcase": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.includes": {
|
"node_modules/lodash.includes": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
@ -3082,6 +3470,12 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/loose-envify": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
@ -3095,6 +3489,15 @@
|
|||||||
"loose-envify": "cli.js"
|
"loose-envify": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@ -3831,6 +4234,35 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prettier-linter-helpers": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-diff": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/process-nextick-args": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||||
@ -3849,6 +4281,30 @@
|
|||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/protobufjs": {
|
||||||
|
"version": "7.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz",
|
||||||
|
"integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.2",
|
||||||
|
"@protobufjs/base64": "^1.1.2",
|
||||||
|
"@protobufjs/codegen": "^2.0.4",
|
||||||
|
"@protobufjs/eventemitter": "^1.1.0",
|
||||||
|
"@protobufjs/fetch": "^1.1.0",
|
||||||
|
"@protobufjs/float": "^1.0.2",
|
||||||
|
"@protobufjs/inquire": "^1.1.0",
|
||||||
|
"@protobufjs/path": "^1.1.2",
|
||||||
|
"@protobufjs/pool": "^1.1.0",
|
||||||
|
"@protobufjs/utf8": "^1.1.0",
|
||||||
|
"@types/node": ">=13.7.0",
|
||||||
|
"long": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
@ -3862,6 +4318,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@ -4026,6 +4488,15 @@
|
|||||||
"semver": "bin/semver"
|
"semver": "bin/semver"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
@ -4638,6 +5109,20 @@
|
|||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string.prototype.matchall": {
|
"node_modules/string.prototype.matchall": {
|
||||||
"version": "4.0.11",
|
"version": "4.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz",
|
||||||
@ -4732,7 +5217,6 @@
|
|||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1"
|
"ansi-regex": "^5.0.1"
|
||||||
@ -4790,6 +5274,22 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/synckit": {
|
||||||
|
"version": "0.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
|
||||||
|
"integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@pkgr/core": "^0.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.18.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/synckit"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||||
@ -5180,6 +5680,23 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wrappy": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
@ -5229,6 +5746,42 @@
|
|||||||
"node": ">=0.4"
|
"node": ">=0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "17.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^8.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|||||||
17
package.json
17
package.json
@ -3,14 +3,23 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Farmcontrol Web Socket microservice",
|
"description": "Farmcontrol Web Socket microservice",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "node app.js",
|
"start": "node src/index.js",
|
||||||
"dev": "nodemon app.js"
|
"dev": "nodemon src/index.js",
|
||||||
|
"lint": "eslint src/",
|
||||||
|
"lint:fix": "eslint src/ --fix",
|
||||||
|
"format": "prettier --write \"src/**/*.{js,json}\"",
|
||||||
|
"format:check": "prettier --check \"src/**/*.{js,json}\"",
|
||||||
|
"fix": "npm run lint:fix && npm run format"
|
||||||
},
|
},
|
||||||
"author": "Tom Butcher",
|
"author": "Tom Butcher",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.10.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"etcd3": "^1.1.2",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
@ -21,6 +30,10 @@
|
|||||||
"socketio-jwt": "^4.6.2"
|
"socketio-jwt": "^4.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-prettier": "^10.1.5",
|
||||||
|
"eslint-plugin-prettier": "^5.5.1",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"standard": "^17.1.0"
|
"standard": "^17.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
147
src/auth/auth.js
Normal file
147
src/auth/auth.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// auth.js - Keycloak authentication handler
|
||||||
|
import axios from 'axios';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
// Load configuration
|
||||||
|
import { loadConfig } from '../config.js';
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
const logger = log4js.getLogger('Auth');
|
||||||
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
export class KeycloakAuth {
|
||||||
|
constructor(config) {
|
||||||
|
this.config = config.auth;
|
||||||
|
this.tokenCache = new Map(); // Cache for verified tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify a token with Keycloak server
|
||||||
|
async verifyToken(token) {
|
||||||
|
// Check cache first
|
||||||
|
if (this.tokenCache.has(token)) {
|
||||||
|
const cachedInfo = this.tokenCache.get(token);
|
||||||
|
if (cachedInfo.expiresAt > Date.now()) {
|
||||||
|
return { valid: true, user: cachedInfo.user };
|
||||||
|
} else {
|
||||||
|
// Token expired, remove from cache
|
||||||
|
this.tokenCache.delete(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Verify token with Keycloak introspection endpoint
|
||||||
|
const response = await axios.post(
|
||||||
|
`${this.config.keycloak.url}/realms/${this.config.keycloak.realm}/protocol/openid-connect/token/introspect`,
|
||||||
|
new URLSearchParams({
|
||||||
|
token,
|
||||||
|
client_id: this.config.keycloak.clientId,
|
||||||
|
client_secret: this.config.keycloak.clientSecret
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const introspection = response.data;
|
||||||
|
|
||||||
|
if (!introspection.active) {
|
||||||
|
logger.info('Token is not active');
|
||||||
|
return { valid: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify required roles if configured
|
||||||
|
if (this.config.requiredRoles && this.config.requiredRoles.length > 0) {
|
||||||
|
const hasRequiredRole = this.checkRoles(
|
||||||
|
introspection,
|
||||||
|
this.config.requiredRoles
|
||||||
|
);
|
||||||
|
if (!hasRequiredRole) {
|
||||||
|
logger.info("User doesn't have required roles");
|
||||||
|
return { valid: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse token to extract user info
|
||||||
|
const decodedToken = jwt.decode(token);
|
||||||
|
const user = {
|
||||||
|
id: decodedToken.sub,
|
||||||
|
username: decodedToken.preferred_username,
|
||||||
|
email: decodedToken.email,
|
||||||
|
name: decodedToken.name,
|
||||||
|
roles: this.extractRoles(decodedToken)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cache the verified token
|
||||||
|
const expiresAt = introspection.exp * 1000; // Convert to milliseconds
|
||||||
|
this.tokenCache.set(token, { expiresAt, user });
|
||||||
|
|
||||||
|
return { valid: true, user };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Token verification error:', error.message);
|
||||||
|
return { valid: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract roles from token
|
||||||
|
extractRoles(token) {
|
||||||
|
const roles = [];
|
||||||
|
|
||||||
|
// Extract realm roles
|
||||||
|
if (token.realm_access && token.realm_access.roles) {
|
||||||
|
roles.push(...token.realm_access.roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract client roles
|
||||||
|
if (token.resource_access) {
|
||||||
|
for (const client in token.resource_access) {
|
||||||
|
if (token.resource_access[client].roles) {
|
||||||
|
roles.push(
|
||||||
|
...token.resource_access[client].roles.map(
|
||||||
|
role => `${client}:${role}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has required roles
|
||||||
|
checkRoles(tokenInfo, requiredRoles) {
|
||||||
|
// Extract roles from token
|
||||||
|
const userRoles = this.extractRoles(tokenInfo);
|
||||||
|
|
||||||
|
// Check if user has any of the required roles
|
||||||
|
return requiredRoles.some(role => userRoles.includes(role));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Socket.IO middleware for authentication
|
||||||
|
export function createAuthMiddleware(auth) {
|
||||||
|
return async (socket, next) => {
|
||||||
|
const { token } = socket.handshake.auth;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return next(new Error('Authentication token is required'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const authResult = await auth.verifyToken(token);
|
||||||
|
|
||||||
|
if (!authResult.valid) {
|
||||||
|
return next(new Error('Invalid authentication token'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach user information to socket
|
||||||
|
socket.user = authResult.user;
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Authentication error:', err);
|
||||||
|
next(new Error('Authentication failed'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
40
src/config.js
Normal file
40
src/config.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// config.js - Configuration handling
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
// Configure paths relative to this file
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const CONFIG_PATH = path.resolve(__dirname, '../config.json');
|
||||||
|
|
||||||
|
// Determine environment
|
||||||
|
const NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
|
// Load config file
|
||||||
|
export function loadConfig() {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(CONFIG_PATH)) {
|
||||||
|
throw new Error(`Configuration file not found at ${CONFIG_PATH}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const configData = fs.readFileSync(CONFIG_PATH, 'utf8');
|
||||||
|
const config = JSON.parse(configData);
|
||||||
|
|
||||||
|
if (!config[NODE_ENV]) {
|
||||||
|
throw new Error(
|
||||||
|
`Configuration for environment '${NODE_ENV}' not found in config.json`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config[NODE_ENV];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading config:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current environment
|
||||||
|
export function getEnvironment() {
|
||||||
|
return NODE_ENV;
|
||||||
|
}
|
||||||
256
src/database/etcd.js
Normal file
256
src/database/etcd.js
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
import { Etcd3 } from 'etcd3';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
import { loadConfig } from '../config.js';
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
const logger = log4js.getLogger('Etcd');
|
||||||
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
class EtcdServer {
|
||||||
|
constructor() {
|
||||||
|
this.client = null;
|
||||||
|
this.watchers = new Map();
|
||||||
|
const etcdConfig = config.database?.etcd || config.database; // fallback for production config
|
||||||
|
const host = etcdConfig.host || 'localhost';
|
||||||
|
const port = etcdConfig.port || 2379;
|
||||||
|
this.hosts = [`${host}:${port}`];
|
||||||
|
logger.debug(
|
||||||
|
`EtcdServer constructor: hosts set to ${JSON.stringify(this.hosts)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
if (!this.client) {
|
||||||
|
logger.info('Connecting to Etcd...');
|
||||||
|
logger.debug(
|
||||||
|
`Creating Etcd client with hosts ${JSON.stringify(this.hosts)}`
|
||||||
|
);
|
||||||
|
this.client = new Etcd3({
|
||||||
|
hosts: this.hosts
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test connection
|
||||||
|
try {
|
||||||
|
await this.client.get('test-connection').string();
|
||||||
|
logger.debug('Etcd client connected successfully.');
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'NOT_FOUND') {
|
||||||
|
logger.debug(
|
||||||
|
'Etcd client connected successfully (test key not found as expected).'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug('Etcd client already exists, skipping connection.');
|
||||||
|
}
|
||||||
|
return this.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getClient() {
|
||||||
|
logger.trace('Checking if Etcd client exists.');
|
||||||
|
if (!this.client) {
|
||||||
|
logger.debug('No client found, calling connect().');
|
||||||
|
await this.connect();
|
||||||
|
}
|
||||||
|
logger.trace('Returning Etcd client.');
|
||||||
|
return this.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash-like functionality using etcd
|
||||||
|
async set(key, value) {
|
||||||
|
const client = await this.getClient();
|
||||||
|
const stringValue =
|
||||||
|
typeof value === 'string' ? value : JSON.stringify(value);
|
||||||
|
|
||||||
|
await client.put(key).value(stringValue);
|
||||||
|
logger.debug(`Set key: ${key}, value: ${stringValue}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key) {
|
||||||
|
const client = await this.getClient();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const value = await client.get(key).string();
|
||||||
|
logger.debug(`Retrieved key: ${key}, value: ${value}`);
|
||||||
|
|
||||||
|
// Try to parse as JSON, fallback to string
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'NOT_FOUND') {
|
||||||
|
logger.debug(`Key not found: ${key}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(key) {
|
||||||
|
const client = await this.getClient();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.delete().key(key);
|
||||||
|
logger.debug(`Deleted key: ${key}`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'NOT_FOUND') {
|
||||||
|
logger.debug(`Key not found for deletion: ${key}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPrefixEvent(prefix, callback) {
|
||||||
|
const client = await this.getClient();
|
||||||
|
logger.debug(`Setting up watcher for prefix events: ${prefix}`);
|
||||||
|
|
||||||
|
client
|
||||||
|
.watch()
|
||||||
|
.prefix(prefix)
|
||||||
|
.create()
|
||||||
|
.then(watcher => {
|
||||||
|
// Handle put events
|
||||||
|
watcher.on('put', (kv, previous) => {
|
||||||
|
logger.debug(`Prefix put event detected: ${prefix}, key: ${kv.key}`);
|
||||||
|
try {
|
||||||
|
const value = kv.value.toString();
|
||||||
|
let parsedValue;
|
||||||
|
try {
|
||||||
|
parsedValue = JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
parsedValue = value;
|
||||||
|
}
|
||||||
|
callback(kv.key.toString(), parsedValue, kv, previous);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`Error in onPrefixEvent put callback for prefix ${prefix}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle delete events
|
||||||
|
watcher.on('delete', (kv, previous) => {
|
||||||
|
logger.debug(
|
||||||
|
`Prefix delete event detected: ${prefix}, key: ${kv.key}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
callback(kv.key.toString(), null, kv, previous);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`Error in onPrefixEvent delete callback for prefix ${prefix}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store watcher with a unique key
|
||||||
|
const watcherKey = `event:${prefix}`;
|
||||||
|
this.watchers.set(watcherKey, watcher);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPrefixPut(prefix, callback) {
|
||||||
|
const client = await this.getClient();
|
||||||
|
logger.debug(`Setting up watcher for prefix put: ${prefix}`);
|
||||||
|
|
||||||
|
client
|
||||||
|
.watch()
|
||||||
|
.prefix(prefix)
|
||||||
|
.create()
|
||||||
|
.then(watcher => {
|
||||||
|
watcher.on('put', (kv, previous) => {
|
||||||
|
logger.debug(`Prefix put event detected: ${prefix}, key: ${kv.key}`);
|
||||||
|
try {
|
||||||
|
const value = kv.value.toString();
|
||||||
|
let parsedValue;
|
||||||
|
try {
|
||||||
|
parsedValue = JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
parsedValue = value;
|
||||||
|
}
|
||||||
|
callback(kv.key.toString(), parsedValue, kv, previous);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`Error in onPrefixPut callback for prefix ${prefix}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.watchers.set(`put:${prefix}`, watcher);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPrefixDelete(prefix, callback) {
|
||||||
|
const client = await this.getClient();
|
||||||
|
logger.debug(`Setting up watcher for prefix delete: ${prefix}`);
|
||||||
|
|
||||||
|
client
|
||||||
|
.watch()
|
||||||
|
.prefix(prefix)
|
||||||
|
.create()
|
||||||
|
.then(watcher => {
|
||||||
|
watcher.on('delete', (kv, previous) => {
|
||||||
|
logger.debug(
|
||||||
|
`Prefix delete event detected: ${prefix}, key: ${kv.key}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
callback(kv.key.toString(), kv, previous);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`Error in onPrefixDelete callback for prefix ${prefix}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.watchers.set(`delete:${prefix}`, watcher);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeWatcher(prefix, type = 'put') {
|
||||||
|
const watcherKey = `${type}:${prefix}`;
|
||||||
|
const watcher = this.watchers.get(watcherKey);
|
||||||
|
|
||||||
|
if (watcher) {
|
||||||
|
logger.debug(`Removing watcher: ${watcherKey}`);
|
||||||
|
watcher.removeAllListeners();
|
||||||
|
await watcher.close();
|
||||||
|
this.watchers.delete(watcherKey);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logger.debug(`Watcher not found: ${watcherKey}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect() {
|
||||||
|
logger.info('Disconnecting from Etcd...');
|
||||||
|
|
||||||
|
// Stop all watchers
|
||||||
|
for (const [key, watcher] of this.watchers) {
|
||||||
|
logger.debug(`Stopping watcher: ${key}`);
|
||||||
|
watcher.removeAllListeners();
|
||||||
|
await watcher.close();
|
||||||
|
}
|
||||||
|
this.watchers.clear();
|
||||||
|
|
||||||
|
if (this.client) {
|
||||||
|
await this.client.close();
|
||||||
|
this.client = null;
|
||||||
|
logger.info('Disconnected from Etcd');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const etcdServer = new EtcdServer();
|
||||||
|
|
||||||
|
export { EtcdServer, etcdServer };
|
||||||
51
src/database/mongo.js
Normal file
51
src/database/mongo.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import { loadConfig } from '../config.js';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
const logger = log4js.getLogger('Mongo DB');
|
||||||
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
class MongoServer {
|
||||||
|
constructor() {
|
||||||
|
this.connected = false;
|
||||||
|
this.connecting = false;
|
||||||
|
this.connectionPromise = null;
|
||||||
|
this.url = config.database.mongo.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
if (this.connected) return mongoose.connection;
|
||||||
|
if (this.connecting) return this.connectionPromise;
|
||||||
|
this.connecting = true;
|
||||||
|
logger.info('Connecting to MongoDB...');
|
||||||
|
logger.debug('Connection URL:', this.url);
|
||||||
|
this.connectionPromise = mongoose
|
||||||
|
.connect(this.url, {})
|
||||||
|
.then(conn => {
|
||||||
|
this.connected = true;
|
||||||
|
logger.info('Database connected.');
|
||||||
|
return conn.connection;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.connected = false;
|
||||||
|
logger.error('MongoDB connection error:', err);
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.connecting = false;
|
||||||
|
});
|
||||||
|
return this.connectionPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConnection() {
|
||||||
|
if (!this.connected) {
|
||||||
|
await this.connect();
|
||||||
|
}
|
||||||
|
return mongoose.connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mongoServer = new MongoServer();
|
||||||
|
|
||||||
|
export { MongoServer, mongoServer };
|
||||||
20
src/database/user.schema.js
Normal file
20
src/database/user.schema.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
const userSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
username: { required: true, type: String },
|
||||||
|
name: { required: true, type: String },
|
||||||
|
firstName: { required: false, type: String },
|
||||||
|
lastName: { required: false, type: String },
|
||||||
|
email: { required: true, type: String }
|
||||||
|
},
|
||||||
|
{ timestamps: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
userSchema.virtual('id').get(function () {
|
||||||
|
return this._id.toHexString();
|
||||||
|
});
|
||||||
|
|
||||||
|
userSchema.set('toJSON', { virtuals: true });
|
||||||
|
|
||||||
|
export const userModel = mongoose.model('User', userSchema);
|
||||||
53
src/index.js
Normal file
53
src/index.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { loadConfig } from './config.js';
|
||||||
|
import { KeycloakAuth } from './auth/auth.js';
|
||||||
|
import { SocketManager } from './socket/socketmanager.js';
|
||||||
|
import { etcdServer } from './database/etcd.js';
|
||||||
|
import express from 'express';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
import http from 'http';
|
||||||
|
import { mongoServer } from './database/mongo.js';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Load configuration
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
// Setup logger
|
||||||
|
const logger = log4js.getLogger('FarmControl API WS');
|
||||||
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
// Create Express app and HTTP server
|
||||||
|
const app = express();
|
||||||
|
const server = http.createServer(app);
|
||||||
|
|
||||||
|
// Setup Keycloak Integration
|
||||||
|
const keycloakAuth = new KeycloakAuth(config);
|
||||||
|
new SocketManager(keycloakAuth, server);
|
||||||
|
|
||||||
|
// Connect to Etcd (await)
|
||||||
|
try {
|
||||||
|
await etcdServer.connect();
|
||||||
|
logger.info('Connected to Etcd');
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Failed to connect to Etcd:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to Mongo DB (await)
|
||||||
|
try {
|
||||||
|
await mongoServer.connect();
|
||||||
|
logger.info('Connected to Mongo DB');
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Failed to connect to Mongo DB:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start HTTP server
|
||||||
|
server.listen(config.server.port, () => {
|
||||||
|
logger.info(`Server listening on port ${config.server.port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGINT', async () => {
|
||||||
|
logger.info('Shutting down...');
|
||||||
|
await etcdServer.disconnect();
|
||||||
|
});
|
||||||
|
})();
|
||||||
100
src/lock/lockmanager.js
Normal file
100
src/lock/lockmanager.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { etcdServer } from '../database/etcd.js';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
import { loadConfig } from '../config.js';
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
// Setup logger
|
||||||
|
const logger = log4js.getLogger('Lock Manager');
|
||||||
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LockManager handles distributed locking using Etcd and broadcasts lock events via websockets.
|
||||||
|
*/
|
||||||
|
export class LockManager {
|
||||||
|
constructor(socketManager) {
|
||||||
|
this.socketManager = socketManager;
|
||||||
|
this.setupLocksListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
async lockObject(object) {
|
||||||
|
// Add a 'lock' event to the 'locks' stream
|
||||||
|
logger.debug('Locking object:', object._id);
|
||||||
|
try {
|
||||||
|
await etcdServer.set(`/locks/${object.type}s/${object._id}`, object);
|
||||||
|
logger.info(`Lock event to id: ${object._id}`);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`Error adding lock event to: ${object._id}:`, err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async unlockObject(object) {
|
||||||
|
// Add an 'unlock' event to the 'locks' stream
|
||||||
|
const key = `/locks/${object.type}s/${object._id}`;
|
||||||
|
console.log('unlocking');
|
||||||
|
try {
|
||||||
|
logger.debug('Checking user can unlock:', object._id);
|
||||||
|
|
||||||
|
const lockEvent = await etcdServer.get(key);
|
||||||
|
|
||||||
|
if (lockEvent?.user === object.user) {
|
||||||
|
logger.debug('Unlocking object:', object._id);
|
||||||
|
await etcdServer.delete(key);
|
||||||
|
logger.info(`Unlocked object: ${object._id}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`Error unlocking object ${object._id}:`, err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getObjectLock(object) {
|
||||||
|
// Get the current lock status of an object and broadcast it
|
||||||
|
logger.info('Getting lock status for object:', object._id);
|
||||||
|
try {
|
||||||
|
const lockKey = `/locks/${object.type}s/${object._id}`;
|
||||||
|
const lockValue = await etcdServer.get(lockKey);
|
||||||
|
|
||||||
|
if (lockValue) {
|
||||||
|
// Object is locked
|
||||||
|
logger.debug(`Object ${object._id} is locked`);
|
||||||
|
return {
|
||||||
|
...lockValue,
|
||||||
|
locked: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Object is not locked
|
||||||
|
logger.debug(`Object ${object._id} is not locked`);
|
||||||
|
return {
|
||||||
|
_id: object._id,
|
||||||
|
locked: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`Error getting lock status for object ${object._id}:`, err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupLocksListeners() {
|
||||||
|
etcdServer.onPrefixPut('/locks', (key, value) => {
|
||||||
|
const id = key.split('/').pop();
|
||||||
|
logger.debug('Lock object event:', id);
|
||||||
|
this.socketManager.broadcast('notify_lock_update', {
|
||||||
|
...value,
|
||||||
|
locked: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
etcdServer.onPrefixDelete('/locks', key => {
|
||||||
|
const id = key.split('/').pop();
|
||||||
|
logger.debug('Unlock object event:', id);
|
||||||
|
this.socketManager.broadcast('notify_lock_update', {
|
||||||
|
_id: id,
|
||||||
|
locked: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
logger.info('Subscribed to Etcd stream for lock changes.');
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/notification/notificationmanager.js
Normal file
0
src/notification/notificationmanager.js
Normal file
128
src/socket/socketclient.js
Normal file
128
src/socket/socketclient.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import log4js from 'log4js';
|
||||||
|
// Load configuration
|
||||||
|
import { loadConfig } from '../config.js';
|
||||||
|
import { userModel } from '../database/user.schema.js';
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
const logger = log4js.getLogger('Socket Client');
|
||||||
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
export class SocketClient {
|
||||||
|
constructor(socket, socketManager) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.user = null;
|
||||||
|
this.socketManager = socketManager;
|
||||||
|
this.lockManager = socketManager.lockManager;
|
||||||
|
this.updateManager = socketManager.updateManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initUser() {
|
||||||
|
if (this.socket?.user?.username) {
|
||||||
|
try {
|
||||||
|
const userDoc = await userModel
|
||||||
|
.findOne({ username: this.socket.user.username })
|
||||||
|
.lean();
|
||||||
|
this.user = userDoc;
|
||||||
|
logger.debug('ID:', this.user._id.toString());
|
||||||
|
logger.debug('Name:', this.user.name);
|
||||||
|
logger.debug('Username:', this.user.username);
|
||||||
|
logger.debug('Email:', this.user.email);
|
||||||
|
this.setupSocketEventHandlers();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Error looking up user by username:', err);
|
||||||
|
this.user = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupSocketEventHandlers() {
|
||||||
|
this.socket.on('lock', this.handleLockEvent.bind(this));
|
||||||
|
this.socket.on('unlock', this.handleUnlockEvent.bind(this));
|
||||||
|
this.socket.on('getLock', this.handleGetLockEvent.bind(this));
|
||||||
|
this.socket.on('update', this.handleUpdateEvent.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleLockEvent(data) {
|
||||||
|
// data: { _id: string, params?: object }
|
||||||
|
if (!data || !data._id) {
|
||||||
|
this.socket.emit('lock_result', {
|
||||||
|
success: false,
|
||||||
|
error: 'Invalid lock event data'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data = { ...data, user: this.user._id.toString() };
|
||||||
|
try {
|
||||||
|
await this.lockManager.lockObject(data);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Lock event error:', err);
|
||||||
|
this.socket.emit('lock_result', { success: false, error: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleUnlockEvent(data) {
|
||||||
|
// data: { _id: string }
|
||||||
|
if (!data || !data._id) {
|
||||||
|
this.socket.emit('unlock_result', {
|
||||||
|
success: false,
|
||||||
|
error: 'Invalid unlock event data'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data = { ...data, user: this.user._id.toString() };
|
||||||
|
try {
|
||||||
|
await this.lockManager.unlockObject(data);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Unlock event error:', err);
|
||||||
|
this.socket.emit('unlock_result', { success: false, error: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGetLockEvent(data, callback) {
|
||||||
|
// data: { _id: string }
|
||||||
|
if (!data || !data._id) {
|
||||||
|
callback({
|
||||||
|
error: 'Invalid getLock event data'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const lockEvent = await this.lockManager.getObjectLock(data);
|
||||||
|
callback(lockEvent);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('GetLock event error:', err);
|
||||||
|
callback({
|
||||||
|
error: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleUpdateEvent(data) {
|
||||||
|
// data: { _id: string, type: string, ...otherProperties }
|
||||||
|
if (!data || !data._id || !data.type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Add user information to the update data
|
||||||
|
const updateData = {
|
||||||
|
...data,
|
||||||
|
updatedAt: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the updateManager to handle the update
|
||||||
|
if (this.updateManager) {
|
||||||
|
await this.updateManager.updateObject(updateData);
|
||||||
|
} else {
|
||||||
|
throw new Error('UpdateManager not available');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Update event error:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDisconnect() {
|
||||||
|
logger.info('External client disconnected:', this.socket.user?.username);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/socket/socketmanager.js
Normal file
82
src/socket/socketmanager.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// server.js - HTTP and Socket.IO server setup
|
||||||
|
import { Server } from 'socket.io';
|
||||||
|
import { createAuthMiddleware } from '../auth/auth.js';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
// Load configuration
|
||||||
|
import { loadConfig } from '../config.js';
|
||||||
|
import { SocketClient } from './socketclient.js';
|
||||||
|
import { LockManager } from '../lock/lockmanager.js';
|
||||||
|
import { UpdateManager } from '../updates/updatemanager.js';
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
const logger = log4js.getLogger('Socket Manager');
|
||||||
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
export class SocketManager {
|
||||||
|
constructor(auth, server) {
|
||||||
|
this.socketClientConnections = new Map();
|
||||||
|
this.lockManager = new LockManager(this);
|
||||||
|
this.updateManager = new UpdateManager(this);
|
||||||
|
|
||||||
|
// Use the provided HTTP server
|
||||||
|
// Create Socket.IO server
|
||||||
|
const io = new Server(server, {
|
||||||
|
cors: {
|
||||||
|
origin: config.server.corsOrigins || '*',
|
||||||
|
methods: ['GET', 'POST']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply authentication middleware
|
||||||
|
io.use(createAuthMiddleware(auth));
|
||||||
|
|
||||||
|
// Handle client connections
|
||||||
|
io.on('connection', async socket => {
|
||||||
|
logger.info('External client connected:', socket.user?.username);
|
||||||
|
await this.addClient(socket);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.io = io;
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addClient(socket) {
|
||||||
|
const client = new SocketClient(socket, this, this.lockManager);
|
||||||
|
await client.initUser();
|
||||||
|
this.socketClientConnections.set(socket.id, client);
|
||||||
|
logger.info('External client connected:', socket.user?.username);
|
||||||
|
// Handle disconnection
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
logger.info('External client disconnected:', socket.user?.username);
|
||||||
|
this.removeClient(socket.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeClient(socketClientId) {
|
||||||
|
const socketClient = this.socketClientConnections.get(socketClientId);
|
||||||
|
if (socketClient) {
|
||||||
|
this.socketClientConnections.delete(socketClientId);
|
||||||
|
logger.info(
|
||||||
|
'External client disconnected:',
|
||||||
|
socketClient.socket.user?.username
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSocketClient(clientId) {
|
||||||
|
return this.socketClientConnections.get(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllSocketClients() {
|
||||||
|
return Array.from(this.socketClientConnections.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(event, data, excludeClientId = null) {
|
||||||
|
for (const [clientId, socketClient] of this.socketClientConnections) {
|
||||||
|
if (excludeClientId !== clientId) {
|
||||||
|
socketClient.socket.emit(event, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/updates/updatemanager.js
Normal file
51
src/updates/updatemanager.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { etcdServer } from '../database/etcd.js';
|
||||||
|
|
||||||
|
import log4js from 'log4js';
|
||||||
|
import { loadConfig } from '../config.js';
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
// Setup logger
|
||||||
|
const logger = log4js.getLogger('Update Manager');
|
||||||
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UpdateManager handles tracking object updates using Etcd and broadcasts update events via websockets.
|
||||||
|
*/
|
||||||
|
export class UpdateManager {
|
||||||
|
constructor(socketManager) {
|
||||||
|
this.socketManager = socketManager;
|
||||||
|
this.setupUpdatesListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateObject(object) {
|
||||||
|
// Add an 'update' event to the 'updates' stream
|
||||||
|
logger.debug('Updating object:', object._id);
|
||||||
|
try {
|
||||||
|
const updateData = {
|
||||||
|
_id: object._id,
|
||||||
|
type: object.type,
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
await etcdServer.set(
|
||||||
|
`/updates/${object.type}s/${object._id}`,
|
||||||
|
updateData
|
||||||
|
);
|
||||||
|
logger.info(`Update event for id: ${object._id}`);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`Error adding update event to: ${object._id}:`, err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupUpdatesListeners() {
|
||||||
|
etcdServer.onPrefixPut('/updates', (key, value) => {
|
||||||
|
const id = key.split('/').pop();
|
||||||
|
logger.debug('Update object event:', id);
|
||||||
|
this.socketManager.broadcast('notify_object_update', {
|
||||||
|
...value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
logger.info('Subscribed to Etcd stream for update changes.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,436 +1,34 @@
|
|||||||
// WebSocketServer.js
|
// WebSocketServer.js
|
||||||
|
|
||||||
const express = require("express");
|
import express from 'express';
|
||||||
const http = require("http");
|
import http from 'http';
|
||||||
const socketIo = require("socket.io");
|
import { Server as SocketIo } from 'socket.io';
|
||||||
const { MongoClient } = require("mongodb");
|
import log4js from 'log4js';
|
||||||
const socketioJwt = require("socketio-jwt");
|
import dotenv from 'dotenv';
|
||||||
const log4js = require("log4js");
|
import { KeycloakAuth, createAuthMiddleware } from './auth/auth.js';
|
||||||
const config = require("../config.json");
|
|
||||||
const { trace } = require("console");
|
|
||||||
|
|
||||||
const logger = log4js.getLogger("Web Sockets");
|
dotenv.config();
|
||||||
logger.level = config.logLevel;
|
|
||||||
|
|
||||||
class WebSocketServer {
|
const logger = log4js.getLogger('Web Sockets');
|
||||||
|
logger.level = process.env.LOG_LEVEL;
|
||||||
|
|
||||||
|
export default class WebSocketServer {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
this.server = http.createServer(this.app);
|
this.server = http.createServer(this.app);
|
||||||
this.io = socketIo(this.server, {
|
this.io = new SocketIo(this.server, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: "*",
|
origin: '*'
|
||||||
},
|
},
|
||||||
maxHttpBufferSize: 1e8
|
maxHttpBufferSize: 1e8
|
||||||
});
|
});
|
||||||
this.JWT_SECRET = process.env.JWT_SECRET || config.jwt_secret;
|
this.hostServices = [];
|
||||||
this.mongoUri = process.env.MONGO_URI || config.mongo_uri;
|
this.userServices = [];
|
||||||
this.dbName = "farmcontrol"; // DB Name
|
|
||||||
|
|
||||||
this.hostSockets = [];
|
|
||||||
this.userSockets = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async connectToMongo() {
|
|
||||||
try {
|
|
||||||
const client = new MongoClient(this.mongoUri, {});
|
|
||||||
await client.connect();
|
|
||||||
this.db = client.db(this.dbName);
|
|
||||||
logger.info("Connected to MongoDB");
|
|
||||||
this.clearHostsCollection();
|
|
||||||
} catch (err) {
|
|
||||||
logger.error("MongoDB connection error:", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configureMiddleware() {
|
configureMiddleware() {
|
||||||
// Middleware for JWT authentication
|
// Middleware for Keycloak authentication
|
||||||
this.io.use(
|
const keycloakAuth = new KeycloakAuth();
|
||||||
socketioJwt.authorize({
|
this.io.use(createAuthMiddleware(keycloakAuth));
|
||||||
secret: this.JWT_SECRET,
|
|
||||||
handshake: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupSocketIO() {
|
|
||||||
this.io.on("connection", (socket) => {
|
|
||||||
var connectionType = "user";
|
|
||||||
var hostId;
|
|
||||||
var id, email;
|
|
||||||
if (socket.decoded_token.hasOwnProperty("hostId")) {
|
|
||||||
var connectionType = "host";
|
|
||||||
hostId = socket.decoded_token.hostId;
|
|
||||||
logger.info("Host connected:", hostId);
|
|
||||||
this.hostSockets.push(socket.id);
|
|
||||||
this.handleHostOnlineEvent({ hostId });
|
|
||||||
} else {
|
|
||||||
id = socket.decoded_token.id;
|
|
||||||
email = socket.decoded_token.email;
|
|
||||||
logger.info("User connected:", id);
|
|
||||||
this.userSockets.push(socket.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on("disconnect", () => {
|
|
||||||
if (connectionType == "host") {
|
|
||||||
logger.info("Host " + socket.decoded_token.hostId + " disconnected.");
|
|
||||||
this.hostSockets = this.hostSockets.filter(id => id !== socket.id);
|
|
||||||
this.handleHostOfflineEvent({ hostId: hostId });
|
|
||||||
} else {
|
|
||||||
logger.info("User " + socket.decoded_token.id + " disconnected.")
|
|
||||||
this.userSockets = this.userSockets.filter(id => id !== socket.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("status", (data) => {
|
|
||||||
logger.info("Setting", data.remoteAddress, "status to", data.status);
|
|
||||||
if (data.type == "printer") {
|
|
||||||
this.handlePrinterStatusEvent(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("online", (data) => {
|
|
||||||
logger.info("Setting", data.remoteAddress, "to online.");
|
|
||||||
if (data.type == "printer") {
|
|
||||||
this.handlePrinterOnlineEvent(data, socket);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("offline", (data) => {
|
|
||||||
logger.info("Setting", data.remoteAddress, "to offline.");
|
|
||||||
if (data.type == "printer") {
|
|
||||||
this.handlePrinterOfflineEvent(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("join", (data) => {
|
|
||||||
if (data.remoteAddress) {
|
|
||||||
logger.trace("Joining room", data.remoteAddress);
|
|
||||||
socket.join(data.remoteAddress);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("leave", (data) => {
|
|
||||||
if (data.remoteAddress) {
|
|
||||||
logger.trace("Leaving room", data.remoteAddress);
|
|
||||||
socket.leave(data.remoteAddress);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("temperature", (data) => {
|
|
||||||
if (connectionType == "host") {
|
|
||||||
socket.to(data.remoteAddress).emit("temperature", data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("command", (data) => {
|
|
||||||
if (connectionType == "user") {
|
|
||||||
socket.to(data.remoteAddress).emit(data.type, data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearHostsCollection() {
|
|
||||||
try {
|
|
||||||
if (!this.db) {
|
|
||||||
await this.connectToMongo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all documents from hosts collection
|
|
||||||
const deleteResult = await this.db.collection("hosts").deleteMany({});
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
`Deleted ${deleteResult.deletedCount} documents from hosts collection`
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error clearing hosts collection:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handlePrinterOnlineEvent(data, socket) {
|
|
||||||
try {
|
|
||||||
if (!this.db) {
|
|
||||||
await this.connectToMongo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if data.remoteAddress exists in printers collection
|
|
||||||
const existingPrinter = await this.db
|
|
||||||
.collection("printers")
|
|
||||||
.findOne({ remoteAddress: data.remoteAddress });
|
|
||||||
|
|
||||||
if (existingPrinter) {
|
|
||||||
// If exists, update the document
|
|
||||||
const updateResult = await this.db.collection("printers").updateOne(
|
|
||||||
{ remoteAddress: data.remoteAddress },
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
online: true,
|
|
||||||
status: { type: "Online" },
|
|
||||||
connectedAt: new Date(),
|
|
||||||
hostId: data.hostId, // Assuming hostId is passed as a parameter to handleIdentifyEvent
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateResult.modifiedCount > 0) {
|
|
||||||
logger.info(`Printer updated: ${data.remoteAddress}`);
|
|
||||||
} else {
|
|
||||||
logger.warn(`Printer not updated: ${data.remoteAddress}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If not exists, insert a new document
|
|
||||||
const insertData = {
|
|
||||||
remoteAddress: data.remoteAddress,
|
|
||||||
hostId: data.hostId,
|
|
||||||
online: true,
|
|
||||||
status: { type: "Online" },
|
|
||||||
connectedAt: new Date(),
|
|
||||||
friendlyName: "",
|
|
||||||
loadedFillament: null,
|
|
||||||
};
|
|
||||||
const insertResult = await this.db
|
|
||||||
.collection("printers")
|
|
||||||
.insertOne(insertData);
|
|
||||||
|
|
||||||
if (insertResult.insertedCount > 0) {
|
|
||||||
logger.info(`New printer added: ${data.remoteAddress}`);
|
|
||||||
} else {
|
|
||||||
logger.warn(`Failed to add new printer: ${data.remoteAddress}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onlineData = {
|
|
||||||
remoteAddress: data.remoteAddress,
|
|
||||||
hostId: data.hostId,
|
|
||||||
online: true,
|
|
||||||
status: { type: "Online" },
|
|
||||||
connectedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.trace("Sending online data", onlineData)
|
|
||||||
this.sendStatusToUserSockets(onlineData);
|
|
||||||
|
|
||||||
// Join socket room using remoteAddress as name
|
|
||||||
//logger.trace("Joining room", data.remoteAddress);
|
|
||||||
logger.trace("Joining room", data.remoteAddress);
|
|
||||||
socket.join(data.remoteAddress);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error handling online event:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handlePrinterStatusEvent(data) {
|
|
||||||
try {
|
|
||||||
if (!this.db) {
|
|
||||||
await this.connectToMongo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if data.remoteAddress exists in printers collection
|
|
||||||
const existingPrinter = await this.db
|
|
||||||
.collection("printers")
|
|
||||||
.findOne({ remoteAddress: data.remoteAddress });
|
|
||||||
|
|
||||||
if (existingPrinter) {
|
|
||||||
// If exists, update the document
|
|
||||||
const updateResult = await this.db.collection("printers").updateOne(
|
|
||||||
{ remoteAddress: data.remoteAddress },
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
status: data.status,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateResult.modifiedCount > 0) {
|
|
||||||
logger.info(`Printer updated: ${data.remoteAddress}`);
|
|
||||||
} else {
|
|
||||||
logger.warn(`Printer not updated: ${data.remoteAddress}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const onlineData = {
|
|
||||||
remoteAddress: data.remoteAddress,
|
|
||||||
hostId: data.hostId,
|
|
||||||
online: true,
|
|
||||||
connectedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.trace("Sending status data", onlineData)
|
|
||||||
this.sendStatusToUserSockets(data);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error handling status event:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handlePrinterOfflineEvent(data) {
|
|
||||||
try {
|
|
||||||
if (!this.db) {
|
|
||||||
await this.connectToMongo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if data.remoteAddress exists in printers collection
|
|
||||||
const existingPrinter = await this.db
|
|
||||||
.collection("printers")
|
|
||||||
.findOne({ remoteAddress: data.remoteAddress });
|
|
||||||
|
|
||||||
if (existingPrinter) {
|
|
||||||
// If exists, update the document
|
|
||||||
const updateResult = await this.db.collection("printers").updateOne(
|
|
||||||
{ remoteAddress: data.remoteAddress },
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
online: false,
|
|
||||||
status: { type: "Offline" },
|
|
||||||
connectedAt: null,
|
|
||||||
hostId: data.hostId, // Assuming hostId is passed as a parameter to handleIdentifyEvent
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateResult.modifiedCount > 0) {
|
|
||||||
logger.info(`Printer updated: ${data.remoteAddress}`);
|
|
||||||
} else {
|
|
||||||
logger.warn(`Printer not updated: ${data.remoteAddress}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If not exists, insert a new document
|
|
||||||
const insertResult = await this.db.collection("printers").insertOne({
|
|
||||||
remoteAddress: data.remoteAddress,
|
|
||||||
hostId: data.hostId,
|
|
||||||
online: false,
|
|
||||||
status: { type: "Offline" },
|
|
||||||
connectedAt: null,
|
|
||||||
friendlyName: "",
|
|
||||||
loadedFillament: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (insertResult.insertedCount > 0) {
|
|
||||||
logger.info(`New printer added: ${data.remoteAddress}`);
|
|
||||||
} else {
|
|
||||||
logger.warn(`Failed to add new printer: ${data.remoteAddress}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const offlineData = {
|
|
||||||
remoteAddress: data.remoteAddress,
|
|
||||||
hostId: data.hostId,
|
|
||||||
online: false,
|
|
||||||
status: { type: "Offline" },
|
|
||||||
connectedAt: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.trace("Sending offline data", offlineData)
|
|
||||||
this.sendStatusToUserSockets(offlineData);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error handling offline event:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleHostOnlineEvent(data) {
|
|
||||||
try {
|
|
||||||
if (!this.db) {
|
|
||||||
await this.connectToMongo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new entry to hosts collection.
|
|
||||||
const insertResult = await this.db.collection("hosts").insertOne({
|
|
||||||
hostId: data.hostId,
|
|
||||||
online: true,
|
|
||||||
connectedAt: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (insertResult.insertedCount != 0) {
|
|
||||||
logger.info(`New host added: ${data.hostId}`);
|
|
||||||
} else {
|
|
||||||
logger.warn(`Failed to add new host: ${data.hostId}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error handling online event:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleHostOfflineEvent(data) {
|
|
||||||
//try {
|
|
||||||
if (!this.db) {
|
|
||||||
await this.connectToMongo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete user from hosts collection
|
|
||||||
const deleteUserResult = await this.db
|
|
||||||
.collection("hosts")
|
|
||||||
.deleteOne({ hostId: data.hostId });
|
|
||||||
|
|
||||||
if (deleteUserResult.deletedCount > 0) {
|
|
||||||
logger.info(`User deleted from hosts collection: ${data.hostId}`);
|
|
||||||
} else {
|
|
||||||
logger.warn(`User not found in hosts collection: ${data.hostId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update all printers with matching hostId
|
|
||||||
const updatePrintersResult = await this.db
|
|
||||||
.collection("printers")
|
|
||||||
.updateMany(
|
|
||||||
{ hostId: data.hostId },
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
online: false,
|
|
||||||
status: { type: "Offline" },
|
|
||||||
connectedAt: null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const listPrintersResult = await this.db
|
|
||||||
.collection("printers")
|
|
||||||
.find({ hostId: data.hostId }).toArray();
|
|
||||||
|
|
||||||
if (updatePrintersResult.modifiedCount > 0) {
|
|
||||||
logger.info(
|
|
||||||
`Updated ${updatePrintersResult.modifiedCount} printers for user: ${data.hostId}`
|
|
||||||
);
|
|
||||||
for (let i = 0; i < listPrintersResult.length; i++) {
|
|
||||||
const printer = listPrintersResult[i];
|
|
||||||
const offlineData = {
|
|
||||||
remoteAddress: printer.remoteAddress,
|
|
||||||
hostId: printer.hostId,
|
|
||||||
online: false,
|
|
||||||
status: { type: "Offline" },
|
|
||||||
connectedAt: null,
|
|
||||||
};
|
|
||||||
logger.info(
|
|
||||||
`Sending offline status for: ${printer.remoteAddress}`
|
|
||||||
);
|
|
||||||
this.sendStatusToUserSockets(offlineData)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.warn(`No printers found for user: ${data.hostId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//} catch (error) {
|
|
||||||
// logger.error('Error handling user offline event:', error);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendStatusToUserSockets(data) {
|
|
||||||
this.userSockets.forEach(id => {
|
|
||||||
this.io.to(id).emit("status", data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async start(port = 3000) {
|
|
||||||
await this.connectToMongo();
|
|
||||||
this.configureMiddleware();
|
|
||||||
this.setupSocketIO();
|
|
||||||
|
|
||||||
this.server.listen(port, () => {
|
|
||||||
logger.info(`Server is running on port ${port}`);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { WebSocketServer };
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user