source: frontend/node_modules/workbox-expiration/src/models/CacheTimestampsModel.ts

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 6.2 KB
Line 
1/*
2 Copyright 2018 Google LLC
3
4 Use of this source code is governed by an MIT-style
5 license that can be found in the LICENSE file or at
6 https://opensource.org/licenses/MIT.
7*/
8
9import {openDB, DBSchema, IDBPDatabase, deleteDB} from 'idb';
10import '../_version.js';
11
12const DB_NAME = 'workbox-expiration';
13const CACHE_OBJECT_STORE = 'cache-entries';
14
15const normalizeURL = (unNormalizedUrl: string) => {
16 const url = new URL(unNormalizedUrl, location.href);
17 url.hash = '';
18
19 return url.href;
20};
21
22interface CacheTimestampsModelEntry {
23 id: string;
24 cacheName: string;
25 url: string;
26 timestamp: number;
27}
28
29interface CacheDbSchema extends DBSchema {
30 'cache-entries': {
31 key: string;
32 value: CacheTimestampsModelEntry;
33 indexes: {cacheName: string; timestamp: number};
34 };
35}
36
37/**
38 * Returns the timestamp model.
39 *
40 * @private
41 */
42class CacheTimestampsModel {
43 private readonly _cacheName: string;
44 private _db: IDBPDatabase<CacheDbSchema> | null = null;
45
46 /**
47 *
48 * @param {string} cacheName
49 *
50 * @private
51 */
52 constructor(cacheName: string) {
53 this._cacheName = cacheName;
54 }
55
56 /**
57 * Performs an upgrade of indexedDB.
58 *
59 * @param {IDBPDatabase<CacheDbSchema>} db
60 *
61 * @private
62 */
63 private _upgradeDb(db: IDBPDatabase<CacheDbSchema>) {
64 // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we
65 // have to use the `id` keyPath here and create our own values (a
66 // concatenation of `url + cacheName`) instead of simply using
67 // `keyPath: ['url', 'cacheName']`, which is supported in other browsers.
68 const objStore = db.createObjectStore(CACHE_OBJECT_STORE, {keyPath: 'id'});
69
70 // TODO(philipwalton): once we don't have to support EdgeHTML, we can
71 // create a single index with the keyPath `['cacheName', 'timestamp']`
72 // instead of doing both these indexes.
73 objStore.createIndex('cacheName', 'cacheName', {unique: false});
74 objStore.createIndex('timestamp', 'timestamp', {unique: false});
75 }
76
77 /**
78 * Performs an upgrade of indexedDB and deletes deprecated DBs.
79 *
80 * @param {IDBPDatabase<CacheDbSchema>} db
81 *
82 * @private
83 */
84 private _upgradeDbAndDeleteOldDbs(db: IDBPDatabase<CacheDbSchema>) {
85 this._upgradeDb(db);
86 if (this._cacheName) {
87 void deleteDB(this._cacheName);
88 }
89 }
90
91 /**
92 * @param {string} url
93 * @param {number} timestamp
94 *
95 * @private
96 */
97 async setTimestamp(url: string, timestamp: number): Promise<void> {
98 url = normalizeURL(url);
99
100 const entry: CacheTimestampsModelEntry = {
101 url,
102 timestamp,
103 cacheName: this._cacheName,
104 // Creating an ID from the URL and cache name won't be necessary once
105 // Edge switches to Chromium and all browsers we support work with
106 // array keyPaths.
107 id: this._getId(url),
108 };
109 const db = await this.getDb();
110 const tx = db.transaction(CACHE_OBJECT_STORE, 'readwrite', {
111 durability: 'relaxed',
112 });
113 await tx.store.put(entry);
114 await tx.done;
115 }
116
117 /**
118 * Returns the timestamp stored for a given URL.
119 *
120 * @param {string} url
121 * @return {number | undefined}
122 *
123 * @private
124 */
125 async getTimestamp(url: string): Promise<number | undefined> {
126 const db = await this.getDb();
127 const entry = await db.get(CACHE_OBJECT_STORE, this._getId(url));
128 return entry?.timestamp;
129 }
130
131 /**
132 * Iterates through all the entries in the object store (from newest to
133 * oldest) and removes entries once either `maxCount` is reached or the
134 * entry's timestamp is less than `minTimestamp`.
135 *
136 * @param {number} minTimestamp
137 * @param {number} maxCount
138 * @return {Array<string>}
139 *
140 * @private
141 */
142 async expireEntries(
143 minTimestamp: number,
144 maxCount?: number,
145 ): Promise<string[]> {
146 const db = await this.getDb();
147 let cursor = await db
148 .transaction(CACHE_OBJECT_STORE)
149 .store.index('timestamp')
150 .openCursor(null, 'prev');
151 const entriesToDelete: CacheTimestampsModelEntry[] = [];
152 let entriesNotDeletedCount = 0;
153 while (cursor) {
154 const result = cursor.value;
155 // TODO(philipwalton): once we can use a multi-key index, we
156 // won't have to check `cacheName` here.
157 if (result.cacheName === this._cacheName) {
158 // Delete an entry if it's older than the max age or
159 // if we already have the max number allowed.
160 if (
161 (minTimestamp && result.timestamp < minTimestamp) ||
162 (maxCount && entriesNotDeletedCount >= maxCount)
163 ) {
164 // TODO(philipwalton): we should be able to delete the
165 // entry right here, but doing so causes an iteration
166 // bug in Safari stable (fixed in TP). Instead we can
167 // store the keys of the entries to delete, and then
168 // delete the separate transactions.
169 // https://github.com/GoogleChrome/workbox/issues/1978
170 // cursor.delete();
171
172 // We only need to return the URL, not the whole entry.
173 entriesToDelete.push(cursor.value);
174 } else {
175 entriesNotDeletedCount++;
176 }
177 }
178 cursor = await cursor.continue();
179 }
180
181 // TODO(philipwalton): once the Safari bug in the following issue is fixed,
182 // we should be able to remove this loop and do the entry deletion in the
183 // cursor loop above:
184 // https://github.com/GoogleChrome/workbox/issues/1978
185 const urlsDeleted: string[] = [];
186 for (const entry of entriesToDelete) {
187 await db.delete(CACHE_OBJECT_STORE, entry.id);
188 urlsDeleted.push(entry.url);
189 }
190
191 return urlsDeleted;
192 }
193
194 /**
195 * Takes a URL and returns an ID that will be unique in the object store.
196 *
197 * @param {string} url
198 * @return {string}
199 *
200 * @private
201 */
202 private _getId(url: string): string {
203 // Creating an ID from the URL and cache name won't be necessary once
204 // Edge switches to Chromium and all browsers we support work with
205 // array keyPaths.
206 return this._cacheName + '|' + normalizeURL(url);
207 }
208
209 /**
210 * Returns an open connection to the database.
211 *
212 * @private
213 */
214 private async getDb() {
215 if (!this._db) {
216 this._db = await openDB(DB_NAME, 1, {
217 upgrade: this._upgradeDbAndDeleteOldDbs.bind(this),
218 });
219 }
220 return this._db;
221 }
222}
223
224export {CacheTimestampsModel};
Note: See TracBrowser for help on using the repository browser.