Skip to content

Commit 016cc8f

Browse files
committed
Merge remote-tracking branch 'origin/master' into mila/fix-cache-empty-result-bug
2 parents 4651a5b + de32c24 commit 016cc8f

File tree

34 files changed

+937
-162
lines changed

34 files changed

+937
-162
lines changed

.changeset/mighty-insects-judge.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@firebase/app': minor
3+
'@firebase/app-types': minor
4+
'@firebase/util': minor
5+
'@firebase/auth': patch
6+
'@firebase/database': patch
7+
'@firebase/firestore': patch
8+
'@firebase/functions': patch
9+
'@firebase/storage': patch
10+
'firebase': minor
11+
---
12+
13+
Add functionality to auto-initialize project config and emulator settings from global defaults provided by framework tooling.

.changeset/nine-cherries-swim.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/firestore': patch
3+
---
4+
5+
Fix a time travel issue across multiple tabs

.changeset/wicked-tomatoes-grow.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/firestore': minor
3+
'firebase': minor
4+
---
5+
6+
Added `getCountFromServer()` (`getCount()` in the Lite SDK), which fetches the number of documents in the result set without actually downloading the documents.

common/api-review/app.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ export function initializeApp(options: FirebaseOptions, name?: string): Firebase
9393
// @public
9494
export function initializeApp(options: FirebaseOptions, config?: FirebaseAppSettings): FirebaseApp;
9595

96+
// @public
97+
export function initializeApp(): FirebaseApp;
98+
9699
// @public
97100
export function onLog(logCallback: LogCallback | null, options?: LogOptions): void;
98101

common/api-review/firestore-lite.api.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,35 @@ export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unkn
1717
[K in keyof T & string as `${Prefix}.${K}`]+?: T[K];
1818
};
1919

20+
// @public
21+
export class AggregateField<T> {
22+
type: string;
23+
}
24+
25+
// @public
26+
export type AggregateFieldType = AggregateField<number>;
27+
28+
// @public
29+
export class AggregateQuerySnapshot<T extends AggregateSpec> {
30+
data(): AggregateSpecData<T>;
31+
readonly query: Query<unknown>;
32+
readonly type = "AggregateQuerySnapshot";
33+
}
34+
35+
// @public
36+
export function aggregateQuerySnapshotEqual<T extends AggregateSpec>(left: AggregateQuerySnapshot<T>, right: AggregateQuerySnapshot<T>): boolean;
37+
38+
// @public
39+
export interface AggregateSpec {
40+
// (undocumented)
41+
[field: string]: AggregateFieldType;
42+
}
43+
44+
// @public
45+
export type AggregateSpecData<T extends AggregateSpec> = {
46+
[P in keyof T]: T[P] extends AggregateField<infer U> ? U : never;
47+
};
48+
2049
// @public
2150
export function arrayRemove(...elements: unknown[]): FieldValue;
2251

@@ -169,6 +198,11 @@ export class GeoPoint {
169198
};
170199
}
171200

201+
// @public
202+
export function getCount(query: Query<unknown>): Promise<AggregateQuerySnapshot<{
203+
count: AggregateField<number>;
204+
}>>;
205+
172206
// @public
173207
export function getDoc<T>(reference: DocumentReference<T>): Promise<DocumentSnapshot<T>>;
174208

common/api-review/firestore.api.md

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,35 @@ export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unkn
1717
[K in keyof T & string as `${Prefix}.${K}`]+?: T[K];
1818
};
1919

