Skip to content
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
1 change: 1 addition & 0 deletions packages/firestore/src/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ export function configureFirestore(firestore: Firestore): void {
firestore._databaseId,
firestore._app?.options.appId || '',
firestore._persistenceKey,
firestore._app?.options.apiKey,
settings
);
if (!firestore._componentsProvider) {
Expand Down
3 changes: 2 additions & 1 deletion packages/firestore/src/core/database_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export class DatabaseInfo {
readonly autoDetectLongPolling: boolean,
readonly longPollingOptions: ExperimentalLongPollingOptions,
readonly useFetchStreams: boolean,
readonly isUsingEmulator: boolean
readonly isUsingEmulator: boolean,
readonly apiKey: string | undefined
) {}
}

Expand Down
8 changes: 6 additions & 2 deletions packages/firestore/src/core/firestore_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,11 @@ export class FirestoreClient {
* an async I/O to complete).
*/
public asyncQueue: AsyncQueue,
private databaseInfo: DatabaseInfo,
/**
* @internal
* Exposed for testing
*/
public _databaseInfo: DatabaseInfo,
componentProvider?: {
_offline: OfflineComponentProvider;
_online: OnlineComponentProvider;
Expand All @@ -167,7 +171,7 @@ export class FirestoreClient {
get configuration(): ComponentConfiguration {
return {
asyncQueue: this.asyncQueue,
databaseInfo: this.databaseInfo,
databaseInfo: this._databaseInfo,
clientId: this.clientId,
authCredentials: this.authCredentials,
appCheckCredentials: this.appCheckCredentials,
Expand Down
5 changes: 4 additions & 1 deletion packages/firestore/src/lite-api/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export function getDatastore(firestore: FirestoreService): Datastore {
firestore._databaseId,
firestore.app.options.appId || '',
firestore._persistenceKey,
firestore.app.options.apiKey,
firestore._freezeSettings()
);
const connection = newConnection(databaseInfo);
Expand Down Expand Up @@ -108,6 +109,7 @@ export function makeDatabaseInfo(
databaseId: DatabaseId,
appId: string,
persistenceKey: string,
apiKey: string | undefined,
settings: FirestoreSettingsImpl
): DatabaseInfo {
return new DatabaseInfo(
Expand All @@ -120,6 +122,7 @@ export function makeDatabaseInfo(
settings.experimentalAutoDetectLongPolling,
cloneLongPollingOptions(settings.experimentalLongPollingOptions),
settings.useFetchStreams,
settings.isUsingEmulator
settings.isUsingEmulator,
apiKey
);
}
18 changes: 13 additions & 5 deletions packages/firestore/src/platform/node/grpc_connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ function createMetadata(
databasePath: string,
authToken: Token | null,
appCheckToken: Token | null,
appId: string
appId: string,
apiKey: string | undefined
): grpc.Metadata {
hardAssert(
authToken === null || authToken.type === 'OAuth',
Expand All @@ -69,6 +70,9 @@ function createMetadata(
// 11 from Google3.
metadata.set('Google-Cloud-Resource-Prefix', databasePath);
metadata.set('x-goog-request-params', databasePath);
if (apiKey) {
metadata.set('X-Goog-Api-Key', apiKey);
}
return metadata;
}

Expand Down Expand Up @@ -100,7 +104,8 @@ export class GrpcConnection implements Connection {
this.databasePath = `projects/${databaseInfo.databaseId.projectId}/databases/${databaseInfo.databaseId.database}`;
}

private ensureActiveStub(): GeneratedGrpcStub {
/** made protected for testing */
protected ensureActiveStub(): GeneratedGrpcStub {
if (!this.cachedStub) {
logDebug(LOG_TAG, 'Creating Firestore stub.');
const credentials = this.databaseInfo.ssl
Expand All @@ -127,7 +132,8 @@ export class GrpcConnection implements Connection {
this.databasePath,
authToken,
appCheckToken,
this.databaseInfo.appId
this.databaseInfo.appId,
this.databaseInfo.apiKey
);
const jsonRequest = { database: this.databasePath, ...request };

Expand Down Expand Up @@ -187,7 +193,8 @@ export class GrpcConnection implements Connection {
this.databasePath,
authToken,
appCheckToken,
this.databaseInfo.appId
this.databaseInfo.appId,
this.databaseInfo.apiKey
);
const jsonRequest = { ...request, database: this.databasePath };
const stream = stub[rpcName](jsonRequest, metadata);
Expand Down Expand Up @@ -239,7 +246,8 @@ export class GrpcConnection implements Connection {
this.databasePath,
authToken,
appCheckToken,
this.databaseInfo.appId
this.databaseInfo.appId,
this.databaseInfo.apiKey
);
const grpcStream = stub[rpcName](metadata);

Expand Down
10 changes: 8 additions & 2 deletions packages/firestore/src/remote/rest_connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export abstract class RestConnection implements Connection {
protected readonly baseUrl: string;
private readonly databasePath: string;
private readonly requestParams: string;
private readonly apiKey: string | undefined;

get shouldResourcePathBeIncludedInRequest(): boolean {
// Both `invokeRPC()` and `invokeStreamingRPC()` use their `path` arguments to determine
Expand All @@ -82,6 +83,7 @@ export abstract class RestConnection implements Connection {
this.databaseId.database === DEFAULT_DATABASE_NAME
? `project_id=${projectId}`
: `project_id=${projectId}&database_id=${databaseId}`;
this.apiKey = databaseInfo.apiKey;
}

invokeRPC<Req, Resp>(
Expand Down Expand Up @@ -194,13 +196,17 @@ export abstract class RestConnection implements Connection {
_forwardCredentials: boolean
): Promise<Resp>;

private makeUrl(rpcName: string, path: string): string {
protected makeUrl(rpcName: string, path: string): string {
const urlRpcName = RPC_NAME_URL_MAPPING[rpcName];
debugAssert(
urlRpcName !== undefined,
'Unknown REST mapping for: ' + rpcName
);
return `${this.baseUrl}/${RPC_URL_VERSION}/${path}:${urlRpcName}`;
let url = `${this.baseUrl}/${RPC_URL_VERSION}/${path}:${urlRpcName}`;
if (this.apiKey) {
url = `${url}?key=${encodeURIComponent(this.apiKey)}`;
}
return url;
}

/**
Expand Down
16 changes: 15 additions & 1 deletion packages/firestore/test/integration/api/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import {
enableIndexedDbPersistence,
setDoc,
memoryLocalCache,
getDocFromCache
getDocFromCache,
ensureFirestoreConfigured
} from '../util/firebase_export';
import { DEFAULT_SETTINGS } from '../util/settings';

Expand Down Expand Up @@ -200,4 +201,17 @@ describe('Firestore Provider', () => {

return terminate(firestore).then(() => terminate(firestore));
});

it('passes API key to database info', () => {
const app = initializeApp(
{ apiKey: 'fake-api-key-x', projectId: 'test-project' },
'test-app-getFirestore-x'
);
const fs = getFirestore(app);
ensureFirestoreConfigured(fs);

expect(fs._firestoreClient?._databaseInfo.apiKey).to.equal(
'fake-api-key-x'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export function getDefaultDatabaseInfo(): DatabaseInfo {
DEFAULT_SETTINGS.experimentalLongPollingOptions ?? {}
),
/*use FetchStreams= */ false,
/*isUsingEmulator=*/ false
/*isUsingEmulator=*/ false,
undefined
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('Fetch Connection', () => {
DatabaseId.empty(),
'',
'',
'',
new FirestoreSettingsImpl({
host: 'abc.cloudworkstations.dev'
})
Expand Down
81 changes: 81 additions & 0 deletions packages/firestore/test/unit/remote/grpc_connection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* @license
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Metadata } from '@grpc/grpc-js';
import { expect } from 'chai';

import { DatabaseId, DatabaseInfo } from '../../../src/core/database_info';
import { ResourcePath } from '../../../src/model/path';
import { GrpcConnection } from '../../../src/platform/node/grpc_connection';

export class TestGrpcConnection extends GrpcConnection {
mockStub = {
lastMetadata: null,
mockRpc(
req: unknown,
metadata: Metadata,
callback: (err: unknown, resp: unknown) => void
) {
this.lastMetadata = metadata;
callback(null, null);
}
} as {
lastMetadata: null | Metadata;
[index: string]: unknown;
};

protected ensureActiveStub(): unknown {
return this.mockStub;
}
}

describe('GrpcConnection', () => {
const testDatabaseInfo = new DatabaseInfo(
new DatabaseId('testproject'),
'test-app-id',
'persistenceKey',
'example.com',
/*ssl=*/ false,
/*forceLongPolling=*/ false,
/*autoDetectLongPolling=*/ false,
/*longPollingOptions=*/ {},
/*useFetchStreams=*/ false,
/*isUsingEmulator=*/ false,
'grpc-connection-test-api-key'
);
const connection = new TestGrpcConnection(
{ google: { firestore: { v1: {} } } },
testDatabaseInfo
);

it('Passes the API Key from DatabaseInfo to the grpc stub', async () => {
const request = {
database: 'projects/testproject/databases/(default)',
writes: []
};
await connection.invokeRPC(
'mockRpc',
ResourcePath.emptyPath(),
request,
null,
null
);
expect(
connection.mockStub.lastMetadata?.get('x-goog-api-key')
).to.deep.equal(['grpc-connection-test-api-key']);
});
});
5 changes: 3 additions & 2 deletions packages/firestore/test/unit/remote/rest_connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ describe('RestConnection', () => {
/*autoDetectLongPolling=*/ false,
/*longPollingOptions=*/ {},
/*useFetchStreams=*/ false,
/*isUsingEmulator=*/ false
/*isUsingEmulator=*/ false,
'rest-connection-test-api-key'
);
const connection = new TestRestConnection(testDatabaseInfo);

Expand All @@ -83,7 +84,7 @@ describe('RestConnection', () => {
null
);
expect(connection.lastUrl).to.equal(
'http://example.com/v1/projects/testproject/databases/(default)/documents:commit'
'http://example.com/v1/projects/testproject/databases/(default)/documents:commit?key=rest-connection-test-api-key'
);
});

Expand Down
3 changes: 2 additions & 1 deletion packages/firestore/test/unit/specs/spec_test_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ abstract class TestRunner {
/*autoDetectLongPolling=*/ false,
/*longPollingOptions=*/ {},
/*useFetchStreams=*/ false,
/*isUsingEmulator=*/ false
/*isUsingEmulator=*/ false,
'test-api-key'
);

// TODO(mrschmidt): During client startup in `firestore_client`, we block
Expand Down
Loading