Persistent connections even if client disconnects
This commit is contained in:
100
package-lock.json
generated
100
package-lock.json
generated
@@ -14,7 +14,6 @@
|
|||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"howler": "^2.2.4",
|
"howler": "^2.2.4",
|
||||||
"http-proxy-middleware": "^2.0.6",
|
|
||||||
"net": "^1.0.2",
|
"net": "^1.0.2",
|
||||||
"split.js": "^1.6.5",
|
"split.js": "^1.6.5",
|
||||||
"ws": "^8.18.1"
|
"ws": "^8.18.1"
|
||||||
@@ -3199,15 +3198,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/http-proxy": {
|
|
||||||
"version": "1.17.16",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz",
|
|
||||||
"integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.14.1",
|
"version": "22.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
|
||||||
@@ -3707,6 +3697,7 @@
|
|||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.1.1"
|
||||||
@@ -4959,12 +4950,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eventemitter3": {
|
|
||||||
"version": "4.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
|
||||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/events": {
|
"node_modules/events": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||||
@@ -5165,6 +5150,7 @@
|
|||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
@@ -5223,26 +5209,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||||
@@ -5690,44 +5656,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/http-proxy": {
|
|
||||||
"version": "1.18.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
|
||||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"eventemitter3": "^4.0.0",
|
|
||||||
"follow-redirects": "^1.0.0",
|
|
||||||
"requires-port": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/http-proxy-middleware": {
|
|
||||||
"version": "2.0.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
|
|
||||||
"integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/http-proxy": "^1.17.8",
|
|
||||||
"http-proxy": "^1.18.1",
|
|
||||||
"is-glob": "^4.0.1",
|
|
||||||
"is-plain-obj": "^3.0.0",
|
|
||||||
"micromatch": "^4.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/express": "^4.17.13"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/express": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/https-browserify": {
|
"node_modules/https-browserify": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
||||||
@@ -6004,6 +5932,7 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -6058,6 +5987,7 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@@ -6107,6 +6037,7 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
@@ -6139,18 +6070,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-plain-obj": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-reference": {
|
"node_modules/is-reference": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||||
@@ -6638,6 +6557,7 @@
|
|||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.3",
|
"braces": "^3.0.3",
|
||||||
@@ -7216,6 +7136,7 @@
|
|||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
@@ -7760,12 +7681,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/requires-port": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
@@ -9068,6 +8983,7 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
|
|||||||
@@ -184,6 +184,11 @@
|
|||||||
// Listen for sound play events
|
// Listen for sound play events
|
||||||
conn.on('playSound', handlePlaySound);
|
conn.on('playSound', handlePlaySound);
|
||||||
|
|
||||||
|
// Listen for message replay events
|
||||||
|
conn.on('session_resumed', handleSessionResumed);
|
||||||
|
conn.on('message_replay_start', handleMessageReplayStart);
|
||||||
|
conn.on('message_replay_complete', handleMessageReplayComplete);
|
||||||
|
|
||||||
console.log('Connection listeners attached successfully');
|
console.log('Connection listeners attached successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +205,9 @@
|
|||||||
conn.off('error', handleError);
|
conn.off('error', handleError);
|
||||||
conn.off('gmcp', handleGmcp);
|
conn.off('gmcp', handleGmcp);
|
||||||
conn.off('playSound', handlePlaySound);
|
conn.off('playSound', handlePlaySound);
|
||||||
|
conn.off('session_resumed', handleSessionResumed);
|
||||||
|
conn.off('message_replay_start', handleMessageReplayStart);
|
||||||
|
conn.off('message_replay_complete', handleMessageReplayComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -443,6 +451,44 @@
|
|||||||
console.log(`Play sound event for ${profileId}:`, options);
|
console.log(`Play sound event for ${profileId}:`, options);
|
||||||
dispatch('playSound', options);
|
dispatch('playSound', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle session resumed event
|
||||||
|
*/
|
||||||
|
function handleSessionResumed(data) {
|
||||||
|
console.log(`Session resumed for ${profileId}:`, data);
|
||||||
|
|
||||||
|
if (data.messagesReplayed > 0) {
|
||||||
|
// Add a system message to the output to notify the user
|
||||||
|
addToOutputHistory(`[SYSTEM] Reconnected to MUD. ${data.messagesReplayed} messages have been replayed.`, false);
|
||||||
|
} else {
|
||||||
|
addToOutputHistory('[SYSTEM] Reconnected to MUD.', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch('sessionResumed', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle message replay start event
|
||||||
|
*/
|
||||||
|
function handleMessageReplayStart(data) {
|
||||||
|
console.log(`Message replay starting for ${profileId}:`, data);
|
||||||
|
|
||||||
|
const timeAgo = Math.round(data.timespan / 1000);
|
||||||
|
const timeUnit = timeAgo > 60 ? `${Math.round(timeAgo / 60)} minutes` : `${timeAgo} seconds`;
|
||||||
|
|
||||||
|
addToOutputHistory(`[SYSTEM] Replaying ${data.messageCount} messages from the last ${timeUnit}...`, false);
|
||||||
|
dispatch('messageReplayStart', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle message replay complete event
|
||||||
|
*/
|
||||||
|
function handleMessageReplayComplete() {
|
||||||
|
console.log(`Message replay complete for ${profileId}`);
|
||||||
|
addToOutputHistory('[SYSTEM] Message replay complete. You are now up to date.', false);
|
||||||
|
dispatch('messageReplayComplete');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $connectionStatus[profileId]}
|
{#if $connectionStatus[profileId]}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { uiSettings, accessibilitySettings } from '$lib/stores/mudStore';
|
import { uiSettings, accessibilitySettings, connectionSettings } from '$lib/stores/mudStore';
|
||||||
import { settingsManager } from '$lib/settings/SettingsManager';
|
import { settingsManager } from '$lib/settings/SettingsManager';
|
||||||
import BackupPanel from './BackupPanel.svelte';
|
import BackupPanel from './BackupPanel.svelte';
|
||||||
|
|
||||||
@@ -146,6 +146,59 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h4>Connection</h4>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<span class="setting-name">Connection Persistence Timeout</span>
|
||||||
|
<div class="range-control">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="1"
|
||||||
|
max="60"
|
||||||
|
step="1"
|
||||||
|
bind:value={$connectionSettings.persistenceTimeoutMinutes}
|
||||||
|
>
|
||||||
|
<span class="range-value">{$connectionSettings.persistenceTimeoutMinutes} min</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-description">
|
||||||
|
How long to keep MUD connections alive when the app goes to the background (useful for mobile devices)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<span class="setting-name">Message Buffer Size</span>
|
||||||
|
<div class="range-control">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="50"
|
||||||
|
max="500"
|
||||||
|
step="25"
|
||||||
|
bind:value={$connectionSettings.maxBufferMessages}
|
||||||
|
>
|
||||||
|
<span class="range-value">{$connectionSettings.maxBufferMessages} messages</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-description">
|
||||||
|
Maximum number of messages to buffer while disconnected for replay on reconnection
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<span class="setting-name">Buffer Memory Limit</span>
|
||||||
|
<div class="range-control">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="5"
|
||||||
|
max="50"
|
||||||
|
step="5"
|
||||||
|
bind:value={$connectionSettings.maxBufferSizeKB}
|
||||||
|
>
|
||||||
|
<span class="range-value">{$connectionSettings.maxBufferSizeKB} KB</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-description">
|
||||||
|
Maximum memory to use for buffering messages (prevents excessive memory usage)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4>Debugging</h4>
|
<h4>Debugging</h4>
|
||||||
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { EventEmitter } from '$lib/utils/EventEmitter';
|
import { EventEmitter } from '$lib/utils/EventEmitter';
|
||||||
import { GmcpHandler } from '$lib/gmcp/GmcpHandler';
|
import { GmcpHandler } from '$lib/gmcp/GmcpHandler';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
import { connectionSettings } from '$lib/stores/mudStore';
|
||||||
|
|
||||||
// IAC codes for telnet negotiation
|
// IAC codes for telnet negotiation
|
||||||
enum TelnetCommand {
|
enum TelnetCommand {
|
||||||
@@ -130,6 +132,12 @@ export class MudConnection extends EventEmitter {
|
|||||||
console.log(`Reconnecting with session ID: ${this.persistence.sessionId}`);
|
console.log(`Reconnecting with session ID: ${this.persistence.sessionId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include connection settings in URL
|
||||||
|
const settings = get(connectionSettings);
|
||||||
|
wsUrl += `&persistenceTimeout=${settings.persistenceTimeoutMinutes}`;
|
||||||
|
wsUrl += `&maxBufferMessages=${settings.maxBufferMessages}`;
|
||||||
|
wsUrl += `&maxBufferSizeKB=${settings.maxBufferSizeKB}`;
|
||||||
|
|
||||||
console.log(`Connecting to WebSocket server: ${wsUrl}`);
|
console.log(`Connecting to WebSocket server: ${wsUrl}`);
|
||||||
|
|
||||||
this.webSocket = new WebSocket(wsUrl);
|
this.webSocket = new WebSocket(wsUrl);
|
||||||
@@ -241,7 +249,16 @@ export class MudConnection extends EventEmitter {
|
|||||||
// Handle other system messages as needed
|
// Handle other system messages as needed
|
||||||
if (systemData.type === 'session_resumed') {
|
if (systemData.type === 'session_resumed') {
|
||||||
console.log('Session successfully resumed');
|
console.log('Session successfully resumed');
|
||||||
this.emit('session_resumed');
|
if (systemData.messagesReplayed > 0) {
|
||||||
|
console.log(`${systemData.messagesReplayed} messages were replayed`);
|
||||||
|
}
|
||||||
|
this.emit('session_resumed', systemData);
|
||||||
|
} else if (systemData.type === 'message_replay_start') {
|
||||||
|
console.log(`Starting message replay: ${systemData.messageCount} messages from ${systemData.timespan}ms ago`);
|
||||||
|
this.emit('message_replay_start', systemData);
|
||||||
|
} else if (systemData.type === 'message_replay_complete') {
|
||||||
|
console.log('Message replay complete');
|
||||||
|
this.emit('message_replay_complete');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing system message:', error);
|
console.error('Error parsing system message:', error);
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ export interface Settings {
|
|||||||
debugGmcp: boolean;
|
debugGmcp: boolean;
|
||||||
globalVolume: number;
|
globalVolume: number;
|
||||||
};
|
};
|
||||||
|
connection: {
|
||||||
|
persistenceTimeoutMinutes: number;
|
||||||
|
maxBufferMessages: number;
|
||||||
|
maxBufferSizeKB: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SettingsManager extends EventEmitter {
|
export class SettingsManager extends EventEmitter {
|
||||||
@@ -35,6 +40,7 @@ export class SettingsManager extends EventEmitter {
|
|||||||
// Create our own stores rather than depending on the ones from mudStore
|
// Create our own stores rather than depending on the ones from mudStore
|
||||||
public accessibilitySettings: Writable<Settings['accessibility']>;
|
public accessibilitySettings: Writable<Settings['accessibility']>;
|
||||||
public uiSettings: Writable<Settings['ui']>;
|
public uiSettings: Writable<Settings['ui']>;
|
||||||
|
public connectionSettings: Writable<Settings['connection']>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -63,12 +69,18 @@ export class SettingsManager extends EventEmitter {
|
|||||||
font: 'monospace',
|
font: 'monospace',
|
||||||
debugGmcp: false,
|
debugGmcp: false,
|
||||||
globalVolume: 0.7
|
globalVolume: 0.7
|
||||||
|
},
|
||||||
|
connection: {
|
||||||
|
persistenceTimeoutMinutes: 5,
|
||||||
|
maxBufferMessages: 100,
|
||||||
|
maxBufferSizeKB: 10
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the stores with default values
|
// Create the stores with default values
|
||||||
this.accessibilitySettings = writable(this.settings.accessibility);
|
this.accessibilitySettings = writable(this.settings.accessibility);
|
||||||
this.uiSettings = writable(this.settings.ui);
|
this.uiSettings = writable(this.settings.ui);
|
||||||
|
this.connectionSettings = writable(this.settings.connection);
|
||||||
|
|
||||||
// Set up subscriptions to save settings when they change
|
// Set up subscriptions to save settings when they change
|
||||||
this.accessibilitySettings.subscribe(value => {
|
this.accessibilitySettings.subscribe(value => {
|
||||||
@@ -91,6 +103,16 @@ export class SettingsManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.connectionSettings.subscribe(value => {
|
||||||
|
// Skip during initialization
|
||||||
|
if (!this.initialized) return;
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
// Use a small timeout to batch multiple rapid changes
|
||||||
|
setTimeout(() => this.saveSettings(), 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Load settings from storage
|
// Load settings from storage
|
||||||
this.loadSettings();
|
this.loadSettings();
|
||||||
}
|
}
|
||||||
@@ -123,6 +145,10 @@ export class SettingsManager extends EventEmitter {
|
|||||||
ui: {
|
ui: {
|
||||||
...this.settings.ui,
|
...this.settings.ui,
|
||||||
...(parsedSettings.ui || {})
|
...(parsedSettings.ui || {})
|
||||||
|
},
|
||||||
|
connection: {
|
||||||
|
...this.settings.connection,
|
||||||
|
...(parsedSettings.connection || {})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,6 +157,7 @@ export class SettingsManager extends EventEmitter {
|
|||||||
// Set the stores without triggering the save callback
|
// Set the stores without triggering the save callback
|
||||||
this.accessibilitySettings.set(this.settings.accessibility);
|
this.accessibilitySettings.set(this.settings.accessibility);
|
||||||
this.uiSettings.set(this.settings.ui);
|
this.uiSettings.set(this.settings.ui);
|
||||||
|
this.connectionSettings.set(this.settings.connection);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Invalid settings format found in localStorage');
|
console.warn('Invalid settings format found in localStorage');
|
||||||
}
|
}
|
||||||
@@ -158,6 +185,7 @@ export class SettingsManager extends EventEmitter {
|
|||||||
// Get current values from stores
|
// Get current values from stores
|
||||||
this.settings.accessibility = get(this.accessibilitySettings);
|
this.settings.accessibility = get(this.accessibilitySettings);
|
||||||
this.settings.ui = get(this.uiSettings);
|
this.settings.ui = get(this.uiSettings);
|
||||||
|
this.settings.connection = get(this.connectionSettings);
|
||||||
|
|
||||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.settings));
|
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.settings));
|
||||||
console.log('Saved settings to localStorage');
|
console.log('Saved settings to localStorage');
|
||||||
@@ -177,6 +205,10 @@ export class SettingsManager extends EventEmitter {
|
|||||||
this.uiSettings.update(current => ({...current, ...newSettings}));
|
this.uiSettings.update(current => ({...current, ...newSettings}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateConnectionSettings(newSettings: Partial<Settings['connection']>): void {
|
||||||
|
this.connectionSettings.update(current => ({...current, ...newSettings}));
|
||||||
|
}
|
||||||
|
|
||||||
// Reset settings to defaults
|
// Reset settings to defaults
|
||||||
public resetSettings(): void {
|
public resetSettings(): void {
|
||||||
const defaults = {
|
const defaults = {
|
||||||
@@ -202,11 +234,17 @@ export class SettingsManager extends EventEmitter {
|
|||||||
font: 'monospace',
|
font: 'monospace',
|
||||||
debugGmcp: false,
|
debugGmcp: false,
|
||||||
globalVolume: 0.7
|
globalVolume: 0.7
|
||||||
|
},
|
||||||
|
connection: {
|
||||||
|
persistenceTimeoutMinutes: 5,
|
||||||
|
maxBufferMessages: 100,
|
||||||
|
maxBufferSizeKB: 10
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.accessibilitySettings.set(defaults.accessibility);
|
this.accessibilitySettings.set(defaults.accessibility);
|
||||||
this.uiSettings.set(defaults.ui);
|
this.uiSettings.set(defaults.ui);
|
||||||
|
this.connectionSettings.set(defaults.connection);
|
||||||
this.emit('settingsReset', defaults);
|
this.emit('settingsReset', defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const connectionStatus = writable<{ [key: string]: 'connected' | 'disconn
|
|||||||
// Use the stores from SettingsManager
|
// Use the stores from SettingsManager
|
||||||
export const accessibilitySettings = settingsManager.accessibilitySettings;
|
export const accessibilitySettings = settingsManager.accessibilitySettings;
|
||||||
export const uiSettings = settingsManager.uiSettings;
|
export const uiSettings = settingsManager.uiSettings;
|
||||||
|
export const connectionSettings = settingsManager.connectionSettings;
|
||||||
|
|
||||||
// Store for input history - keyed by profile ID
|
// Store for input history - keyed by profile ID
|
||||||
export const inputHistory = writable<{ [profileId: string]: string[] }>({});
|
export const inputHistory = writable<{ [profileId: string]: string[] }>({});
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import * as tls from 'tls';
|
|||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { parse } from 'url';
|
import { parse } from 'url';
|
||||||
|
|
||||||
// Configuration for connection persistence
|
// Default configuration for connection persistence (fallback values)
|
||||||
const CONNECTION_PERSISTENCE_TIMEOUT = 5 * 60 * 1000; // 5 minutes in milliseconds
|
const DEFAULT_PERSISTENCE_TIMEOUT = 5 * 60 * 1000; // 5 minutes in milliseconds
|
||||||
|
const DEFAULT_MAX_BUFFER_MESSAGES = 100; // Maximum number of messages to buffer
|
||||||
|
const DEFAULT_MAX_BUFFER_SIZE_KB = 10; // Maximum buffer size in KB
|
||||||
|
|
||||||
const HEARTBEAT_INTERVAL = 30 * 1000; // 30 seconds
|
const HEARTBEAT_INTERVAL = 30 * 1000; // 30 seconds
|
||||||
|
|
||||||
// Create HTTP server
|
// Create HTTP server
|
||||||
@@ -15,17 +18,113 @@ const server = http.createServer();
|
|||||||
const wss = new WebSocketServer({ noServer: true });
|
const wss = new WebSocketServer({ noServer: true });
|
||||||
|
|
||||||
// Active connections and their proxies
|
// Active connections and their proxies
|
||||||
|
// Key: connectionId, Value: { ws, socket, sessionId, settings }
|
||||||
const connections = new Map();
|
const connections = new Map();
|
||||||
|
|
||||||
// Persistent connections waiting for reconnection
|
// Persistent connections waiting for reconnection
|
||||||
// Key: sessionId, Value: { socket, mudHost, mudPort, useSSL, timeoutId, lastActivity }
|
// Key: sessionId, Value: { socket, mudHost, mudPort, useSSL, timeoutId, lastActivity, messageBuffer, settings }
|
||||||
const persistentConnections = new Map();
|
const persistentConnections = new Map();
|
||||||
|
|
||||||
|
// Parse connection settings from URL parameters with defaults
|
||||||
|
function parseConnectionSettings(urlParts) {
|
||||||
|
const persistenceTimeoutParam = urlParts.searchParams.get('persistenceTimeout');
|
||||||
|
const maxBufferMessagesParam = urlParts.searchParams.get('maxBufferMessages');
|
||||||
|
const maxBufferSizeKBParam = urlParts.searchParams.get('maxBufferSizeKB');
|
||||||
|
|
||||||
|
return {
|
||||||
|
persistenceTimeoutMs: persistenceTimeoutParam ?
|
||||||
|
parseInt(persistenceTimeoutParam) * 60 * 1000 : // Convert minutes to milliseconds
|
||||||
|
DEFAULT_PERSISTENCE_TIMEOUT,
|
||||||
|
maxBufferMessages: maxBufferMessagesParam ?
|
||||||
|
parseInt(maxBufferMessagesParam) :
|
||||||
|
DEFAULT_MAX_BUFFER_MESSAGES,
|
||||||
|
maxBufferSizeKB: maxBufferSizeKBParam ?
|
||||||
|
parseInt(maxBufferSizeKBParam) :
|
||||||
|
DEFAULT_MAX_BUFFER_SIZE_KB
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Generate a unique session ID for persistent connections
|
// Generate a unique session ID for persistent connections
|
||||||
function generateSessionId() {
|
function generateSessionId() {
|
||||||
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Buffer a message for a persistent connection
|
||||||
|
function bufferMessage(sessionId, data) {
|
||||||
|
const persistentConn = persistentConnections.get(sessionId);
|
||||||
|
if (!persistentConn) {
|
||||||
|
return; // No persistent connection to buffer for
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!persistentConn.messageBuffer) {
|
||||||
|
persistentConn.messageBuffer = [];
|
||||||
|
persistentConn.bufferSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add timestamp to the message
|
||||||
|
const bufferedMessage = {
|
||||||
|
data: data,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
persistentConn.messageBuffer.push(bufferedMessage);
|
||||||
|
persistentConn.bufferSize += data.length;
|
||||||
|
|
||||||
|
// Use this connection's specific settings for buffer limits
|
||||||
|
const settings = persistentConn.settings || {
|
||||||
|
maxBufferMessages: DEFAULT_MAX_BUFFER_MESSAGES,
|
||||||
|
maxBufferSizeKB: DEFAULT_MAX_BUFFER_SIZE_KB
|
||||||
|
};
|
||||||
|
|
||||||
|
// Trim buffer if it gets too large
|
||||||
|
while (persistentConn.messageBuffer.length > settings.maxBufferMessages ||
|
||||||
|
persistentConn.bufferSize > settings.maxBufferSizeKB * 1000) {
|
||||||
|
const removed = persistentConn.messageBuffer.shift();
|
||||||
|
if (removed) {
|
||||||
|
persistentConn.bufferSize -= removed.data.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Buffered ${data.length} bytes for session ${sessionId} (${persistentConn.messageBuffer.length} messages, ${persistentConn.bufferSize} bytes total, limits: ${settings.maxBufferMessages} msgs/${settings.maxBufferSizeKB}KB)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay buffered messages to a reconnected client
|
||||||
|
function replayBufferedMessages(ws, sessionId) {
|
||||||
|
const persistentConn = persistentConnections.get(sessionId);
|
||||||
|
if (!persistentConn || !persistentConn.messageBuffer) {
|
||||||
|
return 0; // No messages to replay
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = persistentConn.messageBuffer;
|
||||||
|
console.log(`Replaying ${messages.length} buffered messages for session ${sessionId}`);
|
||||||
|
|
||||||
|
// Send a notification about message replay
|
||||||
|
const replayNotification = `[SYSTEM]${JSON.stringify({
|
||||||
|
type: 'message_replay_start',
|
||||||
|
messageCount: messages.length,
|
||||||
|
timespan: messages.length > 0 ? Date.now() - messages[0].timestamp : 0
|
||||||
|
})}`;
|
||||||
|
ws.send(replayNotification);
|
||||||
|
|
||||||
|
// Send all buffered messages
|
||||||
|
for (const message of messages) {
|
||||||
|
if (ws.readyState === 1) { // WebSocket.OPEN
|
||||||
|
ws.send(message.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send replay complete notification
|
||||||
|
const replayComplete = `[SYSTEM]${JSON.stringify({ type: 'message_replay_complete' })}`;
|
||||||
|
ws.send(replayComplete);
|
||||||
|
|
||||||
|
// Clear the buffer since messages have been replayed
|
||||||
|
const messageCount = messages.length;
|
||||||
|
persistentConn.messageBuffer = [];
|
||||||
|
persistentConn.bufferSize = 0;
|
||||||
|
|
||||||
|
return messageCount;
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up a persistent connection
|
// Clean up a persistent connection
|
||||||
function cleanupPersistentConnection(sessionId) {
|
function cleanupPersistentConnection(sessionId) {
|
||||||
const persistentConn = persistentConnections.get(sessionId);
|
const persistentConn = persistentConnections.get(sessionId);
|
||||||
@@ -54,11 +153,16 @@ wss.on('connection', (ws, req, mudHost, mudPort, useSSL) => {
|
|||||||
// Create a unique ID for this connection
|
// Create a unique ID for this connection
|
||||||
const connectionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
const connectionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
|
||||||
// Check for session ID in query parameters for reconnection
|
// Check for session ID and settings in query parameters
|
||||||
const url = req.url || '';
|
const url = req.url || '';
|
||||||
const urlParts = new URL(`http://localhost${url}`);
|
const urlParts = new URL(`http://localhost${url}`);
|
||||||
const sessionId = urlParts.searchParams.get('sessionId');
|
const sessionId = urlParts.searchParams.get('sessionId');
|
||||||
|
|
||||||
|
// Parse connection settings for this specific connection
|
||||||
|
const connectionSettings = parseConnectionSettings(urlParts);
|
||||||
|
|
||||||
|
console.log(`Connection settings for ${connectionId}: timeout=${connectionSettings.persistenceTimeoutMs/1000/60}min, maxMessages=${connectionSettings.maxBufferMessages}, maxSizeKB=${connectionSettings.maxBufferSizeKB}`);
|
||||||
|
|
||||||
// Special handling for test connections
|
// Special handling for test connections
|
||||||
if (mudHost === 'example.com' && mudPort === '23') {
|
if (mudHost === 'example.com' && mudPort === '23') {
|
||||||
console.log('Test connection detected - using echo server mode');
|
console.log('Test connection detected - using echo server mode');
|
||||||
@@ -98,11 +202,18 @@ wss.on('connection', (ws, req, mudHost, mudPort, useSSL) => {
|
|||||||
clearTimeout(persistentConn.timeoutId);
|
clearTimeout(persistentConn.timeoutId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from persistent connections (now active again)
|
// Replay any buffered messages first
|
||||||
|
const replayedCount = replayBufferedMessages(ws, sessionId);
|
||||||
|
|
||||||
|
// Remove from persistent connections (now active again) - do this after replay
|
||||||
persistentConnections.delete(sessionId);
|
persistentConnections.delete(sessionId);
|
||||||
|
|
||||||
// Send reconnection notification with session ID in proper JSON format
|
// Send reconnection notification with session ID in proper JSON format
|
||||||
ws.send(`[SYSTEM]${JSON.stringify({ type: 'session_resumed', sessionId: sessionId })}`);
|
ws.send(`[SYSTEM]${JSON.stringify({
|
||||||
|
type: 'session_resumed',
|
||||||
|
sessionId: sessionId,
|
||||||
|
messagesReplayed: replayedCount
|
||||||
|
})}`);
|
||||||
} else {
|
} else {
|
||||||
// Create new connection
|
// Create new connection
|
||||||
currentSessionId = generateSessionId();
|
currentSessionId = generateSessionId();
|
||||||
@@ -142,8 +253,13 @@ wss.on('connection', (ws, req, mudHost, mudPort, useSSL) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the connection
|
// Store the connection with its settings
|
||||||
connections.set(connectionId, { ws, socket, sessionId: currentSessionId });
|
connections.set(connectionId, {
|
||||||
|
ws,
|
||||||
|
socket,
|
||||||
|
sessionId: currentSessionId,
|
||||||
|
settings: connectionSettings
|
||||||
|
});
|
||||||
|
|
||||||
// Handle data from the MUD server - only in regular mode, not test mode
|
// Handle data from the MUD server - only in regular mode, not test mode
|
||||||
if (socket) {
|
if (socket) {
|
||||||
@@ -163,6 +279,11 @@ wss.on('connection', (ws, req, mudHost, mudPort, useSSL) => {
|
|||||||
if (ws.readyState === 1) { // WebSocket.OPEN
|
if (ws.readyState === 1) { // WebSocket.OPEN
|
||||||
ws.send(data);
|
ws.send(data);
|
||||||
console.log(`WebSocket server: Sent ${data.length} bytes to client${isGmcp ? ' (contains GMCP data)' : ''}`);
|
console.log(`WebSocket server: Sent ${data.length} bytes to client${isGmcp ? ' (contains GMCP data)' : ''}`);
|
||||||
|
} else {
|
||||||
|
// WebSocket is not open, buffer the message if we have a session
|
||||||
|
if (currentSessionId) {
|
||||||
|
bufferMessage(currentSessionId, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -276,13 +397,14 @@ wss.on('connection', (ws, req, mudHost, mudPort, useSSL) => {
|
|||||||
|
|
||||||
const conn = connections.get(connectionId);
|
const conn = connections.get(connectionId);
|
||||||
if (conn && !conn.testMode && conn.socket && !conn.socket.destroyed) {
|
if (conn && !conn.testMode && conn.socket && !conn.socket.destroyed) {
|
||||||
console.log(`Moving connection to persistent state for ${CONNECTION_PERSISTENCE_TIMEOUT / 1000} seconds`);
|
console.log(`Moving connection to persistent state for ${conn.settings.persistenceTimeoutMs / 1000} seconds`);
|
||||||
|
|
||||||
// Move the connection to persistent storage instead of closing it
|
// Move the connection to persistent storage instead of closing it
|
||||||
|
// Use this connection's specific timeout setting
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
console.log(`Session ${currentSessionId} timed out, closing MUD connection`);
|
console.log(`Session ${currentSessionId} timed out, closing MUD connection`);
|
||||||
cleanupPersistentConnection(currentSessionId);
|
cleanupPersistentConnection(currentSessionId);
|
||||||
}, CONNECTION_PERSISTENCE_TIMEOUT);
|
}, conn.settings.persistenceTimeoutMs);
|
||||||
|
|
||||||
persistentConnections.set(currentSessionId, {
|
persistentConnections.set(currentSessionId, {
|
||||||
socket: conn.socket,
|
socket: conn.socket,
|
||||||
@@ -290,10 +412,13 @@ wss.on('connection', (ws, req, mudHost, mudPort, useSSL) => {
|
|||||||
mudPort,
|
mudPort,
|
||||||
useSSL,
|
useSSL,
|
||||||
timeoutId,
|
timeoutId,
|
||||||
lastActivity: Date.now()
|
lastActivity: Date.now(),
|
||||||
|
messageBuffer: [],
|
||||||
|
bufferSize: 0,
|
||||||
|
settings: conn.settings // Store the connection's settings
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Session ${currentSessionId} will persist for ${CONNECTION_PERSISTENCE_TIMEOUT / 1000} seconds`);
|
console.log(`Session ${currentSessionId} will persist for ${conn.settings.persistenceTimeoutMs / 1000} seconds with settings: ${conn.settings.maxBufferMessages} msgs/${conn.settings.maxBufferSizeKB}KB`);
|
||||||
} else if (conn && conn.socket) {
|
} else if (conn && conn.socket) {
|
||||||
// Fallback to immediate cleanup if needed
|
// Fallback to immediate cleanup if needed
|
||||||
conn.socket.end();
|
conn.socket.end();
|
||||||
@@ -347,18 +472,20 @@ setInterval(() => {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
for (const [sessionId, persistentConn] of persistentConnections.entries()) {
|
for (const [sessionId, persistentConn] of persistentConnections.entries()) {
|
||||||
// Clean up connections that have been inactive for too long
|
// Clean up connections that have been inactive for too long
|
||||||
if (now - persistentConn.lastActivity > CONNECTION_PERSISTENCE_TIMEOUT * 2) {
|
// Use double the connection's specific timeout or default
|
||||||
|
const timeoutThreshold = (persistentConn.settings?.persistenceTimeoutMs || DEFAULT_PERSISTENCE_TIMEOUT) * 2;
|
||||||
|
if (now - persistentConn.lastActivity > timeoutThreshold) {
|
||||||
console.log(`Cleaning up abandoned session: ${sessionId}`);
|
console.log(`Cleaning up abandoned session: ${sessionId}`);
|
||||||
cleanupPersistentConnection(sessionId);
|
cleanupPersistentConnection(sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, CONNECTION_PERSISTENCE_TIMEOUT);
|
}, DEFAULT_PERSISTENCE_TIMEOUT); // Run cleanup every default timeout period
|
||||||
|
|
||||||
// Start the WebSocket server
|
// Start the WebSocket server
|
||||||
const PORT = process.env.WS_PORT || 3001;
|
const PORT = process.env.WS_PORT || 3001;
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log(`WebSocket server is running on port ${PORT}`);
|
console.log(`WebSocket server is running on port ${PORT}`);
|
||||||
console.log(`Connection persistence timeout: ${CONNECTION_PERSISTENCE_TIMEOUT / 1000} seconds`);
|
console.log(`Default connection persistence timeout: ${DEFAULT_PERSISTENCE_TIMEOUT / 1000} seconds (configurable per connection)`);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default server;
|
export default server;
|
||||||
Reference in New Issue
Block a user