mirror of
https://github.com/suitenumerique/docs.git
synced 2026-04-25 17:15:01 +02:00
🥅(sw) improve requests fallback
We improve overall SW requests fallback. If the plugin fails we try to refetch the request without the plugin modifications, meaning the status code will be more in correlation with the actual server response and not the plugin error. We improved as well the cache fallback, if the cache failed because a store was missing, we delete the DB to be sure to have a DB in correlation with the current app version.
This commit is contained in:
committed by
Manuel Raynaud
parent
1a536712b3
commit
6370f4dbce
@@ -142,18 +142,33 @@ export class DocsDB {
|
||||
key: string,
|
||||
body: DocsResponse | Doc | DBRequest | DocContentCacheEntry,
|
||||
tableName: TableName,
|
||||
isRetry = false,
|
||||
): Promise<void> {
|
||||
const db = await DocsDB.open();
|
||||
|
||||
try {
|
||||
await db.put(tableName, body, key);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'SW: Failed to save response in IndexedDB',
|
||||
error,
|
||||
key,
|
||||
body,
|
||||
);
|
||||
db.close();
|
||||
// If the store is missing and we haven't retried yet, reset the DB once
|
||||
// (handles a PR that added a store without a version bump).
|
||||
// The isRetry guard prevents an infinite loop if the store name is invalid.
|
||||
if (!isRetry && !db.objectStoreNames.contains(tableName)) {
|
||||
console.warn(
|
||||
'SW: Missing object store, resetting IndexedDB and retrying',
|
||||
tableName,
|
||||
);
|
||||
await deleteDB(DocsDB.DBNAME);
|
||||
await DocsDB.cacheResponse(key, body, tableName, true);
|
||||
} else {
|
||||
console.error(
|
||||
'SW: Failed to save response in IndexedDB',
|
||||
error,
|
||||
key,
|
||||
body,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
||||
@@ -52,64 +52,68 @@ export class ApiPlugin implements WorkboxPlugin {
|
||||
request,
|
||||
response,
|
||||
}) => {
|
||||
// For content requests, a 304 means the document hasn't changed:
|
||||
// transparently serve the cached version from IDB.
|
||||
if (this.options.type === 'content' && response.status === 304) {
|
||||
const db = await DocsDB.open();
|
||||
const entry = await db.get('doc-content', request.url);
|
||||
db.close();
|
||||
if (entry) {
|
||||
return new Response(entry.content, {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
...(entry.etag && { ETag: entry.etag }),
|
||||
...(entry.lastModified && {
|
||||
'Last-Modified': entry.lastModified,
|
||||
}),
|
||||
},
|
||||
});
|
||||
try {
|
||||
// For content requests, a 304 means the document hasn't changed:
|
||||
// transparently serve the cached version from IDB.
|
||||
if (this.options.type === 'content' && response.status === 304) {
|
||||
const db = await DocsDB.open();
|
||||
const entry = await db.get('doc-content', request.url);
|
||||
db.close();
|
||||
if (entry) {
|
||||
return new Response(entry.content, {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
...(entry.etag && { ETag: entry.etag }),
|
||||
...(entry.lastModified && {
|
||||
'Last-Modified': entry.lastModified,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
return response;
|
||||
}
|
||||
|
||||
if (this.options.type === 'list' || this.options.type === 'item') {
|
||||
const tableName = this.options.tableName;
|
||||
const body = (await response.clone().json()) as DocsResponse | Doc;
|
||||
await DocsDB.cacheResponse(request.url, body, tableName);
|
||||
} else if (this.options.type === 'content') {
|
||||
// Cache the content response with its ETag / Last-Modified to be
|
||||
// able to use it for conditional requests and offline access.
|
||||
const content = await response.clone().text();
|
||||
const etag = response.headers.get('ETag') ?? '';
|
||||
const lastModified = response.headers.get('Last-Modified') ?? '';
|
||||
await DocsDB.cacheResponse(
|
||||
request.url,
|
||||
{ etag, lastModified, content },
|
||||
'doc-content',
|
||||
);
|
||||
} else if (this.options.type === 'update') {
|
||||
const db = await DocsDB.open();
|
||||
const storedResponse = await db.get('doc-item', request.url);
|
||||
|
||||
if (!storedResponse || !this.initialRequest) {
|
||||
if (response.status !== 200) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const bodyMutate = (await this.initialRequest
|
||||
.clone()
|
||||
.json()) as Partial<Doc>;
|
||||
if (this.options.type === 'list' || this.options.type === 'item') {
|
||||
const tableName = this.options.tableName;
|
||||
const body = (await response.clone().json()) as DocsResponse | Doc;
|
||||
await DocsDB.cacheResponse(request.url, body, tableName);
|
||||
} else if (this.options.type === 'content') {
|
||||
// Cache the content response with its ETag / Last-Modified to be
|
||||
// able to use it for conditional requests and offline access.
|
||||
const content = await response.clone().text();
|
||||
const etag = response.headers.get('ETag') ?? '';
|
||||
const lastModified = response.headers.get('Last-Modified') ?? '';
|
||||
await DocsDB.cacheResponse(
|
||||
request.url,
|
||||
{ etag, lastModified, content },
|
||||
'doc-content',
|
||||
);
|
||||
} else if (this.options.type === 'update') {
|
||||
const db = await DocsDB.open();
|
||||
const storedResponse = await db.get('doc-item', request.url);
|
||||
|
||||
const newResponse = {
|
||||
...storedResponse,
|
||||
...bodyMutate,
|
||||
};
|
||||
if (!storedResponse || !this.initialRequest) {
|
||||
return response;
|
||||
}
|
||||
|
||||
await DocsDB.cacheResponse(request.url, newResponse, 'doc-item');
|
||||
const bodyMutate = (await this.initialRequest
|
||||
.clone()
|
||||
.json()) as Partial<Doc>;
|
||||
|
||||
const newResponse = {
|
||||
...storedResponse,
|
||||
...bodyMutate,
|
||||
};
|
||||
|
||||
await DocsDB.cacheResponse(request.url, newResponse, 'doc-item');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('SW: ApiPlugin fetchDidSucceed DB error', error);
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -143,17 +147,21 @@ export class ApiPlugin implements WorkboxPlugin {
|
||||
// For content requests, add If-None-Match / If-Modified-Since from IDB
|
||||
// so the backend can return a 304 when the document hasn't changed.
|
||||
if (this.options.type === 'content') {
|
||||
const db = await DocsDB.open();
|
||||
const entry = await db.get('doc-content', request.url);
|
||||
db.close();
|
||||
if (entry?.etag || entry?.lastModified) {
|
||||
const headers = new Headers(request.headers);
|
||||
if (entry.etag) {
|
||||
headers.set('If-None-Match', entry.etag);
|
||||
} else {
|
||||
headers.set('If-Modified-Since', entry.lastModified);
|
||||
try {
|
||||
const db = await DocsDB.open();
|
||||
const entry = await db.get('doc-content', request.url);
|
||||
db.close();
|
||||
if (entry?.etag || entry?.lastModified) {
|
||||
const headers = new Headers(request.headers);
|
||||
if (entry.etag) {
|
||||
headers.set('If-None-Match', entry.etag);
|
||||
} else {
|
||||
headers.set('If-Modified-Since', entry.lastModified);
|
||||
}
|
||||
return new Request(request, { headers });
|
||||
}
|
||||
return new Request(request, { headers });
|
||||
} catch (error) {
|
||||
console.error('SW: ApiPlugin requestWillFetch content error', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +173,12 @@ export class ApiPlugin implements WorkboxPlugin {
|
||||
*/
|
||||
handlerDidError: WorkboxPlugin['handlerDidError'] = async ({ request }) => {
|
||||
if (!this.isFetchDidFailed) {
|
||||
return Promise.resolve(ApiPlugin.getApiCatchHandler());
|
||||
// it could be a plugin error, not a network error, so we try to do the request without the plugin.
|
||||
try {
|
||||
return await fetch(request);
|
||||
} catch {
|
||||
return ApiPlugin.getApiCatchHandler();
|
||||
}
|
||||
}
|
||||
|
||||
switch (this.options.type) {
|
||||
|
||||
Reference in New Issue
Block a user