minercon 3.0.2 → 3.0.4

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "minercon",
3
3
  "displayName": "Minercon",
4
4
  "description": "A powerful RCON client for Minecraft servers with fancy tab completion",
5
- "version": "3.0.2",
5
+ "version": "3.0.4",
6
6
  "author": "xton <thisischriston@gmail.com>",
7
7
  "icon": "images/icon.png",
8
8
  "license": "MIT",
@@ -1,43 +0,0 @@
1
- "use strict";
2
- // src/argumentHint.ts
3
- //
4
- // Pure formatting for the "argument hint" display: given a command's usage
5
- // string (e.g. "gamemode <mode> [<target>]") and the line the user has typed
6
- // so far, work out which argument position they're at and which token in the
7
- // usage string corresponds to it (so it can be highlighted).
8
- //
9
- // This is presentation logic, not decision logic — it has no notion of
10
- // fetching, timing, or staleness — so it lives apart from completionEngine's
11
- // state machine, as its own pure, independently-testable function.
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.formatArgumentHint = formatArgumentHint;
14
- const ARGUMENT_TOKEN_PATTERN = /(<[^>]+>|\[[^\]]+\]|\([^)]+\))/g;
15
- /** Returns null when there's no usage text to show anything for. */
16
- function formatArgumentHint(usage, line) {
17
- if (!usage) {
18
- return null;
19
- }
20
- const tokens = usage.match(ARGUMENT_TOKEN_PATTERN) || [];
21
- // The literal words before the first argument token are the command path —
22
- // derived straight from the usage string so we don't need a separate
23
- // "commandPath" concept threaded in from wherever the usage came from.
24
- const firstTokenStart = usage.search(ARGUMENT_TOKEN_PATTERN);
25
- const literalPrefix = (firstTokenStart >= 0 ? usage.slice(0, firstTokenStart) : usage).trim();
26
- const commandPrefixWordCount = literalPrefix.length > 0 ? literalPrefix.split(/\s+/).length : 0;
27
- const commandPrefixText = '/' + literalPrefix;
28
- const parts = line.trim().split(' ').filter(p => p.length > 0);
29
- const hasTrailingSpace = line.endsWith(' ');
30
- const argumentCount = Math.max(0, parts.length - commandPrefixWordCount);
31
- let currentArgIndex;
32
- if (argumentCount === 0 && !hasTrailingSpace) {
33
- currentArgIndex = -1; // still typing the command/subcommand itself
34
- }
35
- else if (hasTrailingSpace) {
36
- currentArgIndex = argumentCount; // ready for the next argument
37
- }
38
- else {
39
- currentArgIndex = argumentCount - 1; // currently typing this argument
40
- }
41
- return { commandPrefixText, tokens, currentArgIndex };
42
- }
43
- //# sourceMappingURL=argumentHint.js.map
@@ -1,62 +0,0 @@
1
- "use strict";
2
- // src/bukkitHelpParsing.ts
3
- //
4
- // Pure parsing of Bukkit's hand-written `/help <command>` pages - a
5
- // "§e--------- §fHelp: /<cmd> ----...§e" banner line followed by
6
- // `Description:`/`Usage:`/`Aliases:` lines. This is a different grammar from
7
- // the flat Brigadier `/cmd <args>` blobs `helpTextParsing.ts` handles
8
- // (`minecraft:help`, vanilla's plain `help`): on a server where the
9
- // `minecraft:` namespace is supported (Paper/Spigot), `help <path>` is
10
- // *always* one of these pages, for every command - vanilla-backed or
11
- // Bukkit-added alike - so no shape-sniffing is needed to decide which parser
12
- // applies, only `LocalCommandTree.supportsMinecraftNamespace`.
13
- //
14
- // No state, no IO — every export here is a deterministic function of its
15
- // arguments.
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.extractBukkitUsageLines = extractBukkitUsageLines;
18
- exports.extractBukkitAliases = extractBukkitAliases;
19
- const ansi_1 = require("./ansi");
20
- /**
21
- * Extract the `Usage: ...` line(s) from a Bukkit-style `/help <command>`
22
- * response (e.g. "Description: ...\nUsage: /version [plugin name]\nAliases:
23
- * ..."), normalized so each reads as `<commandPath> ...` for `parseHelpLines`.
24
- * Returns `[]` if there's no Usage line, or if its content is just the bare
25
- * command name with nothing after it — the generic response Bukkit gives for
26
- * Brigadier-backed (vanilla) commands ("Description: A Mojang provided
27
- * command.\nUsage: <name>"), which carries no argument info.
28
- */
29
- function extractBukkitUsageLines(helpText, commandPath) {
30
- const lines = (0, ansi_1.stripColors)(helpText).split('\n').map(line => line.trim());
31
- const usageIndex = lines.findIndex(line => /^Usage:\s*/i.test(line));
32
- if (usageIndex === -1) {
33
- return [];
34
- }
35
- const result = [lines[usageIndex].replace(/^Usage:\s*/i, '')];
36
- for (let i = usageIndex + 1; i < lines.length; i++) {
37
- const line = lines[i];
38
- if (!line || /^[A-Za-z][A-Za-z ]*:/.test(line) || line.startsWith('---')) {
39
- break;
40
- }
41
- result.push(line);
42
- }
43
- const normalizedPath = commandPath.toLowerCase();
44
- return result.filter(line => line.replace(/^\//, '').toLowerCase() !== normalizedPath);
45
- }
46
- /**
47
- * Extract alias names from a Bukkit-style `/help <command>` response's
48
- * `Aliases: a, b, c` line (e.g. "Description: ...\nUsage: ...\nAliases: ver,
49
- * about"). Returns `[]` if there's no Aliases line.
50
- */
51
- function extractBukkitAliases(helpText) {
52
- const lines = (0, ansi_1.stripColors)(helpText).split('\n').map(line => line.trim());
53
- const aliasesLine = lines.find(line => /^Aliases:\s*/i.test(line));
54
- if (!aliasesLine) {
55
- return [];
56
- }
57
- return aliasesLine.replace(/^Aliases:\s*/i, '')
58
- .split(',')
59
- .map(alias => alias.trim())
60
- .filter(alias => alias.length > 0);
61
- }
62
- //# sourceMappingURL=bukkitHelpParsing.js.map
@@ -1,202 +0,0 @@
1
- "use strict";
2
- // src/commandSuggestions.ts
3
- //
4
- // Pure suggestion generation: given the command tree `LocalCommandTree`
5
- // builds and the user's current input line, work out what to suggest next
6
- // and what argument-help text to show. No state, no IO — a deterministic
7
- // function of the tree and the input.
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.getSuggestions = getSuggestions;
10
- const commandTree_1 = require("./commandTree");
11
- /**
12
- * Get suggestions based on current input
13
- */
14
- function getSuggestions(rootCommands, isReady, input) {
15
- if (!isReady) {
16
- return { suggestions: [], argumentHelp: undefined };
17
- }
18
- const trimmed = input.trim();
19
- if (!trimmed.startsWith('/')) {
20
- return { suggestions: [], argumentHelp: undefined };
21
- }
22
- const hasTrailingSpace = input.endsWith(' ');
23
- const parts = trimmed.slice(1).split(' ').filter(p => p.length > 0);
24
- const commandName = parts[0];
25
- // Handle root command suggestions
26
- if (parts.length === 0 || (parts.length === 1 && !hasTrailingSpace)) {
27
- const suggestions = Array.from(rootCommands.keys())
28
- .filter(cmd => cmd.startsWith(commandName || ''))
29
- .sort();
30
- return { suggestions, argumentHelp: undefined };
31
- }
32
- // Find the command node
33
- const rootNode = rootCommands.get(commandName);
34
- if (!rootNode) {
35
- return { suggestions: [], argumentHelp: undefined };
36
- }
37
- // Navigate through the parameter tree
38
- let currentParameters = rootNode.members || [];
39
- let paramIndex = 1; // Start after the command name
40
- // The command path consumed so far (root command + any literal/subcommand
41
- // tokens navigated past) - see SuggestionResult.commandPath.
42
- const pathParts = [commandName];
43
- // Navigate through completed parts (not including what we're currently typing)
44
- const partsToNavigate = hasTrailingSpace ? parts.length : parts.length - 1;
45
- while (paramIndex < partsToNavigate && currentParameters.length > 0) {
46
- const currentPart = parts[paramIndex];
47
- let navigated = false;
48
- // Get the first parameter at this position
49
- const firstParam = currentParameters[0];
50
- if (firstParam.type === commandTree_1.ParameterType.SUBCOMMAND) {
51
- // Direct subcommand
52
- if (firstParam.name === currentPart || firstParam.literal === currentPart) {
53
- currentParameters = firstParam.members || [];
54
- navigated = true;
55
- }
56
- }
57
- else if (firstParam.type === commandTree_1.ParameterType.CHOICE_LIST && firstParam.choices) {
58
- // Choice list - find matching choice and navigate into it
59
- for (const choice of firstParam.choices) {
60
- if (choice.type === commandTree_1.ParameterType.SUBCOMMAND &&
61
- (choice.name === currentPart || choice.literal === currentPart)) {
62
- // IMPORTANT: Navigate into the selected choice's members
63
- currentParameters = choice.members || [];
64
- navigated = true;
65
- break;
66
- }
67
- else if (choice.type === commandTree_1.ParameterType.LITERAL && choice.literal === currentPart) {
68
- // For literal choices, move to next parameter position
69
- currentParameters = currentParameters.slice(1);
70
- navigated = true;
71
- break;
72
- }
73
- }
74
- }
75
- else if (firstParam.type === commandTree_1.ParameterType.LITERAL && firstParam.literal === currentPart) {
76
- // Literal parameter
77
- currentParameters = currentParameters.slice(1);
78
- navigated = true;
79
- }
80
- paramIndex++;
81
- if (navigated) {
82
- pathParts.push(currentPart);
83
- }
84
- else {
85
- // It's an argument value, skip to next position
86
- currentParameters = currentParameters.slice(1);
87
- }
88
- }
89
- // Build argument help from current position
90
- const argumentHelp = buildArgumentHelp(currentParameters);
91
- const commandPath = pathParts.join(' ');
92
- // Generate suggestions based on current position
93
- let suggestions = [];
94
- if (hasTrailingSpace) {
95
- // We want suggestions for the NEXT parameter
96
- suggestions = generateSuggestionsForNextPosition(currentParameters);
97
- }
98
- else {
99
- // We're typing something, get matching suggestions
100
- const currentPart = parts[parts.length - 1] || '';
101
- suggestions = generateSuggestionsForCurrentPart(currentParameters, currentPart);
102
- }
103
- return { suggestions, argumentHelp, commandPath };
104
- }
105
- /**
106
- * Generate suggestions for what we're currently typing
107
- * Must handle CHOICE_LIST parameters properly
108
- */
109
- function generateSuggestionsForCurrentPart(parameters, currentPart) {
110
- const suggestions = [];
111
- for (const param of parameters) {
112
- if (param.type === commandTree_1.ParameterType.SUBCOMMAND) {
113
- // Direct subcommand
114
- const name = param.name || param.literal || '';
115
- if (name.startsWith(currentPart)) {
116
- suggestions.push(name);
117
- }
118
- }
119
- else if (param.type === commandTree_1.ParameterType.CHOICE_LIST && param.choices) {
120
- // Choice list - add all matching choices
121
- for (const choice of param.choices) {
122
- if (choice.type === commandTree_1.ParameterType.SUBCOMMAND) {
123
- const name = choice.name || choice.literal || '';
124
- if (name.startsWith(currentPart)) {
125
- suggestions.push(name);
126
- }
127
- }
128
- else if (choice.type === commandTree_1.ParameterType.LITERAL) {
129
- const literal = choice.literal || '';
130
- if (literal.startsWith(currentPart)) {
131
- suggestions.push(literal);
132
- }
133
- }
134
- }
135
- }
136
- else if (param.type === commandTree_1.ParameterType.LITERAL) {
137
- const literal = param.literal || '';
138
- if (literal.startsWith(currentPart)) {
139
- suggestions.push(literal);
140
- }
141
- }
142
- // We only process the first parameter position
143
- break;
144
- }
145
- return suggestions.sort();
146
- }
147
- /**
148
- * Generate suggestions for the next parameter position
149
- * Must handle CHOICE_LIST parameters properly
150
- */
151
- function generateSuggestionsForNextPosition(parameters) {
152
- const suggestions = [];
153
- if (parameters.length === 0) {
154
- return suggestions;
155
- }
156
- const firstParam = parameters[0];
157
- if (firstParam.type === commandTree_1.ParameterType.SUBCOMMAND) {
158
- // Direct subcommand
159
- suggestions.push(firstParam.name || firstParam.literal || '');
160
- }
161
- else if (firstParam.type === commandTree_1.ParameterType.CHOICE_LIST && firstParam.choices) {
162
- // Choice list - add all choices as suggestions
163
- for (const choice of firstParam.choices) {
164
- if (choice.type === commandTree_1.ParameterType.SUBCOMMAND) {
165
- suggestions.push(choice.name || choice.literal || '');
166
- }
167
- else if (choice.type === commandTree_1.ParameterType.LITERAL) {
168
- suggestions.push(choice.literal || '');
169
- }
170
- }
171
- }
172
- else if (firstParam.type === commandTree_1.ParameterType.LITERAL) {
173
- suggestions.push(firstParam.literal || '');
174
- }
175
- // Don't suggest anything for ARGUMENT types
176
- return suggestions.sort();
177
- }
178
- /**
179
- * Build argument help string from parameters
180
- */
181
- function buildArgumentHelp(parameters) {
182
- if (parameters.length === 0) {
183
- return '';
184
- }
185
- return parameters.map(param => {
186
- if (param.type === commandTree_1.ParameterType.ARGUMENT) {
187
- return param.optional ? `[<${param.name}>]` : `<${param.name}>`;
188
- }
189
- else if (param.type === commandTree_1.ParameterType.CHOICE_LIST && param.choices) {
190
- const choices = param.choices.map((c) => c.literal).join('|');
191
- return `(${choices})`;
192
- }
193
- else if (param.type === commandTree_1.ParameterType.LITERAL) {
194
- return param.literal;
195
- }
196
- else if (param.type === commandTree_1.ParameterType.SUBCOMMAND) {
197
- return param.name; // Show subcommand name
198
- }
199
- return '';
200
- }).join(' ');
201
- }
202
- //# sourceMappingURL=commandSuggestions.js.map
@@ -1,97 +0,0 @@
1
- "use strict";
2
- // src/completionsBackend.ts
3
- //
4
- // The completionEngine state machine knows it needs completions and usage
5
- // text for a given input line — it doesn't know or care whether those come
6
- // from a network round trip or an in-memory lookup. A CompletionsBackend is
7
- // that boundary: one implementation asks the server-side TabComplete
8
- // plugin over RCON, the other asks the locally-built command tree.
9
- //
10
- // Driving both modes through the same backend interface (and therefore the
11
- // same engine, the same dispatch plumbing, and the same rendering) is what
12
- // lets RconSession be entirely mode-blind — "which backend" is decided once,
13
- // not branched on at every call site.
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.LocalCompletionsBackend = exports.RconCompletionsBackend = void 0;
16
- const completionEngine_1 = require("./completionEngine");
17
- /**
18
- * Server-side completions via the TabComplete plugin's `tabcomplete`/`cmdusage` commands.
19
- *
20
- * Takes a `controller` thunk rather than a fixed `RconController` — the
21
- * connection manager replaces its controller wholesale on every reconnect, so
22
- * capturing one instance up front would silently start sending through a dead
23
- * (disconnected) controller after the first reconnect, with every fetch
24
- * throwing "Not connected" (swallowed by the caller as "no completions").
25
- * Looking it up fresh on each call always reaches whatever's live.
26
- */
27
- class RconCompletionsBackend {
28
- getController;
29
- constructor(getController) {
30
- this.getController = getController;
31
- }
32
- async fetchCompletions(line) {
33
- const query = (0, completionEngine_1.buildCompletionsQuery)(line);
34
- if (query === null) {
35
- return [];
36
- }
37
- const response = await this.getController().send(`tabcomplete ${query}`);
38
- return (0, completionEngine_1.parseCompletionsResponse)(response ?? undefined);
39
- }
40
- async fetchUsage(line) {
41
- const query = (0, completionEngine_1.buildUsageQuery)(line);
42
- if (query === null) {
43
- return '';
44
- }
45
- const response = await this.getController().send(`cmdusage ${query}`);
46
- return (0, completionEngine_1.parseUsageResponse)(response ?? undefined);
47
- }
48
- }
49
- exports.RconCompletionsBackend = RconCompletionsBackend;
50
- /**
51
- * Local completions via the command tree LocalCommandTree builds at
52
- * startup — a synchronous in-memory lookup, wrapped in `async` so it flows
53
- * through exactly the same dispatch path as the RCON backend (the `await`
54
- * still yields to the microtask queue, so a `dispatchToEngine` call made from
55
- * here lands *after* the effect loop that triggered it has finished — same
56
- * ordering guarantee the real async backend gets for free from the network).
57
- */
58
- class LocalCompletionsBackend {
59
- commandTree;
60
- // getSuggestions sometimes returns incomplete argument help for inputs that
61
- // are mid-command (e.g. once the user is typing a free-form argument like a
62
- // player name, the locally-built tree may not have anything to say). Stick
63
- // with the first full version we saw for this command rather than flicker
64
- // between that and "(nothing)" — mirrors the original local-mode behavior.
65
- cachedCommand = null;
66
- cachedHelp = null;
67
- constructor(commandTree) {
68
- this.commandTree = commandTree;
69
- }
70
- async fetchCompletions(line) {
71
- return this.commandTree.getSuggestions(line).suggestions;
72
- }
73
- async fetchUsage(line) {
74
- const result = this.commandTree.getSuggestions(line);
75
- // Everything up to the first space, including any "namespace:" prefix -
76
- // \S rather than \w so "minecraft:clear" isn't truncated to "minecraft".
77
- const commandName = (line.match(/^\/?(\S+)/) || [])[1] || '';
78
- if (commandName !== this.cachedCommand) {
79
- this.cachedCommand = commandName;
80
- this.cachedHelp = null;
81
- }
82
- if (result.argumentHelp !== undefined) {
83
- // Match the shape of the server's `cmdusage` response (e.g. "clear
84
- // [<targets>] [<item>]") - formatArgumentHint derives the command
85
- // prefix from this leading commandPath, so it must be present even
86
- // when there's no argument help (e.g. "reload").
87
- const usage = result.argumentHelp ? `${result.commandPath} ${result.argumentHelp}` : result.commandPath;
88
- if (this.cachedHelp === null) {
89
- this.cachedHelp = usage;
90
- }
91
- return this.cachedHelp;
92
- }
93
- return this.cachedHelp ?? '';
94
- }
95
- }
96
- exports.LocalCompletionsBackend = LocalCompletionsBackend;
97
- //# sourceMappingURL=completionsBackend.js.map
@@ -1,209 +0,0 @@
1
- "use strict";
2
- // src/connectionManager.ts
3
- //
4
- // Owns the RCON connection lifecycle: the live `RconController` (recreated on
5
- // each reconnect attempt), connection/reconnection status, and the
6
- // exponential-backoff retry loop. Pulled out of RconTerminal as part of the
7
- // mega-module split — see lineEditor.ts / suggestionDisplay.ts for the sibling
8
- // extractions and their rationale.
9
- //
10
- // `RconSession` keeps `detectAndInitialize`/`initializeCommands` (they're
11
- // about the autocomplete command-tree and `pluginMode`, a different concern
12
- // that happens to run at similar times) but reads connection status and reaches
13
- // the live controller through this class, and is notified via `onReconnected`
14
- // when it should reload the command tree.
15
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- var desc = Object.getOwnPropertyDescriptor(m, k);
18
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
- desc = { enumerable: true, get: function() { return m[k]; } };
20
- }
21
- Object.defineProperty(o, k2, desc);
22
- }) : (function(o, m, k, k2) {
23
- if (k2 === undefined) k2 = k;
24
- o[k2] = m[k];
25
- }));
26
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
- Object.defineProperty(o, "default", { enumerable: true, value: v });
28
- }) : function(o, v) {
29
- o["default"] = v;
30
- });
31
- var __importStar = (this && this.__importStar) || (function () {
32
- var ownKeys = function(o) {
33
- ownKeys = Object.getOwnPropertyNames || function (o) {
34
- var ar = [];
35
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
- return ar;
37
- };
38
- return ownKeys(o);
39
- };
40
- return function (mod) {
41
- if (mod && mod.__esModule) return mod;
42
- var result = {};
43
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
- __setModuleDefault(result, mod);
45
- return result;
46
- };
47
- })();
48
- Object.defineProperty(exports, "__esModule", { value: true });
49
- exports.ConnectionManager = void 0;
50
- const rconClient_1 = require("./rconClient");
51
- const logger_1 = require("./logger");
52
- const ansi = __importStar(require("./ansi"));
53
- const defaultControllerFactory = (host, port, password, logger) => new rconClient_1.RconController(host, port, password, logger);
54
- class ConnectionManager {
55
- serverHost;
56
- serverPort;
57
- password;
58
- logger;
59
- host;
60
- controllerFactory;
61
- _controller;
62
- _isConnected = true;
63
- _isReconnecting = false;
64
- reconnectAttempts = 0;
65
- maxReconnectAttempts = 5;
66
- reconnectDelay = 2000;
67
- reconnectTimeout = null;
68
- constructor(serverHost, serverPort, password, logger, controller, host, controllerFactory = defaultControllerFactory) {
69
- this.serverHost = serverHost;
70
- this.serverPort = serverPort;
71
- this.password = password;
72
- this.logger = logger;
73
- this.host = host;
74
- this.controllerFactory = controllerFactory;
75
- this._controller = controller;
76
- }
77
- get controller() {
78
- return this._controller;
79
- }
80
- get isConnected() {
81
- return this._isConnected;
82
- }
83
- get isReconnecting() {
84
- return this._isReconnecting;
85
- }
86
- /** Resets the reconnect-attempt counter, backoff delay, and any pending reconnect timer back to their initial state. */
87
- resetReconnectState() {
88
- this.reconnectAttempts = 0;
89
- this.reconnectDelay = 2000;
90
- if (this.reconnectTimeout) {
91
- clearTimeout(this.reconnectTimeout);
92
- this.reconnectTimeout = null;
93
- }
94
- }
95
- /**
96
- * Records that an in-flight command discovered the connection is gone, and
97
- * schedules an auto-reconnect attempt shortly after. The "Connection
98
- * lost..." message itself stays with the caller (`executeCommand` accounts
99
- * for it in its output-line bookkeeping).
100
- */
101
- reportConnectionLost() {
102
- this._isConnected = false;
103
- this.resetReconnectState();
104
- this.reconnectTimeout = setTimeout(() => {
105
- this.reconnectTimeout = null;
106
- this.attemptReconnect();
107
- }, 1000);
108
- }
109
- disconnect() {
110
- // No key-chord echo here: this runs for the typed /disconnect built-in.
111
- // Ctrl+D echoes its own ^D in RconSession.handleCtrlD before closing.
112
- this.host.write('Disconnecting...\r\n');
113
- // Clear any pending reconnect
114
- if (this.reconnectTimeout) {
115
- clearTimeout(this.reconnectTimeout);
116
- this.reconnectTimeout = null;
117
- }
118
- try {
119
- this._controller.disconnect();
120
- }
121
- catch (err) {
122
- this.logger.error(`Error during disconnect: ${err}`);
123
- }
124
- this._isConnected = false;
125
- this._isReconnecting = false;
126
- this.host.write('Connection closed. Type ' + ansi.yellow('/reconnect') + ' to reconnect.\r\n\r\n');
127
- this.host.showPrompt();
128
- }
129
- async manualReconnect() {
130
- if (this._isReconnecting) {
131
- this.host.write(ansi.yellow('Already reconnecting...') + '\r\n\r\n');
132
- this.host.showPrompt();
133
- return;
134
- }
135
- this.resetReconnectState();
136
- await this.attemptReconnect();
137
- }
138
- async attemptReconnect() {
139
- if (this._isReconnecting) {
140
- return;
141
- }
142
- if (this._isConnected) {
143
- this.host.write(ansi.green('Already connected.') + '\r\n\r\n');
144
- this.host.showPrompt();
145
- return;
146
- }
147
- this._isReconnecting = true;
148
- this.reconnectAttempts++;
149
- const attemptText = this.reconnectAttempts > 1 ? ` (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})` : '';
150
- this.host.write(ansi.yellow('Reconnecting to ' + this.serverHost + ':' + this.serverPort + attemptText + '...') + '\r\n');
151
- try {
152
- // Disconnect existing controller
153
- try {
154
- await this._controller.disconnect();
155
- }
156
- catch (err) {
157
- // Ignore disconnect errors during reconnect
158
- }
159
- // Create new controller
160
- this._controller = this.controllerFactory(this.serverHost, this.serverPort, this.password, this.logger);
161
- await this._controller.connect();
162
- this._isConnected = true;
163
- this._isReconnecting = false;
164
- this.resetReconnectState();
165
- this.host.write(ansi.boldGreen('✓ Reconnected successfully!') + '\r\n\r\n');
166
- // Reload commands after reconnection
167
- this.host.onReconnected();
168
- }
169
- catch (err) {
170
- this._isReconnecting = false;
171
- if (this.reconnectAttempts < this.maxReconnectAttempts) {
172
- this.host.write(ansi.red('✗ Connection failed: ' + (0, logger_1.errorMessage)(err)) + '\r\n');
173
- this.host.write(ansi.yellow('Retrying in ' + (this.reconnectDelay / 1000) + ' seconds...') + '\r\n');
174
- // Clear any existing timeout
175
- if (this.reconnectTimeout) {
176
- clearTimeout(this.reconnectTimeout);
177
- }
178
- this.reconnectTimeout = setTimeout(() => {
179
- this.reconnectTimeout = null;
180
- this.attemptReconnect();
181
- }, this.reconnectDelay);
182
- // Exponential backoff with max delay
183
- this.reconnectDelay = Math.min(this.reconnectDelay * 2, 32000);
184
- }
185
- else {
186
- // Max attempts reached
187
- this.host.write(ansi.boldRed('✗ Reconnection failed after ' + this.maxReconnectAttempts + ' attempts.') + '\r\n');
188
- this.host.write('Type ' + ansi.yellow('/reconnect') + ' to try again.\r\n\r\n');
189
- this.resetReconnectState();
190
- this.host.showPrompt();
191
- }
192
- }
193
- }
194
- /** Tears down any pending reconnect timer and disconnects the controller — used by `RconSession.close()`. */
195
- dispose() {
196
- if (this.reconnectTimeout) {
197
- clearTimeout(this.reconnectTimeout);
198
- this.reconnectTimeout = null;
199
- }
200
- try {
201
- this._controller.disconnect();
202
- }
203
- catch (err) {
204
- this.logger.error(`Error during close: ${err}`);
205
- }
206
- }
207
- }
208
- exports.ConnectionManager = ConnectionManager;
209
- //# sourceMappingURL=connectionManager.js.map