UNPKG

msw

Version:

Seamless REST/GraphQL API mocking library for browser and Node.js.

146 lines (124 loc) 4.33 kB
import { DeferredPromise } from '@open-draft/deferred-promise' import { WebSocketClientConnectionProtocol } from '@mswjs/interceptors/lib/browser/interceptors/WebSocket' import { type SerializedWebSocketClient, WebSocketClientStore, } from './WebSocketClientStore' const DB_NAME = 'msw-websocket-clients' const DB_STORE_NAME = 'clients' export class WebSocketIndexedDBClientStore implements WebSocketClientStore { private db: Promise<IDBDatabase> constructor() { this.db = this.createDatabase() } public async add(client: WebSocketClientConnectionProtocol): Promise<void> { const promise = new DeferredPromise<void>() const store = await this.getStore() /** * @note Use `.put()` instead of `.add()` to allow setting clients * that already exist in the database. This can happen if a single page * has multiple event handlers. Each handler will receive the "connection" * event in parallel, and try to set that WebSocket client in the database. */ const request = store.put({ id: client.id, url: client.url.href, } satisfies SerializedWebSocketClient) request.onsuccess = () => { promise.resolve() } request.onerror = () => { // eslint-disable-next-line no-console console.error(request.error) promise.reject( new Error( `Failed to add WebSocket client "${client.id}". There is likely an additional output above.`, ), ) } return promise } public async getAll(): Promise<Array<SerializedWebSocketClient>> { const promise = new DeferredPromise<Array<SerializedWebSocketClient>>() const store = await this.getStore() const request = store.getAll() as IDBRequest< Array<SerializedWebSocketClient> > request.onsuccess = () => { promise.resolve(request.result) } request.onerror = () => { // eslint-disable-next-line no-console console.log(request.error) promise.reject( new Error( `Failed to get all WebSocket clients. There is likely an additional output above.`, ), ) } return promise } public async deleteMany(clientIds: Array<string>): Promise<void> { const promise = new DeferredPromise<void>() const store = await this.getStore() for (const clientId of clientIds) { store.delete(clientId) } store.transaction.oncomplete = () => { promise.resolve() } store.transaction.onerror = () => { // eslint-disable-next-line no-console console.error(store.transaction.error) promise.reject( new Error( `Failed to delete WebSocket clients [${clientIds.join(', ')}]. There is likely an additional output above.`, ), ) } return promise } private async createDatabase(): Promise<IDBDatabase> { const promise = new DeferredPromise<IDBDatabase>() const request = indexedDB.open(DB_NAME, 1) request.onsuccess = ({ currentTarget }) => { const db = Reflect.get(currentTarget!, 'result') as IDBDatabase if (db.objectStoreNames.contains(DB_STORE_NAME)) { return promise.resolve(db) } } request.onupgradeneeded = async ({ currentTarget }) => { const db = Reflect.get(currentTarget!, 'result') as IDBDatabase if (db.objectStoreNames.contains(DB_STORE_NAME)) { return } const store = db.createObjectStore(DB_STORE_NAME, { keyPath: 'id' }) store.transaction.oncomplete = () => { promise.resolve(db) } store.transaction.onerror = () => { // eslint-disable-next-line no-console console.error(store.transaction.error) promise.reject( new Error( 'Failed to create WebSocket client store. There is likely an additional output above.', ), ) } } request.onerror = () => { // eslint-disable-next-line no-console console.error(request.error) promise.reject( new Error( 'Failed to open an IndexedDB database. There is likely an additional output above.', ), ) } return promise } private async getStore(): Promise<IDBObjectStore> { const db = await this.db return db.transaction(DB_STORE_NAME, 'readwrite').objectStore(DB_STORE_NAME) } }