fix(backup): synchronize calls to localForage (#3932)
This commit is contained in:
parent
330fadd1d7
commit
86562ad47a
@ -114,7 +114,9 @@ describe('Backend', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getLocalDraftBackup', () => {
|
describe('getLocalDraftBackup', () => {
|
||||||
const { localForage } = require('netlify-cms-lib-util');
|
const { localForage, asyncLock } = require('netlify-cms-lib-util');
|
||||||
|
|
||||||
|
asyncLock.mockImplementation(() => ({ acquire: jest.fn(), release: jest.fn() }));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
@ -34,6 +34,8 @@ import {
|
|||||||
getPathDepth,
|
getPathDepth,
|
||||||
Config as ImplementationConfig,
|
Config as ImplementationConfig,
|
||||||
blobToFileObj,
|
blobToFileObj,
|
||||||
|
asyncLock,
|
||||||
|
AsyncLock,
|
||||||
} from 'netlify-cms-lib-util';
|
} from 'netlify-cms-lib-util';
|
||||||
import { basename, join, extname, dirname } from 'path';
|
import { basename, join, extname, dirname } from 'path';
|
||||||
import { status } from './constants/publishModes';
|
import { status } from './constants/publishModes';
|
||||||
@ -178,6 +180,7 @@ export class Backend {
|
|||||||
authStore: AuthStore | null;
|
authStore: AuthStore | null;
|
||||||
config: Config;
|
config: Config;
|
||||||
user?: User | null;
|
user?: User | null;
|
||||||
|
backupSync: AsyncLock;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
implementation: Implementation,
|
implementation: Implementation,
|
||||||
@ -196,6 +199,7 @@ export class Backend {
|
|||||||
if (this.implementation === null) {
|
if (this.implementation === null) {
|
||||||
throw new Error('Cannot instantiate a Backend with no implementation');
|
throw new Error('Cannot instantiate a Backend with no implementation');
|
||||||
}
|
}
|
||||||
|
this.backupSync = asyncLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
async status() {
|
async status() {
|
||||||
@ -535,39 +539,56 @@ export class Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async persistLocalDraftBackup(entry: EntryMap, collection: Collection) {
|
async persistLocalDraftBackup(entry: EntryMap, collection: Collection) {
|
||||||
const key = getEntryBackupKey(collection.get('name'), entry.get('slug'));
|
try {
|
||||||
const raw = this.entryToRaw(collection, entry);
|
await this.backupSync.acquire();
|
||||||
if (!raw.trim()) {
|
const key = getEntryBackupKey(collection.get('name'), entry.get('slug'));
|
||||||
return;
|
const raw = this.entryToRaw(collection, entry);
|
||||||
|
|
||||||
|
if (!raw.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaFiles = await Promise.all<MediaFile>(
|
||||||
|
entry
|
||||||
|
.get('mediaFiles')
|
||||||
|
.toJS()
|
||||||
|
.map(async (file: MediaFile) => {
|
||||||
|
// make sure to serialize the file
|
||||||
|
if (file.url?.startsWith('blob:')) {
|
||||||
|
const blob = await fetch(file.url as string).then(res => res.blob());
|
||||||
|
return { ...file, file: blobToFileObj(file.name, blob) };
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await localForage.setItem<BackupEntry>(key, {
|
||||||
|
raw,
|
||||||
|
path: entry.get('path'),
|
||||||
|
mediaFiles,
|
||||||
|
});
|
||||||
|
const result = await localForage.setItem(getEntryBackupKey(), raw);
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('persistLocalDraftBackup', e);
|
||||||
|
} finally {
|
||||||
|
this.backupSync.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaFiles = await Promise.all<MediaFile>(
|
|
||||||
entry
|
|
||||||
.get('mediaFiles')
|
|
||||||
.toJS()
|
|
||||||
.map(async (file: MediaFile) => {
|
|
||||||
// make sure to serialize the file
|
|
||||||
if (file.url?.startsWith('blob:')) {
|
|
||||||
const blob = await fetch(file.url as string).then(res => res.blob());
|
|
||||||
return { ...file, file: blobToFileObj(file.name, blob) };
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
await localForage.setItem<BackupEntry>(key, {
|
|
||||||
raw,
|
|
||||||
path: entry.get('path'),
|
|
||||||
mediaFiles,
|
|
||||||
});
|
|
||||||
return localForage.setItem(getEntryBackupKey(), raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteLocalDraftBackup(collection: Collection, slug: string) {
|
async deleteLocalDraftBackup(collection: Collection, slug: string) {
|
||||||
await localForage.removeItem(getEntryBackupKey(collection.get('name'), slug));
|
try {
|
||||||
// delete new entry backup if not deleted
|
await this.backupSync.acquire();
|
||||||
slug && (await localForage.removeItem(getEntryBackupKey(collection.get('name'))));
|
await localForage.removeItem(getEntryBackupKey(collection.get('name'), slug));
|
||||||
return this.deleteAnonymousBackup();
|
// delete new entry backup if not deleted
|
||||||
|
slug && (await localForage.removeItem(getEntryBackupKey(collection.get('name'))));
|
||||||
|
const result = await this.deleteAnonymousBackup();
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('deleteLocalDraftBackup', e);
|
||||||
|
} finally {
|
||||||
|
this.backupSync.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unnamed backup for use in the global error boundary, should always be
|
// Unnamed backup for use in the global error boundary, should always be
|
||||||
|
Loading…
x
Reference in New Issue
Block a user