Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support IndexedDB base URL storage #677

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
13 changes: 11 additions & 2 deletions lib/browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@ import { enableDebugLog } from '../logger.js'
import DetailedError from '../error.js'

import { canStoreURLs, WebStorageUrlStorage } from './urlStorage.js'
import {IndexedDBUrlStorage,canStoreURLsInIndexedDB} from './urlStorageIndexedDB.js'
import DefaultHttpStack from './httpStack.js'
import FileReader from './fileReader.js'
import fingerprint from './fileSignature.js'

const getUrlStorage=(useIndexedDBForUrlStorage)=>{
if(useIndexedDBForUrlStorage && canStoreURLsInIndexedDB){
return new IndexedDBUrlStorage()
}
return canStoreURLs ? new WebStorageUrlStorage() : new NoopUrlStorage()
}

const defaultOptions = {
...BaseUpload.defaultOptions,
httpStack: new DefaultHttpStack(),
Expand All @@ -18,12 +26,12 @@ const defaultOptions = {

class Upload extends BaseUpload {
constructor(file = null, options = {}) {
options = { ...defaultOptions, ...options }
options = { ...defaultOptions, ...options, urlStorage: getUrlStorage(options.useIndexedDBForUrlStorage) }
super(file, options)
}

static terminate(url, options = {}) {
options = { ...defaultOptions, ...options }
options = { ...defaultOptions, ...options, urlStorage: getUrlStorage(options.useIndexedDBForUrlStorage) }
return BaseUpload.terminate(url, options)
}
}
Expand All @@ -37,6 +45,7 @@ const isSupported =
export {
Upload,
canStoreURLs,
canStoreURLsInIndexedDB,
defaultOptions,
isSupported,
enableDebugLog,
Expand Down
112 changes: 112 additions & 0 deletions lib/browser/urlStorageIndexedDB.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
const isSupportIndexedDB = () => {
return 'indexedDB' in window

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some reason not to use globalThis rather than window here?

};

let hasStorage = false;
try {
hasStorage = isSupportIndexedDB();
} catch (e) {
if (e.code === e.SECURITY_ERR || e.code === e.QUOTA_EXCEEDED_ERR) {
hasStorage = false;
} else {
throw e;
}
}

export const canStoreURLsInIndexedDB = hasStorage;

export class IndexedDBUrlStorage {
constructor() {
this.dbName = 'tusUrlStorage';
this.storeName = 'upload';
this.dbPromise = this.openDatabase();
}

openDatabase() {
return new Promise((resolve, reject) => {
const openRequest = indexedDB.open(this.dbName);
openRequest.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName, {keyPath: 'urlStorageKey'});
}
};
openRequest.onsuccess = () => resolve(openRequest.result);
openRequest.onerror = (event) => reject(event);
});
}

async _getAllUploadWithKeys() {
try {
const db = await this.dbPromise;
const transaction = db.transaction(this.storeName, 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.getAll();
const results = await new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = reject;
});
return results.map((result) => ({
...result,
urlStorageKey: result.urlStorageKey,
}));
} catch (error) {
throw error;
}
}

async findAllUploads() {
try {
const results = await this._getAllUploadWithKeys();
return results;
} catch (error) {
throw error;
}
}

async findUploadsByFingerprint(fingerprint) {
try {
const allData = await this._getAllUploadWithKeys();
const results = allData.find(
(data) => data.urlStorageKey.startsWith(`tus::${fingerprint}::`)
);

return results ? [results] : [];
} catch (error) {
throw error;
}
}

async removeUpload(urlStorageKey) {
try {
const db = await this.dbPromise;
const transaction = db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.delete(urlStorageKey);
await new Promise((resolve, reject) => {
request.onsuccess = resolve;
request.onerror = reject;
});
} catch (error) {
throw error;
}
}

async addUpload(fingerprint, upload) {
try {
const id = Math.round(Math.random() * 1e12);
const key = `tus::${fingerprint}::${id}`;
const db = await this.dbPromise;
const transaction = db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.put({urlStorageKey: key, ...upload});
await new Promise((resolve, reject) => {
request.onsuccess = () => resolve(key);
request.onerror = reject;
});
return key;
} catch (error) {
throw error;
}
}
}
2 changes: 1 addition & 1 deletion lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface UploadOptions {
removeFingerprintOnSuccess?: boolean
uploadLengthDeferred?: boolean
uploadDataDuringCreation?: boolean

useIndexedDBForUrlStorage?: boolean
urlStorage?: UrlStorage
fileReader?: FileReader
httpStack?: HttpStack
Expand Down