More persistent connection stuff
This commit is contained in:
@@ -31,6 +31,17 @@ interface ConnectionPersistence {
|
||||
lastDisconnectTime?: number;
|
||||
}
|
||||
|
||||
// Stored session data in localStorage
|
||||
interface StoredSessionData {
|
||||
sessionId: string;
|
||||
profileId: string;
|
||||
host: string;
|
||||
port: number;
|
||||
useSSL: boolean;
|
||||
lastActivity: number;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* MudConnection - Handles a single connection to a MUD server
|
||||
* Each instance has its own GMCP handler and maintains its own state
|
||||
@@ -71,6 +82,9 @@ export class MudConnection extends EventEmitter {
|
||||
// Set up GMCP event forwarding
|
||||
this.setupGmcpEvents();
|
||||
|
||||
// Try to restore session from localStorage
|
||||
this.loadStoredSession();
|
||||
|
||||
console.log(`MudConnection created for ${this.host}:${this.port} with ID ${this.id}`);
|
||||
}
|
||||
|
||||
@@ -149,6 +163,9 @@ export class MudConnection extends EventEmitter {
|
||||
console.log(`Connected to ${this.host}:${this.port}`);
|
||||
this.emit('connected');
|
||||
|
||||
// Update stored session activity
|
||||
this.updateStoredSessionActivity();
|
||||
|
||||
// Send GMCP negotiation upon connection
|
||||
console.log('Sending GMCP negotiation');
|
||||
this.sendIAC(TelnetCommand.WILL, TelnetCommand.GMCP);
|
||||
@@ -182,6 +199,7 @@ export class MudConnection extends EventEmitter {
|
||||
} else {
|
||||
// Text data - let listeners process it directly
|
||||
// TriggerSystem will handle gagging and replacing in the component
|
||||
this.updateStoredSessionActivity();
|
||||
this.emit('received', event.data);
|
||||
}
|
||||
} else if (event.data instanceof Blob) {
|
||||
@@ -220,6 +238,9 @@ export class MudConnection extends EventEmitter {
|
||||
const data = new TextEncoder().encode(text + '\n');
|
||||
this.webSocket.send(data);
|
||||
|
||||
// Update stored session activity on send
|
||||
this.updateStoredSessionActivity();
|
||||
|
||||
// Emit the data for possible triggers
|
||||
this.emit('sent', text);
|
||||
} catch (error) {
|
||||
@@ -243,6 +264,7 @@ export class MudConnection extends EventEmitter {
|
||||
// Handle session ID updates
|
||||
if (systemData.sessionId) {
|
||||
this.persistence.sessionId = systemData.sessionId;
|
||||
this.saveSessionToStorage();
|
||||
console.log('Updated session ID:', this.persistence.sessionId);
|
||||
}
|
||||
|
||||
@@ -289,6 +311,9 @@ export class MudConnection extends EventEmitter {
|
||||
this.persistence.sessionId = undefined;
|
||||
this.persistence.reconnectAttempts = 0;
|
||||
|
||||
// Remove stored session from localStorage
|
||||
this.clearStoredSession();
|
||||
|
||||
// Clear reconnect timeout if active
|
||||
if (this.reconnectTimeoutId !== null) {
|
||||
clearTimeout(this.reconnectTimeoutId);
|
||||
@@ -544,4 +569,152 @@ export class MudConnection extends EventEmitter {
|
||||
this.reconnectTimeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current session to localStorage
|
||||
*/
|
||||
private saveSessionToStorage(): void {
|
||||
if (!this.persistence.sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionData: StoredSessionData = {
|
||||
sessionId: this.persistence.sessionId,
|
||||
profileId: this.id,
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
useSSL: this.useSSL,
|
||||
lastActivity: Date.now(),
|
||||
createdAt: Date.now()
|
||||
};
|
||||
|
||||
try {
|
||||
const storageKey = `mudSession_${this.id}`;
|
||||
localStorage.setItem(storageKey, JSON.stringify(sessionData));
|
||||
console.log(`Saved session ${this.persistence.sessionId} to localStorage for profile ${this.id}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to save session to localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load stored session from localStorage
|
||||
*/
|
||||
private loadStoredSession(): void {
|
||||
try {
|
||||
const storageKey = `mudSession_${this.id}`;
|
||||
const storedData = localStorage.getItem(storageKey);
|
||||
|
||||
if (!storedData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionData: StoredSessionData = JSON.parse(storedData);
|
||||
|
||||
// Validate that the stored session matches this connection
|
||||
if (sessionData.profileId === this.id &&
|
||||
sessionData.host === this.host &&
|
||||
sessionData.port === this.port &&
|
||||
sessionData.useSSL === this.useSSL) {
|
||||
|
||||
// Check if the session is still within a reasonable timeframe
|
||||
const maxAge = 60 * 60 * 1000; // 1 hour max age
|
||||
const age = Date.now() - sessionData.lastActivity;
|
||||
|
||||
if (age <= maxAge) {
|
||||
this.persistence.sessionId = sessionData.sessionId;
|
||||
console.log(`Restored session ${sessionData.sessionId} from localStorage for profile ${this.id} (age: ${Math.round(age/1000)}s)`);
|
||||
} else {
|
||||
console.log(`Stored session for profile ${this.id} is too old (${Math.round(age/1000)}s), discarding`);
|
||||
this.clearStoredSession();
|
||||
}
|
||||
} else {
|
||||
console.log(`Stored session for profile ${this.id} doesn't match current connection parameters, discarding`);
|
||||
this.clearStoredSession();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load session from localStorage:', error);
|
||||
this.clearStoredSession();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear stored session from localStorage
|
||||
*/
|
||||
private clearStoredSession(): void {
|
||||
try {
|
||||
const storageKey = `mudSession_${this.id}`;
|
||||
localStorage.removeItem(storageKey);
|
||||
console.log(`Cleared stored session for profile ${this.id}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to clear stored session:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update last activity timestamp in stored session
|
||||
*/
|
||||
private updateStoredSessionActivity(): void {
|
||||
if (!this.persistence.sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const storageKey = `mudSession_${this.id}`;
|
||||
const storedData = localStorage.getItem(storageKey);
|
||||
|
||||
if (storedData) {
|
||||
const sessionData: StoredSessionData = JSON.parse(storedData);
|
||||
sessionData.lastActivity = Date.now();
|
||||
localStorage.setItem(storageKey, JSON.stringify(sessionData));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update stored session activity:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old stored sessions from localStorage (static method)
|
||||
*/
|
||||
public static cleanupOldStoredSessions(): void {
|
||||
try {
|
||||
const maxAge = 60 * 60 * 1000; // 1 hour
|
||||
const now = Date.now();
|
||||
const keysToRemove: string[] = [];
|
||||
|
||||
// Iterate through all localStorage keys
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key && key.startsWith('mudSession_')) {
|
||||
try {
|
||||
const storedData = localStorage.getItem(key);
|
||||
if (storedData) {
|
||||
const sessionData: StoredSessionData = JSON.parse(storedData);
|
||||
const age = now - sessionData.lastActivity;
|
||||
|
||||
if (age > maxAge) {
|
||||
keysToRemove.push(key);
|
||||
console.log(`Marking old session for cleanup: ${key} (age: ${Math.round(age/1000)}s)`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// If we can't parse the session data, remove it
|
||||
keysToRemove.push(key);
|
||||
console.log(`Marking corrupted session for cleanup: ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove old sessions
|
||||
for (const key of keysToRemove) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
if (keysToRemove.length > 0) {
|
||||
console.log(`Cleaned up ${keysToRemove.length} old stored sessions`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to cleanup old stored sessions:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
import { AccessibilityManager } from '$lib/accessibility/AccessibilityManager';
|
||||
import { settingsManager } from '$lib/settings/SettingsManager';
|
||||
import { shortcutManager } from '$lib/utils/KeyboardShortcutManager';
|
||||
import { MudConnection } from '$lib/connection/MudConnection';
|
||||
import PwaUpdater from '$lib/components/PwaUpdater.svelte';
|
||||
import '../app.css';
|
||||
|
||||
@@ -156,6 +157,9 @@
|
||||
// Only execute browser-specific code in browser environment
|
||||
if (!isBrowser) return;
|
||||
|
||||
// Clean up old stored sessions on app startup
|
||||
MudConnection.cleanupOldStoredSessions();
|
||||
|
||||
profileManager = new ProfileManager();
|
||||
|
||||
// Settings manager is initialized as a singleton when imported
|
||||
|
||||
Reference in New Issue
Block a user