2024-05-11 10:51:20 +00:00
|
|
|
|
import * as plugins from './plugins.js';
|
|
|
|
|
import * as interfaces from './env.js';
|
2024-05-24 23:24:02 +00:00
|
|
|
|
import { logger } from './logging.js';
|
|
|
|
|
import { ServiceWorker } from './classes.serviceworker.js';
|
2024-05-11 10:51:20 +00:00
|
|
|
|
|
|
|
|
|
export class CacheManager {
|
2024-05-23 13:28:41 +00:00
|
|
|
|
public losslessServiceWorkerRef: ServiceWorker;
|
2024-05-11 10:51:20 +00:00
|
|
|
|
|
|
|
|
|
public usedCacheNames = {
|
|
|
|
|
runtimeCacheName: 'runtime'
|
|
|
|
|
};
|
|
|
|
|
|
2024-05-23 13:28:41 +00:00
|
|
|
|
constructor(losslessServiceWorkerRefArg: ServiceWorker) {
|
2024-05-11 10:51:20 +00:00
|
|
|
|
this.losslessServiceWorkerRef = losslessServiceWorkerRefArg;
|
|
|
|
|
this._setupCache();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _setupCache = () => {
|
|
|
|
|
const createMatchRequest = (requestArg: Request) => {
|
|
|
|
|
// lets create a matchRequest
|
|
|
|
|
let matchRequest: Request;
|
|
|
|
|
if (requestArg.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin)) {
|
|
|
|
|
// internal request
|
|
|
|
|
matchRequest = requestArg;
|
|
|
|
|
} else {
|
|
|
|
|
matchRequest = new Request(requestArg.url, {
|
|
|
|
|
...requestArg.clone(),
|
|
|
|
|
mode: 'cors'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return matchRequest;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* creates a 500 response
|
|
|
|
|
*/
|
|
|
|
|
const create500Response = async (requestArg: Request, responseArg: 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
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// A list of local resources we always want to be cached.
|
|
|
|
|
this.losslessServiceWorkerRef.serviceWindowRef.addEventListener('fetch', async (fetchEventArg: any) => {
|
|
|
|
|
// Lets block scopes we don't want to be passing through the serviceworker
|
|
|
|
|
const parsedUrl = new URL(fetchEventArg.request.url)
|
|
|
|
|
if (
|
|
|
|
|
parsedUrl.hostname.includes('paddle.com')
|
|
|
|
|
|| parsedUrl.hostname.includes('paypal.com')
|
|
|
|
|
|| parsedUrl.hostname.includes('reception.lossless.one')
|
|
|
|
|
|| parsedUrl.pathname.startsWith('/socket.io')
|
|
|
|
|
) {
|
|
|
|
|
logger.log('note',`serviceworker not active for ${parsedUrl.toString()}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// lets continue for the rest
|
|
|
|
|
const done = plugins.smartpromise.defer<Response>();
|
|
|
|
|
fetchEventArg.respondWith(done.promise);
|
|
|
|
|
const originalRequest: Request = fetchEventArg.request;
|
|
|
|
|
|
|
|
|
|
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.lossless.one/public') ||
|
|
|
|
|
originalRequest.url.includes('https://assetbroker.lossless.one/brandfiles') ||
|
|
|
|
|
originalRequest.url.includes('https://assetbroker.lossless.one/websites') ||
|
|
|
|
|
originalRequest.url.includes('https://unpkg.com') ||
|
|
|
|
|
originalRequest.url.includes('https://fonts.googleapis.com') ||
|
|
|
|
|
originalRequest.url.includes('https://fonts.gstatic.com')
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
// lets see if things need to be updated
|
|
|
|
|
// not waiting here
|
|
|
|
|
this.losslessServiceWorkerRef.updateManager.checkUpdate(this);
|
|
|
|
|
|
|
|
|
|
// this code block is executed for local requests
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// in case there is no cached response
|
|
|
|
|
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));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// fill cache
|
|
|
|
|
// Put a copy of the response in the runtime cache.
|
|
|
|
|
if (newResponse.status > 299 || newResponse.type === 'opaque') {
|
|
|
|
|
logger.log(
|
|
|
|
|
'error',
|
|
|
|
|
`NOTCACHED: can't cache response for ${matchRequest.url} due to status ${
|
|
|
|
|
newResponse.status
|
|
|
|
|
} and type ${newResponse.type}`
|
|
|
|
|
);
|
|
|
|
|
done.resolve(await create500Response(matchRequest, newResponse));
|
|
|
|
|
} else {
|
|
|
|
|
const cache = await caches.open(this.usedCacheNames.runtimeCacheName);
|
|
|
|
|
const responseToPutToCache = newResponse.clone();
|
|
|
|
|
const headers = new Headers();
|
|
|
|
|
responseToPutToCache.headers.forEach((value, key) => {
|
|
|
|
|
if (
|
|
|
|
|
value !== 'Cache-Control'
|
|
|
|
|
&& value !== 'cache-control'
|
|
|
|
|
&& value !== 'Expires'
|
|
|
|
|
&& value !== 'expires'
|
|
|
|
|
&& value !== 'Pragma'
|
|
|
|
|
&& value !== 'pragma'
|
|
|
|
|
) {
|
|
|
|
|
headers.set(key, value);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
|
|
|
headers.set('Pragma', 'no-cache');
|
|
|
|
|
headers.set('Expires', '0');
|
|
|
|
|
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 {
|
|
|
|
|
// this code block is executed for remote requests
|
|
|
|
|
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));
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* update caches
|
|
|
|
|
* @param reasonArg
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* cleans all caches
|
|
|
|
|
* should only be run when running a new service worker
|
|
|
|
|
* @param reasonArg
|
|
|
|
|
*/
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* revalidate cache
|
|
|
|
|
*/
|
|
|
|
|
public async revalidateCache() {
|
|
|
|
|
const runtimeCache = await caches.open(this.usedCacheNames.runtimeCacheName);
|
|
|
|
|
const cacheKeys = await runtimeCache.keys();
|
|
|
|
|
for (const requestArg of cacheKeys) {
|
|
|
|
|
const cachedResponse = runtimeCache.match(requestArg);
|
|
|
|
|
|
|
|
|
|
// lets get a new response for comparison
|
|
|
|
|
const clonedRequest = requestArg.clone();
|
|
|
|
|
const response = await plugins.smartpromise.timeoutWrap(fetch(clonedRequest), 1000);
|
|
|
|
|
if (response && response.status >= 200 && response.status < 300) {
|
|
|
|
|
await runtimeCache.delete(requestArg);
|
|
|
|
|
await runtimeCache.put(requestArg, response);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|