File size: 3,289 Bytes
7aec436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// https://github.com/jakearchibald/safari-14-idb-fix/blob/582bbdc7230891113bfb5743391550cbf29d21f2/src/index.ts
const idbReady = () => {
  const isSafari =
    !navigator.userAgentData &&
    /Safari\//.test(navigator.userAgent) &&
    !/Chrom(e|ium)\//.test(navigator.userAgent);

  // No point putting other browsers or older versions of Safari through this mess.
  if (!isSafari || !indexedDB.databases) return Promise.resolve();

  let intervalId;
  return new Promise((resolve) => {
    const tryIdb = () => indexedDB.databases().finally(resolve);
    intervalId = setInterval(tryIdb, 100);
    tryIdb();
  }).finally(() => clearInterval(intervalId));
};

const allDatabases = [];

class Database {
  constructor (name, version, storeName) {
    this.name = name;
    this.version = version;
    this.storeName = storeName;
    this.db = null;
    this.dbPromise = null;
    allDatabases.push(this);
  }

  open () {
    if (this.db) {
      return this.db;
    }
    if (this.dbPromise) {
      return this.dbPromise;
    }
    if (typeof indexedDB === 'undefined') {
      throw new Error('indexedDB is not supported');
    }

    this.dbPromise = idbReady()
      .then(() => new Promise((resolve, reject) => {
        const request = indexedDB.open(this.name, this.version);
        request.onupgradeneeded = (e) => {
          const db = e.target.result;
          db.createObjectStore(this.storeName, {
            keyPath: 'id'
          });
        };
        request.onsuccess = (e) => {
          const db = e.target.result;
          resolve(db);
        };
        request.onerror = (e) => {
          reject(new Error(`IDB Error ${e.target.error}`));
        };
      }))
      .then((db) => {
        this.dbPromise = null;
        this.db = db;
        return db;
      })
      .catch((err) => {
        this.dbPromise = null;
        throw err;
      });

    return this.dbPromise;
  }

  close () {
    if (this.db) {
      this.db.close();
      this.db = null;
    }
    if (this.dbPromise) {
      this.dbPromise.then((db) => {
        db.close();
      });
      this.dbPromise = null;
    }
  }

  async createTransaction (readwrite) {
    const db = await this.open();
    const transaction = db.transaction(this.storeName, readwrite);
    const store = transaction.objectStore(this.storeName);
    return {
      db,
      transaction,
      store
    };
  }

  async deleteEverything () {
    const {transaction, store} = await this.createTransaction('readwrite');
    return new Promise((resolve, reject) => {
      Database.setTransactionErrorHandler(transaction, reject);
      const request = store.clear();
      request.onsuccess = () => {
        resolve();
      };
    });
  }
}

Database.setTransactionErrorHandler = (transaction, reject) => {
  transaction.onerror = () => {
    reject(new Error(`Transaction error: ${transaction.error}`))
  };
};

const closeAllDatabases = () => {
  for (const database of allDatabases) {
    database.close();
  }
};
// Closing databases makes us more likely to be put in the browser's back/forward cache
window.addEventListener('pagehide', closeAllDatabases);

export default Database;