Compare commits

...

14 Commits

Author SHA1 Message Date
e022ffc2ba 7.8.8
Some checks failed
Default (tags) / security (push) Failing after 50s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-04 22:20:55 +00:00
25e92f4351 chore: update @api.global/typedrequest to version 3.2.1 2025-12-04 22:20:44 +00:00
b508cbe927 7.8.7
Some checks failed
Default (tags) / security (push) Failing after 52s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-04 21:54:08 +00:00
4cbc37c888 feat: implement handler initialization for cache invalidation in ServiceWorker 2025-12-04 21:53:45 +00:00
16f759c2b9 7.8.6
Some checks failed
Default (tags) / security (push) Failing after 52s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-04 21:40:08 +00:00
f8fee04751 chore: update @api.global/typedrequest to version 3.2.0 and @push.rocks/taskbuffer to version 3.5.0 2025-12-04 21:40:05 +00:00
9406cfa0e2 7.8.5
Some checks failed
Default (tags) / security (push) Failing after 55s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-04 21:33:09 +00:00
1f310ef8f1 refactor: Remove SW-TypedRequest controller and update related references 2025-12-04 21:33:02 +00:00
9cd10118e3 7.8.4
Some checks failed
Default (tags) / security (push) Failing after 51s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-04 21:04:38 +00:00
6308e0126d feat(controller): Add SW-TypedRequest controller for service worker communication 2025-12-04 21:04:33 +00:00
e1310269fe 7.8.3
Some checks failed
Default (tags) / security (push) Failing after 53s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-04 20:56:34 +00:00
1aadc2da21 feat(serviceworker): Add endpoint to serve serviceworker bundle with error handling 2025-12-04 20:56:16 +00:00
37426f0708 7.8.2
Some checks failed
Default (tags) / security (push) Failing after 55s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-04 20:17:18 +00:00
c124a06bc6 feat(dashboard): Add error handling to serveMetrics method for improved resilience 2025-12-04 20:17:10 +00:00
10 changed files with 167 additions and 85 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@api.global/typedserver",
"version": "7.8.1",
"version": "7.8.8",
"description": "A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.",
"type": "module",
"exports": {
@@ -58,7 +58,7 @@
],
"homepage": "https://code.foss.global/api.global/typedserver",
"dependencies": {
"@api.global/typedrequest": "^3.1.11",
"@api.global/typedrequest": "^3.2.1",
"@api.global/typedrequest-interfaces": "^3.0.19",
"@api.global/typedsocket": "^4.1.0",
"@cloudflare/workers-types": "^4.20251202.0",
@@ -87,7 +87,7 @@
"@push.rocks/smartstream": "^3.2.5",
"@push.rocks/smarttime": "^4.1.1",
"@push.rocks/smartwatch": "^5.0.0",
"@push.rocks/taskbuffer": "^3.4.0",
"@push.rocks/taskbuffer": "^3.5.0",
"@push.rocks/webrequest": "^4.0.1",
"@push.rocks/webstore": "^2.0.20",
"@tsclass/tsclass": "^9.3.0",

42
pnpm-lock.yaml generated
View File

@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@api.global/typedrequest':
specifier: ^3.1.11
version: 3.1.11
specifier: ^3.2.1
version: 3.2.1
'@api.global/typedrequest-interfaces':
specifier: ^3.0.19
version: 3.0.19
@@ -96,8 +96,8 @@ importers:
specifier: ^5.0.0
version: 5.0.0
'@push.rocks/taskbuffer':
specifier: ^3.4.0
version: 3.4.0
specifier: ^3.5.0
version: 3.5.0
'@push.rocks/webrequest':
specifier: ^4.0.1
version: 4.0.1
@@ -135,8 +135,8 @@ packages:
'@api.global/typedrequest-interfaces@3.0.19':
resolution: {integrity: sha512-uuHUXJeOy/inWSDrwD0Cwax2rovpxYllDhM2RWh+6mVpQuNmZ3uw6IVg6dA2G1rOe24Ebs+Y9SzEogo+jYN7vw==}
'@api.global/typedrequest@3.1.11':
resolution: {integrity: sha512-j8EO3na0WMw8pFkAfEaEui2a4TaAL1G/dv1CYl8LEPXckSKkl1BCAS1kFOW2xuI9pwZkmSqlo3xpQ3KmkmHaGQ==}
'@api.global/typedrequest@3.2.1':
resolution: {integrity: sha512-BDgKC+F5H4OriFG5kbfSY5MdF5b9hGvBnYt25sETOXyU7ZPj4vF9OhhPthMls3SORqQEc3FxoNuCLn7hIVEN9g==}
'@api.global/typedserver@3.0.80':
resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==}
@@ -1256,8 +1256,8 @@ packages:
'@push.rocks/smartyaml@3.0.4':
resolution: {integrity: sha512-1JRt+hnoc2zHw3AW+vXKlCdSVwqOmY/01fu+2HBviS0UDjoZCa+/rp6E3GaQb5lEEafKi8ENbffAfjXXp3N2xQ==}
'@push.rocks/taskbuffer@3.4.0':
resolution: {integrity: sha512-Rvwr1CzYztB9PMboojRzVSq3xGp8288kvtvWx4Mg3rvps913znMja1UOjNn52ivOxu3dHUNYE3NDSP+j84cUWQ==}
'@push.rocks/taskbuffer@3.5.0':
resolution: {integrity: sha512-Y9WwIEIyp6oVFdj06j84tfrZIvjhbMb3DF52rYxlTeYLk3W7RPhSg1bGPCbtkXWeKdBrSe37V90BkOG7Qq8Pqg==}
'@push.rocks/webrequest@3.0.37':
resolution: {integrity: sha512-fLN7kP6GeHFxE4UH4r9C9pjcQb0QkJxHeAMwXvbOqB9hh0MFNKhtGU7GoaTn8SVRGRMPc9UqZVNwo6u5l8Wn0A==}
@@ -3896,7 +3896,7 @@ snapshots:
'@api.global/typedrequest-interfaces@3.0.19': {}
'@api.global/typedrequest@3.1.11':
'@api.global/typedrequest@3.2.1':
dependencies:
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/isounique': 1.0.5
@@ -3910,7 +3910,7 @@ snapshots:
'@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.1.2)':
dependencies:
'@api.global/typedrequest': 3.1.11
'@api.global/typedrequest': 3.2.1
'@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.1.2)
'@cloudflare/workers-types': 4.20251202.0
@@ -3937,7 +3937,7 @@ snapshots:
'@push.rocks/smartsitemap': 2.0.4
'@push.rocks/smartstream': 3.2.5
'@push.rocks/smarttime': 4.1.1
'@push.rocks/taskbuffer': 3.4.0
'@push.rocks/taskbuffer': 3.5.0
'@push.rocks/webrequest': 3.0.37
'@push.rocks/webstore': 2.0.20
'@tsclass/tsclass': 9.3.0
@@ -3958,7 +3958,7 @@ snapshots:
'@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.1.2)':
dependencies:
'@api.global/typedrequest': 3.1.11
'@api.global/typedrequest': 3.2.1
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/isohash': 2.0.1
'@push.rocks/smartjson': 5.2.0
@@ -3978,7 +3978,7 @@ snapshots:
'@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.1.2)':
dependencies:
'@api.global/typedrequest': 3.1.11
'@api.global/typedrequest': 3.2.1
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/isohash': 2.0.1
'@push.rocks/smartdelay': 3.0.5
@@ -5259,14 +5259,14 @@ snapshots:
'@design.estate/dees-comms@1.0.27':
dependencies:
'@api.global/typedrequest': 3.1.11
'@api.global/typedrequest': 3.2.1
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/smartdelay': 3.0.5
broadcast-channel: 7.0.0
'@design.estate/dees-domtools@2.0.65':
dependencies:
'@api.global/typedrequest': 3.1.11
'@api.global/typedrequest': 3.2.1
'@design.estate/dees-comms': 1.0.27
'@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5
@@ -5288,7 +5288,7 @@ snapshots:
'@design.estate/dees-domtools@2.3.6':
dependencies:
'@api.global/typedrequest': 3.1.11
'@api.global/typedrequest': 3.2.1
'@design.estate/dees-comms': 1.0.27
'@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5
@@ -5849,7 +5849,7 @@ snapshots:
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartstring': 4.1.0
'@push.rocks/smartunique': 3.0.9
'@push.rocks/taskbuffer': 3.4.0
'@push.rocks/taskbuffer': 3.5.0
'@tsclass/tsclass': 9.3.0
transitivePeerDependencies:
- '@nuxt/kit'
@@ -5889,7 +5889,7 @@ snapshots:
'@push.rocks/qenv@6.1.3':
dependencies:
'@api.global/typedrequest': 3.1.11
'@api.global/typedrequest': 3.2.1
'@configvault.io/interfaces': 1.0.17
'@push.rocks/smartfile': 11.2.7
'@push.rocks/smartlog': 3.1.10
@@ -6015,7 +6015,7 @@ snapshots:
'@push.rocks/smartstring': 4.1.0
'@push.rocks/smarttime': 4.1.1
'@push.rocks/smartunique': 3.0.9
'@push.rocks/taskbuffer': 3.4.0
'@push.rocks/taskbuffer': 3.5.0
'@tsclass/tsclass': 9.3.0
mongodb: 6.21.0(@aws-sdk/credential-providers@3.787.0)(socks@2.8.7)
transitivePeerDependencies:
@@ -6376,7 +6376,7 @@ snapshots:
'@push.rocks/smartserve@1.1.2':
dependencies:
'@api.global/typedrequest': 3.1.11
'@api.global/typedrequest': 3.2.1
'@push.rocks/lik': 6.2.2
'@push.rocks/smartenv': 6.0.0
'@push.rocks/smartlog': 3.1.10
@@ -6523,7 +6523,7 @@ snapshots:
dependencies:
yaml: 2.8.1
'@push.rocks/taskbuffer@3.4.0':
'@push.rocks/taskbuffer@3.5.0':
dependencies:
'@design.estate/dees-element': 2.1.3
'@push.rocks/lik': 6.2.2

