Skip to content

Commit d627474

Browse files
committed
Merge branch 'master' into fei-v9-main-storage
2 parents 307ca6c + 749f5a2 commit d627474

File tree

38 files changed

+314
-99
lines changed

38 files changed

+314
-99
lines changed

.changeset/afraid-poets-hammer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/rules-unit-testing": patch
3+
---
4+
5+
Fix JWT format and interop with Storage Emulator. Fixes #3442.

.changeset/great-tigers-doubt.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

.changeset/wise-forks-poke.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

integration/firebase/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"test:ci": "node ../../scripts/run_tests_in_ci.js -s test"
88
},
99
"devDependencies": {
10-
"firebase": "8.8.1",
10+
"firebase": "8.9.1",
1111
"@types/chai": "4.2.14",
1212
"@types/mocha": "8.2.3",
1313
"chai": "4.3.4",

integration/messaging/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"test:manual": "mocha --exit"
1010
},
1111
"devDependencies": {
12-
"firebase": "8.8.1",
12+
"firebase": "8.9.1",
1313
"chai": "4.3.4",
1414
"chromedriver": "91.0.0",
1515
"express": "4.17.1",

packages-exp/analytics-compat/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"dependencies": {
4747
"@firebase/component": "0.5.5",
4848
"@firebase/analytics-exp": "0.0.900",
49-
"@firebase/analytics-types": "0.5.0",
49+
"@firebase/analytics-types": "0.6.0",
5050
"@firebase/util": "1.2.0",
5151
"tslib": "^2.1.0"
5252
},

packages-exp/auth-compat-exp/src/auth.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('auth compat', () => {
6565
providerStub.getImmediate.returns(underlyingAuth);
6666
const authCompat = new Auth(
6767
app,
68-
(providerStub as unknown) as Provider<'auth-exp'>
68+
providerStub as unknown as Provider<'auth-exp'>
6969
);
7070
// eslint-disable-next-line @typescript-eslint/no-floating-promises
7171
await authCompat.signInWithRedirect(new exp.GoogleAuthProvider());
@@ -83,15 +83,16 @@ describe('auth compat', () => {
8383
);
8484
providerStub.isInitialized.returns(false);
8585
providerStub.initialize.returns(underlyingAuth);
86-
new Auth(app, (providerStub as unknown) as Provider<'auth-exp'>);
86+
new Auth(app, providerStub as unknown as Provider<'auth-exp'>);
8787
// eslint-disable-next-line @typescript-eslint/no-floating-promises
8888
expect(providerStub.initialize).to.have.been.calledWith({
8989
options: {
9090
popupRedirectResolver: CompatPopupRedirectResolver,
9191
persistence: [
9292
exp.inMemoryPersistence,
9393
exp.indexedDBLocalPersistence,
94-
exp.browserLocalPersistence
94+
exp.browserLocalPersistence,
95+
exp.browserSessionPersistence
9596
]
9697
}
9798
});

packages-exp/auth-compat-exp/src/auth.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export class Auth
6767

6868
for (const persistence of [
6969
exp.indexedDBLocalPersistence,
70-
exp.browserLocalPersistence
70+
exp.browserLocalPersistence,
71+
exp.browserSessionPersistence
7172
]) {
7273
if (!persistences.includes(persistence)) {
7374
persistences.push(persistence);

packages-exp/auth-exp/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { initializeAuth } from './src';
2727
import { registerAuth } from './src/core/auth/register';
2828
import { ClientPlatform } from './src/core/util/version';
2929
import { browserLocalPersistence } from './src/platform_browser/persistence/local_storage';
30+
import { browserSessionPersistence } from './src/platform_browser/persistence/session_storage';
3031
import { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db';
3132
import { browserPopupRedirectResolver } from './src/platform_browser/popup_redirect';
3233
import { Auth } from './src/model/public_types';
@@ -136,7 +137,11 @@ export function getAuth(app: FirebaseApp = getApp()): Auth {
136137

137138
return initializeAuth(app, {
138139
popupRedirectResolver: browserPopupRedirectResolver,
139-
persistence: [indexedDBLocalPersistence, browserLocalPersistence]
140+
persistence: [
141+
indexedDBLocalPersistence,
142+
browserLocalPersistence,
143+
browserSessionPersistence
144+
]
140145
});
141146
}
142147

packages-exp/auth-exp/src/core/persistence/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,6 @@ export interface PersistenceInternal extends Persistence {
4444
_remove(key: string): Promise<void>;
4545
_addListener(key: string, listener: StorageEventListener): void;
4646
_removeListener(key: string, listener: StorageEventListener): void;
47+
// Should this persistence allow migration up the chosen hierarchy?
48+
_shouldAllowMigration?: boolean;
4749
}

packages-exp/auth-exp/src/core/persistence/persistence_user_manager.test.ts

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import { KeyName, PersistenceUserManager } from './persistence_user_manager';
3535
chai.use(sinonChai);
3636

3737
function makePersistence(
38-
type = PersistenceType.NONE
38+
type = PersistenceType.NONE,
39+
shouldAllowMigration = false
3940
): {
4041
persistence: PersistenceInternal;
4142
stub: sinon.SinonStubbedInstance<PersistenceInternal>;
@@ -49,7 +50,8 @@ function makePersistence(
4950
},
5051
_remove: async () => {},
5152
_addListener(_key: string, _listener: StorageEventListener) {},
52-
_removeListener(_key: string, _listener: StorageEventListener) {}
53+
_removeListener(_key: string, _listener: StorageEventListener) {},
54+
_shouldAllowMigration: shouldAllowMigration
5355
};
5456

5557
const stub = sinon.stub(persistence);
@@ -69,7 +71,7 @@ describe('core/persistence/persistence_user_manager', () => {
6971
expect(manager.persistence).to.eq(_getInstance(inMemoryPersistence));
7072
});
7173

72-
it('chooses the first one available', async () => {
74+
it('chooses the first one with a user', async () => {
7375
const a = makePersistence();
7476
const b = makePersistence();
7577
const c = makePersistence();
@@ -78,11 +80,31 @@ describe('core/persistence/persistence_user_manager', () => {
7880
a.stub._isAvailable.resolves(false);
7981
a.stub._get.onFirstCall().resolves(testUser(auth, 'uid').toJSON());
8082
b.stub._isAvailable.resolves(true);
83+
a.stub._get.onFirstCall().resolves(testUser(auth, 'uid-b').toJSON());
8184

8285
const out = await PersistenceUserManager.create(auth, search);
8386
expect(a.stub._isAvailable).to.have.been.calledOnce;
8487
expect(b.stub._isAvailable).to.have.been.calledOnce;
85-
expect(c.stub._isAvailable).to.not.have.been.called;
88+
expect(c.stub._isAvailable).to.have.been.calledOnce;
89+
90+
// a should not be chosen since it is not available (despite having a user).
91+
expect(out.persistence).to.eq(a.persistence);
92+
});
93+
94+
it('defaults to first available persistence if no user', async () => {
95+
const a = makePersistence();
96+
const b = makePersistence();
97+
const c = makePersistence();
98+
const search = [a.persistence, b.persistence, c.persistence];
99+
const auth = await testAuth();
100+
a.stub._isAvailable.resolves(false);
101+
b.stub._isAvailable.resolves(true);
102+
c.stub._isAvailable.resolves(true);
103+
104+
const out = await PersistenceUserManager.create(auth, search);
105+
expect(a.stub._isAvailable).to.have.been.calledOnce;
106+
expect(b.stub._isAvailable).to.have.been.calledOnce;
107+
expect(c.stub._isAvailable).to.have.been.calledOnce;
86108

87109
// a should not be chosen since it is not available (despite having a user).
88110
expect(out.persistence).to.eq(b.persistence);
@@ -108,14 +130,16 @@ describe('core/persistence/persistence_user_manager', () => {
108130
expect((await out.getCurrentUser())!.uid).to.eq(user.uid);
109131
});
110132

111-
it('migrate found user to the selected persistence and clear others', async () => {
112-
const a = makePersistence();
113-
const b = makePersistence();
114-
const c = makePersistence();
133+
it('migrate found user to higher order persistence, if applicable', async () => {
134+
const a = makePersistence(PersistenceType.NONE, true);
135+
const b = makePersistence(PersistenceType.NONE, true);
136+
const c = makePersistence(PersistenceType.NONE, true);
115137
const search = [a.persistence, b.persistence, c.persistence];
116138
const auth = await testAuth();
117139
const user = testUser(auth, 'uid');
118140
a.stub._isAvailable.resolves(true);
141+
b.stub._isAvailable.resolves(true);
142+
c.stub._isAvailable.resolves(true);
119143
b.stub._get.resolves(user.toJSON());
120144
c.stub._get.resolves(testUser(auth, 'wrong-uid').toJSON());
121145

@@ -143,8 +167,46 @@ describe('core/persistence/persistence_user_manager', () => {
143167
expect((await out.getCurrentUser())!.uid).to.eq(user.uid);
144168
});
145169

170+
it('migrate found user to available persistence, if applicable', async () => {
171+
const a = makePersistence(PersistenceType.NONE, true);
172+
const b = makePersistence(PersistenceType.NONE, true);
173+
const c = makePersistence(PersistenceType.NONE, true);
174+
const search = [a.persistence, b.persistence, c.persistence];
175+
const auth = await testAuth();
176+
const user = testUser(auth, 'uid');
177+
a.stub._isAvailable.resolves(false); // Important
178+
b.stub._isAvailable.resolves(true);
179+
c.stub._isAvailable.resolves(true);
180+
a.stub._get.resolves(user.toJSON());
181+
c.stub._get.resolves(testUser(auth, 'wrong-uid').toJSON());
182+
183+
let persistedUserInB: PersistenceValue | null = null;
184+
b.stub._set.callsFake(async (_, value) => {
185+
persistedUserInB = value;
186+
});
187+
b.stub._get.callsFake(async () => persistedUserInB);
188+
189+
const out = await PersistenceUserManager.create(auth, search);
190+
expect(b.stub._set).to.have.been.calledOnceWith(
191+
'firebase:authUser:test-api-key:test-app',
192+
user.toJSON()
193+
);
194+
expect(a.stub._set).to.not.have.been.called;
195+
expect(c.stub._set).to.not.have.been.called;
196+
expect(a.stub._remove).to.have.been.calledOnceWith(
197+
'firebase:authUser:test-api-key:test-app'
198+
);
199+
expect(c.stub._remove).to.have.been.calledOnceWith(
200+
'firebase:authUser:test-api-key:test-app'
201+
);
202+
203+
expect(out.persistence).to.eq(b.persistence);
204+
expect((await out.getCurrentUser())!.uid).to.eq(user.uid);
205+
});
206+
146207
it('uses default user key if none provided', async () => {
147208
const { stub, persistence } = makePersistence();
209+
stub._isAvailable.resolves(true);
148210
await PersistenceUserManager.create(auth, [persistence]);
149211
expect(stub._get).to.have.been.calledWith(
150212
'firebase:authUser:test-api-key:test-app'
@@ -153,6 +215,7 @@ describe('core/persistence/persistence_user_manager', () => {
153215

154216
it('uses user key if provided', async () => {
155217
const { stub, persistence } = makePersistence();
218+
stub._isAvailable.resolves(true);
156219
await PersistenceUserManager.create(
157220
auth,
158221
[persistence],
@@ -176,7 +239,7 @@ describe('core/persistence/persistence_user_manager', () => {
176239
expect(out.persistence).to.eq(_getInstance(inMemoryPersistence));
177240
expect(a.stub._get).to.have.been.calledOnce;
178241
expect(b.stub._get).to.have.been.calledOnce;
179-
expect(c.stub._get).to.have.been.called;
242+
expect(c.stub._get).to.have.been.calledOnce;
180243
});
181244
});
182245

@@ -235,10 +298,8 @@ describe('core/persistence/persistence_user_manager', () => {
235298
});
236299

237300
it('removes current user & sets it in the new persistene', async () => {
238-
const {
239-
persistence: nextPersistence,
240-
stub: nextStub
241-
} = makePersistence();
301+
const { persistence: nextPersistence, stub: nextStub } =
302+
makePersistence();
242303
const auth = await testAuth();
243304
const user = testUser(auth, 'uid');
244305
persistenceStub._get.returns(Promise.resolve(user.toJSON()));
@@ -259,10 +320,8 @@ describe('core/persistence/persistence_user_manager', () => {
259320
const user = testUser(auth, 'uid');
260321
stub._get.returns(Promise.resolve(user.toJSON()));
261322

262-
const {
263-
persistence: nextPersistence,
264-
stub: nextStub
265-
} = makePersistence(PersistenceType.LOCAL);
323+
const { persistence: nextPersistence, stub: nextStub } =
324+
makePersistence(PersistenceType.LOCAL);
266325

267326
// This should migrate the user even if both has type LOCAL. For example, developer may want
268327
// to switch from localStorage to indexedDB (both type LOCAL) and we should honor that.

packages-exp/auth-exp/src/core/persistence/persistence_user_manager.ts

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -113,52 +113,77 @@ export class PersistenceUserManager {
113113
);
114114
}
115115

116-
// Use the first persistence that supports a full read-write roundtrip (or fallback to memory).
117-
let chosenPersistence = _getInstance<PersistenceInternal>(
118-
inMemoryPersistence
119-
);
120-
for (const persistence of persistenceHierarchy) {
121-
if (await persistence._isAvailable()) {
122-
chosenPersistence = persistence;
123-
break;
124-
}
125-
}
116+
// Eliminate any persistences that are not available
117+
const availablePersistences = (
118+
await Promise.all(
119+
persistenceHierarchy.map(async persistence => {
120+
if (await persistence._isAvailable()) {
121+
return persistence;
122+
}
123+
return undefined;
124+
})
125+
)
126+
).filter(persistence => persistence) as PersistenceInternal[];
127+
128+
// Fall back to the first persistence listed, or in memory if none available
129+
let selectedPersistence =
130+
availablePersistences[0] ||
131+
_getInstance<PersistenceInternal>(inMemoryPersistence);
126132

127-
// However, attempt to migrate users stored in other persistences (in the hierarchy order).
128-
let userToMigrate: UserInternal | null = null;
129133
const key = _persistenceKeyName(userKey, auth.config.apiKey, auth.name);
134+
135+
// Pull out the existing user, setting the chosen persistence to that
136+
// persistence if the user exists.
137+
let userToMigrate: UserInternal | null = null;
138+
// Note, here we check for a user in _all_ persistences, not just the
139+
// ones deemed available. If we can migrate a user out of a broken
140+
// persistence, we will (but only if that persistence supports migration).
130141
for (const persistence of persistenceHierarchy) {
131-
// We attempt to call _get without checking _isAvailable since here we don't care if the full
132-
// round-trip (read+write) is supported. We'll take the first one that we can read or give up.
133142
try {
134-
const blob = await persistence._get<PersistedBlob>(key); // throws if unsupported
143+
const blob = await persistence._get<PersistedBlob>(key);
135144
if (blob) {
136145
const user = UserImpl._fromJSON(auth, blob); // throws for unparsable blob (wrong format)
137-
if (persistence !== chosenPersistence) {
146+
if (persistence !== selectedPersistence) {
138147
userToMigrate = user;
139148
}
149+
selectedPersistence = persistence;
140150
break;
141151
}
142152
} catch {}
143153
}
144154

155+
// If we find the user in a persistence that does support migration, use
156+
// that migration path (of only persistences that support migration)
157+
const migrationHierarchy = availablePersistences.filter(
158+
p => p._shouldAllowMigration
159+
);
160+
161+
// If the persistence does _not_ allow migration, just finish off here
162+
if (
163+
!selectedPersistence._shouldAllowMigration ||
164+
!migrationHierarchy.length
165+
) {
166+
return new PersistenceUserManager(selectedPersistence, auth, userKey);
167+
}
168+
169+
selectedPersistence = migrationHierarchy[0];
145170
if (userToMigrate) {
146171
// This normally shouldn't throw since chosenPersistence.isAvailable() is true, but if it does
147172
// we'll just let it bubble to surface the error.
148-
await chosenPersistence._set(key, userToMigrate.toJSON());
173+
await selectedPersistence._set(key, userToMigrate.toJSON());
149174
}
150175

151176
// Attempt to clear the key in other persistences but ignore errors. This helps prevent issues
152177
// such as users getting stuck with a previous account after signing out and refreshing the tab.
153178
await Promise.all(
154179
persistenceHierarchy.map(async persistence => {
155-
if (persistence !== chosenPersistence) {
180+
if (persistence !== selectedPersistence) {
156181
try {
157182
await persistence._remove(key);
158183
} catch {}
159184
}
160185
})
161186
);
162-
return new PersistenceUserManager(chosenPersistence, auth, userKey);
187+
return new PersistenceUserManager(selectedPersistence, auth, userKey);
163188
}
164189
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ describe('core/auth/initializeAuth', () => {
324324
const stub = sinon.stub(
325325
_getInstance<PersistenceInternal>(browserSessionPersistence)
326326
);
327+
stub._isAvailable.returns(Promise.resolve(true));
327328
stub._remove.returns(Promise.resolve());
328329
completeRedirectFnStub.returns(Promise.reject(new Error('no')));
329330

0 commit comments

Comments
 (0)