Skip to content

Fix tab sync and add tests. #4660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { Receiver } from '../messagechannel/receiver';
import { Sender } from '../messagechannel/sender';
import * as workerUtil from '../util/worker';
import {
_deleteObject,
indexedDBLocalPersistence,
_clearDatabase,
_openDatabase,
Expand Down Expand Up @@ -135,6 +136,18 @@ describe('platform_browser/persistence/indexed_db', () => {
expect(callback).to.have.been.calledWith(newValue);
});

it('should trigger the listener when the key is removed', async () => {
await _putObject(db, key, newValue);
await waitUntilPoll(clock);
callback.resetHistory();

await _deleteObject(db, key);

await waitUntilPoll(clock);

expect(callback).to.have.been.calledOnceWith(null);
});

it('should not trigger the listener when a different key changes', async () => {
await _putObject(db, 'other-key', newValue);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ async function getObject(
return data === undefined ? null : data.value;
}

function deleteObject(db: IDBDatabase, key: string): Promise<void> {
export function _deleteObject(db: IDBDatabase, key: string): Promise<void> {
const request = getObjectStore(db, true).delete(key);
return new DBPromise<void>(request).toPromise();
}
Expand Down Expand Up @@ -317,7 +317,7 @@ class IndexedDBLocalPersistence implements InternalPersistence {
}
const db = await _openDatabase();
await _putObject(db, STORAGE_AVAILABLE_KEY, '1');
await deleteObject(db, STORAGE_AVAILABLE_KEY);
await _deleteObject(db, STORAGE_AVAILABLE_KEY);
return true;
} catch {}
return false;
Expand Down Expand Up @@ -350,7 +350,7 @@ class IndexedDBLocalPersistence implements InternalPersistence {

async _remove(key: string): Promise<void> {
return this._withPendingWrite(async () => {
await this._withRetries((db: IDBDatabase) => deleteObject(db, key));
await this._withRetries((db: IDBDatabase) => _deleteObject(db, key));
delete this.localCache[key];
return this.notifyServiceWorker(key);
});
Expand All @@ -373,12 +373,21 @@ class IndexedDBLocalPersistence implements InternalPersistence {
}

const keys = [];
const keysInResult = new Set();
for (const { fbase_key: key, value } of result) {
keysInResult.add(key);
if (JSON.stringify(this.localCache[key]) !== JSON.stringify(value)) {
this.notifyListeners(key, value as PersistenceValue);
keys.push(key);
}
}
for (const localKey of Object.keys(this.localCache)) {
if (!keysInResult.has(localKey)) {
// Deleted
this.notifyListeners(localKey, null);
keys.push(localKey);
}
}
return keys;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import {
PersistenceInternal as InternalPersistence,
PersistenceType,
PersistenceValue,
StorageEventListener
} from '../../core/persistence';
import { BrowserPersistenceClass } from './browser';
Expand Down Expand Up @@ -239,6 +240,24 @@ class BrowserLocalPersistence
this.stopPolling();
}
}

// Update local cache on base operations:

async _set(key: string, value: PersistenceValue): Promise<void> {
await super._set(key, value);
this.localCache[key] = JSON.stringify(value);
}

async _get<T extends PersistenceValue>(key: string): Promise<T | null> {
const value = await super._get<T>(key);
this.localCache[key] = JSON.stringify(value);
return value;
}

async _remove(key: string): Promise<void> {
await super._remove(key);
delete this.localCache[key];
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import { UserCredential } from '@firebase/auth-exp';
import { expect } from 'chai';
import { createAnonAccount } from '../../helpers/integration/emulator_rest_helpers';
import { API_KEY } from '../../helpers/integration/settings';
import { START_FUNCTION } from './util/auth_driver';
import {
AnonFunction,
CoreFunction,
PersistenceFunction
} from './util/functions';
import { JsLoadCondition } from './util/js_load_condition';
import { browserDescribe } from './util/test_runner';

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
Expand Down Expand Up @@ -417,4 +419,73 @@ browserDescribe('WebDriver persistence test', driver => {
}
});
});

context('persistence sync across windows and tabs', () => {
it('sync current user across windows with indexedDB', async () => {
const cred: UserCredential = await driver.call(
AnonFunction.SIGN_IN_ANONYMOUSLY
);
const uid = cred.user.uid;
await driver.webDriver.executeScript('window.open(".");');
await driver.selectPopupWindow();
await driver.webDriver.wait(new JsLoadCondition(START_FUNCTION));
await driver.injectConfigAndInitAuth();
await driver.waitForAuthInit();
const userInPopup = await driver.getUserSnapshot();
expect(userInPopup).not.to.be.null;
expect(userInPopup.uid).to.equal(uid);

await driver.call(CoreFunction.SIGN_OUT);
expect(await driver.getUserSnapshot()).to.be.null;
await driver.selectMainWindow({ noWait: true });
await driver.pause(500);
expect(await driver.getUserSnapshot()).to.be.null;

const cred2: UserCredential = await driver.call(
AnonFunction.SIGN_IN_ANONYMOUSLY
);
const uid2 = cred2.user.uid;

await driver.selectPopupWindow();
await driver.pause(500);
expect(await driver.getUserSnapshot()).to.contain({ uid: uid2 });
});

it('sync current user across windows with localStorage', async () => {
await driver.webDriver.navigate().refresh();
// Simulate browsers that do not support indexedDB.
await driver.webDriver.executeScript('delete window.indexedDB');
await driver.injectConfigAndInitAuth();
await driver.waitForAuthInit();
const cred: UserCredential = await driver.call(
AnonFunction.SIGN_IN_ANONYMOUSLY
);
const uid = cred.user.uid;
await driver.webDriver.executeScript('window.open(".");');
await driver.selectPopupWindow();
await driver.webDriver.wait(new JsLoadCondition(START_FUNCTION));
// Simulate browsers that do not support indexedDB.
await driver.webDriver.executeScript('delete window.indexedDB');
await driver.injectConfigAndInitAuth();
await driver.waitForAuthInit();
const userInPopup = await driver.getUserSnapshot();
expect(userInPopup).not.to.be.null;
expect(userInPopup.uid).to.equal(uid);

await driver.call(CoreFunction.SIGN_OUT);
expect(await driver.getUserSnapshot()).to.be.null;
await driver.selectMainWindow({ noWait: true });
await driver.pause(500);
expect(await driver.getUserSnapshot()).to.be.null;

const cred2: UserCredential = await driver.call(
AnonFunction.SIGN_IN_ANONYMOUSLY
);
const uid2 = cred2.user.uid;

await driver.selectPopupWindow();
await driver.pause(500);
expect(await driver.getUserSnapshot()).to.contain({ uid: uid2 });
});
});
});
29 changes: 15 additions & 14 deletions packages-exp/auth-exp/test/integration/webdriver/static/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,21 @@ window.startLegacySDK = async persistence => {
// TODO: Find some way to make the tests work without Internet.
appScript.src = 'https://www.gstatic.com/firebasejs/8.3.0/firebase-app.js';
appScript.onerror = reject;
document.head.appendChild(appScript);

const authScript = document.createElement('script');
authScript.src =
'https://www.gstatic.com/firebasejs/8.3.0/firebase-auth.js';
authScript.onerror = reject;
authScript.onload = function () {
firebase.initializeApp(firebaseConfig);
const legacyAuth = firebase.auth();
legacyAuth.useEmulator(emulatorUrl);
legacyAuth.setPersistence(persistence.toLowerCase());
window.legacyAuth = legacyAuth;
resolve();
appScript.onload = () => {
const authScript = document.createElement('script');
authScript.src =
'https://www.gstatic.com/firebasejs/8.3.0/firebase-auth.js';
authScript.onerror = reject;
authScript.onload = () => {
firebase.initializeApp(firebaseConfig);
const legacyAuth = firebase.auth();
legacyAuth.useEmulator(emulatorUrl);
legacyAuth.setPersistence(persistence.toLowerCase());
window.legacyAuth = legacyAuth;
resolve();
};
document.head.appendChild(authScript);
};
document.head.appendChild(authScript);
document.head.appendChild(appScript);
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function dbPromise(dbRequest) {
reject(dbRequest.error);
});
dbRequest.addEventListener('blocked', () => {
reject(dbRequest.error || 'blocked');
reject('blocked');
});
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { CoreFunction } from './functions';
import { JsLoadCondition } from './js_load_condition';
import { authTestServer } from './test_server';

const START_FUNCTION = 'startAuth';
export const START_FUNCTION = 'startAuth';
const START_LEGACY_SDK_FUNCTION = 'startLegacySDK';
const PASSED_ARGS = '...Array.prototype.slice.call(arguments, 0, -1)';

Expand Down Expand Up @@ -200,14 +200,16 @@ export class AuthDriver {
.window(handles.find(h => h !== currentWindowHandle)!);
}

async selectMainWindow(): Promise<void> {
const condition = new Condition(
'Waiting for popup to close',
async driver => {
return (await driver.getAllWindowHandles()).length === 1;
}
);
await this.webDriver.wait(condition);
async selectMainWindow(options: { noWait?: boolean } = {}): Promise<void> {
if (!options.noWait) {
const condition = new Condition(
'Waiting for popup to close',
async driver => {
return (await driver.getAllWindowHandles()).length === 1;
}
);
await this.webDriver.wait(condition);
}
const handles = await this.webDriver.getAllWindowHandles();
return this.webDriver.switchTo().window(handles[0]);
}
Expand All @@ -218,6 +220,16 @@ export class AuthDriver {
return this.selectMainWindow();
}

async closeExtraWindows(): Promise<void> {
const handles = await this.webDriver.getAllWindowHandles();
await this.webDriver.switchTo().window(handles[handles.length - 1]);
while (handles.length > 1) {
await this.webDriver.close();
handles.pop();
await this.webDriver.switchTo().window(handles[handles.length - 1]);
}
}

isCompatLayer(): boolean {
return process.env.COMPAT_LAYER === 'true';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ setTimeout(() => {
// It's assumed that the tests will start with a clean slate (i.e.
// no storage).
beforeEach(async () => {
await DRIVER.closeExtraWindows();
await DRIVER.reset();
await DRIVER.injectConfigAndInitAuth();
});
Expand Down