mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
215 lines
6.0 KiB
TypeScript
215 lines
6.0 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2018 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { debugAssert, fail } from '../../src/util/assert';
|
|
import { DocumentLike, WindowLike } from '../../src/util/types';
|
|
|
|
/* eslint-disable no-restricted-globals */
|
|
|
|
/**
|
|
* `Window` fake that implements the event and storage API that is used by
|
|
* Firestore.
|
|
*/
|
|
export class FakeWindow implements WindowLike {
|
|
private readonly fakeStorageArea: Storage;
|
|
private readonly fakeIndexedDb: IDBFactory | null;
|
|
|
|
private storageListeners: EventListener[] = [];
|
|
|
|
constructor(
|
|
sharedFakeStorage: SharedFakeWebStorage,
|
|
fakeIndexedDb?: IDBFactory
|
|
) {
|
|
this.fakeStorageArea = sharedFakeStorage.getStorageArea(event => {
|
|
for (const listener of this.storageListeners) {
|
|
listener(event);
|
|
}
|
|
});
|
|
this.fakeIndexedDb =
|
|
fakeIndexedDb ||
|
|
(typeof window !== 'undefined' && window.indexedDB) ||
|
|
null;
|
|
}
|
|
|
|
get localStorage(): Storage {
|
|
return this.fakeStorageArea;
|
|
}
|
|
|
|
get indexedDB(): IDBFactory | null {
|
|
return this.fakeIndexedDb;
|
|
}
|
|
|
|
addEventListener(type: string, listener: EventListener): void {
|
|
switch (type) {
|
|
case 'storage':
|
|
this.storageListeners.push(listener);
|
|
break;
|
|
case 'pagehide':
|
|
case 'visibilitychange':
|
|
// The spec tests currently do not rely on `pagehide`/`visibilitychange`
|
|
// listeners.
|
|
break;
|
|
default:
|
|
fail(0xe53d, `MockWindow doesn't support events of type '${type}'`);
|
|
}
|
|
}
|
|
|
|
removeEventListener(type: string, listener: EventListener): void {
|
|
if (type === 'storage') {
|
|
const oldCount = this.storageListeners.length;
|
|
this.storageListeners = this.storageListeners.filter(
|
|
registeredListener => listener !== registeredListener
|
|
);
|
|
const newCount = this.storageListeners.length;
|
|
debugAssert(
|
|
newCount === oldCount - 1,
|
|
"Listener passed to 'removeEventListener' doesn't match any registered listener."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function testWindow(
|
|
sharedWebStorage = new SharedFakeWebStorage()
|
|
): FakeWindow {
|
|
return new FakeWindow(sharedWebStorage);
|
|
}
|
|
|
|
/**
|
|
* `Document` fake that implements the `visibilitychange` API used by Firestore.
|
|
*/
|
|
export class FakeDocument implements DocumentLike {
|
|
private _visibilityState: DocumentVisibilityState = 'hidden';
|
|
private visibilityListener: EventListener | null = null;
|
|
|
|
get visibilityState(): DocumentVisibilityState {
|
|
return this._visibilityState;
|
|
}
|
|
|
|
addEventListener(type: string, listener: EventListener): void {
|
|
debugAssert(
|
|
type === 'visibilitychange',
|
|
"FakeDocument only supports events of type 'visibilitychange'"
|
|
);
|
|
this.visibilityListener = listener;
|
|
}
|
|
|
|
removeEventListener(type: string, listener: EventListener): void {
|
|
if (listener === this.visibilityListener) {
|
|
this.visibilityListener = null;
|
|
}
|
|
}
|
|
|
|
raiseVisibilityEvent(visibility: DocumentVisibilityState): void {
|
|
this._visibilityState = visibility;
|
|
if (this.visibilityListener) {
|
|
this.visibilityListener(new Event('visibilitychange'));
|
|
}
|
|
}
|
|
}
|
|
|
|
export function testDocument(): FakeDocument {
|
|
return new FakeDocument();
|
|
}
|
|
|
|
/**
|
|
* `WebStorage` mock that implements the WebStorage behavior for multiple
|
|
* clients. To get a client-specific storage area that implements the WebStorage
|
|
* API, invoke `getStorageArea(storageListener)`.
|
|
*/
|
|
export class SharedFakeWebStorage {
|
|
private readonly data = new Map<string, string>();
|
|
private readonly activeClients: Array<{
|
|
storageListener: EventListener;
|
|
storageArea: Storage;
|
|
}> = [];
|
|
|
|
getStorageArea(storageListener: EventListener): Storage {
|
|
const clientIndex = this.activeClients.length;
|
|
const self = this;
|
|
|
|
const storageArea: Storage = {
|
|
get length(): number {
|
|
return self.length;
|
|
},
|
|
getItem: (key: string) => this.getItem(key),
|
|
key: (index: number) => this.key(index),
|
|
clear: () => this.clear(),
|
|
removeItem: (key: string) => {
|
|
const oldValue = this.getItem(key);
|
|
this.removeItem(key);
|
|
this.raiseStorageEvent(clientIndex, key, oldValue, null);
|
|
},
|
|
setItem: (key: string, value: string) => {
|
|
const oldValue = this.getItem(key);
|
|
this.setItem(key, value);
|
|
this.raiseStorageEvent(clientIndex, key, oldValue, value);
|
|
}
|
|
};
|
|
|
|
this.activeClients[clientIndex] = { storageListener, storageArea };
|
|
|
|
return storageArea;
|
|
}
|
|
|
|
private clear(): void {
|
|
this.data.clear();
|
|
}
|
|
|
|
private getItem(key: string): string | null {
|
|
return this.data.has(key) ? this.data.get(key)! : null;
|
|
}
|
|
|
|
private key(index: number): string | null {
|
|
const key = Array.from(this.data.keys())[index];
|
|
return key !== undefined ? key : null;
|
|
}
|
|
|
|
private removeItem(key: string): void {
|
|
this.data.delete(key);
|
|
}
|
|
|
|
private setItem(key: string, data: string): void {
|
|
this.data.set(key, data);
|
|
}
|
|
|
|
private get length(): number {
|
|
return this.data.size;
|
|
}
|
|
|
|
private raiseStorageEvent(
|
|
sourceClientIndex: number,
|
|
key: string,
|
|
oldValue: string | null,
|
|
newValue: string | null
|
|
): void {
|
|
this.activeClients.forEach((client, index) => {
|
|
// WebStorage doesn't raise events for writes from the originating client.
|
|
if (sourceClientIndex === index) {
|
|
return;
|
|
}
|
|
|
|
client.storageListener({
|
|
key,
|
|
oldValue,
|
|
newValue,
|
|
storageArea: client.storageArea
|
|
} as any); /* eslint-disable-line @typescript-eslint/no-explicit-any*/ // Not mocking entire Event type.
|
|
});
|
|
}
|
|
}
|