20+
// @public
21+
export class AggregateField<T> {
22+
type: string;
23+
}
24+
25+
// @public
26+
export type AggregateFieldType = AggregateField<number>;
27+
28+
// @public
29+
export class AggregateQuerySnapshot<T extends AggregateSpec> {
30+
data(): AggregateSpecData<T>;
31+
readonly query: Query<unknown>;
32+
readonly type = "AggregateQuerySnapshot";
33+
}
34+
35+
// @public
36+
export function aggregateQuerySnapshotEqual<T extends AggregateSpec>(left: AggregateQuerySnapshot<T>, right: AggregateQuerySnapshot<T>): boolean;
37+
38+
// @public
39+
export interface AggregateSpec {
40+
// (undocumented)
41+
[field: string]: AggregateFieldType;
42+
}
43+
44+
// @public
45+
export type AggregateSpecData<T extends AggregateSpec> = {
46+
[P in keyof T]: T[P] extends AggregateField<infer U> ? U : never;
47+
};
48+
2049
// @public
2150
export function arrayRemove(...elements: unknown[]): FieldValue;
2251

@@ -209,6 +238,11 @@ export class GeoPoint {
209238
};
210239
}
211240

241+
// @public
242+
export function getCountFromServer(query: Query<unknown>): Promise<AggregateQuerySnapshot<{
243+
count: AggregateField<number>;
244+
}>>;
245+
212246
// @public
213247
export function getDoc<T>(reference: DocumentReference<T>): Promise<DocumentSnapshot<T>>;
214248

@@ -228,14 +262,38 @@ export function getDocsFromCache<T>(query: Query<T>): Promise<QuerySnapshot<T>>;
228262
export function getDocsFromServer<T>(query: Query<T>): Promise<QuerySnapshot<T>>;
229263

230264
// @public
231-
export function getFirestore(): Firestore;
265+
export function getFirestore(app: FirebaseApp): Firestore;
232266

233267
// @public
234-
export function getFirestore(app: FirebaseApp): Firestore;
268+
export function getFirestore(): Firestore;
235269

236270
// @public
237271
export function increment(n: number): FieldValue;
238272

273+
// @beta
274+
export interface Index {
275+
// (undocumented)
276+
[key: string]: unknown;
277+
readonly collectionGroup: string;
278+
readonly fields?: IndexField[];
279+
}
280+
281+
// @beta
282+
export interface IndexConfiguration {
283+
// (undocumented)
284+
[key: string]: unknown;
285+
readonly indexes?: Index[];
286+
}
287+
288+
// @beta
289+
export interface IndexField {
290+
// (undocumented)
291+
[key: string]: unknown;
292+
readonly arrayConfig?: 'CONTAINS';
293+
readonly fieldPath: string;
294+
readonly order?: 'ASCENDING' | 'DESCENDING';
295+
}
296+
239297
// @public
240298
export function initializeFirestore(app: FirebaseApp, settings: FirestoreSettings, databaseId?: string): Firestore;
241299

