Skip to content
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