More persistent connection stuff
This commit is contained in:
@@ -31,6 +31,17 @@ interface ConnectionPersistence {
|
|||||||
lastDisconnectTime?: number;
|
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
|
* MudConnection - Handles a single connection to a MUD server
|
||||||
* Each instance has its own GMCP handler and maintains its own state
|
* 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
|
// Set up GMCP event forwarding
|
||||||
this.setupGmcpEvents();
|
this.setupGmcpEvents();
|
||||||
|
|
||||||
|
// Try to restore session from localStorage
|
||||||
|
this.loadStoredSession();
|
||||||
|
|
||||||
console.log(`MudConnection created for ${this.host}:${this.port} with ID ${this.id}`);
|
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}`);
|
console.log(`Connected to ${this.host}:${this.port}`);
|
||||||
this.emit('connected');
|
this.emit('connected');
|
||||||
|
|
||||||
|
// Update stored session activity
|
||||||
|
this.updateStoredSessionActivity();
|
||||||
|
|
||||||
// Send GMCP negotiation upon connection
|
// Send GMCP negotiation upon connection
|
||||||
console.log('Sending GMCP negotiation');
|
console.log('Sending GMCP negotiation');
|
||||||
this.sendIAC(TelnetCommand.WILL, TelnetCommand.GMCP);
|
this.sendIAC(TelnetCommand.WILL, TelnetCommand.GMCP);
|
||||||
@@ -182,6 +199,7 @@ export class MudConnection extends EventEmitter {
|
|||||||
} else {
|
} else {
|
||||||
// Text data - let listeners process it directly
|
// Text data - let listeners process it directly
|
||||||
// TriggerSystem will handle gagging and replacing in the component
|
// TriggerSystem will handle gagging and replacing in the component
|
||||||
|
this.updateStoredSessionActivity();
|
||||||
this.emit('received', event.data);
|
this.emit('received', event.data);
|
||||||
}
|
}
|
||||||
} else if (event.data instanceof Blob) {
|
} else if (event.data instanceof Blob) {
|
||||||
@@ -220,6 +238,9 @@ export class MudConnection extends EventEmitter {
|
|||||||
const data = new TextEncoder().encode(text + '\n');
|
const data = new TextEncoder().encode(text + '\n');
|
||||||
this.webSocket.send(data);
|
this.webSocket.send(data);
|
||||||
|
|
||||||
|
// Update stored session activity on send
|
||||||
|
this.updateStoredSessionActivity();
|
||||||
|
|
||||||
// Emit the data for possible triggers
|
// Emit the data for possible triggers
|
||||||
this.emit('sent', text);
|
this.emit('sent', text);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -243,6 +264,7 @@ export class MudConnection extends EventEmitter {
|
|||||||
// Handle session ID updates
|
// Handle session ID updates
|
||||||
if (systemData.sessionId) {
|
if (systemData.sessionId) {
|
||||||
this.persistence.sessionId = systemData.sessionId;
|
this.persistence.sessionId = systemData.sessionId;
|
||||||
|
this.saveSessionToStorage();
|
||||||
console.log('Updated session ID:', this.persistence.sessionId);
|
console.log('Updated session ID:', this.persistence.sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,6 +311,9 @@ export class MudConnection extends EventEmitter {
|
|||||||
this.persistence.sessionId = undefined;
|
this.persistence.sessionId = undefined;
|
||||||
this.persistence.reconnectAttempts = 0;
|
this.persistence.reconnectAttempts = 0;
|
||||||
|
|
||||||
|
// Remove stored session from localStorage
|
||||||
|
this.clearStoredSession();
|
||||||
|
|
||||||
// Clear reconnect timeout if active
|
// Clear reconnect timeout if active
|
||||||
if (this.reconnectTimeoutId !== null) {
|
if (this.reconnectTimeoutId !== null) {
|
||||||
clearTimeout(this.reconnectTimeoutId);
|
clearTimeout(this.reconnectTimeoutId);
|
||||||
@@ -544,4 +569,152 @@ export class MudConnection extends EventEmitter {
|
|||||||
this.reconnectTimeoutId = null;
|
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 { AccessibilityManager } from '$lib/accessibility/AccessibilityManager';
|
||||||
import { settingsManager } from '$lib/settings/SettingsManager';
|
import { settingsManager } from '$lib/settings/SettingsManager';
|
||||||
import { shortcutManager } from '$lib/utils/KeyboardShortcutManager';
|
import { shortcutManager } from '$lib/utils/KeyboardShortcutManager';
|
||||||
|
import { MudConnection } from '$lib/connection/MudConnection';
|
||||||
import PwaUpdater from '$lib/components/PwaUpdater.svelte';
|
import PwaUpdater from '$lib/components/PwaUpdater.svelte';
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
|
|
||||||
@@ -156,6 +157,9 @@
|
|||||||
// Only execute browser-specific code in browser environment
|
// Only execute browser-specific code in browser environment
|
||||||
if (!isBrowser) return;
|
if (!isBrowser) return;
|
||||||
|
|
||||||
|
// Clean up old stored sessions on app startup
|
||||||
|
MudConnection.cleanupOldStoredSessions();
|
||||||
|
|
||||||
profileManager = new ProfileManager();
|
profileManager = new ProfileManager();
|
||||||
|
|
||||||
// Settings manager is initialized as a singleton when imported
|
// Settings manager is initialized as a singleton when imported
|
||||||
|
|||||||
Reference in New Issue
Block a user