@@ -399,6 +457,12 @@ export function setDoc<T>(reference: DocumentReference<T>, data: WithFieldValue<
399457
// @public
400458
export function setDoc<T>(reference: DocumentReference<T>, data: PartialWithFieldValue<T>, options: SetOptions): Promise<void>;
401459

460+
// @beta
461+
export function setIndexConfiguration(firestore: Firestore, configuration: IndexConfiguration): Promise<void>;
462+
463+
// @beta
464+
export function setIndexConfiguration(firestore: Firestore, json: string): Promise<void>;
465+
402466
// @public
403467
export function setLogLevel(logLevel: LogLevel): void;
404468

common/api-review/util.api.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,28 @@ export function errorPrefix(fnName: string, argName: string): string;
173173
// @public (undocumented)
174174
export type Executor<T> = (observer: Observer<T>) => void;
175175

176+
// @public
177+
export type ExperimentalKey = 'authTokenSyncURL' | 'authIdTokenMaxAge';
178+
176179
// Warning: (ae-missing-release-tag) "extractQuerystring" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
177180
//
178181
// @public
179182
export function extractQuerystring(url: string): string;
180183

184+
// @public
185+
export interface FirebaseDefaults {
186+
// (undocumented)
187+
[key: string]: unknown;
188+
// (undocumented)
189+
_authIdTokenMaxAge?: number;
190+
// (undocumented)
191+
_authTokenSyncURL?: string;
192+
// (undocumented)
193+
config?: Record<string, string>;
194+
// (undocumented)
195+
emulatorHosts?: Record<string, string>;
196+
}
197+
181198
// Warning: (ae-missing-release-tag) "FirebaseError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
182199
//
183200
// @public (undocumented)
@@ -195,6 +212,15 @@ export class FirebaseError extends Error {
195212
// @public
196213
export type FirebaseSignInProvider = 'custom' | 'email' | 'password' | 'phone' | 'anonymous' | 'google.com' | 'facebook.com' | 'github.com' | 'twitter.com' | 'microsoft.com' | 'apple.com';
197214

215+
// @public
216+
export const getDefaultAppConfig: () => Record<string, string> | undefined;
217+
218+
// @public
219+
export const getDefaultEmulatorHost: (productName: string) => string | undefined;
220+
221+
// @public
222+
export const getExperimentalSetting: <T extends ExperimentalKey>(name: T) => FirebaseDefaults[`_${T}`];
223+
198224
// Warning: (ae-missing-release-tag) "getGlobal" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
199225
//
200226
// @public

packages/app/src/api.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {
3939
LogOptions,
4040
setUserLogHandler
4141
} from '@firebase/logger';
42-
import { deepEqual } from '@firebase/util';
42+
import { deepEqual, getDefaultAppConfig } from '@firebase/util';
4343

4444
export { FirebaseError } from '@firebase/util';
4545

@@ -110,10 +110,18 @@ export function initializeApp(
110110
options: FirebaseOptions,
111111
config?: FirebaseAppSettings
112112
): FirebaseApp;
113+
/**
114+
* Creates and initializes a FirebaseApp instance.
115+
*
116+
* @public
117+
*/
118+
export function initializeApp(): FirebaseApp;
113119
export function initializeApp(
114-
options: FirebaseOptions,
120+
_options?: FirebaseOptions,
115121
rawConfig = {}
116122
): FirebaseApp {
123+
let options = _options;
124+
117125
if (typeof rawConfig !== 'object') {
118126
const name = rawConfig;
119127
rawConfig = { name };
@@ -132,6 +140,12 @@ export function initializeApp(
132140
});
133141
}
134142

143+
options ||= getDefaultAppConfig();
144+
145+
if (!options) {
146+
throw ERROR_FACTORY.create(AppError.NO_OPTIONS);
147+
}
148+
135149
const existingApp = _apps.get(name) as FirebaseAppImpl;
136150
if (existingApp) {
137151
// return the existing app if options and config deep equal the ones in the existing app.
@@ -188,6 +202,9 @@ export function initializeApp(
188202
*/
189203
export function getApp(name: string = DEFAULT_ENTRY_NAME): FirebaseApp {
190204
const app = _apps.get(name);
205+
if (!app && name === DEFAULT_ENTRY_NAME) {
206+
return initializeApp();
207+
}
191208
if (!app) {
192209
throw ERROR_FACTORY.create(AppError.NO_APP, { appName: name });
193210
}

packages/app/src/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const enum AppError {
2222
BAD_APP_NAME = 'bad-app-name',
2323
DUPLICATE_APP = 'duplicate-app',
2424
APP_DELETED = 'app-deleted',
25+
NO_OPTIONS = 'no-options',
2526
INVALID_APP_ARGUMENT = 'invalid-app-argument',
2627
INVALID_LOG_ARGUMENT = 'invalid-log-argument',
2728
IDB_OPEN = 'idb-open',
@@ -38,6 +39,8 @@ const ERRORS: ErrorMap<AppError> = {
3839
[AppError.DUPLICATE_APP]:
3940
"Firebase App named '{$appName}' already exists with different options or config",
4041
[AppError.APP_DELETED]: "Firebase App named '{$appName}' already deleted",
42+
[AppError.NO_OPTIONS]:
43+
'Need to provide options, when not being deployed to hosting via source.',
4144
[AppError.INVALID_APP_ARGUMENT]:
4245
'firebase.{$appName}() takes either no argument or a ' +
4346
'Firebase App instance.',

packages/auth/src/platform_browser/index.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,50 @@
1717

1818
import { FirebaseApp, getApp, _getProvider } from '@firebase/app';
1919

20-
import { initializeAuth } from '..';
20+
import {
21+
initializeAuth,
22+
beforeAuthStateChanged,
23+
onIdTokenChanged,
24+
connectAuthEmulator
25+
} from '..';
2126
import { registerAuth } from '../core/auth/register';
2227
import { ClientPlatform } from '../core/util/version';
2328
import { browserLocalPersistence } from './persistence/local_storage';
2429
import { browserSessionPersistence } from './persistence/session_storage';
2530
import { indexedDBLocalPersistence } from './persistence/indexed_db';
2631
import { browserPopupRedirectResolver } from './popup_redirect';
27-
import { Auth } from '../model/public_types';
32+
import { Auth, User } from '../model/public_types';
33+
import { getDefaultEmulatorHost, getExperimentalSetting } from '@firebase/util';
34+
35+
const DEFAULT_ID_TOKEN_MAX_AGE = 5 * 60;
36+
const authIdTokenMaxAge =
37+
getExperimentalSetting('authIdTokenMaxAge') || DEFAULT_ID_TOKEN_MAX_AGE;
38+
39+
let lastPostedIdToken: string | undefined | null = null;
40+
41+
const mintCookieFactory = (url: string) => async (user: User | null) => {
42+
const idTokenResult = user && (await user.getIdTokenResult());
43+
const idTokenAge =
44+
idTokenResult &&
45+
(new Date().getTime() - Date.parse(idTokenResult.issuedAtTime)) / 1_000;
46+
if (idTokenAge && idTokenAge > authIdTokenMaxAge) {
47+
return;
48+
}
49+
// Specifically trip null => undefined when logged out, to delete any existing cookie
50+
const idToken = idTokenResult?.token;
51+
if (lastPostedIdToken === idToken) {
52+
return;
53+
}
54+
lastPostedIdToken = idToken;
55+
await fetch(url, {
56+
method: idToken ? 'POST' : 'DELETE',
57+
headers: idToken
58+
? {
59+
'Authorization': `Bearer ${idToken}`
60+
}
61+
: {}
62+
});
63+
};
2864

2965
/**
3066
* Returns the Auth instance associated with the provided {@link @firebase/app#FirebaseApp}.
@@ -41,14 +77,30 @@ export function getAuth(app: FirebaseApp = getApp()): Auth {
4177
return provider.getImmediate();
4278
}
4379

44-
return initializeAuth(app, {
80+
const auth = initializeAuth(app, {
4581
popupRedirectResolver: browserPopupRedirectResolver,
4682
persistence: [
4783
indexedDBLocalPersistence,
4884
browserLocalPersistence,
4985
browserSessionPersistence
5086
]
5187
});
88+
89+
const authTokenSyncUrl = getExperimentalSetting('authTokenSyncURL');
90+
if (authTokenSyncUrl) {
91+
const mintCookie = mintCookieFactory(authTokenSyncUrl);
92+
beforeAuthStateChanged(auth, mintCookie, () =>
93+
mintCookie(auth.currentUser)
94+
);
95+
onIdTokenChanged(auth, user => mintCookie(user));
96+
}
97+
98+
const authEmulatorHost = getDefaultEmulatorHost('auth');
99+
if (authEmulatorHost) {
100+
connectAuthEmulator(auth, `http://${authEmulatorHost}`);
101+
}
102+
103+
return auth;
52104
}
53105

54106
registerAuth(ClientPlatform.BROWSER);

0 commit comments

Comments
 (0)