Skip to content

Commit 4c4b6ae

Browse files
Fire onInit callback if instance is already available (#4971)
1 parent ed4f95c commit 4c4b6ae

File tree

4 files changed

+82
-16
lines changed

4 files changed

+82
-16
lines changed

.changeset/cold-llamas-breathe.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@firebase/component": patch
3+
"@firebase/firestore": patch
4+
---
5+
6+
Fixes a regression that prevented Firestore from detecting Auth during its initial initialization, which could cause some writes to not be send.

packages/component/src/provider.test.ts

+43-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
import { expect } from 'chai';
19-
import { fake, SinonSpy } from 'sinon';
19+
import { fake, SinonSpy, match } from 'sinon';
2020
import { ComponentContainer } from './component_container';
2121
import { FirebaseService } from '@firebase/app-types/private';
2222
// eslint-disable-next-line import/no-extraneous-dependencies
@@ -181,6 +181,46 @@ describe('Provider', () => {
181181
expect(callback2).to.have.been.calledOnce;
182182
});
183183

184+
it('invokes callback for existing instance', () => {
185+
provider.setComponent(
186+
getFakeComponent(
187+
'test',
188+
() => ({ test: true }),
189+
false,
190+
InstantiationMode.EXPLICIT
191+
)
192+
);
193+
const callback = fake();
194+
provider.initialize();
195+
provider.onInit(callback);
196+
197+
expect(callback).to.have.been.calledOnce;
198+
});
199+
200+
it('passes instance identifier', () => {
201+
provider.setComponent(
202+
getFakeComponent(
203+
'test',
204+
() => ({ test: true }),
205+
true,
206+
InstantiationMode.EAGER
207+
)
208+
);
209+
const callback1 = fake();
210+
const callback2 = fake();
211+
212+
provider.getImmediate({ identifier: 'id1' });
213+
provider.getImmediate({ identifier: 'id2' });
214+
215+
provider.onInit(callback1, 'id1');
216+
provider.onInit(callback2, 'id2');
217+
218+
expect(callback1).to.have.been.calledOnce;
219+
expect(callback1).to.have.been.calledWith(match.any, 'id1');
220+
expect(callback2).to.have.been.calledOnce;
221+
expect(callback2).to.have.been.calledWith(match.any, 'id2');
222+
});
223+
184224
it('returns a function to unregister the callback', () => {
185225
provider.setComponent(
186226
getFakeComponent(
@@ -193,8 +233,8 @@ describe('Provider', () => {
193233
const callback1 = fake();
194234
const callback2 = fake();
195235
provider.onInit(callback1);
196-
const unregsiter = provider.onInit(callback2);
197-
unregsiter();
236+
const unregister = provider.onInit(callback2);
237+
unregister();
198238

199239
provider.initialize();
200240
expect(callback1).to.have.been.calledOnce;

packages/component/src/provider.ts

+30-13
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class Provider<T extends Name> {
3838
string,
3939
Deferred<NameServiceMapping[T]>
4040
> = new Map();
41-
private onInitCallbacks: Set<OnInitCallBack<T>> = new Set();
41+
private onInitCallbacks: Map<string, Set<OnInitCallBack<T>>> = new Map();
4242

4343
constructor(
4444
private readonly name: T,
@@ -49,7 +49,7 @@ export class Provider<T extends Name> {
4949
* @param identifier A provider can provide mulitple instances of a service
5050
* if this.component.multipleInstances is true.
5151
*/
52-
get(identifier: string = DEFAULT_ENTRY_NAME): Promise<NameServiceMapping[T]> {
52+
get(identifier?: string): Promise<NameServiceMapping[T]> {
5353
// if multipleInstances is not supported, use the default name
5454
const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
5555

@@ -99,11 +99,11 @@ export class Provider<T extends Name> {
9999
identifier?: string;
100100
optional?: boolean;
101101
}): NameServiceMapping[T] | null {
102-
const identifier = options?.identifier ?? DEFAULT_ENTRY_NAME;
103-
const optional = options?.optional ?? false;
104-
105102
// if multipleInstances is not supported, use the default name
106-
const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
103+
const normalizedIdentifier = this.normalizeInstanceIdentifier(
104+
options?.identifier
105+
);
106+
const optional = options?.optional ?? false;
107107

108108
if (
109109
this.isInitialized(normalizedIdentifier) ||
@@ -219,9 +219,9 @@ export class Provider<T extends Name> {
219219
}
220220

221221
initialize(opts: InitializeOptions = {}): NameServiceMapping[T] {
222-
const { instanceIdentifier = DEFAULT_ENTRY_NAME, options = {} } = opts;
222+
const { options = {} } = opts;
223223
const normalizedIdentifier = this.normalizeInstanceIdentifier(
224-
instanceIdentifier
224+
opts.instanceIdentifier
225225
);
226226
if (this.isInitialized(normalizedIdentifier)) {
227227
throw Error(
@@ -261,13 +261,24 @@ export class Provider<T extends Name> {
261261
* @param callback - a function that will be invoked after the provider has been initialized by calling provider.initialize().
262262
* The function is invoked SYNCHRONOUSLY, so it should not execute any longrunning tasks in order to not block the program.
263263
*
264+
* @param identifier An optional instance identifier
264265
* @returns a function to unregister the callback
265266
*/
266-
onInit(callback: OnInitCallBack<T>): () => void {
267-
this.onInitCallbacks.add(callback);
267+
onInit(callback: OnInitCallBack<T>, identifier?: string): () => void {
268+
const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
269+
const existingCallbacks =
270+
this.onInitCallbacks.get(normalizedIdentifier) ??
271+
new Set<OnInitCallBack<T>>();
272+
existingCallbacks.add(callback);
273+
this.onInitCallbacks.set(normalizedIdentifier, existingCallbacks);
274+
275+
const existingInstance = this.instances.has(normalizedIdentifier);
276+
if (existingInstance) {
277+
callback(existingInstance, normalizedIdentifier);
278+
}
268279

269280
return () => {
270-
this.onInitCallbacks.delete(callback);
281+
existingCallbacks.delete(callback);
271282
};
272283
}
273284

@@ -279,7 +290,11 @@ export class Provider<T extends Name> {
279290
instance: NameServiceMapping[T],
280291
identifier: string
281292
): void {
282-
for (const callback of this.onInitCallbacks) {
293+
const callbacks = this.onInitCallbacks.get(identifier);
294+
if (!callbacks) {
295+
return;
296+
}
297+
for (const callback of callbacks) {
283298
try {
284299
callback(instance, identifier);
285300
} catch {
@@ -324,7 +339,9 @@ export class Provider<T extends Name> {
324339
return instance || null;
325340
}
326341

327-
private normalizeInstanceIdentifier(identifier: string): string {
342+
private normalizeInstanceIdentifier(
343+
identifier: string = DEFAULT_ENTRY_NAME
344+
): string {
328345
if (this.component) {
329346
return this.component.multipleInstances ? identifier : DEFAULT_ENTRY_NAME;
330347
} else {

packages/firestore/src/api/credentials.ts

+3
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
307307
this.invokeChangeListener = true;
308308
this.asyncQueue = asyncQueue;
309309
this.changeListener = changeListener;
310+
if (this.auth) {
311+
this.awaitTokenAndRaiseInitialEvent();
312+
}
310313
}
311314

312315
removeChangeListener(): void {

0 commit comments

Comments
 (0)