@uppy/golden-retriever
Advanced tools
Comparing version 3.0.4 to 3.1.0
# @uppy/golden-retriever | ||
## 3.1.0 | ||
Released: 2023-07-06 | ||
Included in: Uppy v3.11.0 | ||
- @uppy/golden-retriever: refactor to modernize the codebase (Antoine du Hamel / #4520) | ||
## 3.0.4 | ||
@@ -4,0 +11,0 @@ |
@@ -7,3 +7,3 @@ import throttle from 'lodash/throttle.js'; | ||
const packageJson = { | ||
"version": "3.0.4" | ||
"version": "3.1.0" | ||
}; | ||
@@ -198,5 +198,6 @@ /** | ||
}; | ||
const fileToSaveEntries = Object.entries(filesToSave); | ||
// If all files have been removed by the user, clear recovery state | ||
if (Object.keys(filesToSave).length === 0) { | ||
if (fileToSaveEntries.length === 0) { | ||
if (this.uppy.getState().recoveredState !== null) { | ||
@@ -214,18 +215,14 @@ this.uppy.setState({ | ||
// Also adding file.isRestored to all files, since they will be restored from local storage | ||
const filesToSaveWithoutData = {}; | ||
Object.keys(filesToSave).forEach(file => { | ||
if (filesToSave[file].isRemote) { | ||
filesToSaveWithoutData[file] = { | ||
...filesToSave[file], | ||
isRestored: true | ||
}; | ||
} else { | ||
filesToSaveWithoutData[file] = { | ||
...filesToSave[file], | ||
isRestored: true, | ||
data: null, | ||
preview: null | ||
}; | ||
} | ||
}); | ||
const filesToSaveWithoutData = Object.fromEntries(fileToSaveEntries.map(_ref2 => { | ||
let [id, fileInfo] = _ref2; | ||
return [id, fileInfo.isRemote ? { | ||
...fileInfo, | ||
isRestored: true | ||
} : { | ||
...fileInfo, | ||
isRestored: true, | ||
data: null, | ||
preview: null | ||
}]; | ||
})); | ||
const pluginData = {}; | ||
@@ -329,12 +326,6 @@ // TODO Find a better way to do this? | ||
deleteBlobs(fileIDs) { | ||
const promises = []; | ||
fileIDs.forEach(id => { | ||
if (this.ServiceWorkerStore) { | ||
promises.push(this.ServiceWorkerStore.delete(id)); | ||
} | ||
if (this.IndexedDBStore) { | ||
promises.push(this.IndexedDBStore.delete(id)); | ||
} | ||
}); | ||
return Promise.all(promises); | ||
return Promise.all(fileIDs.map(id => { | ||
var _this$ServiceWorkerSt, _this$ServiceWorkerSt2, _this$IndexedDBStore; | ||
return (_this$ServiceWorkerSt = (_this$ServiceWorkerSt2 = this.ServiceWorkerStore) == null ? void 0 : _this$ServiceWorkerSt2.delete(id)) != null ? _this$ServiceWorkerSt : (_this$IndexedDBStore = this.IndexedDBStore) == null ? void 0 : _this$IndexedDBStore.delete(id); | ||
})); | ||
} | ||
@@ -341,0 +332,0 @@ install() { |
@@ -0,1 +1,7 @@ | ||
function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; } | ||
var id = 0; | ||
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; } | ||
/** | ||
* @type {typeof window.indexedDB} | ||
*/ | ||
const indexedDB = typeof window !== 'undefined' && (window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB); | ||
@@ -7,4 +13,9 @@ const isSupported = !!indexedDB; | ||
const DB_VERSION = 3; | ||
const MiB = 0x100000; | ||
// Set default `expires` dates on existing stored blobs. | ||
/** | ||
* Set default `expires` dates on existing stored blobs. | ||
* | ||
* @param {IDBObjectStore} store | ||
*/ | ||
function migrateExpiration(store) { | ||
@@ -22,2 +33,7 @@ const request = store.openCursor(); | ||
} | ||
/** | ||
* @param {string} dbName | ||
* @returns {Promise<IDBDatabase>} | ||
*/ | ||
function connect(dbName) { | ||
@@ -27,3 +43,9 @@ const request = indexedDB.open(dbName, DB_VERSION); | ||
request.onupgradeneeded = event => { | ||
/** | ||
* @type {IDBDatabase} | ||
*/ | ||
const db = event.target.result; | ||
/** | ||
* @type {IDBTransaction} | ||
*/ | ||
const { | ||
@@ -59,2 +81,8 @@ transaction | ||
} | ||
/** | ||
* @template T | ||
* @param {IDBRequest<T>} request | ||
* @returns {Promise<T>} | ||
*/ | ||
function waitForRequest(request) { | ||
@@ -69,4 +97,12 @@ return new Promise((resolve, reject) => { | ||
let cleanedUp = false; | ||
var _ready = /*#__PURE__*/_classPrivateFieldLooseKey("ready"); | ||
class IndexedDBStore { | ||
constructor(opts) { | ||
/** | ||
* @type {Promise<IDBDatabase> | IDBDatabase} | ||
*/ | ||
Object.defineProperty(this, _ready, { | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.opts = { | ||
@@ -77,19 +113,27 @@ dbName: DB_NAME, | ||
// 24 hours | ||
maxFileSize: 10 * 1024 * 1024, | ||
// 10 MB | ||
maxTotalSize: 300 * 1024 * 1024, | ||
// 300 MB | ||
maxFileSize: 10 * MiB, | ||
maxTotalSize: 300 * MiB, | ||
...opts | ||
}; | ||
this.name = this.opts.storeName; | ||
const createConnection = () => { | ||
return connect(this.opts.dbName); | ||
const createConnection = async () => { | ||
const db = await connect(this.opts.dbName); | ||
_classPrivateFieldLooseBase(this, _ready)[_ready] = db; | ||
return db; | ||
}; | ||
if (!cleanedUp) { | ||
cleanedUp = true; | ||
this.ready = IndexedDBStore.cleanup().then(createConnection, createConnection); | ||
_classPrivateFieldLooseBase(this, _ready)[_ready] = IndexedDBStore.cleanup().then(createConnection, createConnection); | ||
} else { | ||
this.ready = createConnection(); | ||
_classPrivateFieldLooseBase(this, _ready)[_ready] = createConnection(); | ||
} | ||
} | ||
get ready() { | ||
return Promise.resolve(_classPrivateFieldLooseBase(this, _ready)[_ready]); | ||
} | ||
// TODO: remove this setter in the next major | ||
set ready(val) { | ||
_classPrivateFieldLooseBase(this, _ready)[_ready] = val; | ||
} | ||
key(fileID) { | ||
@@ -102,15 +146,9 @@ return `${this.name}!${fileID}`; | ||
*/ | ||
list() { | ||
return this.ready.then(db => { | ||
const transaction = db.transaction([STORE_NAME], 'readonly'); | ||
const store = transaction.objectStore(STORE_NAME); | ||
const request = store.index('store').getAll(IDBKeyRange.only(this.name)); | ||
return waitForRequest(request); | ||
}).then(files => { | ||
const result = {}; | ||
files.forEach(file => { | ||
result[file.fileID] = file.data; | ||
}); | ||
return result; | ||
}); | ||
async list() { | ||
const db = await _classPrivateFieldLooseBase(this, _ready)[_ready]; | ||
const transaction = db.transaction([STORE_NAME], 'readonly'); | ||
const store = transaction.objectStore(STORE_NAME); | ||
const request = store.index('store').getAll(IDBKeyRange.only(this.name)); | ||
const files = await waitForRequest(request); | ||
return Object.fromEntries(files.map(file => [file.fileID, file.data])); | ||
} | ||
@@ -121,11 +159,13 @@ | ||
*/ | ||
get(fileID) { | ||
return this.ready.then(db => { | ||
const transaction = db.transaction([STORE_NAME], 'readonly'); | ||
const request = transaction.objectStore(STORE_NAME).get(this.key(fileID)); | ||
return waitForRequest(request); | ||
}).then(result => ({ | ||
id: result.data.fileID, | ||
data: result.data.data | ||
})); | ||
async get(fileID) { | ||
const db = await _classPrivateFieldLooseBase(this, _ready)[_ready]; | ||
const transaction = db.transaction([STORE_NAME], 'readonly'); | ||
const request = transaction.objectStore(STORE_NAME).get(this.key(fileID)); | ||
const { | ||
data | ||
} = await waitForRequest(request); | ||
return { | ||
id: data.fileID, | ||
data: data.data | ||
}; | ||
} | ||
@@ -137,23 +177,23 @@ | ||
* @private | ||
* @returns {Promise<number>} | ||
*/ | ||
getSize() { | ||
return this.ready.then(db => { | ||
const transaction = db.transaction([STORE_NAME], 'readonly'); | ||
const store = transaction.objectStore(STORE_NAME); | ||
const request = store.index('store').openCursor(IDBKeyRange.only(this.name)); | ||
return new Promise((resolve, reject) => { | ||
let size = 0; | ||
request.onsuccess = event => { | ||
const cursor = event.target.result; | ||
if (cursor) { | ||
size += cursor.value.data.size; | ||
cursor.continue(); | ||
} else { | ||
resolve(size); | ||
} | ||
}; | ||
request.onerror = () => { | ||
reject(new Error('Could not retrieve stored blobs size')); | ||
}; | ||
}); | ||
async getSize() { | ||
const db = await _classPrivateFieldLooseBase(this, _ready)[_ready]; | ||
const transaction = db.transaction([STORE_NAME], 'readonly'); | ||
const store = transaction.objectStore(STORE_NAME); | ||
const request = store.index('store').openCursor(IDBKeyRange.only(this.name)); | ||
return new Promise((resolve, reject) => { | ||
let size = 0; | ||
request.onsuccess = event => { | ||
const cursor = event.target.result; | ||
if (cursor) { | ||
size += cursor.value.data.size; | ||
cursor.continue(); | ||
} else { | ||
resolve(size); | ||
} | ||
}; | ||
request.onerror = () => { | ||
reject(new Error('Could not retrieve stored blobs size')); | ||
}; | ||
}); | ||
@@ -165,22 +205,20 @@ } | ||
*/ | ||
put(file) { | ||
async put(file) { | ||
if (file.data.size > this.opts.maxFileSize) { | ||
return Promise.reject(new Error('File is too big to store.')); | ||
throw new Error('File is too big to store.'); | ||
} | ||
return this.getSize().then(size => { | ||
if (size > this.opts.maxTotalSize) { | ||
return Promise.reject(new Error('No space left')); | ||
} | ||
return this.ready; | ||
}).then(db => { | ||
const transaction = db.transaction([STORE_NAME], 'readwrite'); | ||
const request = transaction.objectStore(STORE_NAME).add({ | ||
id: this.key(file.id), | ||
fileID: file.id, | ||
store: this.name, | ||
expires: Date.now() + this.opts.expires, | ||
data: file.data | ||
}); | ||
return waitForRequest(request); | ||
const size = await this.getSize(); | ||
if (size > this.opts.maxTotalSize) { | ||
throw new Error('No space left'); | ||
} | ||
const db = _classPrivateFieldLooseBase(this, _ready)[_ready]; | ||
const transaction = db.transaction([STORE_NAME], 'readwrite'); | ||
const request = transaction.objectStore(STORE_NAME).add({ | ||
id: this.key(file.id), | ||
fileID: file.id, | ||
store: this.name, | ||
expires: Date.now() + this.opts.expires, | ||
data: file.data | ||
}); | ||
return waitForRequest(request); | ||
} | ||
@@ -191,8 +229,7 @@ | ||
*/ | ||
delete(fileID) { | ||
return this.ready.then(db => { | ||
const transaction = db.transaction([STORE_NAME], 'readwrite'); | ||
const request = transaction.objectStore(STORE_NAME).delete(this.key(fileID)); | ||
return waitForRequest(request); | ||
}); | ||
async delete(fileID) { | ||
const db = await _classPrivateFieldLooseBase(this, _ready)[_ready]; | ||
const transaction = db.transaction([STORE_NAME], 'readwrite'); | ||
const request = transaction.objectStore(STORE_NAME).delete(this.key(fileID)); | ||
return waitForRequest(request); | ||
} | ||
@@ -204,22 +241,20 @@ | ||
*/ | ||
static cleanup() { | ||
return connect(DB_NAME).then(db => { | ||
const transaction = db.transaction([STORE_NAME], 'readwrite'); | ||
const store = transaction.objectStore(STORE_NAME); | ||
const request = store.index('expires').openCursor(IDBKeyRange.upperBound(Date.now())); | ||
return new Promise((resolve, reject) => { | ||
request.onsuccess = event => { | ||
const cursor = event.target.result; | ||
if (cursor) { | ||
cursor.delete(); // Ignoring return value … it's not terrible if this goes wrong. | ||
cursor.continue(); | ||
} else { | ||
resolve(db); | ||
} | ||
}; | ||
request.onerror = reject; | ||
}); | ||
}).then(db => { | ||
db.close(); | ||
static async cleanup() { | ||
const db = await connect(DB_NAME); | ||
const transaction = db.transaction([STORE_NAME], 'readwrite'); | ||
const store = transaction.objectStore(STORE_NAME); | ||
const request = store.index('expires').openCursor(IDBKeyRange.upperBound(Date.now())); | ||
await new Promise((resolve, reject) => { | ||
request.onsuccess = event => { | ||
const cursor = event.target.result; | ||
if (cursor) { | ||
cursor.delete(); // Ignoring return value … it's not terrible if this goes wrong. | ||
cursor.continue(); | ||
} else { | ||
resolve(); | ||
} | ||
}; | ||
request.onerror = reject; | ||
}); | ||
db.close(); | ||
} | ||
@@ -226,0 +261,0 @@ } |
@@ -8,3 +8,3 @@ /** | ||
const key = localStorage.key(i); | ||
if (/^uppyState:/.test(key)) { | ||
if (key.startsWith('uppyState:')) { | ||
instances.push(key.slice('uppyState:'.length)); | ||
@@ -22,3 +22,3 @@ } | ||
return JSON.parse(str); | ||
} catch (err) { | ||
} catch { | ||
return null; | ||
@@ -25,0 +25,0 @@ } |
@@ -5,5 +5,4 @@ /* globals clients */ | ||
function getCache(name) { | ||
if (!fileCache[name]) { | ||
fileCache[name] = Object.create(null); | ||
} | ||
var _fileCache$name; | ||
(_fileCache$name = fileCache[name]) != null ? _fileCache$name : fileCache[name] = Object.create(null); | ||
return fileCache[name]; | ||
@@ -10,0 +9,0 @@ } |
@@ -0,1 +1,4 @@ | ||
function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; } | ||
var id = 0; | ||
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; } | ||
const isSupported = typeof navigator !== 'undefined' && 'serviceWorker' in navigator; | ||
@@ -16,27 +19,38 @@ function waitForServiceWorker() { | ||
} | ||
var _ready = /*#__PURE__*/_classPrivateFieldLooseKey("ready"); | ||
class ServiceWorkerStore { | ||
constructor(opts) { | ||
this.ready = waitForServiceWorker(); | ||
Object.defineProperty(this, _ready, { | ||
writable: true, | ||
value: void 0 | ||
}); | ||
_classPrivateFieldLooseBase(this, _ready)[_ready] = waitForServiceWorker().then(val => { | ||
_classPrivateFieldLooseBase(this, _ready)[_ready] = val; | ||
}); | ||
this.name = opts.storeName; | ||
} | ||
list() { | ||
const defer = {}; | ||
const promise = new Promise((resolve, reject) => { | ||
defer.resolve = resolve; | ||
defer.reject = reject; | ||
}); | ||
const onMessage = event => { | ||
if (event.data.store !== this.name) { | ||
return; | ||
} | ||
switch (event.data.type) { | ||
case 'uppy/ALL_FILES': | ||
defer.resolve(event.data.files); | ||
navigator.serviceWorker.removeEventListener('message', onMessage); | ||
break; | ||
default: | ||
defer.reject(); | ||
} | ||
}; | ||
this.ready.then(() => { | ||
get ready() { | ||
return Promise.resolve(_classPrivateFieldLooseBase(this, _ready)[_ready]); | ||
} | ||
// TODO: remove this setter in the next major | ||
set ready(val) { | ||
_classPrivateFieldLooseBase(this, _ready)[_ready] = val; | ||
} | ||
async list() { | ||
await _classPrivateFieldLooseBase(this, _ready)[_ready]; | ||
return new Promise((resolve, reject) => { | ||
const onMessage = event => { | ||
if (event.data.store !== this.name) { | ||
return; | ||
} | ||
switch (event.data.type) { | ||
case 'uppy/ALL_FILES': | ||
resolve(event.data.files); | ||
navigator.serviceWorker.removeEventListener('message', onMessage); | ||
break; | ||
default: | ||
reject(); | ||
} | ||
}; | ||
navigator.serviceWorker.addEventListener('message', onMessage); | ||
@@ -48,20 +62,17 @@ navigator.serviceWorker.controller.postMessage({ | ||
}); | ||
return promise; | ||
} | ||
put(file) { | ||
return this.ready.then(() => { | ||
navigator.serviceWorker.controller.postMessage({ | ||
type: 'uppy/ADD_FILE', | ||
store: this.name, | ||
file | ||
}); | ||
async put(file) { | ||
await _classPrivateFieldLooseBase(this, _ready)[_ready]; | ||
navigator.serviceWorker.controller.postMessage({ | ||
type: 'uppy/ADD_FILE', | ||
store: this.name, | ||
file | ||
}); | ||
} | ||
delete(fileID) { | ||
return this.ready.then(() => { | ||
navigator.serviceWorker.controller.postMessage({ | ||
type: 'uppy/REMOVE_FILE', | ||
store: this.name, | ||
fileID | ||
}); | ||
async delete(fileID) { | ||
await _classPrivateFieldLooseBase(this, _ready)[_ready]; | ||
navigator.serviceWorker.controller.postMessage({ | ||
type: 'uppy/REMOVE_FILE', | ||
store: this.name, | ||
fileID | ||
}); | ||
@@ -68,0 +79,0 @@ } |
{ | ||
"name": "@uppy/golden-retriever", | ||
"description": "The GoldenRetriever Uppy plugin saves selected files in browser cache to seamlessly resume uploding after browser crash or accidentally closed tab", | ||
"version": "3.0.4", | ||
"version": "3.1.0", | ||
"license": "MIT", | ||
@@ -32,4 +32,4 @@ "main": "lib/index.js", | ||
"peerDependencies": { | ||
"@uppy/core": "^3.2.1" | ||
"@uppy/core": "^3.3.0" | ||
} | ||
} |
@@ -113,5 +113,6 @@ import throttle from 'lodash/throttle.js' | ||
} | ||
const fileToSaveEntries = Object.entries(filesToSave) | ||
// If all files have been removed by the user, clear recovery state | ||
if (Object.keys(filesToSave).length === 0) { | ||
if (fileToSaveEntries.length === 0) { | ||
if (this.uppy.getState().recoveredState !== null) { | ||
@@ -127,18 +128,14 @@ this.uppy.setState({ recoveredState: null }) | ||
// Also adding file.isRestored to all files, since they will be restored from local storage | ||
const filesToSaveWithoutData = {} | ||
Object.keys(filesToSave).forEach((file) => { | ||
if (filesToSave[file].isRemote) { | ||
filesToSaveWithoutData[file] = { | ||
...filesToSave[file], | ||
isRestored: true, | ||
} | ||
} else { | ||
filesToSaveWithoutData[file] = { | ||
...filesToSave[file], | ||
isRestored: true, | ||
data: null, | ||
preview: null, | ||
} | ||
const filesToSaveWithoutData = Object.fromEntries(fileToSaveEntries.map(([id, fileInfo]) => [id, fileInfo.isRemote | ||
? { | ||
...fileInfo, | ||
isRestored: true, | ||
} | ||
}) | ||
: { | ||
...fileInfo, | ||
isRestored: true, | ||
data: null, | ||
preview: null, | ||
}, | ||
])) | ||
@@ -250,12 +247,3 @@ const pluginData = {} | ||
deleteBlobs (fileIDs) { | ||
const promises = [] | ||
fileIDs.forEach((id) => { | ||
if (this.ServiceWorkerStore) { | ||
promises.push(this.ServiceWorkerStore.delete(id)) | ||
} | ||
if (this.IndexedDBStore) { | ||
promises.push(this.IndexedDBStore.delete(id)) | ||
} | ||
}) | ||
return Promise.all(promises) | ||
return Promise.all(fileIDs.map(id => this.ServiceWorkerStore?.delete(id) ?? this.IndexedDBStore?.delete(id))) | ||
} | ||
@@ -262,0 +250,0 @@ |
@@ -0,1 +1,4 @@ | ||
/** | ||
* @type {typeof window.indexedDB} | ||
*/ | ||
const indexedDB = typeof window !== 'undefined' | ||
@@ -10,4 +13,9 @@ && (window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB) | ||
const DB_VERSION = 3 | ||
const MiB = 0x10_00_00 | ||
// Set default `expires` dates on existing stored blobs. | ||
/** | ||
* Set default `expires` dates on existing stored blobs. | ||
* | ||
* @param {IDBObjectStore} store | ||
*/ | ||
function migrateExpiration (store) { | ||
@@ -26,2 +34,6 @@ const request = store.openCursor() | ||
/** | ||
* @param {string} dbName | ||
* @returns {Promise<IDBDatabase>} | ||
*/ | ||
function connect (dbName) { | ||
@@ -31,3 +43,9 @@ const request = indexedDB.open(dbName, DB_VERSION) | ||
request.onupgradeneeded = (event) => { | ||
/** | ||
* @type {IDBDatabase} | ||
*/ | ||
const db = event.target.result | ||
/** | ||
* @type {IDBTransaction} | ||
*/ | ||
const { transaction } = event.currentTarget | ||
@@ -60,2 +78,7 @@ | ||
/** | ||
* @template T | ||
* @param {IDBRequest<T>} request | ||
* @returns {Promise<T>} | ||
*/ | ||
function waitForRequest (request) { | ||
@@ -72,2 +95,7 @@ return new Promise((resolve, reject) => { | ||
class IndexedDBStore { | ||
/** | ||
* @type {Promise<IDBDatabase> | IDBDatabase} | ||
*/ | ||
#ready | ||
constructor (opts) { | ||
@@ -78,4 +106,4 @@ this.opts = { | ||
expires: DEFAULT_EXPIRY, // 24 hours | ||
maxFileSize: 10 * 1024 * 1024, // 10 MB | ||
maxTotalSize: 300 * 1024 * 1024, // 300 MB | ||
maxFileSize: 10 * MiB, | ||
maxTotalSize: 300 * MiB, | ||
...opts, | ||
@@ -86,4 +114,6 @@ } | ||
const createConnection = () => { | ||
return connect(this.opts.dbName) | ||
const createConnection = async () => { | ||
const db = await connect(this.opts.dbName) | ||
this.#ready = db | ||
return db | ||
} | ||
@@ -93,9 +123,18 @@ | ||
cleanedUp = true | ||
this.ready = IndexedDBStore.cleanup() | ||
this.#ready = IndexedDBStore.cleanup() | ||
.then(createConnection, createConnection) | ||
} else { | ||
this.ready = createConnection() | ||
this.#ready = createConnection() | ||
} | ||
} | ||
get ready () { | ||
return Promise.resolve(this.#ready) | ||
} | ||
// TODO: remove this setter in the next major | ||
set ready (val) { | ||
this.#ready = val | ||
} | ||
key (fileID) { | ||
@@ -108,16 +147,10 @@ return `${this.name}!${fileID}` | ||
*/ | ||
list () { | ||
return this.ready.then((db) => { | ||
const transaction = db.transaction([STORE_NAME], 'readonly') | ||
const store = transaction.objectStore(STORE_NAME) | ||
const request = store.index('store') | ||
.getAll(IDBKeyRange.only(this.name)) | ||
return waitForRequest(request) | ||
}).then((files) => { | ||
const result = {} | ||
files.forEach((file) => { | ||
result[file.fileID] = file.data | ||
}) | ||
return result | ||
}) | ||
async list () { | ||
const db = await this.#ready | ||
const transaction = db.transaction([STORE_NAME], 'readonly') | ||
const store = transaction.objectStore(STORE_NAME) | ||
const request = store.index('store') | ||
.getAll(IDBKeyRange.only(this.name)) | ||
const files = await waitForRequest(request) | ||
return Object.fromEntries(files.map(file => [file.fileID, file.data])) | ||
} | ||
@@ -128,12 +161,12 @@ | ||
*/ | ||
get (fileID) { | ||
return this.ready.then((db) => { | ||
const transaction = db.transaction([STORE_NAME], 'readonly') | ||
const request = transaction.objectStore(STORE_NAME) | ||
.get(this.key(fileID)) | ||
return waitForRequest(request) | ||
}).then((result) => ({ | ||
id: result.data.fileID, | ||
data: result.data.data, | ||
})) | ||
async get (fileID) { | ||
const db = await this.#ready | ||
const transaction = db.transaction([STORE_NAME], 'readonly') | ||
const request = transaction.objectStore(STORE_NAME) | ||
.get(this.key(fileID)) | ||
const { data } = await waitForRequest(request) | ||
return { | ||
id: data.fileID, | ||
data: data.data, | ||
} | ||
} | ||
@@ -145,24 +178,24 @@ | ||
* @private | ||
* @returns {Promise<number>} | ||
*/ | ||
getSize () { | ||
return this.ready.then((db) => { | ||
const transaction = db.transaction([STORE_NAME], 'readonly') | ||
const store = transaction.objectStore(STORE_NAME) | ||
const request = store.index('store') | ||
.openCursor(IDBKeyRange.only(this.name)) | ||
return new Promise((resolve, reject) => { | ||
let size = 0 | ||
request.onsuccess = (event) => { | ||
const cursor = event.target.result | ||
if (cursor) { | ||
size += cursor.value.data.size | ||
cursor.continue() | ||
} else { | ||
resolve(size) | ||
} | ||
async getSize () { | ||
const db = await this.#ready | ||
const transaction = db.transaction([STORE_NAME], 'readonly') | ||
const store = transaction.objectStore(STORE_NAME) | ||
const request = store.index('store') | ||
.openCursor(IDBKeyRange.only(this.name)) | ||
return new Promise((resolve, reject) => { | ||
let size = 0 | ||
request.onsuccess = (event) => { | ||
const cursor = event.target.result | ||
if (cursor) { | ||
size += cursor.value.data.size | ||
cursor.continue() | ||
} else { | ||
resolve(size) | ||
} | ||
request.onerror = () => { | ||
reject(new Error('Could not retrieve stored blobs size')) | ||
} | ||
}) | ||
} | ||
request.onerror = () => { | ||
reject(new Error('Could not retrieve stored blobs size')) | ||
} | ||
}) | ||
@@ -174,22 +207,20 @@ } | ||
*/ | ||
put (file) { | ||
async put (file) { | ||
if (file.data.size > this.opts.maxFileSize) { | ||
return Promise.reject(new Error('File is too big to store.')) | ||
throw new Error('File is too big to store.') | ||
} | ||
return this.getSize().then((size) => { | ||
if (size > this.opts.maxTotalSize) { | ||
return Promise.reject(new Error('No space left')) | ||
} | ||
return this.ready | ||
}).then((db) => { | ||
const transaction = db.transaction([STORE_NAME], 'readwrite') | ||
const request = transaction.objectStore(STORE_NAME).add({ | ||
id: this.key(file.id), | ||
fileID: file.id, | ||
store: this.name, | ||
expires: Date.now() + this.opts.expires, | ||
data: file.data, | ||
}) | ||
return waitForRequest(request) | ||
const size = await this.getSize() | ||
if (size > this.opts.maxTotalSize) { | ||
throw new Error('No space left') | ||
} | ||
const db = this.#ready | ||
const transaction = db.transaction([STORE_NAME], 'readwrite') | ||
const request = transaction.objectStore(STORE_NAME).add({ | ||
id: this.key(file.id), | ||
fileID: file.id, | ||
store: this.name, | ||
expires: Date.now() + this.opts.expires, | ||
data: file.data, | ||
}) | ||
return waitForRequest(request) | ||
} | ||
@@ -200,9 +231,8 @@ | ||
*/ | ||
delete (fileID) { | ||
return this.ready.then((db) => { | ||
const transaction = db.transaction([STORE_NAME], 'readwrite') | ||
const request = transaction.objectStore(STORE_NAME) | ||
.delete(this.key(fileID)) | ||
return waitForRequest(request) | ||
}) | ||
async delete (fileID) { | ||
const db = await this.#ready | ||
const transaction = db.transaction([STORE_NAME], 'readwrite') | ||
const request = transaction.objectStore(STORE_NAME) | ||
.delete(this.key(fileID)) | ||
return waitForRequest(request) | ||
} | ||
@@ -214,23 +244,21 @@ | ||
*/ | ||
static cleanup () { | ||
return connect(DB_NAME).then((db) => { | ||
const transaction = db.transaction([STORE_NAME], 'readwrite') | ||
const store = transaction.objectStore(STORE_NAME) | ||
const request = store.index('expires') | ||
.openCursor(IDBKeyRange.upperBound(Date.now())) | ||
return new Promise((resolve, reject) => { | ||
request.onsuccess = (event) => { | ||
const cursor = event.target.result | ||
if (cursor) { | ||
cursor.delete() // Ignoring return value … it's not terrible if this goes wrong. | ||
cursor.continue() | ||
} else { | ||
resolve(db) | ||
} | ||
static async cleanup () { | ||
const db = await connect(DB_NAME) | ||
const transaction = db.transaction([STORE_NAME], 'readwrite') | ||
const store = transaction.objectStore(STORE_NAME) | ||
const request = store.index('expires') | ||
.openCursor(IDBKeyRange.upperBound(Date.now())) | ||
await new Promise((resolve, reject) => { | ||
request.onsuccess = (event) => { | ||
const cursor = event.target.result | ||
if (cursor) { | ||
cursor.delete() // Ignoring return value … it's not terrible if this goes wrong. | ||
cursor.continue() | ||
} else { | ||
resolve() | ||
} | ||
request.onerror = reject | ||
}) | ||
}).then((db) => { | ||
db.close() | ||
} | ||
request.onerror = reject | ||
}) | ||
db.close() | ||
} | ||
@@ -237,0 +265,0 @@ } |
@@ -8,3 +8,3 @@ /** | ||
const key = localStorage.key(i) | ||
if (/^uppyState:/.test(key)) { | ||
if (key.startsWith('uppyState:')) { | ||
instances.push(key.slice('uppyState:'.length)) | ||
@@ -22,3 +22,3 @@ } | ||
return JSON.parse(str) | ||
} catch (err) { | ||
} catch { | ||
return null | ||
@@ -25,0 +25,0 @@ } |
@@ -6,5 +6,3 @@ /* globals clients */ | ||
function getCache (name) { | ||
if (!fileCache[name]) { | ||
fileCache[name] = Object.create(null) | ||
} | ||
fileCache[name] ??= Object.create(null) | ||
return fileCache[name] | ||
@@ -11,0 +9,0 @@ } |
@@ -19,29 +19,36 @@ const isSupported = typeof navigator !== 'undefined' && 'serviceWorker' in navigator | ||
class ServiceWorkerStore { | ||
#ready | ||
constructor (opts) { | ||
this.ready = waitForServiceWorker() | ||
this.#ready = waitForServiceWorker().then((val) => { this.#ready = val }) | ||
this.name = opts.storeName | ||
} | ||
list () { | ||
const defer = {} | ||
const promise = new Promise((resolve, reject) => { | ||
defer.resolve = resolve | ||
defer.reject = reject | ||
}) | ||
get ready () { | ||
return Promise.resolve(this.#ready) | ||
} | ||
const onMessage = (event) => { | ||
if (event.data.store !== this.name) { | ||
return | ||
// TODO: remove this setter in the next major | ||
set ready (val) { | ||
this.#ready = val | ||
} | ||
async list () { | ||
await this.#ready | ||
return new Promise((resolve, reject) => { | ||
const onMessage = (event) => { | ||
if (event.data.store !== this.name) { | ||
return | ||
} | ||
switch (event.data.type) { | ||
case 'uppy/ALL_FILES': | ||
resolve(event.data.files) | ||
navigator.serviceWorker.removeEventListener('message', onMessage) | ||
break | ||
default: | ||
reject() | ||
} | ||
} | ||
switch (event.data.type) { | ||
case 'uppy/ALL_FILES': | ||
defer.resolve(event.data.files) | ||
navigator.serviceWorker.removeEventListener('message', onMessage) | ||
break | ||
default: | ||
defer.reject() | ||
} | ||
} | ||
this.ready.then(() => { | ||
navigator.serviceWorker.addEventListener('message', onMessage) | ||
@@ -54,23 +61,19 @@ | ||
}) | ||
return promise | ||
} | ||
put (file) { | ||
return this.ready.then(() => { | ||
navigator.serviceWorker.controller.postMessage({ | ||
type: 'uppy/ADD_FILE', | ||
store: this.name, | ||
file, | ||
}) | ||
async put (file) { | ||
await this.#ready | ||
navigator.serviceWorker.controller.postMessage({ | ||
type: 'uppy/ADD_FILE', | ||
store: this.name, | ||
file, | ||
}) | ||
} | ||
delete (fileID) { | ||
return this.ready.then(() => { | ||
navigator.serviceWorker.controller.postMessage({ | ||
type: 'uppy/REMOVE_FILE', | ||
store: this.name, | ||
fileID, | ||
}) | ||
async delete (fileID) { | ||
await this.#ready | ||
navigator.serviceWorker.controller.postMessage({ | ||
type: 'uppy/REMOVE_FILE', | ||
store: this.name, | ||
fileID, | ||
}) | ||
@@ -77,0 +80,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
110270
1562