diff --git a/config/webpack.test.js b/config/webpack.test.js
index 8094079e1e2..5da3c423b29 100644
--- a/config/webpack.test.js
+++ b/config/webpack.test.js
@@ -86,9 +86,10 @@ module.exports = {
new webpack.NormalModuleReplacementPlugin(
FIRESTORE_PLATFORM_RE,
resource => {
+ const targetPlatform = process.env.TEST_PLATFORM || 'browser';
resource.request = resource.request.replace(
FIRESTORE_PLATFORM_RE,
- '$1/platform/browser/$2.ts'
+ `$1/platform/${targetPlatform}/$2.ts`
);
}
),
diff --git a/packages/firestore/.idea/runConfigurations/firestore_lite_Tests__Emulator_.xml b/packages/firestore/.idea/runConfigurations/firestore_lite_Tests__Emulator_.xml
index 0d03257c114..49fe37e3a8f 100644
--- a/packages/firestore/.idea/runConfigurations/firestore_lite_Tests__Emulator_.xml
+++ b/packages/firestore/.idea/runConfigurations/firestore_lite_Tests__Emulator_.xml
@@ -9,6 +9,7 @@
+
bdd
--require ts-node/register/type-check --require lite/index.ts --timeout 5000
diff --git a/packages/firestore/karma.conf.js b/packages/firestore/karma.conf.js
index 3e301c94245..5c32e76f546 100644
--- a/packages/firestore/karma.conf.js
+++ b/packages/firestore/karma.conf.js
@@ -55,6 +55,7 @@ function getTestFiles(argv) {
} else if (argv.integration) {
return [legcayIntegrationTests];
} else if (argv.lite) {
+ process.env.TEST_PLATFORM = 'browser_lite';
return [liteIntegrationTests];
} else if (argv.exp) {
return [expIntegrationTests];
diff --git a/packages/firestore/package.json b/packages/firestore/package.json
index 74e2bb8c75d..d9942ea7528 100644
--- a/packages/firestore/package.json
+++ b/packages/firestore/package.json
@@ -24,7 +24,7 @@
"gendeps:exp": "../../scripts/exp/extract-deps.sh --types ./exp-types/index.d.ts --bundle ./dist/exp/tmp.js --output ./exp/dependencies.json",
"pregendeps:lite": "node scripts/build-bundle.js --input ./lite/index.ts --output ./dist/lite/tmp.js",
"gendeps:lite": "../../scripts/exp/extract-deps.sh --types ./lite-types/index.d.ts --bundle ./dist/lite/tmp.js --output ./lite/dependencies.json",
- "test:lite": "node ./scripts/run-tests.js --emulator --main=lite/index.ts 'lite/test/**/*.test.ts'",
+ "test:lite": "node ./scripts/run-tests.js --emulator --platform node_lite --main=lite/index.ts 'lite/test/**/*.test.ts'",
"test:lite:browser": "karma start --single-run --lite",
"test:lite:browser:debug": "karma start --single-run --lite --auto-watch",
"test:exp": "node ./scripts/run-tests.js --emulator --main=exp/index.ts test/integration/api/*.test.ts",
@@ -63,6 +63,7 @@
"@firebase/webchannel-wrapper": "0.2.41",
"@grpc/grpc-js": "^1.0.0",
"@grpc/proto-loader": "^0.5.0",
+ "node-fetch": "2.6.0",
"tslib": "^1.11.1"
},
"peerDependencies": {
diff --git a/packages/firestore/rollup.config.lite.js b/packages/firestore/rollup.config.lite.js
index bdf76719242..a808eb040d1 100644
--- a/packages/firestore/rollup.config.lite.js
+++ b/packages/firestore/rollup.config.lite.js
@@ -71,7 +71,7 @@ const allBuilds = [
format: 'es',
sourcemap: true
},
- plugins: [alias(util.generateAliasConfig('node')), ...nodePlugins],
+ plugins: [alias(util.generateAliasConfig('node_lite')), ...nodePlugins],
external: util.resolveNodeExterns,
treeshake: {
moduleSideEffects: false
@@ -111,7 +111,10 @@ const allBuilds = [
format: 'es',
sourcemap: true
},
- plugins: [alias(util.generateAliasConfig('browser')), ...browserPlugins],
+ plugins: [
+ alias(util.generateAliasConfig('browser_lite')),
+ ...browserPlugins
+ ],
external: util.resolveBrowserExterns,
treeshake: {
moduleSideEffects: false
@@ -125,7 +128,7 @@ const allBuilds = [
format: 'es',
sourcemap: true
},
- plugins: [alias(util.generateAliasConfig('rn')), ...browserPlugins],
+ plugins: [alias(util.generateAliasConfig('rn_lite')), ...browserPlugins],
external: util.resolveBrowserExterns,
treeshake: {
moduleSideEffects: false
diff --git a/packages/firestore/scripts/run-tests.js b/packages/firestore/scripts/run-tests.js
index 4ef3286aceb..3bee79d139e 100644
--- a/packages/firestore/scripts/run-tests.js
+++ b/packages/firestore/scripts/run-tests.js
@@ -14,4 +14,4 @@
* 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.
- */exports.__esModule=true;var yargs=require("yargs");var path_1=require("path");var child_process_promise_1=require("child-process-promise");var argv=yargs.options({main:{type:"string",demandOption:true},emulator:{type:"boolean"},persistence:{type:"boolean"}}).argv;var nyc=path_1.resolve(__dirname,"../../../node_modules/.bin/nyc");var mocha=path_1.resolve(__dirname,"../../../node_modules/.bin/mocha");process.env.TS_NODE_CACHE="NO";process.env.TS_NODE_COMPILER_OPTIONS='{"module":"commonjs"}';var args=[mocha,"--require","ts-node/register","--require",argv.main,"--config","../../config/mocharc.node.js"];if(argv.emulator){process.env.FIRESTORE_EMULATOR_PORT="8080";process.env.FIRESTORE_EMULATOR_PROJECT_ID="test-emulator"}if(argv.persistence){process.env.USE_MOCK_PERSISTENCE="YES";args.push("--require","test/util/node_persistence.ts")}args=args.concat(argv._);var childProcess=child_process_promise_1.spawn(nyc,args,{stdio:"inherit",cwd:process.cwd()}).childProcess;process.once("exit",(function(){return childProcess.kill()}));process.once("SIGINT",(function(){return childProcess.kill("SIGINT")}));process.once("SIGTERM",(function(){return childProcess.kill("SIGTERM")}));
\ No newline at end of file
+ */exports.__esModule=true;var yargs=require("yargs");var path_1=require("path");var child_process_promise_1=require("child-process-promise");var argv=yargs.options({main:{type:"string",demandOption:true},platform:{type:"string",default:"node"},emulator:{type:"boolean"},persistence:{type:"boolean"}}).argv;var nyc=path_1.resolve(__dirname,"../../../node_modules/.bin/nyc");var mocha=path_1.resolve(__dirname,"../../../node_modules/.bin/mocha");process.env.TS_NODE_CACHE="NO";process.env.TS_NODE_COMPILER_OPTIONS='{"module":"commonjs"}';process.env.TEST_PLATFORM=argv.platform;var args=[mocha,"--require","ts-node/register","--require",argv.main,"--config","../../config/mocharc.node.js"];if(argv.emulator){process.env.FIRESTORE_EMULATOR_PORT="8080";process.env.FIRESTORE_EMULATOR_PROJECT_ID="test-emulator"}if(argv.persistence){process.env.USE_MOCK_PERSISTENCE="YES";args.push("--require","test/util/node_persistence.ts")}args=args.concat(argv._);var childProcess=child_process_promise_1.spawn(nyc,args,{stdio:"inherit",cwd:process.cwd()}).childProcess;process.once("exit",(function(){return childProcess.kill()}));process.once("SIGINT",(function(){return childProcess.kill("SIGINT")}));process.once("SIGTERM",(function(){return childProcess.kill("SIGTERM")}));
\ No newline at end of file
diff --git a/packages/firestore/scripts/run-tests.ts b/packages/firestore/scripts/run-tests.ts
index 42013c78969..8cf75e3aae0 100644
--- a/packages/firestore/scripts/run-tests.ts
+++ b/packages/firestore/scripts/run-tests.ts
@@ -24,6 +24,10 @@ const argv = yargs.options({
type: 'string',
demandOption: true
},
+ platform: {
+ type: 'string',
+ default: 'node'
+ },
emulator: {
type: 'boolean'
},
@@ -37,6 +41,7 @@ const mocha = resolve(__dirname, '../../../node_modules/.bin/mocha');
process.env.TS_NODE_CACHE = 'NO';
process.env.TS_NODE_COMPILER_OPTIONS = '{"module":"commonjs"}';
+process.env.TEST_PLATFORM = argv.platform;
let args = [
mocha,
diff --git a/packages/firestore/src/platform/base64.ts b/packages/firestore/src/platform/base64.ts
index c40155c8d5b..b794158d909 100644
--- a/packages/firestore/src/platform/base64.ts
+++ b/packages/firestore/src/platform/base64.ts
@@ -15,41 +15,21 @@
* limitations under the License.
*/
-import { isNode, isReactNative } from '@firebase/util';
-
-import * as node from './node/base64';
-import * as rn from './rn/base64';
-import * as browser from './browser/base64';
+// This file is only used under ts-node.
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/base64`);
/** Converts a Base64 encoded string to a binary string. */
export function decodeBase64(encoded: string): string {
- if (isNode()) {
- return node.decodeBase64(encoded);
- } else if (isReactNative()) {
- return rn.decodeBase64(encoded);
- } else {
- return browser.decodeBase64(encoded);
- }
+ return platform.decodeBase64(encoded);
}
/** Converts a binary string to a Base64 encoded string. */
export function encodeBase64(raw: string): string {
- if (isNode()) {
- return node.encodeBase64(raw);
- } else if (isReactNative()) {
- return rn.encodeBase64(raw);
- } else {
- return browser.encodeBase64(raw);
- }
+ return platform.encodeBase64(raw);
}
/** True if and only if the Base64 conversion functions are available. */
export function isBase64Available(): boolean {
- if (isNode()) {
- return node.isBase64Available();
- } else if (isReactNative()) {
- return rn.isBase64Available();
- } else {
- return browser.isBase64Available();
- }
+ return platform.isBase64Available();
}
diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts
index 5e3256ec42e..0c0bb901863 100644
--- a/packages/firestore/src/platform/browser/webchannel_connection.ts
+++ b/packages/firestore/src/platform/browser/webchannel_connection.ts
@@ -35,79 +35,40 @@ import {
} from '@firebase/util';
import { Token } from '../../api/credentials';
-import { DatabaseId, DatabaseInfo } from '../../core/database_info';
-import { SDK_VERSION } from '../../core/version';
-import { Connection, Stream } from '../../remote/connection';
+import { DatabaseInfo } from '../../core/database_info';
+import { Stream } from '../../remote/connection';
import {
mapCodeFromRpcStatus,
mapCodeFromHttpResponseErrorStatus
} from '../../remote/rpc_error';
import { StreamBridge } from '../../remote/stream_bridge';
-import { debugAssert, fail, hardAssert } from '../../util/assert';
+import { fail, hardAssert } from '../../util/assert';
import { Code, FirestoreError } from '../../util/error';
import { logDebug, logWarn } from '../../util/log';
-import { Indexable } from '../../util/misc';
import { Rejecter, Resolver } from '../../util/promise';
import { StringMap } from '../../util/types';
+import { RestConnection } from '../../remote/rest_connection';
const LOG_TAG = 'Connection';
const RPC_STREAM_SERVICE = 'google.firestore.v1.Firestore';
-const RPC_URL_VERSION = 'v1';
-
-/**
- * Maps RPC names to the corresponding REST endpoint name.
- * Uses Object Literal notation to avoid renaming.
- */
-const RPC_NAME_REST_MAPPING: { [key: string]: string } = {};
-RPC_NAME_REST_MAPPING['BatchGetDocuments'] = 'batchGet';
-RPC_NAME_REST_MAPPING['Commit'] = 'commit';
-RPC_NAME_REST_MAPPING['RunQuery'] = 'runQuery';
-
-// TODO(b/38203344): The SDK_VERSION is set independently from Firebase because
-// we are doing out-of-band releases. Once we release as part of Firebase, we
-// should use the Firebase version instead.
-const X_GOOG_API_CLIENT_VALUE = 'gl-js/ fire/' + SDK_VERSION;
const XHR_TIMEOUT_SECS = 15;
-export class WebChannelConnection implements Connection {
- private readonly databaseId: DatabaseId;
- private readonly baseUrl: string;
+export class WebChannelConnection extends RestConnection {
private readonly forceLongPolling: boolean;
constructor(info: DatabaseInfo) {
- this.databaseId = info.databaseId;
- const proto = info.ssl ? 'https' : 'http';
- this.baseUrl = proto + '://' + info.host;
+ super(info);
this.forceLongPolling = info.forceLongPolling;
}
- /**
- * Modifies the headers for a request, adding any authorization token if
- * present and any additional headers for the request.
- */
- private modifyHeadersForRequest(
- headers: StringMap,
- token: Token | null
- ): void {
- if (token) {
- for (const header in token.authHeaders) {
- if (token.authHeaders.hasOwnProperty(header)) {
- headers[header] = token.authHeaders[header];
- }
- }
- }
- headers['X-Goog-Api-Client'] = X_GOOG_API_CLIENT_VALUE;
- }
-
- invokeRPC(
+ protected performRPCRequest(
rpcName: string,
- request: Req,
- token: Token | null
+ url: string,
+ headers: StringMap,
+ body: Req
): Promise {
- const url = this.makeUrl(rpcName);
-
return new Promise((resolve: Resolver, reject: Rejecter) => {
const xhr = new XhrIo();
xhr.listenOnce(EventType.COMPLETE, () => {
@@ -161,7 +122,6 @@ export class WebChannelConnection implements Connection {
} else {
// If we received an HTTP_ERROR but there's no status code,
// it's most probably a connection issue
- logDebug(LOG_TAG, 'RPC "' + rpcName + '" failed');
reject(
new FirestoreError(Code.UNAVAILABLE, 'Connection failed.')
);
@@ -184,37 +144,11 @@ export class WebChannelConnection implements Connection {
}
});
- // The database field is already encoded in URL. Specifying it again in
- // the body is not necessary in production, and will cause duplicate field
- // errors in the Firestore Emulator. Let's remove it.
- const jsonObj = ({ ...request } as unknown) as Indexable;
- delete jsonObj.database;
-
- const requestString = JSON.stringify(jsonObj);
- logDebug(LOG_TAG, 'XHR sending: ', url + ' ' + requestString);
- // Content-Type: text/plain will avoid preflight requests which might
- // mess with CORS and redirects by proxies. If we add custom headers
- // we will need to change this code to potentially use the
- // $httpOverwrite parameter supported by ESF to avoid
- // triggering preflight requests.
- const headers: StringMap = { 'Content-Type': 'text/plain' };
-
- this.modifyHeadersForRequest(headers, token);
-
+ const requestString = JSON.stringify(body);
xhr.send(url, 'POST', requestString, headers, XHR_TIMEOUT_SECS);
});
}
- invokeStreamingRPC(
- rpcName: string,
- request: Req,
- token: Token | null
- ): Promise {
- // The REST API automatically aggregates all of the streamed results, so we
- // can just use the normal invoke() method.
- return this.invokeRPC(rpcName, request, token);
- }
-
openStream(
rpcName: string,
token: Token | null
@@ -283,7 +217,7 @@ export class WebChannelConnection implements Connection {
}
const url = urlParts.join('');
- logDebug(LOG_TAG, 'Creating WebChannel: ' + url + ' ' + request);
+ logDebug(LOG_TAG, 'Creating WebChannel: ' + url, request);
const channel = webchannelTransport.createWebChannel(url, request);
// WebChannel supports sending the first message with the handshake - saving
@@ -420,24 +354,4 @@ export class WebChannelConnection implements Connection {
}, 0);
return streamBridge;
}
-
- // visible for testing
- makeUrl(rpcName: string): string {
- const urlRpcName = RPC_NAME_REST_MAPPING[rpcName];
- debugAssert(
- urlRpcName !== undefined,
- 'Unknown REST mapping for: ' + rpcName
- );
- return (
- this.baseUrl +
- '/' +
- RPC_URL_VERSION +
- '/projects/' +
- this.databaseId.projectId +
- '/databases/' +
- this.databaseId.database +
- '/documents:' +
- urlRpcName
- );
- }
}
diff --git a/packages/firestore/src/platform/browser_lite/base64.ts b/packages/firestore/src/platform/browser_lite/base64.ts
new file mode 100644
index 00000000000..9214f8da36b
--- /dev/null
+++ b/packages/firestore/src/platform/browser_lite/base64.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../browser/base64';
diff --git a/packages/firestore/src/platform/browser_lite/connection.ts b/packages/firestore/src/platform/browser_lite/connection.ts
new file mode 100644
index 00000000000..1aca0a8ee9b
--- /dev/null
+++ b/packages/firestore/src/platform/browser_lite/connection.ts
@@ -0,0 +1,27 @@
+/**
+ * @license
+ * Copyright 2020 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 { DatabaseInfo } from '../../core/database_info';
+import { Connection } from '../../remote/connection';
+import { FetchConnection } from './fetch_connection';
+
+export { newConnectivityMonitor } from '../browser/connection';
+
+/** Initializes the HTTP connection for the REST API. */
+export function newConnection(databaseInfo: DatabaseInfo): Promise {
+ return Promise.resolve(new FetchConnection(databaseInfo, fetch.bind(null)));
+}
diff --git a/packages/firestore/src/platform/browser_lite/dom.ts b/packages/firestore/src/platform/browser_lite/dom.ts
new file mode 100644
index 00000000000..7704d7a790b
--- /dev/null
+++ b/packages/firestore/src/platform/browser_lite/dom.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../browser/dom';
diff --git a/packages/firestore/src/platform/browser_lite/fetch_connection.ts b/packages/firestore/src/platform/browser_lite/fetch_connection.ts
new file mode 100644
index 00000000000..d0644f1c7de
--- /dev/null
+++ b/packages/firestore/src/platform/browser_lite/fetch_connection.ts
@@ -0,0 +1,80 @@
+/**
+ * @license
+ * Copyright 2020 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 { Token } from '../../api/credentials';
+import { Stream } from '../../remote/connection';
+import { mapCodeFromHttpStatus } from '../../remote/rpc_error';
+import { FirestoreError } from '../../util/error';
+import { StringMap } from '../../util/types';
+import { RestConnection } from '../../remote/rest_connection';
+import { DatabaseInfo } from '../../core/database_info';
+
+/**
+ * A Rest-based connection that relies on the native HTTP stack
+ * (e.g. `fetch` or a polyfill).
+ */
+export class FetchConnection extends RestConnection {
+ /**
+ * @param databaseInfo The connection info.
+ * @param fetchImpl `fetch` or a Polyfill that implements the fetch API.
+ */
+ constructor(
+ databaseInfo: DatabaseInfo,
+ private readonly fetchImpl: typeof fetch
+ ) {
+ super(databaseInfo);
+ }
+
+ openStream(
+ rpcName: string,
+ token: Token | null
+ ): Stream {
+ throw new Error('Not supported by FetchConnection');
+ }
+
+ protected async performRPCRequest(
+ rpcName: string,
+ url: string,
+ headers: StringMap,
+ body: Req
+ ): Promise {
+ const requestJson = JSON.stringify(body);
+ let response: Response;
+
+ try {
+ response = await this.fetchImpl(url, {
+ method: 'POST',
+ headers,
+ body: requestJson
+ });
+ } catch (err) {
+ throw new FirestoreError(
+ mapCodeFromHttpStatus(err.status),
+ 'Request failed with error: ' + err.statusText
+ );
+ }
+
+ if (!response.ok) {
+ throw new FirestoreError(
+ mapCodeFromHttpStatus(response.status),
+ 'Request failed with error: ' + response.statusText
+ );
+ }
+
+ return response.json();
+ }
+}
diff --git a/packages/firestore/src/platform/browser_lite/format_json.ts b/packages/firestore/src/platform/browser_lite/format_json.ts
new file mode 100644
index 00000000000..278b5dfbba1
--- /dev/null
+++ b/packages/firestore/src/platform/browser_lite/format_json.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../browser/format_json';
diff --git a/packages/firestore/src/platform/browser_lite/random_bytes.ts b/packages/firestore/src/platform/browser_lite/random_bytes.ts
new file mode 100644
index 00000000000..6270b257114
--- /dev/null
+++ b/packages/firestore/src/platform/browser_lite/random_bytes.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../browser/random_bytes';
diff --git a/packages/firestore/src/platform/browser_lite/serializer.ts b/packages/firestore/src/platform/browser_lite/serializer.ts
new file mode 100644
index 00000000000..ed490bf16d7
--- /dev/null
+++ b/packages/firestore/src/platform/browser_lite/serializer.ts
@@ -0,0 +1,23 @@
+/**
+ * @license
+ * Copyright 2020 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 { JsonProtoSerializer } from '../../remote/serializer';
+import { DatabaseId } from '../../core/database_info';
+
+export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer {
+ return new JsonProtoSerializer(databaseId, /* useProto3Json= */ true);
+}
diff --git a/packages/firestore/src/platform/connection.ts b/packages/firestore/src/platform/connection.ts
index 6b8148ccde5..bb50a94f008 100644
--- a/packages/firestore/src/platform/connection.ts
+++ b/packages/firestore/src/platform/connection.ts
@@ -14,31 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-import { isNode, isReactNative } from '@firebase/util';
-import * as node from './node/connection';
-import * as rn from './rn/connection';
-import * as browser from './browser/connection';
import { ConnectivityMonitor } from '../remote/connectivity_monitor';
import { DatabaseInfo } from '../core/database_info';
import { Connection } from '../remote/connection';
+// This file is only used under ts-node.
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/connection`);
+
export function newConnectivityMonitor(): ConnectivityMonitor {
- if (isNode()) {
- return node.newConnectivityMonitor();
- } else if (isReactNative()) {
- return rn.newConnectivityMonitor();
- } else {
- return browser.newConnectivityMonitor();
- }
+ return platform.newConnectivityMonitor();
}
+// TODO(firestorexp): This doesn't need to return a Promise
export function newConnection(databaseInfo: DatabaseInfo): Promise {
- if (isNode()) {
- return node.newConnection(databaseInfo);
- } else if (isReactNative()) {
- return rn.newConnection(databaseInfo);
- } else {
- return browser.newConnection(databaseInfo);
- }
+ return platform.newConnection(databaseInfo);
}
diff --git a/packages/firestore/src/platform/dom.ts b/packages/firestore/src/platform/dom.ts
index 33090224beb..4ad06156ac3 100644
--- a/packages/firestore/src/platform/dom.ts
+++ b/packages/firestore/src/platform/dom.ts
@@ -15,29 +15,16 @@
* limitations under the License.
*/
-import { isNode, isReactNative } from '@firebase/util';
-import * as node from './node/dom';
-import * as rn from './rn/dom';
-import * as browser from './browser/dom';
+// This file is only used under ts-node.
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/dom`);
/** The Platform's 'window' implementation or null if not available. */
export function getWindow(): Window | null {
- if (isNode()) {
- return node.getWindow();
- } else if (isReactNative()) {
- return rn.getWindow();
- } else {
- return browser.getWindow();
- }
+ return platform.getWindow();
}
/** The Platform's 'document' implementation or null if not available. */
export function getDocument(): Document | null {
- if (isNode()) {
- return node.getDocument();
- } else if (isReactNative()) {
- return rn.getDocument();
- } else {
- return browser.getDocument();
- }
+ return platform.getDocument();
}
diff --git a/packages/firestore/src/platform/format_json.ts b/packages/firestore/src/platform/format_json.ts
index 042fe5c00f2..e25fca4ad94 100644
--- a/packages/firestore/src/platform/format_json.ts
+++ b/packages/firestore/src/platform/format_json.ts
@@ -15,18 +15,13 @@
* limitations under the License.
*/
-import { isNode, isReactNative } from '@firebase/util';
-import * as node from './node/format_json';
-import * as rn from './rn/format_json';
-import * as browser from './browser/format_json';
+// This file is only used under ts-node.
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const platform = require(`./${
+ process.env.TEST_PLATFORM ?? 'node'
+}/format_json`);
/** Formats an object as a JSON string, suitable for logging. */
export function formatJSON(value: unknown): string {
- if (isNode()) {
- return node.formatJSON(value);
- } else if (isReactNative()) {
- return rn.formatJSON(value);
- } else {
- return browser.formatJSON(value);
- }
+ return platform.formatJSON(value);
}
diff --git a/packages/firestore/src/platform/node/grpc_connection.ts b/packages/firestore/src/platform/node/grpc_connection.ts
index 13bdf0f6bb4..46855192be1 100644
--- a/packages/firestore/src/platform/node/grpc_connection.ts
+++ b/packages/firestore/src/platform/node/grpc_connection.ts
@@ -46,10 +46,7 @@ const LOG_TAG = 'Connection';
// should use the Firebase version instead.
const X_GOOG_API_CLIENT_VALUE = `gl-node/${process.versions.node} fire/${SDK_VERSION} grpc/${grpcVersion}`;
-function createMetadata(
- databaseInfo: DatabaseInfo,
- token: Token | null
-): Metadata {
+function createMetadata(databasePath: string, token: Token | null): Metadata {
hardAssert(
token === null || token.type === 'OAuth',
'If provided, token must be OAuth'
@@ -66,11 +63,7 @@ function createMetadata(
metadata.set('x-goog-api-client', X_GOOG_API_CLIENT_VALUE);
// This header is used to improve routing and project isolation by the
// backend.
- metadata.set(
- 'google-cloud-resource-prefix',
- `projects/${databaseInfo.databaseId.projectId}/` +
- `databases/${databaseInfo.databaseId.database}`
- );
+ metadata.set('google-cloud-resource-prefix', databasePath);
return metadata;
}
@@ -83,8 +76,9 @@ type GeneratedGrpcStub = any;
* A Connection implemented by GRPC-Node.
*/
export class GrpcConnection implements Connection {
+ private readonly databasePath: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- private firestore: any;
+ private readonly firestore: any;
// We cache stubs for the most-recently-used token.
private cachedStub: GeneratedGrpcStub | null = null;
@@ -92,6 +86,7 @@ export class GrpcConnection implements Connection {
constructor(protos: GrpcObject, private databaseInfo: DatabaseInfo) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.firestore = (protos as any)['google']['firestore']['v1'];
+ this.databasePath = `projects/${databaseInfo.databaseId.projectId}/databases/${databaseInfo.databaseId.database}`;
}
private ensureActiveStub(): GeneratedGrpcStub {
@@ -110,16 +105,18 @@ export class GrpcConnection implements Connection {
invokeRPC(
rpcName: string,
+ path: string,
request: Req,
token: Token | null
): Promise {
const stub = this.ensureActiveStub();
- const metadata = createMetadata(this.databaseInfo, token);
+ const metadata = createMetadata(this.databasePath, token);
+ const jsonRequest = { database: this.databasePath, ...request };
return nodePromise((callback: NodeCallback) => {
logDebug(LOG_TAG, `RPC '${rpcName}' invoked with request:`, request);
return stub[rpcName](
- request,
+ jsonRequest,
metadata,
(grpcError?: ServiceError, value?: Resp) => {
if (grpcError) {
@@ -145,6 +142,7 @@ export class GrpcConnection implements Connection {
invokeStreamingRPC(
rpcName: string,
+ path: string,
request: Req,
token: Token | null
): Promise {
@@ -157,8 +155,9 @@ export class GrpcConnection implements Connection {
request
);
const stub = this.ensureActiveStub();
- const metadata = createMetadata(this.databaseInfo, token);
- const stream = stub[rpcName](request, metadata);
+ const metadata = createMetadata(this.databasePath, token);
+ const jsonRequest = { ...request, database: this.databasePath };
+ const stream = stub[rpcName](jsonRequest, metadata);
stream.on('data', (response: Resp) => {
logDebug(LOG_TAG, `RPC ${rpcName} received result:`, response);
results.push(response);
@@ -182,7 +181,7 @@ export class GrpcConnection implements Connection {
token: Token | null
): Stream {
const stub = this.ensureActiveStub();
- const metadata = createMetadata(this.databaseInfo, token);
+ const metadata = createMetadata(this.databasePath, token);
const grpcStream = stub[rpcName](metadata);
let closed = false;
diff --git a/packages/firestore/src/platform/node_lite/base64.ts b/packages/firestore/src/platform/node_lite/base64.ts
new file mode 100644
index 00000000000..6f07d2a6591
--- /dev/null
+++ b/packages/firestore/src/platform/node_lite/base64.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../node/base64';
diff --git a/packages/firestore/src/platform/node_lite/connection.ts b/packages/firestore/src/platform/node_lite/connection.ts
new file mode 100644
index 00000000000..8063ff8e5f9
--- /dev/null
+++ b/packages/firestore/src/platform/node_lite/connection.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright 2020 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 * as nodeFetch from 'node-fetch';
+
+import { FetchConnection } from '../browser_lite/fetch_connection';
+import { DatabaseInfo } from '../../core/database_info';
+import { Connection } from '../../remote/connection';
+
+export { newConnectivityMonitor } from '../browser/connection';
+
+/** Initializes the HTTP connection for the REST API. */
+export function newConnection(databaseInfo: DatabaseInfo): Promise {
+ // node-fetch is meant to be API compatible with `fetch`, but its type don't
+ // match 100%.
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return Promise.resolve(new FetchConnection(databaseInfo, nodeFetch as any));
+}
diff --git a/packages/firestore/src/platform/node_lite/dom.ts b/packages/firestore/src/platform/node_lite/dom.ts
new file mode 100644
index 00000000000..5b47065bde1
--- /dev/null
+++ b/packages/firestore/src/platform/node_lite/dom.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../node/dom';
diff --git a/packages/firestore/src/platform/node_lite/format_json.ts b/packages/firestore/src/platform/node_lite/format_json.ts
new file mode 100644
index 00000000000..391b770ff86
--- /dev/null
+++ b/packages/firestore/src/platform/node_lite/format_json.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../node/format_json';
diff --git a/packages/firestore/src/platform/node_lite/random_bytes.ts b/packages/firestore/src/platform/node_lite/random_bytes.ts
new file mode 100644
index 00000000000..5a6aa143df2
--- /dev/null
+++ b/packages/firestore/src/platform/node_lite/random_bytes.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../node/random_bytes';
diff --git a/packages/firestore/src/platform/node_lite/serializer.ts b/packages/firestore/src/platform/node_lite/serializer.ts
new file mode 100644
index 00000000000..c53bf2cbd7e
--- /dev/null
+++ b/packages/firestore/src/platform/node_lite/serializer.ts
@@ -0,0 +1,24 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+/** Return the Platform-specific serializer monitor. */
+import { JsonProtoSerializer } from '../../remote/serializer';
+import { DatabaseId } from '../../core/database_info';
+
+export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer {
+ return new JsonProtoSerializer(databaseId, /* useProto3Json= */ true);
+}
diff --git a/packages/firestore/src/platform/random_bytes.ts b/packages/firestore/src/platform/random_bytes.ts
index 37edcc2d2dc..7db2c4cb7ba 100644
--- a/packages/firestore/src/platform/random_bytes.ts
+++ b/packages/firestore/src/platform/random_bytes.ts
@@ -15,10 +15,11 @@
* limitations under the License.
*/
-import { isNode, isReactNative } from '@firebase/util';
-import * as node from './node/random_bytes';
-import * as rn from './rn/random_bytes';
-import * as browser from './browser/random_bytes';
+// This file is only used under ts-node.
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const platform = require(`./${
+ process.env.TEST_PLATFORM ?? 'node'
+}/random_bytes`);
/**
* Generates `nBytes` of random bytes.
@@ -26,11 +27,5 @@ import * as browser from './browser/random_bytes';
* If `nBytes < 0` , an error will be thrown.
*/
export function randomBytes(nBytes: number): Uint8Array {
- if (isNode()) {
- return node.randomBytes(nBytes);
- } else if (isReactNative()) {
- return rn.randomBytes(nBytes);
- } else {
- return browser.randomBytes(nBytes);
- }
+ return platform.randomBytes(nBytes);
}
diff --git a/packages/firestore/src/platform/rn_lite/base64.ts b/packages/firestore/src/platform/rn_lite/base64.ts
new file mode 100644
index 00000000000..4dfcab364b1
--- /dev/null
+++ b/packages/firestore/src/platform/rn_lite/base64.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../rn/base64';
diff --git a/packages/firestore/src/platform/rn_lite/connection.ts b/packages/firestore/src/platform/rn_lite/connection.ts
new file mode 100644
index 00000000000..4ba28de1dce
--- /dev/null
+++ b/packages/firestore/src/platform/rn_lite/connection.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../browser_lite/connection';
diff --git a/packages/firestore/src/platform/rn_lite/dom.ts b/packages/firestore/src/platform/rn_lite/dom.ts
new file mode 100644
index 00000000000..51a78621df9
--- /dev/null
+++ b/packages/firestore/src/platform/rn_lite/dom.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../rn/dom';
diff --git a/packages/firestore/src/platform/rn_lite/format_json.ts b/packages/firestore/src/platform/rn_lite/format_json.ts
new file mode 100644
index 00000000000..b1767402da3
--- /dev/null
+++ b/packages/firestore/src/platform/rn_lite/format_json.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../rn/format_json';
diff --git a/packages/firestore/src/platform/rn_lite/random_bytes.ts b/packages/firestore/src/platform/rn_lite/random_bytes.ts
new file mode 100644
index 00000000000..ac340911f15
--- /dev/null
+++ b/packages/firestore/src/platform/rn_lite/random_bytes.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../rn/random_bytes';
diff --git a/packages/firestore/src/platform/rn_lite/serializer.ts b/packages/firestore/src/platform/rn_lite/serializer.ts
new file mode 100644
index 00000000000..cdf6bf1dfb1
--- /dev/null
+++ b/packages/firestore/src/platform/rn_lite/serializer.ts
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2020 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.
+ */
+
+export * from '../browser_lite/serializer';
diff --git a/packages/firestore/src/platform/serializer.ts b/packages/firestore/src/platform/serializer.ts
index f7990dc4496..c2e416abdc1 100644
--- a/packages/firestore/src/platform/serializer.ts
+++ b/packages/firestore/src/platform/serializer.ts
@@ -14,20 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-import { isNode, isReactNative } from '@firebase/util';
-import * as node from './node/serializer';
-import * as rn from './rn/serializer';
-import * as browser from './browser/serializer';
import { DatabaseId } from '../core/database_info';
import { JsonProtoSerializer } from '../remote/serializer';
+// This file is only used under ts-node.
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/serializer`);
+
export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer {
- if (isNode()) {
- return node.newSerializer(databaseId);
- } else if (isReactNative()) {
- return rn.newSerializer(databaseId);
- } else {
- return browser.newSerializer(databaseId);
- }
+ return platform.newSerializer(databaseId);
}
diff --git a/packages/firestore/src/remote/connection.ts b/packages/firestore/src/remote/connection.ts
index 3ad18ff8f21..adf4cd5a46b 100644
--- a/packages/firestore/src/remote/connection.ts
+++ b/packages/firestore/src/remote/connection.ts
@@ -38,12 +38,14 @@ export interface Connection {
* representing the JSON to send.
*
* @param rpcName the name of the RPC to invoke
+ * @param path the path to invoke this RPC on
* @param request the Raw JSON object encoding of the request message
* @param token the Token to use for the RPC.
* @return a Promise containing the JSON object encoding of the response
*/
invokeRPC(
rpcName: string,
+ path: string,
request: Req,
token: Token | null
): Promise;
@@ -54,6 +56,7 @@ export interface Connection {
* completion and then returned as an array.
*
* @param rpcName the name of the RPC to invoke
+ * @param path the path to invoke this RPC on
* @param request the Raw JSON object encoding of the request message
* @param token the Token to use for the RPC.
* @return a Promise containing an array with the JSON object encodings of the
@@ -61,6 +64,7 @@ export interface Connection {
*/
invokeStreamingRPC(
rpcName: string,
+ path: string,
request: Req,
token: Token | null
): Promise;
diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts
index 96ec71c960d..e7f7f24b66a 100644
--- a/packages/firestore/src/remote/datastore.ts
+++ b/packages/firestore/src/remote/datastore.ts
@@ -82,12 +82,21 @@ class DatastoreImpl extends Datastore {
}
/** Gets an auth token and invokes the provided RPC. */
- invokeRPC(rpcName: string, request: Req): Promise {
+ invokeRPC(
+ rpcName: string,
+ path: string,
+ request: Req
+ ): Promise {
this.verifyInitialized();
return this.credentials
.getToken()
.then(token => {
- return this.connection.invokeRPC(rpcName, request, token);
+ return this.connection.invokeRPC(
+ rpcName,
+ path,
+ request,
+ token
+ );
})
.catch((error: FirestoreError) => {
if (error.code === Code.UNAUTHENTICATED) {
@@ -100,6 +109,7 @@ class DatastoreImpl extends Datastore {
/** Gets an auth token and invokes the provided RPC with streamed results. */
invokeStreamingRPC(
rpcName: string,
+ path: string,
request: Req
): Promise {
this.verifyInitialized();
@@ -108,6 +118,7 @@ class DatastoreImpl extends Datastore {
.then(token => {
return this.connection.invokeStreamingRPC(
rpcName,
+ path,
request,
token
);
@@ -139,11 +150,11 @@ export async function invokeCommitRpc(
mutations: Mutation[]
): Promise {
const datastoreImpl = debugCast(datastore, DatastoreImpl);
- const params = {
- database: getEncodedDatabaseId(datastoreImpl.serializer),
+ const path = getEncodedDatabaseId(datastoreImpl.serializer) + '/documents';
+ const request = {
writes: mutations.map(m => toMutation(datastoreImpl.serializer, m))
};
- await datastoreImpl.invokeRPC('Commit', params);
+ await datastoreImpl.invokeRPC('Commit', path, request);
}
export async function invokeBatchGetDocumentsRpc(
@@ -151,14 +162,14 @@ export async function invokeBatchGetDocumentsRpc(
keys: DocumentKey[]
): Promise {
const datastoreImpl = debugCast(datastore, DatastoreImpl);
- const params = {
- database: getEncodedDatabaseId(datastoreImpl.serializer),
+ const path = getEncodedDatabaseId(datastoreImpl.serializer) + '/documents';
+ const request = {
documents: keys.map(k => toName(datastoreImpl.serializer, k))
};
const response = await datastoreImpl.invokeStreamingRPC<
api.BatchGetDocumentsRequest,
api.BatchGetDocumentsResponse
- >('BatchGetDocuments', params);
+ >('BatchGetDocuments', path, request);
const docs = new Map();
response.forEach(proto => {
@@ -179,21 +190,11 @@ export async function invokeRunQueryRpc(
query: Query
): Promise {
const datastoreImpl = debugCast(datastore, DatastoreImpl);
- const { structuredQuery, parent } = toQueryTarget(
- datastoreImpl.serializer,
- queryToTarget(query)
- );
- const params = {
- database: getEncodedDatabaseId(datastoreImpl.serializer),
- parent,
- structuredQuery
- };
-
+ const request = toQueryTarget(datastoreImpl.serializer, queryToTarget(query));
const response = await datastoreImpl.invokeStreamingRPC<
api.RunQueryRequest,
api.RunQueryResponse
- >('RunQuery', params);
-
+ >('RunQuery', request.parent!, { structuredQuery: request.structuredQuery });
return (
response
// Omit RunQueryResponses that only contain readTimes.
diff --git a/packages/firestore/src/remote/rest_connection.ts b/packages/firestore/src/remote/rest_connection.ts
new file mode 100644
index 00000000000..e39852707cc
--- /dev/null
+++ b/packages/firestore/src/remote/rest_connection.ts
@@ -0,0 +1,155 @@
+/**
+ * @license
+ * Copyright 2020 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 { Token } from '../api/credentials';
+import { DatabaseId, DatabaseInfo } from '../core/database_info';
+import { SDK_VERSION } from '../../src/core/version';
+import { Connection, Stream } from './connection';
+import { logDebug, logWarn } from '../util/log';
+import { FirestoreError } from '../util/error';
+import { StringMap } from '../util/types';
+import { debugAssert } from '../util/assert';
+
+const LOG_TAG = 'RestConnection';
+
+/**
+ * Maps RPC names to the corresponding REST endpoint name.
+ *
+ * We use array notation to avoid mangling.
+ */
+const RPC_NAME_URL_MAPPING: StringMap = {};
+
+RPC_NAME_URL_MAPPING['BatchGetDocuments'] = 'batchGet';
+RPC_NAME_URL_MAPPING['Commit'] = 'commit';
+RPC_NAME_URL_MAPPING['RunQuery'] = 'runQuery';
+
+const RPC_URL_VERSION = 'v1';
+const X_GOOG_API_CLIENT_VALUE = 'gl-js/ fire/' + SDK_VERSION;
+
+/**
+ * Base class for all Rest-based connections to the backend (WebChannel and
+ * HTTP).
+ */
+export abstract class RestConnection implements Connection {
+ protected readonly databaseId: DatabaseId;
+ protected readonly baseUrl: string;
+ private readonly databaseRoot: string;
+
+ constructor(private readonly databaseInfo: DatabaseInfo) {
+ this.databaseId = databaseInfo.databaseId;
+ const proto = databaseInfo.ssl ? 'https' : 'http';
+ this.baseUrl = proto + '://' + databaseInfo.host;
+ this.databaseRoot =
+ 'projects/' +
+ this.databaseId.projectId +
+ '/databases/' +
+ this.databaseId.database +
+ '/documents';
+ }
+
+ invokeRPC(
+ rpcName: string,
+ path: string,
+ req: Req,
+ token: Token | null
+ ): Promise {
+ const url = this.makeUrl(rpcName, path);
+ logDebug(LOG_TAG, 'Sending: ', url, req);
+
+ const headers = {};
+ this.modifyHeadersForRequest(headers, token);
+
+ return this.performRPCRequest(rpcName, url, headers, req).then(
+ response => {
+ logDebug(LOG_TAG, 'Received: ', response);
+ return response;
+ },
+ (err: FirestoreError) => {
+ logWarn(
+ LOG_TAG,
+ `${rpcName} failed with error: `,
+ err,
+ 'url: ',
+ url,
+ 'request:',
+ req
+ );
+ throw err;
+ }
+ );
+ }
+
+ invokeStreamingRPC(
+ rpcName: string,
+ path: string,
+ request: Req,
+ token: Token | null
+ ): Promise {
+ // The REST API automatically aggregates all of the streamed results, so we
+ // can just use the normal invoke() method.
+ return this.invokeRPC(rpcName, path, request, token);
+ }
+
+ abstract openStream(
+ rpcName: string,
+ token: Token | null
+ ): Stream;
+
+ /**
+ * Modifies the headers for a request, adding any authorization token if
+ * present and any additional headers for the request.
+ */
+ protected modifyHeadersForRequest(
+ headers: StringMap,
+ token: Token | null
+ ): void {
+ headers['X-Goog-Api-Client'] = X_GOOG_API_CLIENT_VALUE;
+
+ // Content-Type: text/plain will avoid preflight requests which might
+ // mess with CORS and redirects by proxies. If we add custom headers
+ // we will need to change this code to potentially use the $httpOverwrite
+ // parameter supported by ESF to avoid triggering preflight requests.
+ headers['Content-Type'] = 'text/plain';
+
+ if (token) {
+ for (const header in token.authHeaders) {
+ if (token.authHeaders.hasOwnProperty(header)) {
+ headers[header] = token.authHeaders[header];
+ }
+ }
+ }
+ }
+
+ /**
+ * Performs an RPC request using an implementation specific networking layer.
+ */
+ protected abstract performRPCRequest(
+ rpcName: string,
+ url: string,
+ headers: StringMap,
+ body: Req
+ ): Promise;
+
+ private 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}`;
+ }
+}
diff --git a/packages/firestore/src/remote/rpc_error.ts b/packages/firestore/src/remote/rpc_error.ts
index a5362a5907d..0d93662fb93 100644
--- a/packages/firestore/src/remote/rpc_error.ts
+++ b/packages/firestore/src/remote/rpc_error.ts
@@ -231,7 +231,12 @@ export function mapRpcCodeFromCode(code: Code | undefined): number {
* @returns The equivalent Code. Unknown status codes are mapped to
* Code.UNKNOWN.
*/
-export function mapCodeFromHttpStatus(status: number): Code {
+export function mapCodeFromHttpStatus(status?: number): Code {
+ if (status === undefined) {
+ logError('RPC_ERROR', 'HTTP error has no status');
+ return Code.UNKNOWN;
+ }
+
// The canonical error codes for Google APIs [1] specify mapping onto HTTP
// status codes but the mapping is not bijective. In each case of ambiguity
// this function chooses a primary error.
@@ -243,9 +248,9 @@ export function mapCodeFromHttpStatus(status: number): Code {
return Code.OK;
case 400: // Bad Request
- return Code.INVALID_ARGUMENT;
+ return Code.FAILED_PRECONDITION;
// Other possibilities based on the forward mapping
- // return Code.FAILED_PRECONDITION;
+ // return Code.INVALID_ARGUMENT;
// return Code.OUT_OF_RANGE;
case 401: // Unauthorized
diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts
index 2ba3f68d029..474bc25173f 100644
--- a/packages/firestore/test/integration/api/database.test.ts
+++ b/packages/firestore/test/integration/api/database.test.ts
@@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised';
import * as firestore from '@firebase/firestore-types';
import { expect, use } from 'chai';
-import { Deferred } from '../../util/promise';
+import { Deferred } from '@firebase/util';
import { EventsAccumulator } from '../util/events_accumulator';
import * as firebaseExport from '../util/firebase_export';
import {
diff --git a/packages/firestore/test/integration/browser/webchannel.test.ts b/packages/firestore/test/integration/browser/webchannel.test.ts
index 435b3b217dd..3d0f61f677b 100644
--- a/packages/firestore/test/integration/browser/webchannel.test.ts
+++ b/packages/firestore/test/integration/browser/webchannel.test.ts
@@ -16,7 +16,6 @@
*/
import { expect } from 'chai';
-import { DatabaseId, DatabaseInfo } from '../../../src/core/database_info';
import { WebChannelConnection } from '../../../src/platform/browser/webchannel_connection';
import * as api from '../../../src/protos/firestore_proto_api';
import { DEFAULT_PROJECT_ID } from '../util/settings';
@@ -33,26 +32,6 @@ const describeFn =
xdescribe;
describeFn('WebChannel', () => {
- describe('makeUrl', () => {
- const info = new DatabaseInfo(
- new DatabaseId('testproject'),
- 'persistenceKey',
- 'example.com',
- /*ssl=*/ false,
- /*forceLongPolling=*/ false
- );
- const conn = new WebChannelConnection(info);
- const makeUrl = conn.makeUrl.bind(conn);
-
- it('includes project ID and database ID', () => {
- const url = makeUrl('Commit');
- expect(url).to.equal(
- 'http://example.com/v1/projects/testproject/' +
- 'databases/(default)/documents:commit'
- );
- });
- });
-
it('receives error messages', done => {
const projectId = DEFAULT_PROJECT_ID;
const info = getDefaultDatabaseInfo();
diff --git a/packages/firestore/test/integration/util/events_accumulator.ts b/packages/firestore/test/integration/util/events_accumulator.ts
index f68b43cd91b..5bdb62220dc 100644
--- a/packages/firestore/test/integration/util/events_accumulator.ts
+++ b/packages/firestore/test/integration/util/events_accumulator.ts
@@ -16,7 +16,7 @@
*/
import * as firestore from '@firebase/firestore-types';
-import { Deferred } from '@firebase/util';
+import { Deferred } from '../../util/promise';
import { expect } from 'chai';
/**
diff --git a/packages/firestore/test/unit/remote/rest_connection.test.ts b/packages/firestore/test/unit/remote/rest_connection.test.ts
new file mode 100644
index 00000000000..a0153a1b0a5
--- /dev/null
+++ b/packages/firestore/test/unit/remote/rest_connection.test.ts
@@ -0,0 +1,120 @@
+/**
+ * @license
+ * Copyright 2020 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 { expect } from 'chai';
+import { Stream } from '../../../src/remote/connection';
+import { DatabaseId, DatabaseInfo } from '../../../src/core/database_info';
+import { RestConnection } from '../../../src/remote/rest_connection';
+import { Token } from '../../../src/api/credentials';
+import { StringMap } from '../../../src/util/types';
+import { Code, FirestoreError } from '../../../src/util/error';
+import { User } from '../../../src/auth/user';
+import { SDK_VERSION } from '../../../src/core/version';
+import { Indexable } from '../../../src/util/misc';
+
+export class TestRestConnection extends RestConnection {
+ lastUrl: string = '';
+ lastHeaders: StringMap = {};
+ lastRequestBody: unknown = {};
+ nextResponse = Promise.resolve({});
+
+ openStream(
+ rpcName: string,
+ token: Token | null
+ ): Stream {
+ throw new Error('Not Implemented');
+ }
+
+ protected performRPCRequest(
+ rpcName: string,
+ url: string,
+ headers: StringMap,
+ body: Req
+ ): Promise {
+ this.lastUrl = url;
+ this.lastRequestBody = (body as unknown) as Indexable;
+ this.lastHeaders = headers;
+ const response = this.nextResponse;
+ this.nextResponse = Promise.resolve({});
+ return response as Promise;
+ }
+}
+
+describe('RestConnection', () => {
+ const testDatabaseInfo = new DatabaseInfo(
+ new DatabaseId('testproject'),
+ 'persistenceKey',
+ 'example.com',
+ /*ssl=*/ false,
+ /*forceLongPolling=*/ false
+ );
+ const connection = new TestRestConnection(testDatabaseInfo);
+
+ it('url uses from path', async () => {
+ await connection.invokeRPC(
+ 'Commit',
+ 'projects/testproject/databases/(default)/documents',
+ {},
+ null
+ );
+ expect(connection.lastUrl).to.equal(
+ 'http://example.com/v1/projects/testproject/databases/(default)/documents:commit'
+ );
+ });
+
+ it('merges headers', async () => {
+ await connection.invokeRPC(
+ 'RunQuery',
+ 'projects/testproject/databases/(default)/documents/foo',
+ {},
+ {
+ user: User.UNAUTHENTICATED,
+ type: 'OAuth',
+ authHeaders: { 'Authorization': 'Bearer owner' }
+ }
+ );
+ expect(connection.lastHeaders).to.deep.equal({
+ 'Authorization': 'Bearer owner',
+ 'Content-Type': 'text/plain',
+ 'X-Goog-Api-Client': `gl-js/ fire/${SDK_VERSION}`
+ });
+ });
+
+ it('returns success', async () => {
+ connection.nextResponse = Promise.resolve({ response: true });
+ const response = await connection.invokeRPC(
+ 'RunQuery',
+ 'projects/testproject/databases/(default)/documents/coll',
+ {},
+ null
+ );
+ expect(response).to.deep.equal({ response: true });
+ });
+
+ it('returns error', () => {
+ const error = new FirestoreError(Code.UNKNOWN, 'Test exception');
+ connection.nextResponse = Promise.reject(error);
+ return expect(
+ connection.invokeRPC(
+ 'RunQuery',
+ 'projects/testproject/databases/(default)/documents/coll',
+ {},
+ null
+ )
+ ).to.be.eventually.rejectedWith(error);
+ });
+});