fix(network-manager): Refined network management logic for better offline handling.
This commit is contained in:
parent
aebcbe4a61
commit
804537c059
@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-02-03 - 3.0.58 - fix(network-manager)
|
||||
Refined network management logic for better offline handling.
|
||||
|
||||
- Improved logic to handle missing connections more gracefully.
|
||||
- Added detailed online/offline connection status logging.
|
||||
- Implemented a check for stale cache with a grace period for offline scenarios.
|
||||
- Network requests now use optimized retries and timeouts.
|
||||
|
||||
## 2025-02-03 - 3.0.57 - fix(updateManager)
|
||||
Refine cache management for service worker updates.
|
||||
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@api.global/typedserver',
|
||||
version: '3.0.57',
|
||||
version: '3.0.58',
|
||||
description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.'
|
||||
}
|
||||
|
@ -1,18 +1,37 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { ServiceWorker } from './classes.serviceworker.js';
|
||||
import { logger } from './logging.js';
|
||||
|
||||
export class NetworkManager {
|
||||
public serviceWorkerRef: ServiceWorker;
|
||||
public webRequest: plugins.webrequest.WebRequest;
|
||||
private isOffline: boolean = false;
|
||||
private lastOnlineCheck: number = 0;
|
||||
private readonly ONLINE_CHECK_INTERVAL = 30000; // 30 seconds
|
||||
|
||||
public previousState: string;
|
||||
|
||||
constructor(serviceWorkerRefArg: ServiceWorker) {
|
||||
this.serviceWorkerRef = serviceWorkerRefArg;
|
||||
this.webRequest = new plugins.webrequest.WebRequest();
|
||||
|
||||
// Listen for connection changes
|
||||
this.getConnection()?.addEventListener('change', () => {
|
||||
this.updateConnectionStatus();
|
||||
});
|
||||
|
||||
// Listen for online/offline events
|
||||
self.addEventListener('online', () => {
|
||||
this.isOffline = false;
|
||||
logger.log('info', 'Device is now online');
|
||||
this.updateConnectionStatus();
|
||||
});
|
||||
|
||||
self.addEventListener('offline', () => {
|
||||
this.isOffline = true;
|
||||
logger.log('warn', 'Device is now offline');
|
||||
this.updateConnectionStatus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,6 +47,81 @@ export class NetworkManager {
|
||||
}
|
||||
|
||||
public updateConnectionStatus() {
|
||||
console.log(`Connection type changed from ${this.previousState} to ${this.getEffectiveType()}`);
|
||||
const currentType = this.getEffectiveType();
|
||||
logger.log('info', `Connection type changed from ${this.previousState} to ${currentType}`);
|
||||
this.previousState = currentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the device is currently online by attempting to contact the server
|
||||
* @returns Promise<boolean> true if online, false if offline
|
||||
*/
|
||||
public async checkOnlineStatus(): Promise<boolean> {
|
||||
const now = Date.now();
|
||||
// Only check if enough time has passed since last check
|
||||
if (now - this.lastOnlineCheck < this.ONLINE_CHECK_INTERVAL) {
|
||||
return !this.isOffline;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/sw-typedrequest', {
|
||||
method: 'HEAD',
|
||||
cache: 'no-cache'
|
||||
});
|
||||
this.isOffline = false;
|
||||
this.lastOnlineCheck = now;
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.isOffline = true;
|
||||
this.lastOnlineCheck = now;
|
||||
logger.log('warn', 'Device appears to be offline');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a network request with offline handling
|
||||
* @param request The request to make
|
||||
* @param options Additional options
|
||||
* @returns Promise<Response>
|
||||
*/
|
||||
public async makeRequest<T>(request: Request | string, options: {
|
||||
timeoutMs?: number;
|
||||
retries?: number;
|
||||
backoffMs?: number;
|
||||
} = {}): Promise<Response> {
|
||||
const {
|
||||
timeoutMs = 5000,
|
||||
retries = 1,
|
||||
backoffMs = 1000
|
||||
} = options;
|
||||
|
||||
let lastError: Error;
|
||||
for (let i = 0; i <= retries; i++) {
|
||||
try {
|
||||
const isOnline = await this.checkOnlineStatus();
|
||||
if (!isOnline) {
|
||||
throw new Error('Device is offline');
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
||||
|
||||
const response = await fetch(request, {
|
||||
...typeof request === 'string' ? {} : request,
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (i < retries) {
|
||||
await new Promise(resolve => setTimeout(resolve, backoffMs * (i + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export class UpdateManager {
|
||||
*/
|
||||
private readonly MAX_CACHE_AGE = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
||||
private readonly MIN_CHECK_INTERVAL = 100000; // 100 seconds in milliseconds
|
||||
private readonly OFFLINE_GRACE_PERIOD = 7 * 24 * 60 * 60 * 1000; // 7 days grace period when offline
|
||||
private lastCacheTimestamp: number = 0;
|
||||
|
||||
public async checkUpdate(cacheManager: CacheManager): Promise<boolean> {
|
||||
@ -44,11 +45,23 @@ export class UpdateManager {
|
||||
const millisSinceLastCheck = now - this.lastUpdateCheck;
|
||||
const cacheAge = now - this.lastCacheTimestamp;
|
||||
|
||||
// Force update if cache is too old
|
||||
// Check if we need to handle stale cache
|
||||
if (cacheAge > this.MAX_CACHE_AGE) {
|
||||
logger.log('info', `Cache is older than ${this.MAX_CACHE_AGE}ms, forcing update...`);
|
||||
await this.forceUpdate(cacheManager);
|
||||
return true;
|
||||
const isOnline = await this.serviceworkerRef.networkManager.checkOnlineStatus();
|
||||
|
||||
if (isOnline) {
|
||||
logger.log('info', `Cache is older than ${this.MAX_CACHE_AGE}ms, forcing update...`);
|
||||
await this.forceUpdate(cacheManager);
|
||||
return true;
|
||||
} else if (cacheAge > this.OFFLINE_GRACE_PERIOD) {
|
||||
// If we're offline and beyond grace period, warn but continue serving cached content
|
||||
logger.log('warn', `Cache is stale and device is offline. Cache age: ${cacheAge}ms. Using cached content with warning.`);
|
||||
// We could potentially show a warning to the user here
|
||||
return false;
|
||||
} else {
|
||||
logger.log('info', `Cache is stale but device is offline. Within grace period. Using cached content.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Regular update check interval
|
||||
@ -85,11 +98,24 @@ export class UpdateManager {
|
||||
* gets the apphash from the server
|
||||
*/
|
||||
public async getVersionInfoFromServer() {
|
||||
const getAppHashRequest = new plugins.typedrequest.TypedRequest<
|
||||
interfaces.serviceworker.IRequest_Serviceworker_Backend_VersionInfo
|
||||
>('/sw-typedrequest', 'serviceworker_versionInfo');
|
||||
const result = await getAppHashRequest.fire({});
|
||||
return result;
|
||||
try {
|
||||
const getAppHashRequest = new plugins.typedrequest.TypedRequest<
|
||||
interfaces.serviceworker.IRequest_Serviceworker_Backend_VersionInfo
|
||||
>('/sw-typedrequest', 'serviceworker_versionInfo');
|
||||
|
||||
// Use networkManager for the request with retries and timeout
|
||||
const response = await this.serviceworkerRef.networkManager.makeRequest('/sw-typedrequest', {
|
||||
timeoutMs: 5000,
|
||||
retries: 2,
|
||||
backoffMs: 1000
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.log('warn', `Failed to get version info from server: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// tasks
|
||||
@ -97,14 +123,22 @@ export class UpdateManager {
|
||||
* this task is executed once we know that there is a new version available
|
||||
*/
|
||||
private async forceUpdate(cacheManager: CacheManager) {
|
||||
logger.log('info', 'Forcing cache update due to staleness');
|
||||
await this.serviceworkerRef.cacheManager.cleanCaches('Cache is stale, forcing update.');
|
||||
const currentVersionInfo = await this.getVersionInfoFromServer();
|
||||
this.lastVersionInfo = currentVersionInfo;
|
||||
await this.serviceworkerRef.store.set('versionInfo', this.lastVersionInfo);
|
||||
this.lastCacheTimestamp = Date.now();
|
||||
await this.serviceworkerRef.store.set('cacheTimestamp', this.lastCacheTimestamp);
|
||||
await this.serviceworkerRef.leleServiceWorkerBackend.triggerReloadAll();
|
||||
try {
|
||||
logger.log('info', 'Forcing cache update due to staleness');
|
||||
const currentVersionInfo = await this.getVersionInfoFromServer();
|
||||
|
||||
// Only proceed with cache cleaning if we successfully got new version info
|
||||
await this.serviceworkerRef.cacheManager.cleanCaches('Cache is stale, forcing update.');
|
||||
this.lastVersionInfo = currentVersionInfo;
|
||||
await this.serviceworkerRef.store.set('versionInfo', this.lastVersionInfo);
|
||||
this.lastCacheTimestamp = Date.now();
|
||||
await this.serviceworkerRef.store.set('cacheTimestamp', this.lastCacheTimestamp);
|
||||
await this.serviceworkerRef.leleServiceWorkerBackend.triggerReloadAll();
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to force update: ${error.message}. Keeping existing cache.`);
|
||||
// If update fails, we'll keep using the existing cache
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public performAsyncUpdateDebouncedTask = new plugins.taskbuffer.TaskDebounced({
|
||||
|
Loading…
x
Reference in New Issue
Block a user