View File

@@ -203,4 +203,25 @@ export class BuiltInRoutesController {
return new Response('SW-Dash bundle not found', { status: 404 });
}
}
@plugins.smartserve.Get('/serviceworker.bundle.js')
async getServiceWorkerBundle(): Promise<Response> {
try {
const bundleContent = (await plugins.fsInstance
.file(paths.serviceworkerBundlePath)
.encoding('utf8')
.read()) as string;
return new Response(bundleContent, {
status: 200,
headers: {
'Content-Type': 'text/javascript',
'Cache-Control': 'no-cache',
},
});
} catch (error) {
console.error('Failed to serve serviceworker bundle:', error);
return new Response('ServiceWorker bundle not found', { status: 404 });
}
}
}

View File

@@ -11,7 +11,7 @@ export class TypedRequestController {
this.typedRouter = typedRouter;
}
@plugins.smartserve.Post('/')
@plugins.smartserve.Post('')
async handleTypedRequest(ctx: plugins.smartserve.IRequestContext): Promise<Response> {
try {
const response = await this.typedRouter.routeAndAddResponse(ctx.body as plugins.typedrequestInterfaces.ITypedRequest);
@@ -31,4 +31,15 @@ export class TypedRequestController {
});
}
}
@plugins.smartserve.Head('')
async handleTypedRequestHead(): Promise<Response> {
// HEAD request for online checking from service worker
return new Response(null, {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
}
}

View File

@@ -9,6 +9,7 @@ export const injectBundleDir = plugins.path.join(packageDir, './dist_ts_web_inje
export const injectBundlePath = plugins.path.join(injectBundleDir, './bundle.js');
export const serviceworkerBundleDir = plugins.path.join(packageDir, './dist_ts_web_serviceworker');
export const serviceworkerBundlePath = plugins.path.join(serviceworkerBundleDir, './serviceworker.bundle.js');
export const swdashBundleDir = plugins.path.join(packageDir, './dist_ts_swdash');
export const swdashBundlePath = plugins.path.join(swdashBundleDir, './bundle.js');

View File

@@ -34,48 +34,78 @@ export class DashboardGenerator {
* - Resource data
*/
public async serveMetrics(): Promise<Response> {
const metrics = getMetricsCollector();
const persistentStore = getPersistentStore();
await persistentStore.init();
const requestLogStore = getRequestLogStore();
try {
const metrics = getMetricsCollector();
const persistentStore = getPersistentStore();
await persistentStore.init();
const requestLogStore = getRequestLogStore();
// Get event data
const eventResult = await persistentStore.getEventLog({ limit: 50 });
const oneHourAgo = Date.now() - 3600000;
const eventCountLastHour = await persistentStore.getEventCount(oneHourAgo);
// Get event data
const eventResult = await persistentStore.getEventLog({ limit: 50 });
const oneHourAgo = Date.now() - 3600000;
const eventCountLastHour = await persistentStore.getEventCount(oneHourAgo);
// Build comprehensive initial response
const data = {
// Core metrics
...metrics.getMetrics(),
cacheHitRate: metrics.getCacheHitRate(),
networkSuccessRate: metrics.getNetworkSuccessRate(),
resourceCount: metrics.getResourceCount(),
summary: metrics.getSummary(),
// Build comprehensive initial response
const data = {
// Core metrics
...metrics.getMetrics(),
cacheHitRate: metrics.getCacheHitRate(),
networkSuccessRate: metrics.getNetworkSuccessRate(),
resourceCount: metrics.getResourceCount(),
summary: metrics.getSummary(),
// Resources data
resources: metrics.getCachedResources(),
domains: metrics.getDomainStats(),
contentTypes: metrics.getContentTypeStats(),
// Resources data
resources: metrics.getCachedResources(),
domains: metrics.getDomainStats(),
contentTypes: metrics.getContentTypeStats(),
// Events data (initial 50)
events: eventResult.events,
eventTotalCount: eventResult.totalCount,
eventCountLastHour,
// Events data (initial 50)
events: eventResult.events,
eventTotalCount: eventResult.totalCount,
eventCountLastHour,
// Request logs data (initial 50)
requestLogs: requestLogStore.getEntries({ limit: 50 }),
requestTotalCount: requestLogStore.getTotalCount(),
requestStats: requestLogStore.getStats(),
requestMethods: requestLogStore.getMethods(),
};
// Request logs data (initial 50)
requestLogs: requestLogStore.getEntries({ limit: 50 }),
requestTotalCount: requestLogStore.getTotalCount(),
requestStats: requestLogStore.getStats(),
requestMethods: requestLogStore.getMethods(),
};
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
},
});
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
},
});
} catch (error) {
console.error('[SW Dashboard] serveMetrics error:', error);
// Return error response with valid JSON structure so client doesn't crash
return new Response(JSON.stringify({
error: String(error),
cache: { hits: 0, misses: 0, errors: 0, bytesServedFromCache: 0, bytesFetched: 0, averageResponseTime: 0 },
network: { totalRequests: 0, successfulRequests: 0, failedRequests: 0, timeouts: 0, averageLatency: 0, totalBytesTransferred: 0 },
update: { totalChecks: 0, successfulChecks: 0, failedChecks: 0, updatesFound: 0, updatesApplied: 0, lastCheckTimestamp: 0, lastUpdateTimestamp: 0 },
connection: { connectedClients: 0, totalConnectionAttempts: 0, successfulConnections: 0, failedConnections: 0 },
speedtest: { lastDownloadSpeedMbps: 0, lastUploadSpeedMbps: 0, lastLatencyMs: 0, lastTestTimestamp: 0, testCount: 0, isOnline: false },
startTime: Date.now(),
uptime: 0,
cacheHitRate: 0,
networkSuccessRate: 0,
resourceCount: 0,
events: [],
eventTotalCount: 0,
eventCountLastHour: 0,
requestLogs: [],
requestTotalCount: 0,
requestStats: { totalRequests: 0, totalResponses: 0, methodCounts: {}, errorCount: 0, avgDurationMs: 0 },
requestMethods: [],
}), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
},
});
}
}
/**

View File

@@ -64,7 +64,7 @@ export class NetworkManager {
}
try {
const response = await fetch('/sw-typedrequest', {
const response = await fetch('/typedrequest', {
method: 'HEAD',
cache: 'no-cache'
});

View File

@@ -31,6 +31,7 @@ export class ServiceWorker {
// TypedSocket connection for server communication
public typedsocket: plugins.typedsocket.TypedSocket;
public typedrouter = new plugins.typedrequest.TypedRouter();
private handlersInitialized = false;
constructor(selfArg: interfaces.ServiceWindow) {
logger.log('info', `Service worker instantiating at ${Date.now()}`);
@@ -119,33 +120,43 @@ export class ServiceWorker {
}
}
/**
* Initialize typed handlers (idempotent - safe to call multiple times)
*/
private initHandlers(): void {
if (this.handlersInitialized) return;
this.handlersInitialized = true;
// Register handler for cache invalidation from server
this.typedrouter.addTypedHandler<interfaces.serviceworker.IRequest_Serviceworker_CacheInvalidate>(
new plugins.typedrequest.TypedHandler('serviceworker_cacheInvalidate', async (reqArg) => {
logger.log('info', `Cache invalidation requested from server: ${reqArg.reason}`);
// Log cache invalidation event (survives)
const persistentStore = getPersistentStore();
await persistentStore.init(); // Ensure store is initialized
await persistentStore.logEvent('cache_invalidated', `Cache invalidated: ${reqArg.reason}`, {
reason: reqArg.reason,
timestamp: reqArg.timestamp,
});
// Reset cumulative metrics (they don't survive cache invalidation)
await persistentStore.resetCumulativeMetrics();
await this.cacheManager.cleanCaches(reqArg.reason);
// Notify all clients to reload
await this.leleServiceWorkerBackend.triggerReloadAll();
return { success: true };
})
);
}
/**
* Connect to TypedServer via TypedSocket for cache invalidation
*/
private async connectToServer(): Promise<void> {
try {
// Register handler for cache invalidation from server
this.typedrouter.addTypedHandler<interfaces.serviceworker.IRequest_Serviceworker_CacheInvalidate>(
new plugins.typedrequest.TypedHandler('serviceworker_cacheInvalidate', async (reqArg) => {
logger.log('info', `Cache invalidation requested from server: ${reqArg.reason}`);
// Log cache invalidation event (survives)
const persistentStore = getPersistentStore();
await persistentStore.init(); // Ensure store is initialized
await persistentStore.logEvent('cache_invalidated', `Cache invalidated: ${reqArg.reason}`, {
reason: reqArg.reason,
timestamp: reqArg.timestamp,
});
// Reset cumulative metrics (they don't survive cache invalidation)
await persistentStore.resetCumulativeMetrics();
await this.cacheManager.cleanCaches(reqArg.reason);
// Notify all clients to reload
await this.leleServiceWorkerBackend.triggerReloadAll();
return { success: true };
})
);
this.initHandlers();
// Connect to server via TypedSocket
this.typedsocket = await plugins.typedsocket.TypedSocket.createClient(

View File

@@ -177,7 +177,7 @@ export class UpdateManager {
try {
const getAppHashRequest = new plugins.typedrequest.TypedRequest<
interfaces.serviceworker.IRequest_Serviceworker_Backend_VersionInfo
>('/sw-typedrequest', 'serviceworker_versionInfo');
>('/typedrequest', 'serviceworker_versionInfo');
// Use networkManager for the request with retries and timeout
const response = await getAppHashRequest.fire({});

View File

@@ -1,5 +1,6 @@
import * as plugins from './plugins.js';
import { ServiceworkerClient } from './classes.serviceworkerclient.js';
import { ActionManager } from './classes.actionmanager.js';
export class GlobalSW {
losslessSw: ServiceworkerClient;
@@ -8,6 +9,13 @@ export class GlobalSW {
globalThis.globalSw = this;
};
/**
* Exposes the action manager for traffic logging and SW communication
*/
public get actionManager(): ActionManager {
return this.losslessSw.actionManager;
}
/**
* purges the cache of the app's serviceworker
* @returns