Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
b3726cb518 | |||
ec6754be52 | |||
1ced20c887 | |||
3556594501 | |||
dd6babdf81 | |||
75ce27a4bf | |||
435a4a0349 | |||
b1983edcd7 | |||
1a9c656f2e | |||
569fa4fc46 |
31
changelog.md
31
changelog.md
@ -1,5 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-02-07 - 3.0.68 - fix(cache-manager)
|
||||
Simplify cache control headers in cache manager
|
||||
|
||||
- Removed unnecessary cache control headers while setting modern Cache-Control.
|
||||
|
||||
## 2025-02-06 - 3.0.67 - fix(serviceworker)
|
||||
Enhance header security for cached resources in service worker
|
||||
|
||||
- Added Cross-Origin-Resource-Policy header management for service worker cached resources.
|
||||
|
||||
## 2025-02-06 - 3.0.66 - fix(serviceworker)
|
||||
Improve error handling and logging in cache manager and update manager.
|
||||
|
||||
- Enhanced error handling and logging in cache management functions.
|
||||
- Corrected network request handling in update manager.
|
||||
- Added missing error handling for fetch events.
|
||||
|
||||
## 2025-02-04 - 3.0.65 - fix(readme)
|
||||
Update documentation with advanced usage and examples
|
||||
|
||||
- Added section on advanced usage including service worker and edge worker setup
|
||||
- Detailed integration examples for type-safe API requests and WebSocket communication
|
||||
- Expanded configuration options and cache strategies
|
||||
|
||||
## 2025-02-04 - 3.0.64 - fix(serviceworker)
|
||||
Improve cache handling and response header management in service worker.
|
||||
|
||||
- Addressed issue preventing caching of certain responses due to missing CORS headers.
|
||||
- Added 'Vary: Origin' header to ensure proper response handling.
|
||||
- Included 'Access-Control-Expose-Headers' for better CORS support.
|
||||
|
||||
## 2025-02-04 - 3.0.63 - fix(core)
|
||||
Refactored caching strategy for service worker to improve compatibility and performance.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@api.global/typedserver",
|
||||
"version": "3.0.63",
|
||||
"version": "3.0.68",
|
||||
"description": "A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
182
readme.md
182
readme.md
@ -1,52 +1,168 @@
|
||||
```markdown
|
||||
# @api.global/typedserver
|
||||
Easy serving of static files
|
||||
|
||||
## Install
|
||||
To install @api.global/typedserver, run the following command in your terminal:
|
||||
A TypeScript-based framework for serving static files with advanced features including live reloading, compression, and type-safe API requests. Part of the @api.global ecosystem, it integrates seamlessly with @api.global/typedrequest for type-safe HTTP requests and @api.global/typedsocket for WebSocket communication.
|
||||
|
||||
## Features
|
||||
|
||||
- **Type-Safe API Ecosystem**:
|
||||
- HTTP Requests via @api.global/typedrequest
|
||||
- WebSocket Support via @api.global/typedsocket
|
||||
- Full TypeScript support across all endpoints
|
||||
- **Service Worker Integration**: Advanced caching and offline capabilities
|
||||
- **Edge Worker Support**: Optimized edge computing capabilities
|
||||
- **Live Reload**: Automatic browser refresh on file changes
|
||||
- **Compression**: Built-in support for response compression
|
||||
- **CORS Management**: Flexible cross-origin resource sharing
|
||||
- **TypeScript First**: Built with and for TypeScript
|
||||
|
||||
## Components
|
||||
|
||||
### Core Server (`ts/`)
|
||||
- Static file serving with Express
|
||||
- Type-safe request handling
|
||||
- Live reload functionality
|
||||
- Compression middleware
|
||||
|
||||
### Service Worker (`ts_web_serviceworker/`)
|
||||
- `CacheManager`: Advanced caching strategies
|
||||
- `NetworkManager`: Request/response handling
|
||||
- `UpdateManager`: Cache invalidation and updates
|
||||
- `ServiceWorker`: Core service worker implementation
|
||||
|
||||
### Edge Worker (`ts_edgeworker/`)
|
||||
- Edge computing capabilities
|
||||
- Request/response transformation
|
||||
- Edge caching strategies
|
||||
|
||||
### Web Inject (`ts_web_inject/`)
|
||||
- Live reload script injection
|
||||
- Runtime dependency management
|
||||
- Dynamic module loading
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @api.global/typedserver --save
|
||||
npm install @api.global/typedserver
|
||||
```
|
||||
|
||||
This will add `@api.global/typedserver` to your project's dependencies.
|
||||
|
||||
## Usage
|
||||
|
||||
`@api.global/typedserver` is designed to make serving static files and handling web requests in a TypeScript environment easy and efficient. It leverages Express under the hood, providing a powerful API for web server creation with additional utilities for live reloading, typed requests/responses, and more, embracing TypeScript's static typing advantages.
|
||||
|
||||
### Setting up a Basic Web Server
|
||||
|
||||
The following example demonstrates how to set up a basic web server serving files from a directory.
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { TypedServer } from '@api.global/typedserver';
|
||||
|
||||
const serverOptions = {
|
||||
port: 8080, // Port to listen on
|
||||
serveDir: 'public', // Directory to serve static files from
|
||||
watch: true, // Enable live reloading of changes
|
||||
injectReload: true, // Inject live reload script into served HTML files
|
||||
cors: true // Enable CORS
|
||||
};
|
||||
const server = new TypedServer({
|
||||
port: 3000,
|
||||
serveDir: './public',
|
||||
watch: true,
|
||||
compression: true
|
||||
});
|
||||
|
||||
const typedServer = new TypedServer(serverOptions);
|
||||
|
||||
async function startServer() {
|
||||
await typedServer.start();
|
||||
console.log(`Server is running on http://localhost:${serverOptions.port}`);
|
||||
}
|
||||
|
||||
startServer().catch(console.error);
|
||||
server.start();
|
||||
```
|
||||
|
||||
In the example above, `TypedServer` is instantiated with an `IServerOptions` object specifying options like the port to listen on (`8080`), the directory containing static files to serve (`public`), and live reload features. Calling `start()` on the `typedServer` instance initiates the server.
|
||||
## Type-Safe API Integration
|
||||
|
||||
### Using Typed Requests and Responses
|
||||
### HTTP Requests with TypedRequest
|
||||
```typescript
|
||||
import { TypedRequest } from '@api.global/typedrequest';
|
||||
|
||||
`TypedServer` supports typed requests and responses, making API development more robust and maintainable. Define your request and response types, and use them to type-check incoming requests and their responses.
|
||||
// Define your request/response interface
|
||||
interface IUserRequest {
|
||||
method: 'getUser';
|
||||
request: { userId: string };
|
||||
response: { username: string; email: string; };
|
||||
}
|
||||
|
||||
First, define the types:
|
||||
// Create and use a typed request
|
||||
const getUserRequest = new TypedRequest<IUserRequest>('/api/users', 'getUser');
|
||||
const user = await getUserRequest.fire({ userId: '123' });
|
||||
```
|
||||
|
||||
### WebSocket Communication
|
||||
```typescript
|
||||
import { TypedSocket } from '@api.global/typedsocket';
|
||||
|
||||
// Server setup
|
||||
const typedRouter = new TypedRouter();
|
||||
const server = await TypedSocket.createServer(typedRouter);
|
||||
|
||||
// Client connection
|
||||
const client = await TypedSocket.createClient(typedRouter, 'ws://localhost:3000');
|
||||
|
||||
// Type-safe real-time messaging
|
||||
interface IChatMessage {
|
||||
method: 'sendMessage';
|
||||
request: { text: string };
|
||||
response: { id: string; timestamp: number; };
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Service Worker Setup
|
||||
|
||||
```typescript
|
||||
import { ServiceWorker } from '@api.global/typedserver/web_serviceworker';
|
||||
|
||||
const sw = new ServiceWorker({
|
||||
cacheStrategy: 'network-first',
|
||||
offlineSupport: true
|
||||
});
|
||||
|
||||
sw.register();
|
||||
```
|
||||
|
||||
### Edge Worker Configuration
|
||||
|
||||
```typescript
|
||||
import { EdgeWorker } from '@api.global/typedserver/edgeworker';
|
||||
|
||||
const edge = new EdgeWorker({
|
||||
transforms: ['compress', 'minify'],
|
||||
caching: true
|
||||
});
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Server Options
|
||||
```typescript
|
||||
interface IServerOptions {
|
||||
port?: number;
|
||||
host?: string;
|
||||
serveDir: string;
|
||||
watch?: boolean;
|
||||
compression?: boolean;
|
||||
cors?: boolean | CorsOptions;
|
||||
cache?: CacheOptions;
|
||||
}
|
||||
```
|
||||
|
||||
### Cache Strategies
|
||||
```typescript
|
||||
type CacheStrategy =
|
||||
| 'network-first'
|
||||
| 'cache-first'
|
||||
| 'stale-while-revalidate';
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
See [API Documentation](https://api.global/docs/typedserver) for detailed API reference.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create your feature branch
|
||||
3. Commit your changes
|
||||
4. Push to the branch
|
||||
5. Create a Pull Request
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE for details.
|
||||
|
||||
Task Venture Capital GmbH © 2024
|
||||
|
||||
```typescript
|
||||
// Define a request type
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@api.global/typedserver',
|
||||
version: '3.0.63',
|
||||
version: '3.0.68',
|
||||
description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.'
|
||||
}
|
||||
|
@ -15,22 +15,34 @@ export class CacheManager {
|
||||
this._setupCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the service worker's fetch event to intercept and cache responses.
|
||||
*/
|
||||
private _setupCache = () => {
|
||||
// Create a matching request. For internal requests, reuse the original; for external requests, create one with CORS settings.
|
||||
const createMatchRequest = (requestArg: Request): Request => {
|
||||
// Create a matchRequest based on whether the request is internal or external.
|
||||
let matchRequest: Request;
|
||||
if (requestArg.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin)) {
|
||||
// Internal request; use the original.
|
||||
matchRequest = requestArg;
|
||||
} else {
|
||||
// External request; create a new Request with appropriate CORS settings.
|
||||
matchRequest = new Request(requestArg.url, {
|
||||
method: requestArg.method,
|
||||
headers: requestArg.headers,
|
||||
mode: 'cors',
|
||||
credentials: 'same-origin',
|
||||
redirect: 'follow'
|
||||
});
|
||||
try {
|
||||
if (
|
||||
requestArg.url.startsWith(
|
||||
this.losslessServiceWorkerRef.serviceWindowRef.location.origin
|
||||
)
|
||||
) {
|
||||
// Internal request
|
||||
matchRequest = requestArg;
|
||||
} else {
|
||||
// External request: create a new Request with appropriate CORS settings.
|
||||
matchRequest = new Request(requestArg.url, {
|
||||
method: requestArg.method,
|
||||
headers: requestArg.headers,
|
||||
mode: 'cors',
|
||||
credentials: 'same-origin',
|
||||
redirect: 'follow'
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.log('error', `Error creating match request for ${requestArg.url}: ${err}`);
|
||||
throw err;
|
||||
}
|
||||
return matchRequest;
|
||||
};
|
||||
@ -39,188 +51,215 @@ export class CacheManager {
|
||||
* Creates a 500 error response.
|
||||
*/
|
||||
const create500Response = async (requestArg: Request, responseArg: Response): Promise<Response> => {
|
||||
return new Response(
|
||||
`
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.note {
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
background: #000;
|
||||
border-bottom: 1px solid #e4002b;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="note">
|
||||
<strong>serviceworker running, but status 500</strong><br>
|
||||
</div>
|
||||
serviceworker is unable to fetch this request<br>
|
||||
Here is some info about the request/response pair:<br>
|
||||
<br>
|
||||
requestUrl: ${requestArg.url}<br>
|
||||
responseType: ${responseArg.type}<br>
|
||||
responseBody: ${await responseArg.clone().text()}<br>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "text/html"
|
||||
},
|
||||
status: 500
|
||||
}
|
||||
);
|
||||
try {
|
||||
const responseText = await responseArg.clone().text();
|
||||
return new Response(
|
||||
`
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.note {
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
background: #000;
|
||||
border-bottom: 1px solid #e4002b;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="note">
|
||||
<strong>ServiceWorker error 500</strong><br>
|
||||
</div>
|
||||
ServiceWorker is unable to fetch this request.<br>
|
||||
<br>
|
||||
<strong>Request URL:</strong> ${requestArg.url}<br>
|
||||
<strong>Response Type:</strong> ${responseArg.type}<br>
|
||||
<strong>Response Body:</strong> ${responseText}<br>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
{
|
||||
headers: { 'Content-Type': 'text/html' },
|
||||
status: 500
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.log('error', `Error creating 500 response for ${requestArg.url}: ${err}`);
|
||||
return new Response('Internal error', { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for fetch events.
|
||||
// Listen for fetch events on the service worker's controlled window.
|
||||
this.losslessServiceWorkerRef.serviceWindowRef.addEventListener('fetch', async (fetchEventArg: any) => {
|
||||
// Block specific scopes.
|
||||
const originalRequest: Request = fetchEventArg.request;
|
||||
const parsedUrl = new URL(originalRequest.url);
|
||||
if (
|
||||
parsedUrl.hostname.includes('paddle.com') ||
|
||||
parsedUrl.hostname.includes('paypal.com') ||
|
||||
parsedUrl.hostname.includes('reception.lossless.one') ||
|
||||
parsedUrl.pathname.startsWith('/socket.io') ||
|
||||
originalRequest.url.startsWith('https://umami.')
|
||||
) {
|
||||
logger.log('note', `serviceworker not active for ${parsedUrl.toString()}`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const originalRequest: Request = fetchEventArg.request;
|
||||
const parsedUrl = new URL(originalRequest.url);
|
||||
|
||||
// Create a deferred response.
|
||||
const done = plugins.smartpromise.defer<Response>();
|
||||
fetchEventArg.respondWith(done.promise);
|
||||
|
||||
if (
|
||||
(originalRequest.method === 'GET' &&
|
||||
(originalRequest.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin) &&
|
||||
!originalRequest.url.includes('/api/') &&
|
||||
!originalRequest.url.includes('smartserve/reloadcheck'))) ||
|
||||
originalRequest.url.includes('https://assetbroker.') ||
|
||||
originalRequest.url.includes('https://unpkg.com') ||
|
||||
originalRequest.url.includes('https://fonts.googleapis.com') ||
|
||||
originalRequest.url.includes('https://fonts.gstatic.com')
|
||||
) {
|
||||
// Check for updates asynchronously.
|
||||
this.losslessServiceWorkerRef.updateManager.checkUpdate(this);
|
||||
|
||||
// Try to serve from cache.
|
||||
const matchRequest = createMatchRequest(originalRequest);
|
||||
const cachedResponse = await caches.match(matchRequest);
|
||||
if (cachedResponse) {
|
||||
logger.log('ok', `CACHED: found cached response for ${matchRequest.url}`);
|
||||
done.resolve(cachedResponse);
|
||||
// Block requests that we don't want the service worker to handle.
|
||||
if (
|
||||
parsedUrl.hostname.includes('paddle.com') ||
|
||||
parsedUrl.hostname.includes('paypal.com') ||
|
||||
parsedUrl.hostname.includes('reception.lossless.one') ||
|
||||
parsedUrl.pathname.startsWith('/socket.io') ||
|
||||
originalRequest.url.startsWith('https://umami.')
|
||||
) {
|
||||
logger.log('note', `ServiceWorker not active for ${parsedUrl.toString()}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// In case there is no cached response, fetch from the network.
|
||||
logger.log('info', `NOTYETCACHED: trying to cache ${matchRequest.url}`);
|
||||
const newResponse: Response = await fetch(matchRequest).catch(async err => {
|
||||
return await create500Response(matchRequest, new Response(err.message));
|
||||
});
|
||||
// Create a deferred promise for the fetch event's response.
|
||||
const done = plugins.smartpromise.defer<Response>();
|
||||
fetchEventArg.respondWith(done.promise);
|
||||
|
||||
// If the response status is an error or the response is opaque, do not cache it.
|
||||
if (newResponse.status > 299 || newResponse.type === 'opaque') {
|
||||
logger.log(
|
||||
'error',
|
||||
`NOTCACHED: not caching response for ${matchRequest.url} due to status ${newResponse.status} and type ${newResponse.type}`
|
||||
);
|
||||
// Simply return the network response without caching.
|
||||
done.resolve(newResponse);
|
||||
return;
|
||||
} else {
|
||||
// Cache the response.
|
||||
const cache = await caches.open(this.usedCacheNames.runtimeCacheName);
|
||||
const responseToPutToCache = newResponse.clone();
|
||||
const headers = new Headers();
|
||||
responseToPutToCache.headers.forEach((value, key) => {
|
||||
// Preserve all headers except caching-related ones.
|
||||
if (![
|
||||
'Cache-Control',
|
||||
'cache-control',
|
||||
'Expires',
|
||||
'expires',
|
||||
'Pragma',
|
||||
'pragma'
|
||||
].includes(key)) {
|
||||
headers.set(key, value);
|
||||
// Determine whether this request should be cached.
|
||||
if (
|
||||
(originalRequest.method === 'GET' &&
|
||||
originalRequest.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin) &&
|
||||
!originalRequest.url.includes('/api/') &&
|
||||
!originalRequest.url.includes('smartserve/reloadcheck')) ||
|
||||
originalRequest.url.includes('https://assetbroker.') ||
|
||||
originalRequest.url.includes('https://unpkg.com') ||
|
||||
originalRequest.url.includes('https://fonts.googleapis.com') ||
|
||||
originalRequest.url.includes('https://fonts.gstatic.com')
|
||||
) {
|
||||
// Kick off an asynchronous update check.
|
||||
this.losslessServiceWorkerRef.updateManager.checkUpdate(this);
|
||||
|
||||
const matchRequest = createMatchRequest(originalRequest);
|
||||
const cachedResponse = await caches.match(matchRequest);
|
||||
if (cachedResponse) {
|
||||
logger.log('ok', `CACHED: Found cached response for ${matchRequest.url}`);
|
||||
done.resolve(cachedResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('info', `NOTYETCACHED: Trying to cache ${matchRequest.url}`);
|
||||
let newResponse: Response;
|
||||
try {
|
||||
newResponse = await fetch(matchRequest);
|
||||
} catch (err: any) {
|
||||
logger.log('error', `Fetch error for ${matchRequest.url}: ${err}`);
|
||||
newResponse = await create500Response(matchRequest, new Response(err.message));
|
||||
}
|
||||
|
||||
// Check if the response should be cached. In this version, if the response status is >299 or the response is opaque, we do not cache.
|
||||
if (newResponse.status > 299 || newResponse.type === 'opaque') {
|
||||
logger.log(
|
||||
'error',
|
||||
`NOTCACHED: Can't cache response for ${matchRequest.url} (status: ${newResponse.status}, type: ${newResponse.type})`
|
||||
);
|
||||
// Optionally, you can force a 500 response so errors are clearly visible.
|
||||
done.resolve(await create500Response(matchRequest, newResponse));
|
||||
} else {
|
||||
try {
|
||||
const cache = await caches.open(this.usedCacheNames.runtimeCacheName);
|
||||
const responseToPutToCache = newResponse.clone();
|
||||
|
||||
// Create new headers preserving all except caching-related ones.
|
||||
const headers = new Headers();
|
||||
responseToPutToCache.headers.forEach((value, key) => {
|
||||
if (!['Cache-Control', 'cache-control', 'Expires', 'expires', 'Pragma', 'pragma'].includes(key)) {
|
||||
headers.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure that CORS-related headers are present.
|
||||
if (!headers.has('Access-Control-Allow-Origin')) {
|
||||
headers.set('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
if (!headers.has('Access-Control-Allow-Methods')) {
|
||||
headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
}
|
||||
if (!headers.has('Access-Control-Allow-Headers')) {
|
||||
headers.set('Access-Control-Allow-Headers', 'Content-Type');
|
||||
}
|
||||
|
||||
// Set Cross-Origin-Resource-Policy
|
||||
if (matchRequest.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin)) {
|
||||
// For same-origin resources
|
||||
headers.set('Cross-Origin-Resource-Policy', 'same-origin');
|
||||
} else {
|
||||
// For cross-origin resources that we explicitly allow
|
||||
headers.set('Cross-Origin-Resource-Policy', 'cross-origin');
|
||||
}
|
||||
|
||||
// Set caching headers - use modern Cache-Control only
|
||||
headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||
|
||||
// IMPORTANT: Read the full response body as a blob to avoid issues (e.g., Safari locked streams).
|
||||
const bodyBlob = await responseToPutToCache.blob();
|
||||
const newCachedResponse = new Response(bodyBlob, {
|
||||
status: responseToPutToCache.status,
|
||||
statusText: responseToPutToCache.statusText,
|
||||
headers
|
||||
});
|
||||
|
||||
await cache.put(matchRequest, newCachedResponse);
|
||||
logger.log('ok', `NOWCACHED: Cached response for ${matchRequest.url} for subsequent requests!`);
|
||||
done.resolve(newResponse);
|
||||
} catch (err) {
|
||||
logger.log('error', `Error caching response for ${matchRequest.url}: ${err}`);
|
||||
done.resolve(await create500Response(matchRequest, newResponse));
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure CORS headers are present.
|
||||
if (!headers.has('Access-Control-Allow-Origin')) {
|
||||
headers.set('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
if (!headers.has('Access-Control-Allow-Methods')) {
|
||||
headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
} else {
|
||||
// For requests not intended for caching, simply fetch from the origin.
|
||||
logger.log('ok', `NOTCACHED: Not caching ${originalRequest.url}. Fetching from origin...`);
|
||||
try {
|
||||
const originResponse = await fetch(originalRequest);
|
||||
done.resolve(originResponse);
|
||||
} catch (err: any) {
|
||||
logger.log('error', `Fetch error for ${originalRequest.url}: ${err}`);
|
||||
done.resolve(await create500Response(originalRequest, new Response(err.message)));
|
||||
}
|
||||
if (!headers.has('Access-Control-Allow-Headers')) {
|
||||
headers.set('Access-Control-Allow-Headers', 'Content-Type');
|
||||
}
|
||||
// Prevent browser caching while allowing service worker caching.
|
||||
headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||
headers.set('Pragma', 'no-cache');
|
||||
headers.set('Expires', '0');
|
||||
headers.set('Surrogate-Control', 'no-store');
|
||||
|
||||
await cache.put(matchRequest, new Response(responseToPutToCache.body, {
|
||||
...responseToPutToCache,
|
||||
headers
|
||||
}));
|
||||
logger.log('ok', `NOWCACHED: cached response for ${matchRequest.url} for subsequent requests!`);
|
||||
done.resolve(newResponse);
|
||||
}
|
||||
} else {
|
||||
// For remote requests not intended for caching, fetch directly from the origin.
|
||||
logger.log(
|
||||
'ok',
|
||||
`NOTCACHED: not caching any responses for ${originalRequest.url}. Fetching from origin now...`
|
||||
);
|
||||
done.resolve(
|
||||
await fetch(originalRequest).catch(async err => {
|
||||
return await create500Response(originalRequest, new Response(err.message));
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
logger.log('error', `Unhandled fetch event error: ${err}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleans all caches.
|
||||
* Should only be run when a new service worker is activated.
|
||||
* Should only be run when a new ServiceWorker is activated.
|
||||
*/
|
||||
public cleanCaches = async (reasonArg = 'no reason given') => {
|
||||
logger.log('info', `MAJOR CACHEEVENT: cleaning caches now! Reason: ${reasonArg}`);
|
||||
const cacheNames = await caches.keys();
|
||||
|
||||
const deletePromises = cacheNames.map(cacheToDelete => {
|
||||
const deletePromise = caches.delete(cacheToDelete);
|
||||
deletePromise.then(() => {
|
||||
logger.log('ok', `Deleted cache ${cacheToDelete}`);
|
||||
});
|
||||
return deletePromise;
|
||||
});
|
||||
await Promise.all(deletePromises);
|
||||
}
|
||||
public cleanCaches = async (reasonArg = 'no reason given'): Promise<void> => {
|
||||
try {
|
||||
logger.log('info', `MAJOR CACHEEVENT: Cleaning caches now! Reason: ${reasonArg}`);
|
||||
const cacheNames = await caches.keys();
|
||||
const deletePromises = cacheNames.map((cacheToDelete) =>
|
||||
caches.delete(cacheToDelete).then(() => {
|
||||
logger.log('ok', `Deleted cache ${cacheToDelete}`);
|
||||
})
|
||||
);
|
||||
await Promise.all(deletePromises);
|
||||
} catch (err) {
|
||||
logger.log('error', `Error cleaning caches: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Revalidates the runtime cache.
|
||||
* Revalidates the runtime cache by fetching fresh responses and updating the cache.
|
||||
*/
|
||||
public async revalidateCache() {
|
||||
const runtimeCache = await caches.open(this.usedCacheNames.runtimeCacheName);
|
||||
const cacheKeys = await runtimeCache.keys();
|
||||
for (const requestArg of cacheKeys) {
|
||||
// Fetch a new response for comparison.
|
||||
const clonedRequest = requestArg.clone();
|
||||
const response = await plugins.smartpromise.timeoutWrap(fetch(clonedRequest), 5000); // Increased timeout for better mobile compatibility
|
||||
if (response && response.status >= 200 && response.status < 300) {
|
||||
await runtimeCache.delete(requestArg);
|
||||
await runtimeCache.put(requestArg, response);
|
||||
public async revalidateCache(): Promise<void> {
|
||||
try {
|
||||
const runtimeCache = await caches.open(this.usedCacheNames.runtimeCacheName);
|
||||
const cacheKeys = await runtimeCache.keys();
|
||||
for (const requestArg of cacheKeys) {
|
||||
try {
|
||||
const clonedRequest = requestArg.clone();
|
||||
const response = await plugins.smartpromise.timeoutWrap(fetch(clonedRequest), 5000);
|
||||
if (response && response.status >= 200 && response.status < 300) {
|
||||
await runtimeCache.delete(requestArg);
|
||||
await runtimeCache.put(requestArg, response);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.log('error', `Error revalidating cache for ${requestArg.url}: ${err}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.log('error', `Error revalidating runtime cache: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
@ -104,14 +104,9 @@ export class UpdateManager {
|
||||
>('/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;
|
||||
const response = await getAppHashRequest.fire({});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
logger.log('warn', `Failed to get version info from server: ${error.message}`);
|
||||
throw error;
|
||||
|
Reference in New Issue
Block a user