@oussema_mili/test-pkg-123 1.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +220 -0
- package/auth-callback.html +97 -0
- package/auth.js +276 -0
- package/cli-commands.js +1923 -0
- package/containerManager.js +304 -0
- package/daemon/agentRunner.js +429 -0
- package/daemon/daemonEntry.js +64 -0
- package/daemon/daemonManager.js +271 -0
- package/daemon/logManager.js +227 -0
- package/dist/styles.css +504 -0
- package/docker-actions/apps.js +3938 -0
- package/docker-actions/config-transformer.js +380 -0
- package/docker-actions/containers.js +355 -0
- package/docker-actions/general.js +171 -0
- package/docker-actions/images.js +1128 -0
- package/docker-actions/logs.js +224 -0
- package/docker-actions/metrics.js +270 -0
- package/docker-actions/registry.js +1100 -0
- package/docker-actions/setup-tasks.js +859 -0
- package/docker-actions/terminal.js +247 -0
- package/docker-actions/volumes.js +696 -0
- package/helper-functions.js +193 -0
- package/index.html +83 -0
- package/index.js +341 -0
- package/package.json +82 -0
- package/postcss.config.mjs +5 -0
- package/scripts/release.sh +212 -0
- package/setup/setupWizard.js +403 -0
- package/store/agentSessionStore.js +51 -0
- package/store/agentStore.js +113 -0
- package/store/configStore.js +171 -0
- package/store/daemonStore.js +217 -0
- package/store/deviceCredentialStore.js +107 -0
- package/store/npmTokenStore.js +65 -0
- package/store/registryStore.js +329 -0
- package/store/setupState.js +147 -0
- package/styles.css +1 -0
- package/utils/appLogger.js +223 -0
- package/utils/deviceInfo.js +98 -0
- package/utils/ecrAuth.js +225 -0
- package/utils/encryption.js +112 -0
- package/utils/envSetup.js +44 -0
- package/utils/errorHandler.js +327 -0
- package/utils/portUtils.js +59 -0
- package/utils/prerequisites.js +323 -0
- package/utils/prompts.js +318 -0
- package/utils/ssl-certificates.js +256 -0
- package/websocket-server.js +415 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Copyright (c) 2025 Fenwave (Fenleap). All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
PROPRIETARY SOFTWARE LICENSE
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
proprietary property of Fenwave (Fenleap) and are protected by copyright law.
|
|
7
|
+
|
|
8
|
+
TERMS AND CONDITIONS:
|
|
9
|
+
|
|
10
|
+
1. LICENSE GRANT: Fenwave grants authorized customers a limited, non-exclusive,
|
|
11
|
+
non-transferable license to use the Software solely for their internal
|
|
12
|
+
business purposes in connection with Fenwave services.
|
|
13
|
+
|
|
14
|
+
2. RESTRICTIONS: You may NOT:
|
|
15
|
+
- Copy, modify, or distribute the Software
|
|
16
|
+
- Reverse engineer, decompile, or disassemble the Software
|
|
17
|
+
- Create derivative works based on the Software
|
|
18
|
+
- Sublicense, rent, lease, or lend the Software
|
|
19
|
+
- Use the Software for any purpose other than as authorized by Fenwave
|
|
20
|
+
|
|
21
|
+
3. OWNERSHIP: Fenwave retains all right, title, and interest in and to the
|
|
22
|
+
Software, including all intellectual property rights.
|
|
23
|
+
|
|
24
|
+
4. NO WARRANTY: THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
|
|
25
|
+
|
|
26
|
+
5. LIMITATION OF LIABILITY: IN NO EVENT SHALL FENWAVE BE LIABLE FOR ANY
|
|
27
|
+
DAMAGES ARISING FROM THE USE OF THIS SOFTWARE.
|
|
28
|
+
|
|
29
|
+
For licensing inquiries, contact: support@fenwave.com
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Fenwave Agent
|
|
2
|
+
|
|
3
|
+
The Fenwave Agent is a CLI tool for managing Docker containers and local development environments. It integrates with the Fenwave platform for seamless developer experience.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **Docker Desktop** (version 20.10 or later)
|
|
8
|
+
- **Node.js** (version 20 or later)
|
|
9
|
+
- **Fenwave Platform Access** - Valid credentials from your organization
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @fenwave/agent
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### 1. Initialize the Agent
|
|
20
|
+
|
|
21
|
+
Get your registration token from the Fenwave platform, then run:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
fenwave init --token <your-registration-token>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Authenticate
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
fenwave login
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This opens a browser for authentication. After successful auth, you'll be prompted to start the agent.
|
|
34
|
+
|
|
35
|
+
### 3. Start the Agent
|
|
36
|
+
|
|
37
|
+
**Background mode (recommended for production):**
|
|
38
|
+
```bash
|
|
39
|
+
fenwave service start
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Foreground mode (for development):**
|
|
43
|
+
```bash
|
|
44
|
+
fenwave service run
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 4. Access the Dashboard
|
|
48
|
+
|
|
49
|
+
Open your browser to `http://localhost:3003`
|
|
50
|
+
|
|
51
|
+
## Commands
|
|
52
|
+
|
|
53
|
+
### Authentication
|
|
54
|
+
|
|
55
|
+
| Command | Description |
|
|
56
|
+
|---------|-------------|
|
|
57
|
+
| `fenwave login` | Authenticate with Backstage (does not start agent) |
|
|
58
|
+
| `fenwave logout` | Clear session and stop agent if running |
|
|
59
|
+
| `fenwave status` | Check agent and registration status |
|
|
60
|
+
|
|
61
|
+
### Service Management
|
|
62
|
+
|
|
63
|
+
| Command | Description |
|
|
64
|
+
|---------|-------------|
|
|
65
|
+
| `fenwave service start` | Start agent as background daemon |
|
|
66
|
+
| `fenwave service run` | Run agent in foreground (for development) |
|
|
67
|
+
| `fenwave service stop` | Stop the running daemon |
|
|
68
|
+
| `fenwave service restart` | Restart the daemon |
|
|
69
|
+
| `fenwave service status` | Show daemon status (PID, port, uptime) |
|
|
70
|
+
| `fenwave service logs` | View daemon logs |
|
|
71
|
+
| `fenwave service logs -f` | Follow daemon logs in real-time |
|
|
72
|
+
|
|
73
|
+
### Setup & Configuration
|
|
74
|
+
|
|
75
|
+
| Command | Description |
|
|
76
|
+
|---------|-------------|
|
|
77
|
+
| `fenwave init` | Interactive setup wizard |
|
|
78
|
+
| `fenwave register` | Register device with Backstage |
|
|
79
|
+
| `fenwave rotate-credentials` | Rotate device credentials |
|
|
80
|
+
| `fenwave uninstall` | Uninstall agent and clean up |
|
|
81
|
+
|
|
82
|
+
### Container Management
|
|
83
|
+
|
|
84
|
+
| Command | Description |
|
|
85
|
+
|---------|-------------|
|
|
86
|
+
| `fenwave containers` | List containers |
|
|
87
|
+
| `fenwave start <id>` | Start container(s) |
|
|
88
|
+
| `fenwave stop <id>` | Stop container(s) |
|
|
89
|
+
| `fenwave restart <id>` | Restart container(s) |
|
|
90
|
+
| `fenwave rm <id>` | Remove container(s) |
|
|
91
|
+
| `fenwave logs <id>` | View container logs |
|
|
92
|
+
|
|
93
|
+
### Image & Volume Management
|
|
94
|
+
|
|
95
|
+
| Command | Description |
|
|
96
|
+
|---------|-------------|
|
|
97
|
+
| `fenwave images` | List images |
|
|
98
|
+
| `fenwave pull <tag>` | Pull image(s) |
|
|
99
|
+
| `fenwave rmi <id>` | Remove image(s) |
|
|
100
|
+
| `fenwave volumes` | List volumes |
|
|
101
|
+
| `fenwave volume-create <name>` | Create volume(s) |
|
|
102
|
+
| `fenwave volume-rm <name>` | Remove volume(s) |
|
|
103
|
+
|
|
104
|
+
### Other Commands
|
|
105
|
+
|
|
106
|
+
| Command | Description |
|
|
107
|
+
|---------|-------------|
|
|
108
|
+
| `fenwave info` | Display agent information |
|
|
109
|
+
| `fenwave registries` | List connected registries |
|
|
110
|
+
| `fenwave local-env` | Manage local-env container |
|
|
111
|
+
| `fenwave --help` | Show all available commands |
|
|
112
|
+
|
|
113
|
+
## Architecture
|
|
114
|
+
|
|
115
|
+
The agent runs as a background daemon with the following features:
|
|
116
|
+
|
|
117
|
+
- **Port Fallback**: Automatically finds available port (3001-3010)
|
|
118
|
+
- **Single Instance**: Prevents multiple daemon instances
|
|
119
|
+
- **Heartbeat**: WebSocket health checks every 30 seconds
|
|
120
|
+
- **Graceful Shutdown**: Connection draining with client notification
|
|
121
|
+
- **Log Rotation**: Automatic rotation at 10MB, keeps 5 files
|
|
122
|
+
|
|
123
|
+
### File Locations
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
~/.fenwave/
|
|
127
|
+
├── daemon/
|
|
128
|
+
│ ├── state.json # Daemon state (PID, port, status)
|
|
129
|
+
│ ├── agent.pid # Process ID file
|
|
130
|
+
│ └── agent.lock # Instance lock file
|
|
131
|
+
├── logs/
|
|
132
|
+
│ ├── agent.log # Main log file
|
|
133
|
+
│ └── agent.error.log # Error log file
|
|
134
|
+
├── session/
|
|
135
|
+
│ └── config.json # Session credentials
|
|
136
|
+
├── config/
|
|
137
|
+
│ └── agent.json # Agent configuration
|
|
138
|
+
└── ws-token # WebSocket auth token
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Dashboard Features
|
|
142
|
+
|
|
143
|
+
- Container management (start, stop, restart, logs)
|
|
144
|
+
- Docker image management
|
|
145
|
+
- Volume management
|
|
146
|
+
- Registry connections
|
|
147
|
+
- Real-time metrics
|
|
148
|
+
- Terminal access to containers
|
|
149
|
+
|
|
150
|
+
## Troubleshooting
|
|
151
|
+
|
|
152
|
+
**Agent won't start?**
|
|
153
|
+
- Ensure Docker Desktop is running
|
|
154
|
+
- Check if another instance is running: `fenwave service status`
|
|
155
|
+
- Check logs: `fenwave service logs`
|
|
156
|
+
- Try stopping and starting: `fenwave service stop && fenwave service start`
|
|
157
|
+
|
|
158
|
+
**Port already in use?**
|
|
159
|
+
- The agent will automatically try ports 3001-3010
|
|
160
|
+
- Check which port is being used: `fenwave service status`
|
|
161
|
+
|
|
162
|
+
**Authentication issues?**
|
|
163
|
+
- Run `fenwave logout` then `fenwave login`
|
|
164
|
+
- Ensure Backstage is running and accessible
|
|
165
|
+
|
|
166
|
+
**View logs for debugging:**
|
|
167
|
+
```bash
|
|
168
|
+
fenwave service logs -f
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Need help?**
|
|
172
|
+
- Run `fenwave --help` for command options
|
|
173
|
+
- Contact your Fenwave administrator
|
|
174
|
+
|
|
175
|
+
## Development
|
|
176
|
+
|
|
177
|
+
### Local Testing
|
|
178
|
+
|
|
179
|
+
To test the agent locally before publishing:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Navigate to the agent directory
|
|
183
|
+
cd fenwave-agent
|
|
184
|
+
|
|
185
|
+
# Create a global symlink to this local package
|
|
186
|
+
npm link
|
|
187
|
+
|
|
188
|
+
# Now 'fenwave' command will use your local code
|
|
189
|
+
fenwave init --token YOUR_TOKEN --backend-url http://localhost:7007
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
When you're done testing:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Remove the global symlink
|
|
196
|
+
npm unlink -g @fenwave/agent
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Running in Development Mode
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Run agent in foreground with debug output
|
|
203
|
+
fenwave --debug service run
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Publishing
|
|
207
|
+
|
|
208
|
+
To publish a new version:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Bump version (patch/minor/major)
|
|
212
|
+
npm version patch
|
|
213
|
+
|
|
214
|
+
# Publish to npm
|
|
215
|
+
npm publish --access public
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
Copyright 2025 Fenwave. All rights reserved. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Authenticating...</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
10
|
+
text-align: center;
|
|
11
|
+
padding: 50px;
|
|
12
|
+
background: #f5f5f5;
|
|
13
|
+
}
|
|
14
|
+
.container {
|
|
15
|
+
background: white;
|
|
16
|
+
padding: 40px;
|
|
17
|
+
border-radius: 8px;
|
|
18
|
+
max-width: 400px;
|
|
19
|
+
margin: 0 auto;
|
|
20
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
21
|
+
}
|
|
22
|
+
.spinner {
|
|
23
|
+
border: 3px solid #f3f3f3;
|
|
24
|
+
border-top: 3px solid #3498db;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
width: 40px;
|
|
27
|
+
height: 40px;
|
|
28
|
+
animation: spin 1s linear infinite;
|
|
29
|
+
margin: 20px auto;
|
|
30
|
+
}
|
|
31
|
+
@keyframes spin {
|
|
32
|
+
0% {
|
|
33
|
+
transform: rotate(0deg);
|
|
34
|
+
}
|
|
35
|
+
100% {
|
|
36
|
+
transform: rotate(360deg);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
.error {
|
|
40
|
+
color: #dc2626;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<div class="container">
|
|
46
|
+
<div class="spinner" id="spinner"></div>
|
|
47
|
+
<h2 id="status">Processing authentication...</h2>
|
|
48
|
+
<p id="message">Please wait while we complete the authentication.</p>
|
|
49
|
+
</div>
|
|
50
|
+
<script>
|
|
51
|
+
(function () {
|
|
52
|
+
// Read credentials from URL fragment
|
|
53
|
+
let jwt, entityRef;
|
|
54
|
+
|
|
55
|
+
const hash = window.location.hash.substring(1);
|
|
56
|
+
if (hash) {
|
|
57
|
+
const hashParams = new URLSearchParams(hash);
|
|
58
|
+
jwt = hashParams.get("jwt");
|
|
59
|
+
entityRef = hashParams.get("entityRef");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!jwt || !entityRef) {
|
|
63
|
+
document.getElementById("spinner").style.display = "none";
|
|
64
|
+
document.getElementById("status").className = "error";
|
|
65
|
+
document.getElementById("status").textContent =
|
|
66
|
+
"Authentication Failed";
|
|
67
|
+
document.getElementById("message").textContent =
|
|
68
|
+
"Missing credentials. Please try again.";
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// POST credentials to the server (keeps them out of URL/logs)
|
|
73
|
+
fetch("/auth-complete", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({ jwt, entityRef }),
|
|
77
|
+
})
|
|
78
|
+
.then((response) => response.json())
|
|
79
|
+
.then((data) => {
|
|
80
|
+
if (data.success) {
|
|
81
|
+
// Redirect to success page
|
|
82
|
+
window.location.href = data.redirectUrl;
|
|
83
|
+
} else {
|
|
84
|
+
throw new Error(data.error || "Authentication failed");
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
.catch((error) => {
|
|
88
|
+
document.getElementById("spinner").style.display = "none";
|
|
89
|
+
document.getElementById("status").className = "error";
|
|
90
|
+
document.getElementById("status").textContent =
|
|
91
|
+
"Authentication Failed";
|
|
92
|
+
document.getElementById("message").textContent = error.message;
|
|
93
|
+
});
|
|
94
|
+
})();
|
|
95
|
+
</script>
|
|
96
|
+
</body>
|
|
97
|
+
</html>
|
package/auth.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
const AGENT_ROOT_DIR = path.join(
|
|
10
|
+
os.homedir(),
|
|
11
|
+
process.env.AGENT_ROOT_DIR || '.fenwave'
|
|
12
|
+
);
|
|
13
|
+
const SESSION_DIR = process.env.SESSION_DIR || 'session';
|
|
14
|
+
const SESSION_FILE = process.env.SESSION_FILE || 'config.json';
|
|
15
|
+
const CONFIG_FILE = path.join(AGENT_ROOT_DIR, SESSION_DIR, SESSION_FILE);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensures the configuration directory exists
|
|
19
|
+
*/
|
|
20
|
+
function ensureConfigDirectory() {
|
|
21
|
+
const sessionDir = path.dirname(CONFIG_FILE);
|
|
22
|
+
if (!fs.existsSync(sessionDir)) {
|
|
23
|
+
fs.mkdirSync(sessionDir, { recursive: true, mode: 0o700 });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Saves session data to local config file
|
|
29
|
+
* @param {string} token - Session token
|
|
30
|
+
* @param {string} expiresAt - Expiration timestamp
|
|
31
|
+
* @param {string} userEntityRef - User entity reference
|
|
32
|
+
* @param {string} backendUrl - Optional backend URL to save
|
|
33
|
+
*/
|
|
34
|
+
function saveSession(token, expiresAt, userEntityRef, backendUrl = null) {
|
|
35
|
+
ensureConfigDirectory();
|
|
36
|
+
const config = {
|
|
37
|
+
token,
|
|
38
|
+
expiresAt,
|
|
39
|
+
userEntityRef,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Preserve existing backend URL or save new one
|
|
43
|
+
if (backendUrl) {
|
|
44
|
+
config.backendUrl = backendUrl;
|
|
45
|
+
} else {
|
|
46
|
+
const existingConfig = loadSession();
|
|
47
|
+
if (existingConfig && existingConfig.backendUrl) {
|
|
48
|
+
config.backendUrl = existingConfig.backendUrl;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), {
|
|
53
|
+
mode: 0o600,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Loads session data from local config file
|
|
59
|
+
* @returns {Object|null} Session data or null if not found
|
|
60
|
+
*/
|
|
61
|
+
function loadSession() {
|
|
62
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
67
|
+
return config;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(chalk.red('❌ Error loading session config:'), error.message);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Checks if a session is still valid (not expired)
|
|
76
|
+
* @param {Object} session - Session data
|
|
77
|
+
* @returns {boolean} - True if session is valid
|
|
78
|
+
*/
|
|
79
|
+
function isSessionValid(session) {
|
|
80
|
+
if (!session || !session.token || !session.expiresAt) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const now = new Date();
|
|
84
|
+
const expiresAt = new Date(session.expiresAt);
|
|
85
|
+
return now < expiresAt;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handles session expiry by clearing session and exiting process
|
|
90
|
+
* @param {string} message - Optional message to display
|
|
91
|
+
* @param {Object} server - Optional WebSocket server to close
|
|
92
|
+
* @param {Object} wss - Optional WebSocket server instance to close
|
|
93
|
+
*/
|
|
94
|
+
function handleSessionExpiry(
|
|
95
|
+
message = 'Session has expired!',
|
|
96
|
+
server = null,
|
|
97
|
+
wss = null
|
|
98
|
+
) {
|
|
99
|
+
console.log(chalk.red(`❌ ${message}`));
|
|
100
|
+
console.log(chalk.red('🔒 Fenwave Agent shutting down gracefully...'));
|
|
101
|
+
|
|
102
|
+
// Close WebSocket connections and server if provided
|
|
103
|
+
if (wss) {
|
|
104
|
+
wss.clients.forEach((client) => {
|
|
105
|
+
client.send(
|
|
106
|
+
JSON.stringify({
|
|
107
|
+
type: 'session_expired',
|
|
108
|
+
message: 'Session has expired. Please re-authenticate.',
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
client.close();
|
|
112
|
+
});
|
|
113
|
+
wss.close();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (server) {
|
|
117
|
+
server.close();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
clearSession();
|
|
121
|
+
console.log(chalk.yellow("💡 Please run 'fenwave login' to authenticate again."));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Creates a new session with the Fenwave backend
|
|
127
|
+
* @param {string} jwt - Token for authentication
|
|
128
|
+
* @param {string} backendUrl - Backend URL
|
|
129
|
+
* @returns {Object} Session data with token, userEntityRef and expiresAt
|
|
130
|
+
*/
|
|
131
|
+
async function createSession(jwt, backendUrl) {
|
|
132
|
+
try {
|
|
133
|
+
const response = await axios.post(
|
|
134
|
+
`${backendUrl}/api/agent-cli/create-session`,
|
|
135
|
+
{},
|
|
136
|
+
{
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `Bearer ${jwt}`,
|
|
139
|
+
'Content-Type': 'application/json',
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
console.log(chalk.green('✅ Session created successfully!'));
|
|
144
|
+
const { token, expiresAt } = response.data;
|
|
145
|
+
return { token, expiresAt };
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(
|
|
148
|
+
chalk.red('❌ Failed to create session: '),
|
|
149
|
+
error.response?.data || error.message
|
|
150
|
+
);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Clears the stored session
|
|
157
|
+
*/
|
|
158
|
+
function clearSession() {
|
|
159
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
160
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Sets up a file watcher to monitor session file changes
|
|
168
|
+
* @param {Function} onSessionExpired - Callback function to handle session expiry
|
|
169
|
+
* @param {Object} server - WebSocket server instance
|
|
170
|
+
* @param {Object} wss - WebSocket server instance
|
|
171
|
+
* @returns {Object} File watcher instance
|
|
172
|
+
*/
|
|
173
|
+
function setupSessionWatcher(onSessionExpired, server = null, wss = null) {
|
|
174
|
+
let watcher = null;
|
|
175
|
+
let expiryTimer = null;
|
|
176
|
+
|
|
177
|
+
// Set up timer for natural expiry
|
|
178
|
+
const session = loadSession();
|
|
179
|
+
if (session && session.expiresAt) {
|
|
180
|
+
const expiryTime = new Date(session.expiresAt).getTime();
|
|
181
|
+
const currentTime = Date.now();
|
|
182
|
+
const timeUntilExpiry = expiryTime - currentTime;
|
|
183
|
+
|
|
184
|
+
if (timeUntilExpiry > 0) {
|
|
185
|
+
expiryTimer = setTimeout(() => {
|
|
186
|
+
clearSession();
|
|
187
|
+
onSessionExpired('Session expired !', server, wss);
|
|
188
|
+
}, timeUntilExpiry);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
// Watch the config file for changes/deletion
|
|
194
|
+
watcher = fs.watch(CONFIG_FILE, (eventType, filename) => {
|
|
195
|
+
// Clear the natural expiry timer since file changed
|
|
196
|
+
if (expiryTimer) {
|
|
197
|
+
clearTimeout(expiryTimer);
|
|
198
|
+
expiryTimer = null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if file was deleted or renamed (session revoked by logout)
|
|
202
|
+
if (eventType === 'rename' || !fs.existsSync(CONFIG_FILE)) {
|
|
203
|
+
if (watcher) {
|
|
204
|
+
watcher.close();
|
|
205
|
+
}
|
|
206
|
+
onSessionExpired('Session revoked !', server, wss);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
close: () => {
|
|
212
|
+
if (watcher) watcher.close();
|
|
213
|
+
if (expiryTimer) clearTimeout(expiryTimer);
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
// Clear expiry timer if file watcher fails
|
|
218
|
+
if (expiryTimer) {
|
|
219
|
+
clearTimeout(expiryTimer);
|
|
220
|
+
expiryTimer = null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Fallback to periodic checking if file watching fails
|
|
224
|
+
const intervalId = setInterval(() => {
|
|
225
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
226
|
+
clearInterval(intervalId);
|
|
227
|
+
onSessionExpired('Session revoked !', server, wss);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const currentSession = loadSession();
|
|
232
|
+
if (!currentSession || !isSessionValid(currentSession)) {
|
|
233
|
+
clearInterval(intervalId);
|
|
234
|
+
onSessionExpired('Session expired !', server, wss);
|
|
235
|
+
}
|
|
236
|
+
}, 30 * 1000); // Check every 30 seconds as fallback
|
|
237
|
+
|
|
238
|
+
return { close: () => clearInterval(intervalId) };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Loads backend URL from agent config or environment variable
|
|
244
|
+
* NOTE: This function loads synchronously from the config file
|
|
245
|
+
* @returns {string|undefined} Backend URL
|
|
246
|
+
*/
|
|
247
|
+
function loadBackendUrl() {
|
|
248
|
+
// Load directly from config file to avoid circular dependencies
|
|
249
|
+
const configPath = path.join(os.homedir(), '.fenwave', 'config', 'agent.json');
|
|
250
|
+
|
|
251
|
+
// Try to load from config file
|
|
252
|
+
if (fs.existsSync(configPath)) {
|
|
253
|
+
try {
|
|
254
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
255
|
+
if (config.backendUrl) {
|
|
256
|
+
return config.backendUrl;
|
|
257
|
+
}
|
|
258
|
+
} catch (error) {
|
|
259
|
+
// Fall through to env variable
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Fall back to environment variable
|
|
264
|
+
return process.env.BACKEND_URL || 'http://localhost:7007';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export {
|
|
268
|
+
saveSession,
|
|
269
|
+
loadSession,
|
|
270
|
+
isSessionValid,
|
|
271
|
+
handleSessionExpiry,
|
|
272
|
+
createSession,
|
|
273
|
+
clearSession,
|
|
274
|
+
setupSessionWatcher,
|
|
275
|
+
loadBackendUrl,
|
|
276
|
+
};
|