Skip to content

Commit 7b585e8

Browse files
authored
Fix tab sync and add tests. (#4660)
* Add tests for cross-tab sync. * Add legacy test. * Fix tab sync and add tests. * Remove unit test for localStoage sync. * Use drivers.pause.
1 parent 05f5bf3 commit 7b585e8

File tree

8 files changed

+153
-27
lines changed

8 files changed

+153
-27
lines changed

packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.test.ts

+13
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { Receiver } from '../messagechannel/receiver';
3434
import { Sender } from '../messagechannel/sender';
3535
import * as workerUtil from '../util/worker';
3636
import {
37+
_deleteObject,
3738
indexedDBLocalPersistence,
3839
_clearDatabase,
3940
_openDatabase,
@@ -135,6 +136,18 @@ describe('platform_browser/persistence/indexed_db', () => {
135136
expect(callback).to.have.been.calledWith(newValue);
136137
});
137138

139+
it('should trigger the listener when the key is removed', async () => {
140+
await _putObject(db, key, newValue);
141+
await waitUntilPoll(clock);
142+
callback.resetHistory();
143+
144+
await _deleteObject(db, key);
145+
146+
await waitUntilPoll(clock);
147+
148+
expect(callback).to.have.been.calledOnceWith(null);
149+
});
150+
138151
it('should not trigger the listener when a different key changes', async () => {
139152
await _putObject(db, 'other-key', newValue);
140153

packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ async function getObject(
152152
return data === undefined ? null : data.value;
153153
}
154154

155-
function deleteObject(db: IDBDatabase, key: string): Promise<void> {
155+
export function _deleteObject(db: IDBDatabase, key: string): Promise<void> {
156156
const request = getObjectStore(db, true).delete(key);
157157
return new DBPromise<void>(request).toPromise();
158158
}
@@ -317,7 +317,7 @@ class IndexedDBLocalPersistence implements InternalPersistence {
317317
}
318318
const db = await _openDatabase();
319319
await _putObject(db, STORAGE_AVAILABLE_KEY, '1');
320-
await deleteObject(db, STORAGE_AVAILABLE_KEY);
320+
await _deleteObject(db, STORAGE_AVAILABLE_KEY);
321321
return true;
322322
} catch {}
323323
return false;
@@ -350,7 +350,7 @@ class IndexedDBLocalPersistence implements InternalPersistence {
350350

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

375375
const keys = [];
376+
const keysInResult = new Set();
376377
for (const { fbase_key: key, value } of result) {
378+
keysInResult.add(key);
377379
if (JSON.stringify(this.localCache[key]) !== JSON.stringify(value)) {
378380
this.notifyListeners(key, value as PersistenceValue);
379381
keys.push(key);
380382
}
381383
}
384+
for (const localKey of Object.keys(this.localCache)) {
385+
if (!keysInResult.has(localKey)) {
386+
// Deleted
387+
this.notifyListeners(localKey, null);
388+
keys.push(localKey);
389+
}
390+
}
382391
return keys;
383392
}
384393

packages-exp/auth-exp/src/platform_browser/persistence/local_storage.ts

+19
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
import {
2929
PersistenceInternal as InternalPersistence,
3030
PersistenceType,
31+
PersistenceValue,
3132
StorageEventListener
3233
} from '../../core/persistence';
3334
import { BrowserPersistenceClass } from './browser';
@@ -239,6 +240,24 @@ class BrowserLocalPersistence
239240
this.stopPolling();
240241
}
241242
}
243+
244+
// Update local cache on base operations:
245+
246+
async _set(key: string, value: PersistenceValue): Promise<void> {
247+
await super._set(key, value);
248+
this.localCache[key] = JSON.stringify(value);
249+
}
250+
251+
async _get<T extends PersistenceValue>(key: string): Promise<T | null> {
252+
const value = await super._get<T>(key);
253+
this.localCache[key] = JSON.stringify(value);
254+
return value;
255+
}
256+
257+
async _remove(key: string): Promise<void> {
258+
await super._remove(key);
259+
delete this.localCache[key];
260+
}
242261
}
243262

244263
/**

packages-exp/auth-exp/test/integration/webdriver/persistence.test.ts

+71
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import { UserCredential } from '@firebase/auth-exp';
2020
import { expect } from 'chai';
2121
import { createAnonAccount } from '../../helpers/integration/emulator_rest_helpers';
2222
import { API_KEY } from '../../helpers/integration/settings';
23+
import { START_FUNCTION } from './util/auth_driver';
2324
import {
2425
AnonFunction,
2526
CoreFunction,
2627
PersistenceFunction
2728
} from './util/functions';
29+
import { JsLoadCondition } from './util/js_load_condition';
2830
import { browserDescribe } from './util/test_runner';
2931

3032
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@@ -417,4 +419,73 @@ browserDescribe('WebDriver persistence test', driver => {
417419
}
418420
});
419421
});
422+
423+
context('persistence sync across windows and tabs', () => {
424+
it('sync current user across windows with indexedDB', async () => {
425+
const cred: UserCredential = await driver.call(
426+
AnonFunction.SIGN_IN_ANONYMOUSLY
427+
);
428+
const uid = cred.user.uid;
429+
await driver.webDriver.executeScript('window.open(".");');
430+
await driver.selectPopupWindow();
431+
await driver.webDriver.wait(new JsLoadCondition(START_FUNCTION));
432+
await driver.injectConfigAndInitAuth();
433+
await driver.waitForAuthInit();
434+
const userInPopup = await driver.getUserSnapshot();
435+
expect(userInPopup).not.to.be.null;
436+
expect(userInPopup.uid).to.equal(uid);
437+
438+
await driver.call(CoreFunction.SIGN_OUT);
439+
expect(await driver.getUserSnapshot()).to.be.null;
440+
await driver.selectMainWindow({ noWait: true });
441+
await driver.pause(500);
442+
expect(await driver.getUserSnapshot()).to.be.null;
443+
444+
const cred2: UserCredential = await driver.call(
445+
AnonFunction.SIGN_IN_ANONYMOUSLY
446+
);
447+
const uid2 = cred2.user.uid;
448+
449+
await driver.selectPopupWindow();
450+
await driver.pause(500);
451+
expect(await driver.getUserSnapshot()).to.contain({ uid: uid2 });
452+
});
453+
454+
it('sync current user across windows with localStorage', async () => {
455+
await driver.webDriver.navigate().refresh();
456+
// Simulate browsers that do not support indexedDB.
457+
await driver.webDriver.executeScript('delete window.indexedDB');
458+
await driver.injectConfigAndInitAuth();
459+
await driver.waitForAuthInit();
460+
const cred: UserCredential = await driver.call(
461+
AnonFunction.SIGN_IN_ANONYMOUSLY
462+
);
463+
const uid = cred.user.uid;
464+
await driver.webDriver.executeScript('window.open(".");');
465+
await driver.selectPopupWindow();
466+
await driver.webDriver.wait(new JsLoadCondition(START_FUNCTION));
467+
// Simulate browsers that do not support indexedDB.
468+
await driver.webDriver.executeScript('delete window.indexedDB');
469+
await driver.injectConfigAndInitAuth();
470+
await driver.waitForAuthInit();
471+
const userInPopup = await driver.getUserSnapshot();
472+
expect(userInPopup).not.to.be.null;
473+
expect(userInPopup.uid).to.equal(uid);
474+
475+
await driver.call(CoreFunction.SIGN_OUT);
476+
expect(await driver.getUserSnapshot()).to.be.null;
477+
await driver.selectMainWindow({ noWait: true });
478+
await driver.pause(500);
479+
expect(await driver.getUserSnapshot()).to.be.null;
480+
481+
const cred2: UserCredential = await driver.call(
482+
AnonFunction.SIGN_IN_ANONYMOUSLY
483+
);
484+
const uid2 = cred2.user.uid;
485+
486+
await driver.selectPopupWindow();
487+
await driver.pause(500);
488+
expect(await driver.getUserSnapshot()).to.contain({ uid: uid2 });
489+
});
490+
});
420491
});

packages-exp/auth-exp/test/integration/webdriver/static/index.js

+15-14
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,21 @@ window.startLegacySDK = async persistence => {
4949
// TODO: Find some way to make the tests work without Internet.
5050
appScript.src = 'https://www.gstatic.com/firebasejs/8.3.0/firebase-app.js';
5151
appScript.onerror = reject;
52-
document.head.appendChild(appScript);
53-
54-
const authScript = document.createElement('script');
55-
authScript.src =
56-
'https://www.gstatic.com/firebasejs/8.3.0/firebase-auth.js';
57-
authScript.onerror = reject;
58-
authScript.onload = function () {
59-
firebase.initializeApp(firebaseConfig);
60-
const legacyAuth = firebase.auth();
61-
legacyAuth.useEmulator(emulatorUrl);
62-
legacyAuth.setPersistence(persistence.toLowerCase());
63-
window.legacyAuth = legacyAuth;
64-
resolve();
52+
appScript.onload = () => {
53+
const authScript = document.createElement('script');
54+
authScript.src =
55+
'https://www.gstatic.com/firebasejs/8.3.0/firebase-auth.js';
56+
authScript.onerror = reject;
57+
authScript.onload = () => {
58+
firebase.initializeApp(firebaseConfig);
59+
const legacyAuth = firebase.auth();
60+
legacyAuth.useEmulator(emulatorUrl);
61+
legacyAuth.setPersistence(persistence.toLowerCase());
62+
window.legacyAuth = legacyAuth;
63+
resolve();
64+
};
65+
document.head.appendChild(authScript);
6566
};
66-
document.head.appendChild(authScript);
67+
document.head.appendChild(appScript);
6768
});
6869
};

packages-exp/auth-exp/test/integration/webdriver/static/persistence.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ function dbPromise(dbRequest) {
134134
reject(dbRequest.error);
135135
});
136136
dbRequest.addEventListener('blocked', () => {
137-
reject(dbRequest.error || 'blocked');
137+
reject('blocked');
138138
});
139139
});
140140
}

packages-exp/auth-exp/test/integration/webdriver/util/auth_driver.ts

+21-9
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { CoreFunction } from './functions';
2828
import { JsLoadCondition } from './js_load_condition';
2929
import { authTestServer } from './test_server';
3030

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

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

203-
async selectMainWindow(): Promise<void> {
204-
const condition = new Condition(
205-
'Waiting for popup to close',
206-
async driver => {
207-
return (await driver.getAllWindowHandles()).length === 1;
208-
}
209-
);
210-
await this.webDriver.wait(condition);
203+
async selectMainWindow(options: { noWait?: boolean } = {}): Promise<void> {
204+
if (!options.noWait) {
205+
const condition = new Condition(
206+
'Waiting for popup to close',
207+
async driver => {
208+
return (await driver.getAllWindowHandles()).length === 1;
209+
}
210+
);
211+
await this.webDriver.wait(condition);
212+
}
211213
const handles = await this.webDriver.getAllWindowHandles();
212214
return this.webDriver.switchTo().window(handles[0]);
213215
}
@@ -218,6 +220,16 @@ export class AuthDriver {
218220
return this.selectMainWindow();
219221
}
220222

223+
async closeExtraWindows(): Promise<void> {
224+
const handles = await this.webDriver.getAllWindowHandles();
225+
await this.webDriver.switchTo().window(handles[handles.length - 1]);
226+
while (handles.length > 1) {
227+
await this.webDriver.close();
228+
handles.pop();
229+
await this.webDriver.switchTo().window(handles[handles.length - 1]);
230+
}
231+
}
232+
221233
isCompatLayer(): boolean {
222234
return process.env.COMPAT_LAYER === 'true';
223235
}

packages-exp/auth-exp/test/integration/webdriver/util/test_runner.ts

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ setTimeout(() => {
6969
// It's assumed that the tests will start with a clean slate (i.e.
7070
// no storage).
7171
beforeEach(async () => {
72+
await DRIVER.closeExtraWindows();
7273
await DRIVER.reset();
7374
await DRIVER.injectConfigAndInitAuth();
7475
});

0 commit comments

Comments
 (0)