From e63bea79f3d050c8a00b163d3ecf161a3844dcb5 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 30 Jul 2020 20:04:54 -0700 Subject: [PATCH 01/10] Remove WebChannelConnection from Lite SDK --- packages/firestore/package.json | 1 + packages/firestore/rollup.config.lite.js | 6 +- .../platform/browser/webchannel_connection.ts | 126 +++----------- .../src/platform/browser_lite/base64.ts | 18 ++ .../src/platform/browser_lite/connection.ts | 27 +++ .../src/platform/browser_lite/dom.ts | 18 ++ .../platform/browser_lite/fetch_connection.ts | 57 +++++++ .../src/platform/browser_lite/format_json.ts | 18 ++ .../src/platform/browser_lite/random_bytes.ts | 18 ++ .../src/platform/browser_lite/serializer.ts | 24 +++ packages/firestore/src/platform/connection.ts | 24 ++- .../src/platform/node_lite/base64.ts | 18 ++ .../src/platform/node_lite/connection.ts | 27 +++ .../firestore/src/platform/node_lite/dom.ts | 18 ++ .../src/platform/node_lite/format_json.ts | 18 ++ .../src/platform/node_lite/http_connection.ts | 58 +++++++ .../src/platform/node_lite/random_bytes.ts | 18 ++ .../src/platform/node_lite/serializer.ts | 24 +++ .../firestore/src/platform/rn_lite/base64.ts | 18 ++ .../src/platform/rn_lite/connection.ts | 18 ++ .../firestore/src/platform/rn_lite/dom.ts | 18 ++ .../src/platform/rn_lite/format_json.ts | 18 ++ .../src/platform/rn_lite/random_bytes.ts | 18 ++ .../src/platform/rn_lite/serializer.ts | 18 ++ packages/firestore/src/platform/serializer.ts | 18 +- .../firestore/src/remote/rest_connection.ts | 160 ++++++++++++++++++ packages/firestore/src/remote/rpc_error.ts | 11 +- .../integration/browser/webchannel.test.ts | 21 --- .../integration/util/events_accumulator.ts | 2 +- .../test/unit/remote/rest_connection.test.ts | 139 +++++++++++++++ 30 files changed, 836 insertions(+), 141 deletions(-) create mode 100644 packages/firestore/src/platform/browser_lite/base64.ts create mode 100644 packages/firestore/src/platform/browser_lite/connection.ts create mode 100644 packages/firestore/src/platform/browser_lite/dom.ts create mode 100644 packages/firestore/src/platform/browser_lite/fetch_connection.ts create mode 100644 packages/firestore/src/platform/browser_lite/format_json.ts create mode 100644 packages/firestore/src/platform/browser_lite/random_bytes.ts create mode 100644 packages/firestore/src/platform/browser_lite/serializer.ts create mode 100644 packages/firestore/src/platform/node_lite/base64.ts create mode 100644 packages/firestore/src/platform/node_lite/connection.ts create mode 100644 packages/firestore/src/platform/node_lite/dom.ts create mode 100644 packages/firestore/src/platform/node_lite/format_json.ts create mode 100644 packages/firestore/src/platform/node_lite/http_connection.ts create mode 100644 packages/firestore/src/platform/node_lite/random_bytes.ts create mode 100644 packages/firestore/src/platform/node_lite/serializer.ts create mode 100644 packages/firestore/src/platform/rn_lite/base64.ts create mode 100644 packages/firestore/src/platform/rn_lite/connection.ts create mode 100644 packages/firestore/src/platform/rn_lite/dom.ts create mode 100644 packages/firestore/src/platform/rn_lite/format_json.ts create mode 100644 packages/firestore/src/platform/rn_lite/random_bytes.ts create mode 100644 packages/firestore/src/platform/rn_lite/serializer.ts create mode 100644 packages/firestore/src/remote/rest_connection.ts create mode 100644 packages/firestore/test/unit/remote/rest_connection.test.ts diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 74e2bb8c75d..8226faf28f5 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -63,6 +63,7 @@ "@firebase/webchannel-wrapper": "0.2.41", "@grpc/grpc-js": "^1.0.0", "@grpc/proto-loader": "^0.5.0", + "axios": "^0.19.2", "tslib": "^1.11.1" }, "peerDependencies": { diff --git a/packages/firestore/rollup.config.lite.js b/packages/firestore/rollup.config.lite.js index bdf76719242..513a948aed1 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,7 @@ 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 +125,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/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index 5e3256ec42e..405f7dc7e5e 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.') ); @@ -170,13 +130,13 @@ export class WebChannelConnection implements Connection { default: fail( 'RPC "' + - rpcName + - '" failed with unanticipated ' + - 'webchannel error ' + - xhr.getLastErrorCode() + - ': ' + - xhr.getLastError() + - ', giving up.' + rpcName + + '" failed with unanticipated ' + + 'webchannel error ' + + xhr.getLastErrorCode() + + ': ' + + xhr.getLastError() + + ', giving up.' ); } } finally { @@ -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..94077a953c8 --- /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)); +} 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..0afe3b8c7f7 --- /dev/null +++ b/packages/firestore/src/platform/browser_lite/fetch_connection.ts @@ -0,0 +1,57 @@ +/** + * @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'; + +export class FetchConnection extends RestConnection { + 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 fetch(url, { + method: 'POST', + headers, + body: requestJson + }); + } catch (err) { + throw new FirestoreError( + mapCodeFromHttpStatus(err.status), + 'Request failed with error: ' + err.statusText + ); + } + + return JSON.parse(await response.text()); + } +} 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..c53bf2cbd7e --- /dev/null +++ b/packages/firestore/src/platform/browser_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/connection.ts b/packages/firestore/src/platform/connection.ts index 6b8148ccde5..a5b4ffd0e8c 100644 --- a/packages/firestore/src/platform/connection.ts +++ b/packages/firestore/src/platform/connection.ts @@ -15,13 +15,23 @@ * limitations under the License. */ +// This file is only used in when the client is run under ts-node +// eslint-disable-next-line import/no-extraneous-dependencies +import { _components } from '@firebase/app-exp'; + 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'; +import * as node from './node/connection'; +import * as nodeLite from './node_lite/connection'; +import * as rn from './rn/connection'; +import * as browser from './browser/connection'; +import * as browserLite from './browser_lite/connection'; + +function isLite(): boolean { + return _components.has('firestore/lite'); +} export function newConnectivityMonitor(): ConnectivityMonitor { if (isNode()) { @@ -35,10 +45,14 @@ export function newConnectivityMonitor(): ConnectivityMonitor { export function newConnection(databaseInfo: DatabaseInfo): Promise { if (isNode()) { - return node.newConnection(databaseInfo); + return isLite() + ? nodeLite.newConnection(databaseInfo) + : node.newConnection(databaseInfo); } else if (isReactNative()) { return rn.newConnection(databaseInfo); } else { - return browser.newConnection(databaseInfo); + return isLite() + ? browserLite.newConnection(databaseInfo) + : browser.newConnection(databaseInfo); } } 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..60024bf75ab --- /dev/null +++ b/packages/firestore/src/platform/node_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 { HttpConnection } from './http_connection'; + +export { newConnectivityMonitor } from '../browser/connection'; + +/** Initializes the HTTP connection for the REST API. */ +export function newConnection(databaseInfo: DatabaseInfo): Promise { + return Promise.resolve(new HttpConnection(databaseInfo)); +} 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/http_connection.ts b/packages/firestore/src/platform/node_lite/http_connection.ts new file mode 100644 index 00000000000..2ce8154e286 --- /dev/null +++ b/packages/firestore/src/platform/node_lite/http_connection.ts @@ -0,0 +1,58 @@ +/** + * @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 axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; + +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'; + +const TIMEOUT_SECS = 15; + +export class HttpConnection extends RestConnection { + openStream( + rpcName: string, + token: Token | null + ): Stream { + throw new Error('Not supported by HttpConnection'); + } + + protected async performRPCRequest( + rpcName: string, + url: string, + headers: StringMap, + body: Req + ): Promise { + const options: AxiosRequestConfig = { + timeout: TIMEOUT_SECS * 1000, + headers + }; + let response: AxiosResponse; + try { + response = await axios.post(url, body, options); + } catch (err) { + throw new FirestoreError( + mapCodeFromHttpStatus(err.status), + 'Request failed with error: ' + err.statusText + ); + } + return response.data; + } +} 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/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..ce8b8fb793e 100644 --- a/packages/firestore/src/platform/serializer.ts +++ b/packages/firestore/src/platform/serializer.ts @@ -15,19 +15,33 @@ * limitations under the License. */ +// This file is only used in when the client is run under ts-node +// eslint-disable-next-line import/no-extraneous-dependencies +import { _components } from '@firebase/app-exp'; + import { isNode, isReactNative } from '@firebase/util'; import * as node from './node/serializer'; +import * as nodeLite from './node_lite/serializer'; import * as rn from './rn/serializer'; import * as browser from './browser/serializer'; +import * as browserLite from './browser_lite/serializer'; import { DatabaseId } from '../core/database_info'; import { JsonProtoSerializer } from '../remote/serializer'; +function isLite(): boolean { + return _components.has('firestore/lite'); +} + export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer { if (isNode()) { - return node.newSerializer(databaseId); + return isLite() + ? nodeLite.newSerializer(databaseId) + : node.newSerializer(databaseId); } else if (isReactNative()) { return rn.newSerializer(databaseId); } else { - return browser.newSerializer(databaseId); + return isLite() + ? browserLite.newSerializer(databaseId) + : browser.newSerializer(databaseId); } } diff --git a/packages/firestore/src/remote/rest_connection.ts b/packages/firestore/src/remote/rest_connection.ts new file mode 100644 index 00000000000..c605babbb30 --- /dev/null +++ b/packages/firestore/src/remote/rest_connection.ts @@ -0,0 +1,160 @@ +/** + * @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'; +import { Indexable } from '../util/misc'; + +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, + req: Req, + token: Token | null + ): Promise { + const url = this.makeUrl(rpcName, req); + + // The database and/or parent 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 = ({ ...req } as unknown) as Indexable; + delete jsonObj.parent; + delete jsonObj.database; + + logDebug(LOG_TAG, 'Sending: ', url, jsonObj); + + const headers = {}; + this.modifyHeadersForRequest(headers, token); + + return this.performRPCRequest( + rpcName, + url, + headers, + jsonObj + ).then( + response => { + logDebug(LOG_TAG, 'Received: ', response); + return response; + }, + (err: FirestoreError) => { + logWarn(LOG_TAG, `${rpcName} failed with error: `, err.message, 'url : ', url, 'request :', req); + throw err; + } + ); + } + + 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); + } + + 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, req: Req): string { + const urlRpcName = RPC_NAME_URL_MAPPING[rpcName]; + debugAssert( + urlRpcName !== undefined, + 'Unknown REST mapping for: ' + rpcName + ); + const path = ((req as unknown) as Indexable).parent || this.databaseRoot; + 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/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..cf31060115c --- /dev/null +++ b/packages/firestore/test/unit/remote/rest_connection.test.ts @@ -0,0 +1,139 @@ +/** + * @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 { fail } from '../../../src/util/assert'; +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 project ID and database ID', async () => { + await connection.invokeRPC('Commit', '{}', null); + expect(connection.lastUrl).to.equal( + 'http://example.com/v1/projects/testproject/' + + 'databases/(default)/documents:commit' + ); + }); + + it('url uses parent when provided', async () => { + await connection.invokeRPC( + 'RunQuery', + { + parent: 'projects/testproject/databases/(default)/documents/coll' + }, + null + ); + expect(connection.lastUrl).to.equal( + 'http://example.com/v1/projects/testproject/' + + 'databases/(default)/documents/coll:runQuery' + ); + }); + + it('drops parent and database from request', async () => { + await connection.invokeRPC( + 'RunQuery', + { + foo: 'bar', + database: 'projects/testproject/databases/(default)/documents', + parent: 'projects/testproject/databases/(default)/documents/coll' + }, + null + ); + expect(connection.lastRequestBody).to.deep.equal({ + foo: 'bar' + }); + }); + + it('merges headers', async () => { + await connection.invokeRPC( + 'RunQuery', + {}, + { + 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', {}, null); + expect(response).to.deep.equal({ response: true }); + }); + + it('returns error', async () => { + connection.nextResponse = Promise.reject( + new FirestoreError(Code.UNKNOWN, 'Test exception') + ); + try { + await connection.invokeRPC('RunQuery', {}, null); + fail(); + } catch (e) { + expect(e.code).to.equal(Code.UNKNOWN); + } + }); +}); From 289448420f39e30151020ba487b8537f7b00716f Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 31 Jul 2020 10:08:33 -0700 Subject: [PATCH 02/10] CI --- packages/firestore/exp/test/bootstrap.ts | 2 + packages/firestore/lite/test/bootstrap.ts | 2 + packages/firestore/package.json | 2 +- packages/firestore/scripts/run-tests.js | 2 +- packages/firestore/scripts/run-tests.ts | 5 +++ packages/firestore/src/platform/base64.ts | 32 +++----------- packages/firestore/src/platform/connection.ts | 43 ++++--------------- packages/firestore/src/platform/dom.ts | 23 +++------- .../firestore/src/platform/format_json.ts | 15 ++----- .../firestore/src/platform/random_bytes.ts | 15 ++----- packages/firestore/src/platform/serializer.ts | 33 +++----------- .../firestore/test/integration/bootstrap.ts | 2 + packages/firestore/test/unit/bootstrap.ts | 2 + 13 files changed, 48 insertions(+), 130 deletions(-) diff --git a/packages/firestore/exp/test/bootstrap.ts b/packages/firestore/exp/test/bootstrap.ts index ba954f7c192..29bdbb4870d 100644 --- a/packages/firestore/exp/test/bootstrap.ts +++ b/packages/firestore/exp/test/bootstrap.ts @@ -24,6 +24,8 @@ import '../register'; * https://github.com/webpack-contrib/karma-webpack#alternative-usage */ +process.env.TEST_PLATFORM = 'browser'; + // 'context()' definition requires additional dependency on webpack-env package. // eslint-disable-next-line @typescript-eslint/no-explicit-any const testsContext = (require as any).context( diff --git a/packages/firestore/lite/test/bootstrap.ts b/packages/firestore/lite/test/bootstrap.ts index 69812150818..b7f0952a9ba 100644 --- a/packages/firestore/lite/test/bootstrap.ts +++ b/packages/firestore/lite/test/bootstrap.ts @@ -24,6 +24,8 @@ import '../register'; * https://github.com/webpack-contrib/karma-webpack#alternative-usage */ +process.env.TEST_PLATFORM = 'node-lite'; + // 'context()' definition requires additional dependency on webpack-env package. // eslint-disable-next-line @typescript-eslint/no-explicit-any const testsContext = (require as any).context('.', true, /^.*\.test.*$/); diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 8226faf28f5..fe76dd5c331 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", diff --git a/packages/firestore/scripts/run-tests.js b/packages/firestore/scripts/run-tests.js index 4d33498f1ad..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..c61b7770369 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}/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/connection.ts b/packages/firestore/src/platform/connection.ts index a5b4ffd0e8c..13e95c598ad 100644 --- a/packages/firestore/src/platform/connection.ts +++ b/packages/firestore/src/platform/connection.ts @@ -14,45 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import {ConnectivityMonitor} from "../remote/connectivity_monitor"; +import {DatabaseInfo} from "../core/database_info"; +import {Connection} from "../remote/connection"; -// This file is only used in when the client is run under ts-node -// eslint-disable-next-line import/no-extraneous-dependencies -import { _components } from '@firebase/app-exp'; - -import { isNode, isReactNative } from '@firebase/util'; -import { ConnectivityMonitor } from '../remote/connectivity_monitor'; -import { DatabaseInfo } from '../core/database_info'; -import { Connection } from '../remote/connection'; -import * as node from './node/connection'; -import * as nodeLite from './node_lite/connection'; -import * as rn from './rn/connection'; -import * as browser from './browser/connection'; -import * as browserLite from './browser_lite/connection'; - -function isLite(): boolean { - return _components.has('firestore/lite'); -} +// This file is only used under ts-node. +// eslint-disable-next-line @typescript-eslint/no-require-imports +const platform = require(`${process.env.TEST_PLATFORM}/base64`); export function newConnectivityMonitor(): ConnectivityMonitor { - if (isNode()) { - return node.newConnectivityMonitor(); - } else if (isReactNative()) { - return rn.newConnectivityMonitor(); - } else { - return browser.newConnectivityMonitor(); - } + return platform.newConnectivityMonitor(); } export function newConnection(databaseInfo: DatabaseInfo): Promise { - if (isNode()) { - return isLite() - ? nodeLite.newConnection(databaseInfo) - : node.newConnection(databaseInfo); - } else if (isReactNative()) { - return rn.newConnection(databaseInfo); - } else { - return isLite() - ? browserLite.newConnection(databaseInfo) - : 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..8ff5f5aee66 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}/base64`); /** 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..9fc3a2dc897 100644 --- a/packages/firestore/src/platform/format_json.ts +++ b/packages/firestore/src/platform/format_json.ts @@ -15,18 +15,11 @@ * 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}/base64`); /** 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/random_bytes.ts b/packages/firestore/src/platform/random_bytes.ts index 37edcc2d2dc..c79dece6bb4 100644 --- a/packages/firestore/src/platform/random_bytes.ts +++ b/packages/firestore/src/platform/random_bytes.ts @@ -15,10 +15,9 @@ * 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}/base64`); /** * Generates `nBytes` of random bytes. @@ -26,11 +25,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/serializer.ts b/packages/firestore/src/platform/serializer.ts index ce8b8fb793e..91b026cfc7f 100644 --- a/packages/firestore/src/platform/serializer.ts +++ b/packages/firestore/src/platform/serializer.ts @@ -14,34 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import {DatabaseId} from "../core/database_info"; +import {JsonProtoSerializer} from "../remote/serializer"; -// This file is only used in when the client is run under ts-node -// eslint-disable-next-line import/no-extraneous-dependencies -import { _components } from '@firebase/app-exp'; - -import { isNode, isReactNative } from '@firebase/util'; -import * as node from './node/serializer'; -import * as nodeLite from './node_lite/serializer'; -import * as rn from './rn/serializer'; -import * as browser from './browser/serializer'; -import * as browserLite from './browser_lite/serializer'; -import { DatabaseId } from '../core/database_info'; -import { JsonProtoSerializer } from '../remote/serializer'; - -function isLite(): boolean { - return _components.has('firestore/lite'); -} +// This file is only used under ts-node. +// eslint-disable-next-line @typescript-eslint/no-require-imports +const platform = require(`${process.env.TEST_PLATFORM}/serializer`); export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer { - if (isNode()) { - return isLite() - ? nodeLite.newSerializer(databaseId) - : node.newSerializer(databaseId); - } else if (isReactNative()) { - return rn.newSerializer(databaseId); - } else { - return isLite() - ? browserLite.newSerializer(databaseId) - : browser.newSerializer(databaseId); - } + return platform.newSerializer(databaseId); } diff --git a/packages/firestore/test/integration/bootstrap.ts b/packages/firestore/test/integration/bootstrap.ts index a38829ab6b9..131bf37dc36 100644 --- a/packages/firestore/test/integration/bootstrap.ts +++ b/packages/firestore/test/integration/bootstrap.ts @@ -24,6 +24,8 @@ import '../../index'; * https://github.com/webpack-contrib/karma-webpack#alternative-usage */ +process.env.TEST_PLATFORM = 'browser'; + // 'context()' definition requires additional dependency on webpack-env package. // eslint-disable-next-line @typescript-eslint/no-explicit-any const testsContext = (require as any).context( diff --git a/packages/firestore/test/unit/bootstrap.ts b/packages/firestore/test/unit/bootstrap.ts index 9aff9cf328f..2a195fcf0fd 100644 --- a/packages/firestore/test/unit/bootstrap.ts +++ b/packages/firestore/test/unit/bootstrap.ts @@ -22,6 +22,8 @@ * https://github.com/webpack-contrib/karma-webpack#alternative-usage */ +process.env.TEST_PLATFORM = 'browser'; + // 'context()' definition requires additional dependency on webpack-env package. // eslint-disable-next-line @typescript-eslint/no-explicit-any const testsContext = (require as any).context( From f7aaab97de6acbb0c47d5c0c65cf00f884958b26 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 31 Jul 2020 10:52:47 -0700 Subject: [PATCH 03/10] next run --- packages/firestore/src/platform/base64.ts | 2 +- packages/firestore/src/platform/connection.ts | 2 +- packages/firestore/src/platform/dom.ts | 2 +- packages/firestore/src/platform/format_json.ts | 2 +- packages/firestore/src/platform/random_bytes.ts | 2 +- packages/firestore/src/platform/serializer.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/firestore/src/platform/base64.ts b/packages/firestore/src/platform/base64.ts index c61b7770369..c68f83e2eff 100644 --- a/packages/firestore/src/platform/base64.ts +++ b/packages/firestore/src/platform/base64.ts @@ -17,7 +17,7 @@ // This file is only used under ts-node. // eslint-disable-next-line @typescript-eslint/no-require-imports -const platform = require(`${process.env.TEST_PLATFORM}/base64`); +const platform = require(`./${process.env.TEST_PLATFORM}/base64`); /** Converts a Base64 encoded string to a binary string. */ export function decodeBase64(encoded: string): string { diff --git a/packages/firestore/src/platform/connection.ts b/packages/firestore/src/platform/connection.ts index 13e95c598ad..3c67310709e 100644 --- a/packages/firestore/src/platform/connection.ts +++ b/packages/firestore/src/platform/connection.ts @@ -20,7 +20,7 @@ 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}/base64`); +const platform = require(`./${process.env.TEST_PLATFORM}/connection`); export function newConnectivityMonitor(): ConnectivityMonitor { return platform.newConnectivityMonitor(); diff --git a/packages/firestore/src/platform/dom.ts b/packages/firestore/src/platform/dom.ts index 8ff5f5aee66..9877a36cbd9 100644 --- a/packages/firestore/src/platform/dom.ts +++ b/packages/firestore/src/platform/dom.ts @@ -17,7 +17,7 @@ // This file is only used under ts-node. // eslint-disable-next-line @typescript-eslint/no-require-imports -const platform = require(`${process.env.TEST_PLATFORM}/base64`); +const platform = require(`./${process.env.TEST_PLATFORM}/dom`); /** The Platform's 'window' implementation or null if not available. */ export function getWindow(): Window | null { diff --git a/packages/firestore/src/platform/format_json.ts b/packages/firestore/src/platform/format_json.ts index 9fc3a2dc897..3d531f58e12 100644 --- a/packages/firestore/src/platform/format_json.ts +++ b/packages/firestore/src/platform/format_json.ts @@ -17,7 +17,7 @@ // This file is only used under ts-node. // eslint-disable-next-line @typescript-eslint/no-require-imports -const platform = require(`${process.env.TEST_PLATFORM}/base64`); +const platform = require(`./${process.env.TEST_PLATFORM}/format_json`); /** Formats an object as a JSON string, suitable for logging. */ export function formatJSON(value: unknown): string { diff --git a/packages/firestore/src/platform/random_bytes.ts b/packages/firestore/src/platform/random_bytes.ts index c79dece6bb4..64b69aa3206 100644 --- a/packages/firestore/src/platform/random_bytes.ts +++ b/packages/firestore/src/platform/random_bytes.ts @@ -17,7 +17,7 @@ // This file is only used under ts-node. // eslint-disable-next-line @typescript-eslint/no-require-imports -const platform = require(`${process.env.TEST_PLATFORM}/base64`); +const platform = require(`./${process.env.TEST_PLATFORM}/random_bytes`); /** * Generates `nBytes` of random bytes. diff --git a/packages/firestore/src/platform/serializer.ts b/packages/firestore/src/platform/serializer.ts index 91b026cfc7f..d9c21102028 100644 --- a/packages/firestore/src/platform/serializer.ts +++ b/packages/firestore/src/platform/serializer.ts @@ -19,7 +19,7 @@ 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}/serializer`); +const platform = require(`./${process.env.TEST_PLATFORM}/serializer`); export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer { return platform.newSerializer(databaseId); From fd30f0c77f30e94e93d1b9af5d9a72f31f1efb04 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 31 Jul 2020 11:20:19 -0700 Subject: [PATCH 04/10] Using node-fetch --- .../firestore_lite_Tests__Emulator_.xml | 1 + .../firestore/lite/test/integration.test.ts | 2 +- packages/firestore/package.json | 1 + packages/firestore/src/platform/base64.ts | 2 +- .../src/platform/browser_lite/connection.ts | 2 +- .../platform/browser_lite/fetch_connection.ts | 22 +++++-- packages/firestore/src/platform/connection.ts | 3 +- packages/firestore/src/platform/dom.ts | 2 +- .../firestore/src/platform/format_json.ts | 2 +- .../src/platform/node_lite/connection.ts | 13 +++-- .../src/platform/node_lite/http_connection.ts | 58 ------------------- .../firestore/src/platform/random_bytes.ts | 2 +- packages/firestore/src/platform/serializer.ts | 2 +- .../firestore/src/remote/rest_connection.ts | 2 +- .../test/unit/remote/rest_connection.test.ts | 14 ++--- yarn.lock | 29 ---------- 16 files changed, 42 insertions(+), 115 deletions(-) delete mode 100644 packages/firestore/src/platform/node_lite/http_connection.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/lite/test/integration.test.ts b/packages/firestore/lite/test/integration.test.ts index 145fe8de45e..b4fd15f5eaa 100644 --- a/packages/firestore/lite/test/integration.test.ts +++ b/packages/firestore/lite/test/integration.test.ts @@ -625,7 +625,7 @@ function genericMutationTests( // The Transaction tests use backoff for updates that fail with failed // preconditions. This leads to test timeouts. // eslint-disable-next-line no-restricted-properties - (testRunnerMayUseBackoff ? it.skip : it)( + (testRunnerMayUseBackoff ? it.skip : it.only)( 'enforces that document exists', () => { return withTestDoc(async docRef => { diff --git a/packages/firestore/package.json b/packages/firestore/package.json index fe76dd5c331..3351466d547 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -64,6 +64,7 @@ "@grpc/grpc-js": "^1.0.0", "@grpc/proto-loader": "^0.5.0", "axios": "^0.19.2", + "node-fetch": "2.6.0", "tslib": "^1.11.1" }, "peerDependencies": { diff --git a/packages/firestore/src/platform/base64.ts b/packages/firestore/src/platform/base64.ts index c68f83e2eff..b794158d909 100644 --- a/packages/firestore/src/platform/base64.ts +++ b/packages/firestore/src/platform/base64.ts @@ -17,7 +17,7 @@ // This file is only used under ts-node. // eslint-disable-next-line @typescript-eslint/no-require-imports -const platform = require(`./${process.env.TEST_PLATFORM}/base64`); +const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/base64`); /** Converts a Base64 encoded string to a binary string. */ export function decodeBase64(encoded: string): string { diff --git a/packages/firestore/src/platform/browser_lite/connection.ts b/packages/firestore/src/platform/browser_lite/connection.ts index 94077a953c8..070bb5c53d3 100644 --- a/packages/firestore/src/platform/browser_lite/connection.ts +++ b/packages/firestore/src/platform/browser_lite/connection.ts @@ -23,5 +23,5 @@ 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)); + return Promise.resolve(new FetchConnection(databaseInfo, fetch)); } diff --git a/packages/firestore/src/platform/browser_lite/fetch_connection.ts b/packages/firestore/src/platform/browser_lite/fetch_connection.ts index 0afe3b8c7f7..e834970c31e 100644 --- a/packages/firestore/src/platform/browser_lite/fetch_connection.ts +++ b/packages/firestore/src/platform/browser_lite/fetch_connection.ts @@ -13,16 +13,21 @@ * 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 { Token } from '../../api/credentials'; import { Stream } from '../../remote/connection'; import { mapCodeFromHttpStatus } from '../../remote/rpc_error'; -import { FirestoreError } from '../../util/error'; +import { FirestoreError } from '../../util/error'; import { StringMap } from '../../util/types'; import { RestConnection } from '../../remote/rest_connection'; +import {DatabaseInfo} from "../../core/database_info"; export class FetchConnection extends RestConnection { + constructor(databaseInfo: DatabaseInfo, private readonly fetchImpl: typeof fetch) { + super(databaseInfo); + } + openStream( rpcName: string, token: Token | null @@ -40,7 +45,7 @@ export class FetchConnection extends RestConnection { let response: Response; try { - response = await fetch(url, { + response = await this.fetchImpl(url, { method: 'POST', headers, body: requestJson @@ -51,7 +56,14 @@ export class FetchConnection extends RestConnection { 'Request failed with error: ' + err.statusText ); } - - return JSON.parse(await response.text()); + + 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/connection.ts b/packages/firestore/src/platform/connection.ts index 3c67310709e..0f1d106c843 100644 --- a/packages/firestore/src/platform/connection.ts +++ b/packages/firestore/src/platform/connection.ts @@ -20,12 +20,13 @@ 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}/connection`); +const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/connection`); export function newConnectivityMonitor(): ConnectivityMonitor { return platform.newConnectivityMonitor(); } +// TODO(firestorexp): This doesn't need to return a Promise export function newConnection(databaseInfo: DatabaseInfo): Promise { return platform.newConnection(databaseInfo); } diff --git a/packages/firestore/src/platform/dom.ts b/packages/firestore/src/platform/dom.ts index 9877a36cbd9..4ad06156ac3 100644 --- a/packages/firestore/src/platform/dom.ts +++ b/packages/firestore/src/platform/dom.ts @@ -17,7 +17,7 @@ // This file is only used under ts-node. // eslint-disable-next-line @typescript-eslint/no-require-imports -const platform = require(`./${process.env.TEST_PLATFORM}/dom`); +const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/dom`); /** The Platform's 'window' implementation or null if not available. */ export function getWindow(): Window | null { diff --git a/packages/firestore/src/platform/format_json.ts b/packages/firestore/src/platform/format_json.ts index 3d531f58e12..b251411944d 100644 --- a/packages/firestore/src/platform/format_json.ts +++ b/packages/firestore/src/platform/format_json.ts @@ -17,7 +17,7 @@ // This file is only used under ts-node. // eslint-disable-next-line @typescript-eslint/no-require-imports -const platform = require(`./${process.env.TEST_PLATFORM}/format_json`); +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 { diff --git a/packages/firestore/src/platform/node_lite/connection.ts b/packages/firestore/src/platform/node_lite/connection.ts index 60024bf75ab..46aba522a91 100644 --- a/packages/firestore/src/platform/node_lite/connection.ts +++ b/packages/firestore/src/platform/node_lite/connection.ts @@ -15,13 +15,18 @@ * limitations under the License. */ -import { DatabaseInfo } from '../../core/database_info'; -import { Connection } from '../../remote/connection'; -import { HttpConnection } from './http_connection'; +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 { - return Promise.resolve(new HttpConnection(databaseInfo)); + // 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/http_connection.ts b/packages/firestore/src/platform/node_lite/http_connection.ts deleted file mode 100644 index 2ce8154e286..00000000000 --- a/packages/firestore/src/platform/node_lite/http_connection.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @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 axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; - -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'; - -const TIMEOUT_SECS = 15; - -export class HttpConnection extends RestConnection { - openStream( - rpcName: string, - token: Token | null - ): Stream { - throw new Error('Not supported by HttpConnection'); - } - - protected async performRPCRequest( - rpcName: string, - url: string, - headers: StringMap, - body: Req - ): Promise { - const options: AxiosRequestConfig = { - timeout: TIMEOUT_SECS * 1000, - headers - }; - let response: AxiosResponse; - try { - response = await axios.post(url, body, options); - } catch (err) { - throw new FirestoreError( - mapCodeFromHttpStatus(err.status), - 'Request failed with error: ' + err.statusText - ); - } - return response.data; - } -} diff --git a/packages/firestore/src/platform/random_bytes.ts b/packages/firestore/src/platform/random_bytes.ts index 64b69aa3206..9150943abd8 100644 --- a/packages/firestore/src/platform/random_bytes.ts +++ b/packages/firestore/src/platform/random_bytes.ts @@ -17,7 +17,7 @@ // This file is only used under ts-node. // eslint-disable-next-line @typescript-eslint/no-require-imports -const platform = require(`./${process.env.TEST_PLATFORM}/random_bytes`); +const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/random_bytes`); /** * Generates `nBytes` of random bytes. diff --git a/packages/firestore/src/platform/serializer.ts b/packages/firestore/src/platform/serializer.ts index d9c21102028..9901e42f842 100644 --- a/packages/firestore/src/platform/serializer.ts +++ b/packages/firestore/src/platform/serializer.ts @@ -19,7 +19,7 @@ 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}/serializer`); +const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/serializer`); export function newSerializer(databaseId: DatabaseId): JsonProtoSerializer { return platform.newSerializer(databaseId); diff --git a/packages/firestore/src/remote/rest_connection.ts b/packages/firestore/src/remote/rest_connection.ts index c605babbb30..4045bb3c8b4 100644 --- a/packages/firestore/src/remote/rest_connection.ts +++ b/packages/firestore/src/remote/rest_connection.ts @@ -92,7 +92,7 @@ export abstract class RestConnection implements Connection { return response; }, (err: FirestoreError) => { - logWarn(LOG_TAG, `${rpcName} failed with error: `, err.message, 'url : ', url, 'request :', req); + logWarn(LOG_TAG, `${rpcName} failed with error: `, err.message, 'url: ', url, 'request:', req); throw err; } ); diff --git a/packages/firestore/test/unit/remote/rest_connection.test.ts b/packages/firestore/test/unit/remote/rest_connection.test.ts index cf31060115c..5b5605d941f 100644 --- a/packages/firestore/test/unit/remote/rest_connection.test.ts +++ b/packages/firestore/test/unit/remote/rest_connection.test.ts @@ -125,15 +125,9 @@ describe('RestConnection', () => { expect(response).to.deep.equal({ response: true }); }); - it('returns error', async () => { - connection.nextResponse = Promise.reject( - new FirestoreError(Code.UNKNOWN, 'Test exception') - ); - try { - await connection.invokeRPC('RunQuery', {}, null); - fail(); - } catch (e) { - expect(e.code).to.equal(Code.UNKNOWN); - } + it('returns error', () => { + const error = new FirestoreError(Code.UNKNOWN, 'Test exception'); + connection.nextResponse = Promise.reject(error); + return expect(connection.invokeRPC('RunQuery', {}, null)).to.be.eventually.rejectedWith(error); }); }); diff --git a/yarn.lock b/yarn.lock index 6a34c9950f2..345efa22872 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1068,35 +1068,6 @@ unique-filename "^1.1.1" which "^1.3.1" -"@firebase/component@0.1.16": - version "0.1.16" - resolved "https://registry.npmjs.org/@firebase/component/-/component-0.1.16.tgz#7a0dbdfff1485d45b8485db87a982f053e68761a" - integrity sha512-FvffvFN0LWgv1H/FIyruTECOL69Dhy+JfwoTq+mV39V8Mz9lNpo41etonL5AOr7KmXxYJVbNwkx0L9Ei88i7JA== - dependencies: - "@firebase/util" "0.2.50" - tslib "^1.11.1" - -"@firebase/firestore@1.16.1": - version "1.16.1" - resolved "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.16.1.tgz#ddf4e357b4d847abe6a57a89d3e2d5b950339176" - integrity sha512-TGtvNIGHMEFFEuOSsRswou576GPZY39vXIsenn0B1Dqz9ACpyDtvAT9YdbG38srlPq7ZKwsP5x04LB43zZ6eAg== - dependencies: - "@firebase/component" "0.1.16" - "@firebase/firestore-types" "1.12.0" - "@firebase/logger" "0.2.6" - "@firebase/util" "0.2.50" - "@firebase/webchannel-wrapper" "0.2.41" - "@grpc/grpc-js" "^1.0.0" - "@grpc/proto-loader" "^0.5.0" - tslib "^1.11.1" - -"@firebase/util@0.2.50": - version "0.2.50" - resolved "https://registry.npmjs.org/@firebase/util/-/util-0.2.50.tgz#77666b845dcb49bc217650aa296a7a8986c06b44" - integrity sha512-vFE6+Jfc25u0ViSpFxxq0q5s+XmuJ/y7CL3ud79RQe+WLFFg+j0eH1t23k0yNSG9vZNM7h3uHRIXbV97sYLAyw== - dependencies: - tslib "^1.11.1" - "@google-cloud/paginator@^2.0.0": version "2.0.3" resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz#c7987ad05d1c3ebcef554381be80e9e8da4e4882" From c02e623333db1710fe6c6dd2d3fef999ff299d29 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 31 Jul 2020 11:20:58 -0700 Subject: [PATCH 05/10] WIP --- packages/firestore/lite/test/integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/lite/test/integration.test.ts b/packages/firestore/lite/test/integration.test.ts index b4fd15f5eaa..145fe8de45e 100644 --- a/packages/firestore/lite/test/integration.test.ts +++ b/packages/firestore/lite/test/integration.test.ts @@ -625,7 +625,7 @@ function genericMutationTests( // The Transaction tests use backoff for updates that fail with failed // preconditions. This leads to test timeouts. // eslint-disable-next-line no-restricted-properties - (testRunnerMayUseBackoff ? it.skip : it.only)( + (testRunnerMayUseBackoff ? it.skip : it)( 'enforces that document exists', () => { return withTestDoc(async docRef => { From 3ea33271a80bae1703b837c8b5cec6c92538c9b3 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 31 Jul 2020 11:23:35 -0700 Subject: [PATCH 06/10] Lint --- .../firestore/src/platform/browser_lite/fetch_connection.ts | 2 +- packages/firestore/test/unit/remote/rest_connection.test.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/firestore/src/platform/browser_lite/fetch_connection.ts b/packages/firestore/src/platform/browser_lite/fetch_connection.ts index e834970c31e..05077a93afe 100644 --- a/packages/firestore/src/platform/browser_lite/fetch_connection.ts +++ b/packages/firestore/src/platform/browser_lite/fetch_connection.ts @@ -13,7 +13,7 @@ * 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 { Token } from '../../api/credentials'; import { Stream } from '../../remote/connection'; diff --git a/packages/firestore/test/unit/remote/rest_connection.test.ts b/packages/firestore/test/unit/remote/rest_connection.test.ts index 5b5605d941f..dee5a539dbb 100644 --- a/packages/firestore/test/unit/remote/rest_connection.test.ts +++ b/packages/firestore/test/unit/remote/rest_connection.test.ts @@ -22,7 +22,6 @@ 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 { fail } from '../../../src/util/assert'; import { User } from '../../../src/auth/user'; import { SDK_VERSION } from '../../../src/core/version'; import { Indexable } from '../../../src/util/misc'; From 6b37f2c5ba0909aa004de5b98be71a8e5b1d2868 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 31 Jul 2020 13:39:24 -0700 Subject: [PATCH 07/10] Cleanup --- packages/firestore/karma.conf.js | 2 +- packages/firestore/lite/test/bootstrap.ts | 2 +- packages/firestore/package.json | 1 - packages/firestore/rollup.config.lite.js | 5 +- packages/firestore/rollup.shared.js | 6 +- packages/firestore/src/local/simple_db.ts | 10 ++- .../platform/browser/webchannel_connection.ts | 16 ++-- .../platform/browser_lite/fetch_connection.ts | 21 +++-- .../src/platform/browser_lite/serializer.ts | 1 - packages/firestore/src/platform/connection.ts | 6 +- .../firestore/src/platform/format_json.ts | 3 +- .../src/platform/node_lite/connection.ts | 6 +- .../firestore/src/platform/random_bytes.ts | 5 +- packages/firestore/src/platform/serializer.ts | 6 +- .../firestore/src/remote/rest_connection.ts | 18 +++-- .../test/integration/api/batch_writes.test.ts | 14 +++- .../test/integration/api/cursor.test.ts | 15 +++- .../test/integration/api/database.test.ts | 10 ++- .../test/integration/api/transactions.test.ts | 80 +++++++++++++++---- .../test/integration/api/validation.test.ts | 20 ++++- .../test/integration/prime_backend.test.ts | 2 +- .../test/unit/local/mutation_queue.test.ts | 4 +- .../unit/local/remote_document_cache.test.ts | 11 ++- .../test/unit/model/document_set.test.ts | 5 +- .../firestore/test/unit/model/path.test.ts | 25 ++++-- .../test/unit/remote/rest_connection.test.ts | 6 +- .../test/unit/util/sorted_map.test.ts | 10 ++- .../test/unit/util/sorted_set.test.ts | 30 +++++-- .../firestore/test/util/equality_matcher.ts | 2 +- 29 files changed, 253 insertions(+), 89 deletions(-) diff --git a/packages/firestore/karma.conf.js b/packages/firestore/karma.conf.js index 3e301c94245..fdfc24e70e2 100644 --- a/packages/firestore/karma.conf.js +++ b/packages/firestore/karma.conf.js @@ -18,7 +18,7 @@ const karmaBase = require('../../config/karma.base'); const { argv } = require('yargs'); -module.exports = function (config) { +module.exports = function(config) { const karmaConfig = Object.assign({}, karmaBase, { // files to load into karma files: getTestFiles(argv), diff --git a/packages/firestore/lite/test/bootstrap.ts b/packages/firestore/lite/test/bootstrap.ts index b7f0952a9ba..effa47dbdc2 100644 --- a/packages/firestore/lite/test/bootstrap.ts +++ b/packages/firestore/lite/test/bootstrap.ts @@ -24,7 +24,7 @@ import '../register'; * https://github.com/webpack-contrib/karma-webpack#alternative-usage */ -process.env.TEST_PLATFORM = 'node-lite'; +process.env.TEST_PLATFORM = 'browser-lite'; // 'context()' definition requires additional dependency on webpack-env package. // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 3351466d547..d9942ea7528 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -63,7 +63,6 @@ "@firebase/webchannel-wrapper": "0.2.41", "@grpc/grpc-js": "^1.0.0", "@grpc/proto-loader": "^0.5.0", - "axios": "^0.19.2", "node-fetch": "2.6.0", "tslib": "^1.11.1" }, diff --git a/packages/firestore/rollup.config.lite.js b/packages/firestore/rollup.config.lite.js index 513a948aed1..a808eb040d1 100644 --- a/packages/firestore/rollup.config.lite.js +++ b/packages/firestore/rollup.config.lite.js @@ -111,7 +111,10 @@ const allBuilds = [ format: 'es', sourcemap: true }, - plugins: [alias(util.generateAliasConfig('browser_lite')), ...browserPlugins], + plugins: [ + alias(util.generateAliasConfig('browser_lite')), + ...browserPlugins + ], external: util.resolveBrowserExterns, treeshake: { moduleSideEffects: false diff --git a/packages/firestore/rollup.shared.js b/packages/firestore/rollup.shared.js index 00eed3ba349..480644fd8af 100644 --- a/packages/firestore/rollup.shared.js +++ b/packages/firestore/rollup.shared.js @@ -29,7 +29,7 @@ const pkg = require('./package.json'); * references to platform-specific files with implementations for the provided * target platform. */ -exports.generateAliasConfig = function (platform) { +exports.generateAliasConfig = function(platform) { return { entries: [ { @@ -47,12 +47,12 @@ const browserDeps = Object.keys( const nodeDeps = [...browserDeps, 'util', 'path']; /** Resolves the external dependencies for the browser build. */ -exports.resolveBrowserExterns = function (id) { +exports.resolveBrowserExterns = function(id) { return browserDeps.some(dep => id === dep || id.startsWith(`${dep}/`)); }; /** Resolves the external dependencies for the Node build. */ -exports.resolveNodeExterns = function (id) { +exports.resolveNodeExterns = function(id) { return nodeDeps.some(dep => id === dep || id.startsWith(`${dep}/`)); }; diff --git a/packages/firestore/src/local/simple_db.ts b/packages/firestore/src/local/simple_db.ts index 09cc8705863..492e71d3714 100644 --- a/packages/firestore/src/local/simple_db.ts +++ b/packages/firestore/src/local/simple_db.ts @@ -218,7 +218,10 @@ export class SimpleDb { static getIOSVersion(ua: string): number { const iOSVersionRegex = ua.match(/i(?:phone|pad|pod) os ([\d_]+)/i); const version = iOSVersionRegex - ? iOSVersionRegex[1].split('_').slice(0, 2).join('.') + ? iOSVersionRegex[1] + .split('_') + .slice(0, 2) + .join('.') : '-1'; return Number(version); } @@ -228,7 +231,10 @@ export class SimpleDb { static getAndroidVersion(ua: string): number { const androidVersionRegex = ua.match(/Android ([\d.]+)/i); const version = androidVersionRegex - ? androidVersionRegex[1].split('.').slice(0, 2).join('.') + ? androidVersionRegex[1] + .split('.') + .slice(0, 2) + .join('.') : '-1'; return Number(version); } diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index 405f7dc7e5e..0c0bb901863 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -130,13 +130,13 @@ export class WebChannelConnection extends RestConnection { default: fail( 'RPC "' + - rpcName + - '" failed with unanticipated ' + - 'webchannel error ' + - xhr.getLastErrorCode() + - ': ' + - xhr.getLastError() + - ', giving up.' + rpcName + + '" failed with unanticipated ' + + 'webchannel error ' + + xhr.getLastErrorCode() + + ': ' + + xhr.getLastError() + + ', giving up.' ); } } finally { @@ -148,7 +148,7 @@ export class WebChannelConnection extends RestConnection { xhr.send(url, 'POST', requestString, headers, XHR_TIMEOUT_SECS); }); } - + openStream( rpcName: string, token: Token | null diff --git a/packages/firestore/src/platform/browser_lite/fetch_connection.ts b/packages/firestore/src/platform/browser_lite/fetch_connection.ts index 05077a93afe..d0644f1c7de 100644 --- a/packages/firestore/src/platform/browser_lite/fetch_connection.ts +++ b/packages/firestore/src/platform/browser_lite/fetch_connection.ts @@ -18,13 +18,24 @@ import { Token } from '../../api/credentials'; import { Stream } from '../../remote/connection'; import { mapCodeFromHttpStatus } from '../../remote/rpc_error'; -import { FirestoreError } from '../../util/error'; +import { FirestoreError } from '../../util/error'; import { StringMap } from '../../util/types'; import { RestConnection } from '../../remote/rest_connection'; -import {DatabaseInfo} from "../../core/database_info"; +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 { - constructor(databaseInfo: DatabaseInfo, private readonly fetchImpl: typeof fetch) { + /** + * @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); } @@ -56,14 +67,14 @@ export class FetchConnection extends RestConnection { '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/serializer.ts b/packages/firestore/src/platform/browser_lite/serializer.ts index c53bf2cbd7e..ed490bf16d7 100644 --- a/packages/firestore/src/platform/browser_lite/serializer.ts +++ b/packages/firestore/src/platform/browser_lite/serializer.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -/** Return the Platform-specific serializer monitor. */ import { JsonProtoSerializer } from '../../remote/serializer'; import { DatabaseId } from '../../core/database_info'; diff --git a/packages/firestore/src/platform/connection.ts b/packages/firestore/src/platform/connection.ts index 0f1d106c843..bb50a94f008 100644 --- a/packages/firestore/src/platform/connection.ts +++ b/packages/firestore/src/platform/connection.ts @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {ConnectivityMonitor} from "../remote/connectivity_monitor"; -import {DatabaseInfo} from "../core/database_info"; -import {Connection} from "../remote/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 diff --git a/packages/firestore/src/platform/format_json.ts b/packages/firestore/src/platform/format_json.ts index b251411944d..7cd01fe4494 100644 --- a/packages/firestore/src/platform/format_json.ts +++ b/packages/firestore/src/platform/format_json.ts @@ -17,7 +17,8 @@ // 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`); +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 { diff --git a/packages/firestore/src/platform/node_lite/connection.ts b/packages/firestore/src/platform/node_lite/connection.ts index 46aba522a91..8063ff8e5f9 100644 --- a/packages/firestore/src/platform/node_lite/connection.ts +++ b/packages/firestore/src/platform/node_lite/connection.ts @@ -17,9 +17,9 @@ import * as nodeFetch from 'node-fetch'; -import {FetchConnection} from "../browser_lite/fetch_connection"; -import {DatabaseInfo} from "../../core/database_info"; -import {Connection} from "../../remote/connection"; +import { FetchConnection } from '../browser_lite/fetch_connection'; +import { DatabaseInfo } from '../../core/database_info'; +import { Connection } from '../../remote/connection'; export { newConnectivityMonitor } from '../browser/connection'; diff --git a/packages/firestore/src/platform/random_bytes.ts b/packages/firestore/src/platform/random_bytes.ts index 9150943abd8..d299cd16ac4 100644 --- a/packages/firestore/src/platform/random_bytes.ts +++ b/packages/firestore/src/platform/random_bytes.ts @@ -17,7 +17,8 @@ // 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`); +const platform = require(`./${process.env.TEST_PLATFORM ?? + 'node'}/random_bytes`); /** * Generates `nBytes` of random bytes. @@ -25,5 +26,5 @@ const platform = require(`./${process.env.TEST_PLATFORM ?? 'node'}/random_bytes` * If `nBytes < 0` , an error will be thrown. */ export function randomBytes(nBytes: number): Uint8Array { - return platform.randomBytes(nBytes); + return platform.randomBytes(nBytes); } diff --git a/packages/firestore/src/platform/serializer.ts b/packages/firestore/src/platform/serializer.ts index 9901e42f842..c2e416abdc1 100644 --- a/packages/firestore/src/platform/serializer.ts +++ b/packages/firestore/src/platform/serializer.ts @@ -14,13 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {DatabaseId} from "../core/database_info"; -import {JsonProtoSerializer} from "../remote/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 { - return platform.newSerializer(databaseId); + return platform.newSerializer(databaseId); } diff --git a/packages/firestore/src/remote/rest_connection.ts b/packages/firestore/src/remote/rest_connection.ts index 4045bb3c8b4..8cb06af0f95 100644 --- a/packages/firestore/src/remote/rest_connection.ts +++ b/packages/firestore/src/remote/rest_connection.ts @@ -27,9 +27,9 @@ import { Indexable } from '../util/misc'; const LOG_TAG = 'RestConnection'; -/** - * Maps RPC names to the corresponding REST endpoint name. - * +/** + * Maps RPC names to the corresponding REST endpoint name. + * * We use array notation to avoid mangling. */ const RPC_NAME_URL_MAPPING: StringMap = {}; @@ -92,7 +92,15 @@ export abstract class RestConnection implements Connection { return response; }, (err: FirestoreError) => { - logWarn(LOG_TAG, `${rpcName} failed with error: `, err.message, 'url: ', url, 'request:', req); + logWarn( + LOG_TAG, + `${rpcName} failed with error: `, + err.message, + 'url: ', + url, + 'request:', + req + ); throw err; } ); @@ -107,7 +115,7 @@ export abstract class RestConnection implements Connection { // can just use the normal invoke() method. return this.invokeRPC(rpcName, request, token); } - + abstract openStream( rpcName: string, token: Token | null diff --git a/packages/firestore/test/integration/api/batch_writes.test.ts b/packages/firestore/test/integration/api/batch_writes.test.ts index 247866a0bca..1726b693fb8 100644 --- a/packages/firestore/test/integration/api/batch_writes.test.ts +++ b/packages/firestore/test/integration/api/batch_writes.test.ts @@ -76,7 +76,12 @@ apiDescribe('Database batch writes', (persistence: boolean) => { return integrationHelpers.withTestDoc(persistence, doc => { return doc .set({ foo: 'bar' }) - .then(() => doc.firestore.batch().update(doc, { baz: 42 }).commit()) + .then(() => + doc.firestore + .batch() + .update(doc, { baz: 42 }) + .commit() + ) .then(() => doc.get()) .then(snapshot => { expect(snapshot.exists).to.equal(true); @@ -121,7 +126,12 @@ apiDescribe('Database batch writes', (persistence: boolean) => { .then(snapshot => { expect(snapshot.exists).to.equal(true); }) - .then(() => doc.firestore.batch().delete(doc).commit()) + .then(() => + doc.firestore + .batch() + .delete(doc) + .commit() + ) .then(() => doc.get()) .then(snapshot => { expect(snapshot.exists).to.equal(false); diff --git a/packages/firestore/test/integration/api/cursor.test.ts b/packages/firestore/test/integration/api/cursor.test.ts index c2f758ba048..a793ebc1a6c 100644 --- a/packages/firestore/test/integration/api/cursor.test.ts +++ b/packages/firestore/test/integration/api/cursor.test.ts @@ -48,7 +48,10 @@ apiDescribe('Cursors', (persistence: boolean) => { .then(docs => { expect(toDataArray(docs)).to.deep.equal([{ v: 'a' }, { v: 'b' }]); const lastDoc = docs.docs[docs.docs.length - 1]; - return coll.limit(3).startAfter(lastDoc).get(); + return coll + .limit(3) + .startAfter(lastDoc) + .get(); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([ @@ -57,12 +60,18 @@ apiDescribe('Cursors', (persistence: boolean) => { { v: 'e' } ]); const lastDoc = docs.docs[docs.docs.length - 1]; - return coll.limit(1).startAfter(lastDoc).get(); + return coll + .limit(1) + .startAfter(lastDoc) + .get(); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([{ v: 'f' }]); const lastDoc = docs.docs[docs.docs.length - 1]; - return coll.limit(3).startAfter(lastDoc).get(); + return coll + .limit(3) + .startAfter(lastDoc) + .get(); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([]); diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 2ba3f68d029..870cacaa294 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -660,10 +660,16 @@ apiDescribe('Database', (persistence: boolean) => { it('inequality same as first orderBy works.', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll.where('x', '>', 32).orderBy('x').orderBy('y') + coll + .where('x', '>', 32) + .orderBy('x') + .orderBy('y') ).not.to.throw(); expect(() => - coll.orderBy('x').where('x', '>', 32).orderBy('y') + coll + .orderBy('x') + .where('x', '>', 32) + .orderBy('y') ).not.to.throw(); }); }); diff --git a/packages/firestore/test/integration/api/transactions.test.ts b/packages/firestore/test/integration/api/transactions.test.ts index 1cd360e4481..8c797c9ec08 100644 --- a/packages/firestore/test/integration/api/transactions.test.ts +++ b/packages/firestore/test/integration/api/transactions.test.ts @@ -207,7 +207,10 @@ apiDescribe('Database transactions', (persistence: boolean) => { return integrationHelpers.withTestDb(persistence, async db => { const tt = new TransactionTester(db); - await tt.withExistingDoc().run(get, delete1, delete1).expectNoDoc(); + await tt + .withExistingDoc() + .run(get, delete1, delete1) + .expectNoDoc(); await tt .withExistingDoc() .run(get, delete1, update2) @@ -217,7 +220,10 @@ apiDescribe('Database transactions', (persistence: boolean) => { .run(get, delete1, set2) .expectDoc({ foo: 'bar2' }); - await tt.withExistingDoc().run(get, update1, delete1).expectNoDoc(); + await tt + .withExistingDoc() + .run(get, update1, delete1) + .expectNoDoc(); await tt .withExistingDoc() .run(get, update1, update2) @@ -227,7 +233,10 @@ apiDescribe('Database transactions', (persistence: boolean) => { .run(get, update1, set2) .expectDoc({ foo: 'bar2' }); - await tt.withExistingDoc().run(get, set1, delete1).expectNoDoc(); + await tt + .withExistingDoc() + .run(get, set1, delete1) + .expectNoDoc(); await tt .withExistingDoc() .run(get, set1, update2) @@ -243,7 +252,10 @@ apiDescribe('Database transactions', (persistence: boolean) => { return integrationHelpers.withTestDb(persistence, async db => { const tt = new TransactionTester(db); - await tt.withNonexistentDoc().run(get, delete1, delete1).expectNoDoc(); + await tt + .withNonexistentDoc() + .run(get, delete1, delete1) + .expectNoDoc(); await tt .withNonexistentDoc() .run(get, delete1, update2) @@ -266,7 +278,10 @@ apiDescribe('Database transactions', (persistence: boolean) => { .run(get, update1, set1) .expectError('invalid-argument'); - await tt.withNonexistentDoc().run(get, set1, delete1).expectNoDoc(); + await tt + .withNonexistentDoc() + .run(get, set1, delete1) + .expectNoDoc(); await tt .withNonexistentDoc() .run(get, set1, update2) @@ -282,23 +297,44 @@ apiDescribe('Database transactions', (persistence: boolean) => { return integrationHelpers.withTestDb(persistence, async db => { const tt = new TransactionTester(db); - await tt.withExistingDoc().run(delete1, delete1).expectNoDoc(); + await tt + .withExistingDoc() + .run(delete1, delete1) + .expectNoDoc(); await tt .withExistingDoc() .run(delete1, update2) .expectError('invalid-argument'); - await tt.withExistingDoc().run(delete1, set2).expectDoc({ foo: 'bar2' }); + await tt + .withExistingDoc() + .run(delete1, set2) + .expectDoc({ foo: 'bar2' }); - await tt.withExistingDoc().run(update1, delete1).expectNoDoc(); + await tt + .withExistingDoc() + .run(update1, delete1) + .expectNoDoc(); await tt .withExistingDoc() .run(update1, update2) .expectDoc({ foo: 'bar2' }); - await tt.withExistingDoc().run(update1, set2).expectDoc({ foo: 'bar2' }); + await tt + .withExistingDoc() + .run(update1, set2) + .expectDoc({ foo: 'bar2' }); - await tt.withExistingDoc().run(set1, delete1).expectNoDoc(); - await tt.withExistingDoc().run(set1, update2).expectDoc({ foo: 'bar2' }); - await tt.withExistingDoc().run(set1, set2).expectDoc({ foo: 'bar2' }); + await tt + .withExistingDoc() + .run(set1, delete1) + .expectNoDoc(); + await tt + .withExistingDoc() + .run(set1, update2) + .expectDoc({ foo: 'bar2' }); + await tt + .withExistingDoc() + .run(set1, set2) + .expectDoc({ foo: 'bar2' }); }); }); @@ -306,7 +342,10 @@ apiDescribe('Database transactions', (persistence: boolean) => { return integrationHelpers.withTestDb(persistence, async db => { const tt = new TransactionTester(db); - await tt.withNonexistentDoc().run(delete1, delete1).expectNoDoc(); + await tt + .withNonexistentDoc() + .run(delete1, delete1) + .expectNoDoc(); await tt .withNonexistentDoc() .run(delete1, update2) @@ -324,14 +363,23 @@ apiDescribe('Database transactions', (persistence: boolean) => { .withNonexistentDoc() .run(update1, update2) .expectError('not-found'); - await tt.withNonexistentDoc().run(update1, set1).expectError('not-found'); + await tt + .withNonexistentDoc() + .run(update1, set1) + .expectError('not-found'); - await tt.withNonexistentDoc().run(set1, delete1).expectNoDoc(); + await tt + .withNonexistentDoc() + .run(set1, delete1) + .expectNoDoc(); await tt .withNonexistentDoc() .run(set1, update2) .expectDoc({ foo: 'bar2' }); - await tt.withNonexistentDoc().run(set1, set2).expectDoc({ foo: 'bar2' }); + await tt + .withNonexistentDoc() + .run(set1, set2) + .expectDoc({ foo: 'bar2' }); }); }); diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index ab2c93d534c..af4f7ae2470 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -567,13 +567,19 @@ apiDescribe('Validation:', (persistence: boolean) => { return ref .set(data) .then(() => { - return ref.firestore.batch().set(ref, data).commit(); + return ref.firestore + .batch() + .set(ref, data) + .commit(); }) .then(() => { return ref.update(data); }) .then(() => { - return ref.firestore.batch().update(ref, data).commit(); + return ref.firestore + .batch() + .update(ref, data) + .commit(); }) .then(() => { return ref.firestore.runTransaction(async txn => { @@ -1134,10 +1140,16 @@ apiDescribe('Validation:', (persistence: boolean) => { reason ); expect(() => - collection.where('x', '>', 32).orderBy('y').orderBy('x') + collection + .where('x', '>', 32) + .orderBy('y') + .orderBy('x') ).to.throw(reason); expect(() => - collection.orderBy('y').orderBy('x').where('x', '>', 32) + collection + .orderBy('y') + .orderBy('x') + .where('x', '>', 32) ).to.throw(reason); } ); diff --git a/packages/firestore/test/integration/prime_backend.test.ts b/packages/firestore/test/integration/prime_backend.test.ts index 5132536bd3b..2beb69bcffc 100644 --- a/packages/firestore/test/integration/prime_backend.test.ts +++ b/packages/firestore/test/integration/prime_backend.test.ts @@ -27,7 +27,7 @@ const PRIMING_TIMEOUT_MS = 45000; before( 'Prime backend by waiting for a write to show up in the watch stream', - function (): Promise { + function(): Promise { this.timeout(PRIMING_TIMEOUT_MS); return withTestDoc(/*persistence=*/ false, async doc => { diff --git a/packages/firestore/test/unit/local/mutation_queue.test.ts b/packages/firestore/test/unit/local/mutation_queue.test.ts index 067f517bfed..83c2610d507 100644 --- a/packages/firestore/test/unit/local/mutation_queue.test.ts +++ b/packages/firestore/test/unit/local/mutation_queue.test.ts @@ -244,7 +244,9 @@ function genericMutationQueueTests(): void { } const expected = [batches[1], batches[2], batches[4]]; const matches = await mutationQueue.getAllMutationBatchesAffectingDocumentKeys( - documentKeySet().add(key('foo/bar')).add(key('foo/baz')) + documentKeySet() + .add(key('foo/bar')) + .add(key('foo/baz')) ); expectEqualArrays(matches, expected); }); diff --git a/packages/firestore/test/unit/local/remote_document_cache.test.ts b/packages/firestore/test/unit/local/remote_document_cache.test.ts index 4e5d240dec0..000041bbdaf 100644 --- a/packages/firestore/test/unit/local/remote_document_cache.test.ts +++ b/packages/firestore/test/unit/local/remote_document_cache.test.ts @@ -304,7 +304,11 @@ function genericRemoteDocumentCacheTests( return cache .addEntries(docs, version(VERSION)) .then(() => { - return cache.getEntries(documentKeySet().add(key1).add(key2)); + return cache.getEntries( + documentKeySet() + .add(key1) + .add(key2) + ); }) .then(read => { expectEqual(read.get(key1), docs[0]); @@ -324,7 +328,10 @@ function genericRemoteDocumentCacheTests( .addEntries(docs, version(VERSION)) .then(() => { return cache.getEntries( - documentKeySet().add(key1).add(key2).add(missingKey) + documentKeySet() + .add(key1) + .add(key2) + .add(missingKey) ); }) .then(read => { diff --git a/packages/firestore/test/unit/model/document_set.test.ts b/packages/firestore/test/unit/model/document_set.test.ts index 781b0232d72..31fa4382708 100644 --- a/packages/firestore/test/unit/model/document_set.test.ts +++ b/packages/firestore/test/unit/model/document_set.test.ts @@ -67,7 +67,10 @@ describe('DocumentSet', () => { it('updates documents', () => { const comp = DocComparator.byField('sort'); - let set = new DocumentSet(comp).add(d1).add(d2).add(d3); + let set = new DocumentSet(comp) + .add(d1) + .add(d2) + .add(d3); expect(set.size).to.equal(3); const d2prime = doc('docs/2', 2, { sort: 9 }); diff --git a/packages/firestore/test/unit/model/path.test.ts b/packages/firestore/test/unit/model/path.test.ts index a83ed4fbe12..3843d586a26 100644 --- a/packages/firestore/test/unit/model/path.test.ts +++ b/packages/firestore/test/unit/model/path.test.ts @@ -68,9 +68,12 @@ describe('Path', () => { expect(path.popFirst().popFirst()).to.deep.equal( new ResourcePath(['messages']) ); - expect(path.popFirst().popFirst().popFirst()).to.deep.equal( - new ResourcePath([]) - ); + expect( + path + .popFirst() + .popFirst() + .popFirst() + ).to.deep.equal(new ResourcePath([])); expect(path.popFirst(0)).to.deep.equal( new ResourcePath(['rooms', 'Eros', 'messages']) ); @@ -88,7 +91,12 @@ describe('Path', () => { expect(path.lastSegment()).to.equal('messages'); expect(path.popLast().lastSegment()).to.equal('Eros'); - expect(path.popLast().popLast().lastSegment()).to.equal('rooms'); + expect( + path + .popLast() + .popLast() + .lastSegment() + ).to.equal('rooms'); }); it('can create child path', () => { @@ -105,9 +113,12 @@ describe('Path', () => { expect(path.popLast()).to.deep.equal(new ResourcePath(['rooms', 'Eros'])); expect(path.popLast().popLast()).to.deep.equal(new ResourcePath(['rooms'])); - expect(path.popLast().popLast().popLast()).to.deep.equal( - new ResourcePath([]) - ); + expect( + path + .popLast() + .popLast() + .popLast() + ).to.deep.equal(new ResourcePath([])); // original remains unmodified expect(path).to.deep.equal(new ResourcePath(['rooms', 'Eros', 'messages'])); diff --git a/packages/firestore/test/unit/remote/rest_connection.test.ts b/packages/firestore/test/unit/remote/rest_connection.test.ts index dee5a539dbb..eda6119ea36 100644 --- a/packages/firestore/test/unit/remote/rest_connection.test.ts +++ b/packages/firestore/test/unit/remote/rest_connection.test.ts @@ -125,8 +125,10 @@ describe('RestConnection', () => { }); it('returns error', () => { - const error = new FirestoreError(Code.UNKNOWN, 'Test exception'); + const error = new FirestoreError(Code.UNKNOWN, 'Test exception'); connection.nextResponse = Promise.reject(error); - return expect(connection.invokeRPC('RunQuery', {}, null)).to.be.eventually.rejectedWith(error); + return expect( + connection.invokeRPC('RunQuery', {}, null) + ).to.be.eventually.rejectedWith(error); }); }); diff --git a/packages/firestore/test/unit/util/sorted_map.test.ts b/packages/firestore/test/unit/util/sorted_map.test.ts index 3729e0be78a..6a3c653829c 100644 --- a/packages/firestore/test/unit/util/sorted_map.test.ts +++ b/packages/firestore/test/unit/util/sorted_map.test.ts @@ -193,12 +193,18 @@ describe('SortedMap Tests', () => { expect(map.size).to.equal(6); expect(map.root.checkMaxDepth()).to.equal(true); - const m2 = map.insert(20, 20).insert(18, 18).insert(2, 2); + const m2 = map + .insert(20, 20) + .insert(18, 18) + .insert(2, 2); expect(m2.size).to.equal(9); expect(m2.root.checkMaxDepth()).to.equal(true); - const m3 = m2.insert(71, 71).insert(42, 42).insert(88, 88); + const m3 = m2 + .insert(71, 71) + .insert(42, 42) + .insert(88, 88); expect(m3.size).to.equal(12); expect(m3.root.checkMaxDepth()).to.equal(true); diff --git a/packages/firestore/test/unit/util/sorted_set.test.ts b/packages/firestore/test/unit/util/sorted_set.test.ts index 45e9da35fdf..b16787ec553 100644 --- a/packages/firestore/test/unit/util/sorted_set.test.ts +++ b/packages/firestore/test/unit/util/sorted_set.test.ts @@ -25,7 +25,11 @@ describe('SortedSet', () => { it('keeps elements in the right order', () => { let set = new SortedSet(primitiveComparator); - set = set.add(4).add(2).add(-1).add(0); + set = set + .add(4) + .add(2) + .add(-1) + .add(0); expectSetToEqual(set, [-1, 0, 2, 4]); @@ -124,7 +128,12 @@ describe('SortedSet', () => { it('can find element equal or greater to provided.', () => { const empty = new SortedSet(primitiveComparator); - const set = empty.add(0).add(2).add(5).add(10).add(12); + const set = empty + .add(0) + .add(2) + .add(5) + .add(10) + .add(12); expect(set.firstAfterOrEqual(2)).to.equal(2); expect(set.firstAfterOrEqual(3)).to.equal(5); @@ -134,9 +143,20 @@ describe('SortedSet', () => { it('can unionWith another set.', () => { const empty = new SortedSet(primitiveComparator); - const set = empty.add(0).add(1).add(2); - const set2 = empty.add(2).add(3).add(4); - const expected = empty.add(0).add(1).add(2).add(3).add(4); + const set = empty + .add(0) + .add(1) + .add(2); + const set2 = empty + .add(2) + .add(3) + .add(4); + const expected = empty + .add(0) + .add(1) + .add(2) + .add(3) + .add(4); expect(set.unionWith(set2)).to.deep.equal(expected); }); diff --git a/packages/firestore/test/util/equality_matcher.ts b/packages/firestore/test/util/equality_matcher.ts index 46fbe71715a..3ba85f64d9c 100644 --- a/packages/firestore/test/util/equality_matcher.ts +++ b/packages/firestore/test/util/equality_matcher.ts @@ -123,7 +123,7 @@ export function addEqualityMatcher( // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const assertEql = (_super: (r: unknown, l: unknown) => boolean) => { originalFunction = originalFunction || _super; - return function ( + return function( this: Chai.Assertion, expected?: unknown, msg?: unknown From 198e11d56959858ec503ffc2f2578581022fe0b8 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 31 Jul 2020 13:40:36 -0700 Subject: [PATCH 08/10] --amend --- packages/firestore/karma.conf.js | 2 +- packages/firestore/rollup.shared.js | 6 +- packages/firestore/src/local/simple_db.ts | 10 +-- .../firestore/src/platform/format_json.ts | 5 +- .../firestore/src/platform/random_bytes.ts | 5 +- .../test/integration/api/batch_writes.test.ts | 14 +--- .../test/integration/api/cursor.test.ts | 15 +--- .../test/integration/api/database.test.ts | 10 +-- .../test/integration/api/transactions.test.ts | 80 ++++--------------- .../test/integration/api/validation.test.ts | 20 +---- .../test/integration/prime_backend.test.ts | 2 +- .../test/unit/local/mutation_queue.test.ts | 4 +- .../unit/local/remote_document_cache.test.ts | 11 +-- .../test/unit/model/document_set.test.ts | 5 +- .../firestore/test/unit/model/path.test.ts | 25 ++---- .../test/unit/util/sorted_map.test.ts | 10 +-- .../test/unit/util/sorted_set.test.ts | 30 ++----- .../firestore/test/util/equality_matcher.ts | 2 +- 18 files changed, 59 insertions(+), 197 deletions(-) diff --git a/packages/firestore/karma.conf.js b/packages/firestore/karma.conf.js index fdfc24e70e2..3e301c94245 100644 --- a/packages/firestore/karma.conf.js +++ b/packages/firestore/karma.conf.js @@ -18,7 +18,7 @@ const karmaBase = require('../../config/karma.base'); const { argv } = require('yargs'); -module.exports = function(config) { +module.exports = function (config) { const karmaConfig = Object.assign({}, karmaBase, { // files to load into karma files: getTestFiles(argv), diff --git a/packages/firestore/rollup.shared.js b/packages/firestore/rollup.shared.js index 480644fd8af..00eed3ba349 100644 --- a/packages/firestore/rollup.shared.js +++ b/packages/firestore/rollup.shared.js @@ -29,7 +29,7 @@ const pkg = require('./package.json'); * references to platform-specific files with implementations for the provided * target platform. */ -exports.generateAliasConfig = function(platform) { +exports.generateAliasConfig = function (platform) { return { entries: [ { @@ -47,12 +47,12 @@ const browserDeps = Object.keys( const nodeDeps = [...browserDeps, 'util', 'path']; /** Resolves the external dependencies for the browser build. */ -exports.resolveBrowserExterns = function(id) { +exports.resolveBrowserExterns = function (id) { return browserDeps.some(dep => id === dep || id.startsWith(`${dep}/`)); }; /** Resolves the external dependencies for the Node build. */ -exports.resolveNodeExterns = function(id) { +exports.resolveNodeExterns = function (id) { return nodeDeps.some(dep => id === dep || id.startsWith(`${dep}/`)); }; diff --git a/packages/firestore/src/local/simple_db.ts b/packages/firestore/src/local/simple_db.ts index 492e71d3714..09cc8705863 100644 --- a/packages/firestore/src/local/simple_db.ts +++ b/packages/firestore/src/local/simple_db.ts @@ -218,10 +218,7 @@ export class SimpleDb { static getIOSVersion(ua: string): number { const iOSVersionRegex = ua.match(/i(?:phone|pad|pod) os ([\d_]+)/i); const version = iOSVersionRegex - ? iOSVersionRegex[1] - .split('_') - .slice(0, 2) - .join('.') + ? iOSVersionRegex[1].split('_').slice(0, 2).join('.') : '-1'; return Number(version); } @@ -231,10 +228,7 @@ export class SimpleDb { static getAndroidVersion(ua: string): number { const androidVersionRegex = ua.match(/Android ([\d.]+)/i); const version = androidVersionRegex - ? androidVersionRegex[1] - .split('.') - .slice(0, 2) - .join('.') + ? androidVersionRegex[1].split('.').slice(0, 2).join('.') : '-1'; return Number(version); } diff --git a/packages/firestore/src/platform/format_json.ts b/packages/firestore/src/platform/format_json.ts index 7cd01fe4494..e25fca4ad94 100644 --- a/packages/firestore/src/platform/format_json.ts +++ b/packages/firestore/src/platform/format_json.ts @@ -17,8 +17,9 @@ // 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`); +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 { diff --git a/packages/firestore/src/platform/random_bytes.ts b/packages/firestore/src/platform/random_bytes.ts index d299cd16ac4..7db2c4cb7ba 100644 --- a/packages/firestore/src/platform/random_bytes.ts +++ b/packages/firestore/src/platform/random_bytes.ts @@ -17,8 +17,9 @@ // 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`); +const platform = require(`./${ + process.env.TEST_PLATFORM ?? 'node' +}/random_bytes`); /** * Generates `nBytes` of random bytes. diff --git a/packages/firestore/test/integration/api/batch_writes.test.ts b/packages/firestore/test/integration/api/batch_writes.test.ts index 1726b693fb8..247866a0bca 100644 --- a/packages/firestore/test/integration/api/batch_writes.test.ts +++ b/packages/firestore/test/integration/api/batch_writes.test.ts @@ -76,12 +76,7 @@ apiDescribe('Database batch writes', (persistence: boolean) => { return integrationHelpers.withTestDoc(persistence, doc => { return doc .set({ foo: 'bar' }) - .then(() => - doc.firestore - .batch() - .update(doc, { baz: 42 }) - .commit() - ) + .then(() => doc.firestore.batch().update(doc, { baz: 42 }).commit()) .then(() => doc.get()) .then(snapshot => { expect(snapshot.exists).to.equal(true); @@ -126,12 +121,7 @@ apiDescribe('Database batch writes', (persistence: boolean) => { .then(snapshot => { expect(snapshot.exists).to.equal(true); }) - .then(() => - doc.firestore - .batch() - .delete(doc) - .commit() - ) + .then(() => doc.firestore.batch().delete(doc).commit()) .then(() => doc.get()) .then(snapshot => { expect(snapshot.exists).to.equal(false); diff --git a/packages/firestore/test/integration/api/cursor.test.ts b/packages/firestore/test/integration/api/cursor.test.ts index a793ebc1a6c..c2f758ba048 100644 --- a/packages/firestore/test/integration/api/cursor.test.ts +++ b/packages/firestore/test/integration/api/cursor.test.ts @@ -48,10 +48,7 @@ apiDescribe('Cursors', (persistence: boolean) => { .then(docs => { expect(toDataArray(docs)).to.deep.equal([{ v: 'a' }, { v: 'b' }]); const lastDoc = docs.docs[docs.docs.length - 1]; - return coll - .limit(3) - .startAfter(lastDoc) - .get(); + return coll.limit(3).startAfter(lastDoc).get(); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([ @@ -60,18 +57,12 @@ apiDescribe('Cursors', (persistence: boolean) => { { v: 'e' } ]); const lastDoc = docs.docs[docs.docs.length - 1]; - return coll - .limit(1) - .startAfter(lastDoc) - .get(); + return coll.limit(1).startAfter(lastDoc).get(); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([{ v: 'f' }]); const lastDoc = docs.docs[docs.docs.length - 1]; - return coll - .limit(3) - .startAfter(lastDoc) - .get(); + return coll.limit(3).startAfter(lastDoc).get(); }) .then(docs => { expect(toDataArray(docs)).to.deep.equal([]); diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 870cacaa294..2ba3f68d029 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -660,16 +660,10 @@ apiDescribe('Database', (persistence: boolean) => { it('inequality same as first orderBy works.', () => { return withTestCollection(persistence, {}, async coll => { expect(() => - coll - .where('x', '>', 32) - .orderBy('x') - .orderBy('y') + coll.where('x', '>', 32).orderBy('x').orderBy('y') ).not.to.throw(); expect(() => - coll - .orderBy('x') - .where('x', '>', 32) - .orderBy('y') + coll.orderBy('x').where('x', '>', 32).orderBy('y') ).not.to.throw(); }); }); diff --git a/packages/firestore/test/integration/api/transactions.test.ts b/packages/firestore/test/integration/api/transactions.test.ts index 8c797c9ec08..1cd360e4481 100644 --- a/packages/firestore/test/integration/api/transactions.test.ts +++ b/packages/firestore/test/integration/api/transactions.test.ts @@ -207,10 +207,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { return integrationHelpers.withTestDb(persistence, async db => { const tt = new TransactionTester(db); - await tt - .withExistingDoc() - .run(get, delete1, delete1) - .expectNoDoc(); + await tt.withExistingDoc().run(get, delete1, delete1).expectNoDoc(); await tt .withExistingDoc() .run(get, delete1, update2) @@ -220,10 +217,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { .run(get, delete1, set2) .expectDoc({ foo: 'bar2' }); - await tt - .withExistingDoc() - .run(get, update1, delete1) - .expectNoDoc(); + await tt.withExistingDoc().run(get, update1, delete1).expectNoDoc(); await tt .withExistingDoc() .run(get, update1, update2) @@ -233,10 +227,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { .run(get, update1, set2) .expectDoc({ foo: 'bar2' }); - await tt - .withExistingDoc() - .run(get, set1, delete1) - .expectNoDoc(); + await tt.withExistingDoc().run(get, set1, delete1).expectNoDoc(); await tt .withExistingDoc() .run(get, set1, update2) @@ -252,10 +243,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { return integrationHelpers.withTestDb(persistence, async db => { const tt = new TransactionTester(db); - await tt - .withNonexistentDoc() - .run(get, delete1, delete1) - .expectNoDoc(); + await tt.withNonexistentDoc().run(get, delete1, delete1).expectNoDoc(); await tt .withNonexistentDoc() .run(get, delete1, update2) @@ -278,10 +266,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { .run(get, update1, set1) .expectError('invalid-argument'); - await tt - .withNonexistentDoc() - .run(get, set1, delete1) - .expectNoDoc(); + await tt.withNonexistentDoc().run(get, set1, delete1).expectNoDoc(); await tt .withNonexistentDoc() .run(get, set1, update2) @@ -297,44 +282,23 @@ apiDescribe('Database transactions', (persistence: boolean) => { return integrationHelpers.withTestDb(persistence, async db => { const tt = new TransactionTester(db); - await tt - .withExistingDoc() - .run(delete1, delete1) - .expectNoDoc(); + await tt.withExistingDoc().run(delete1, delete1).expectNoDoc(); await tt .withExistingDoc() .run(delete1, update2) .expectError('invalid-argument'); - await tt - .withExistingDoc() - .run(delete1, set2) - .expectDoc({ foo: 'bar2' }); + await tt.withExistingDoc().run(delete1, set2).expectDoc({ foo: 'bar2' }); - await tt - .withExistingDoc() - .run(update1, delete1) - .expectNoDoc(); + await tt.withExistingDoc().run(update1, delete1).expectNoDoc(); await tt .withExistingDoc() .run(update1, update2) .expectDoc({ foo: 'bar2' }); - await tt - .withExistingDoc() - .run(update1, set2) - .expectDoc({ foo: 'bar2' }); + await tt.withExistingDoc().run(update1, set2).expectDoc({ foo: 'bar2' }); - await tt - .withExistingDoc() - .run(set1, delete1) - .expectNoDoc(); - await tt - .withExistingDoc() - .run(set1, update2) - .expectDoc({ foo: 'bar2' }); - await tt - .withExistingDoc() - .run(set1, set2) - .expectDoc({ foo: 'bar2' }); + await tt.withExistingDoc().run(set1, delete1).expectNoDoc(); + await tt.withExistingDoc().run(set1, update2).expectDoc({ foo: 'bar2' }); + await tt.withExistingDoc().run(set1, set2).expectDoc({ foo: 'bar2' }); }); }); @@ -342,10 +306,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { return integrationHelpers.withTestDb(persistence, async db => { const tt = new TransactionTester(db); - await tt - .withNonexistentDoc() - .run(delete1, delete1) - .expectNoDoc(); + await tt.withNonexistentDoc().run(delete1, delete1).expectNoDoc(); await tt .withNonexistentDoc() .run(delete1, update2) @@ -363,23 +324,14 @@ apiDescribe('Database transactions', (persistence: boolean) => { .withNonexistentDoc() .run(update1, update2) .expectError('not-found'); - await tt - .withNonexistentDoc() - .run(update1, set1) - .expectError('not-found'); + await tt.withNonexistentDoc().run(update1, set1).expectError('not-found'); - await tt - .withNonexistentDoc() - .run(set1, delete1) - .expectNoDoc(); + await tt.withNonexistentDoc().run(set1, delete1).expectNoDoc(); await tt .withNonexistentDoc() .run(set1, update2) .expectDoc({ foo: 'bar2' }); - await tt - .withNonexistentDoc() - .run(set1, set2) - .expectDoc({ foo: 'bar2' }); + await tt.withNonexistentDoc().run(set1, set2).expectDoc({ foo: 'bar2' }); }); }); diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index af4f7ae2470..ab2c93d534c 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -567,19 +567,13 @@ apiDescribe('Validation:', (persistence: boolean) => { return ref .set(data) .then(() => { - return ref.firestore - .batch() - .set(ref, data) - .commit(); + return ref.firestore.batch().set(ref, data).commit(); }) .then(() => { return ref.update(data); }) .then(() => { - return ref.firestore - .batch() - .update(ref, data) - .commit(); + return ref.firestore.batch().update(ref, data).commit(); }) .then(() => { return ref.firestore.runTransaction(async txn => { @@ -1140,16 +1134,10 @@ apiDescribe('Validation:', (persistence: boolean) => { reason ); expect(() => - collection - .where('x', '>', 32) - .orderBy('y') - .orderBy('x') + collection.where('x', '>', 32).orderBy('y').orderBy('x') ).to.throw(reason); expect(() => - collection - .orderBy('y') - .orderBy('x') - .where('x', '>', 32) + collection.orderBy('y').orderBy('x').where('x', '>', 32) ).to.throw(reason); } ); diff --git a/packages/firestore/test/integration/prime_backend.test.ts b/packages/firestore/test/integration/prime_backend.test.ts index 2beb69bcffc..5132536bd3b 100644 --- a/packages/firestore/test/integration/prime_backend.test.ts +++ b/packages/firestore/test/integration/prime_backend.test.ts @@ -27,7 +27,7 @@ const PRIMING_TIMEOUT_MS = 45000; before( 'Prime backend by waiting for a write to show up in the watch stream', - function(): Promise { + function (): Promise { this.timeout(PRIMING_TIMEOUT_MS); return withTestDoc(/*persistence=*/ false, async doc => { diff --git a/packages/firestore/test/unit/local/mutation_queue.test.ts b/packages/firestore/test/unit/local/mutation_queue.test.ts index 83c2610d507..067f517bfed 100644 --- a/packages/firestore/test/unit/local/mutation_queue.test.ts +++ b/packages/firestore/test/unit/local/mutation_queue.test.ts @@ -244,9 +244,7 @@ function genericMutationQueueTests(): void { } const expected = [batches[1], batches[2], batches[4]]; const matches = await mutationQueue.getAllMutationBatchesAffectingDocumentKeys( - documentKeySet() - .add(key('foo/bar')) - .add(key('foo/baz')) + documentKeySet().add(key('foo/bar')).add(key('foo/baz')) ); expectEqualArrays(matches, expected); }); diff --git a/packages/firestore/test/unit/local/remote_document_cache.test.ts b/packages/firestore/test/unit/local/remote_document_cache.test.ts index 000041bbdaf..4e5d240dec0 100644 --- a/packages/firestore/test/unit/local/remote_document_cache.test.ts +++ b/packages/firestore/test/unit/local/remote_document_cache.test.ts @@ -304,11 +304,7 @@ function genericRemoteDocumentCacheTests( return cache .addEntries(docs, version(VERSION)) .then(() => { - return cache.getEntries( - documentKeySet() - .add(key1) - .add(key2) - ); + return cache.getEntries(documentKeySet().add(key1).add(key2)); }) .then(read => { expectEqual(read.get(key1), docs[0]); @@ -328,10 +324,7 @@ function genericRemoteDocumentCacheTests( .addEntries(docs, version(VERSION)) .then(() => { return cache.getEntries( - documentKeySet() - .add(key1) - .add(key2) - .add(missingKey) + documentKeySet().add(key1).add(key2).add(missingKey) ); }) .then(read => { diff --git a/packages/firestore/test/unit/model/document_set.test.ts b/packages/firestore/test/unit/model/document_set.test.ts index 31fa4382708..781b0232d72 100644 --- a/packages/firestore/test/unit/model/document_set.test.ts +++ b/packages/firestore/test/unit/model/document_set.test.ts @@ -67,10 +67,7 @@ describe('DocumentSet', () => { it('updates documents', () => { const comp = DocComparator.byField('sort'); - let set = new DocumentSet(comp) - .add(d1) - .add(d2) - .add(d3); + let set = new DocumentSet(comp).add(d1).add(d2).add(d3); expect(set.size).to.equal(3); const d2prime = doc('docs/2', 2, { sort: 9 }); diff --git a/packages/firestore/test/unit/model/path.test.ts b/packages/firestore/test/unit/model/path.test.ts index 3843d586a26..a83ed4fbe12 100644 --- a/packages/firestore/test/unit/model/path.test.ts +++ b/packages/firestore/test/unit/model/path.test.ts @@ -68,12 +68,9 @@ describe('Path', () => { expect(path.popFirst().popFirst()).to.deep.equal( new ResourcePath(['messages']) ); - expect( - path - .popFirst() - .popFirst() - .popFirst() - ).to.deep.equal(new ResourcePath([])); + expect(path.popFirst().popFirst().popFirst()).to.deep.equal( + new ResourcePath([]) + ); expect(path.popFirst(0)).to.deep.equal( new ResourcePath(['rooms', 'Eros', 'messages']) ); @@ -91,12 +88,7 @@ describe('Path', () => { expect(path.lastSegment()).to.equal('messages'); expect(path.popLast().lastSegment()).to.equal('Eros'); - expect( - path - .popLast() - .popLast() - .lastSegment() - ).to.equal('rooms'); + expect(path.popLast().popLast().lastSegment()).to.equal('rooms'); }); it('can create child path', () => { @@ -113,12 +105,9 @@ describe('Path', () => { expect(path.popLast()).to.deep.equal(new ResourcePath(['rooms', 'Eros'])); expect(path.popLast().popLast()).to.deep.equal(new ResourcePath(['rooms'])); - expect( - path - .popLast() - .popLast() - .popLast() - ).to.deep.equal(new ResourcePath([])); + expect(path.popLast().popLast().popLast()).to.deep.equal( + new ResourcePath([]) + ); // original remains unmodified expect(path).to.deep.equal(new ResourcePath(['rooms', 'Eros', 'messages'])); diff --git a/packages/firestore/test/unit/util/sorted_map.test.ts b/packages/firestore/test/unit/util/sorted_map.test.ts index 6a3c653829c..3729e0be78a 100644 --- a/packages/firestore/test/unit/util/sorted_map.test.ts +++ b/packages/firestore/test/unit/util/sorted_map.test.ts @@ -193,18 +193,12 @@ describe('SortedMap Tests', () => { expect(map.size).to.equal(6); expect(map.root.checkMaxDepth()).to.equal(true); - const m2 = map - .insert(20, 20) - .insert(18, 18) - .insert(2, 2); + const m2 = map.insert(20, 20).insert(18, 18).insert(2, 2); expect(m2.size).to.equal(9); expect(m2.root.checkMaxDepth()).to.equal(true); - const m3 = m2 - .insert(71, 71) - .insert(42, 42) - .insert(88, 88); + const m3 = m2.insert(71, 71).insert(42, 42).insert(88, 88); expect(m3.size).to.equal(12); expect(m3.root.checkMaxDepth()).to.equal(true); diff --git a/packages/firestore/test/unit/util/sorted_set.test.ts b/packages/firestore/test/unit/util/sorted_set.test.ts index b16787ec553..45e9da35fdf 100644 --- a/packages/firestore/test/unit/util/sorted_set.test.ts +++ b/packages/firestore/test/unit/util/sorted_set.test.ts @@ -25,11 +25,7 @@ describe('SortedSet', () => { it('keeps elements in the right order', () => { let set = new SortedSet(primitiveComparator); - set = set - .add(4) - .add(2) - .add(-1) - .add(0); + set = set.add(4).add(2).add(-1).add(0); expectSetToEqual(set, [-1, 0, 2, 4]); @@ -128,12 +124,7 @@ describe('SortedSet', () => { it('can find element equal or greater to provided.', () => { const empty = new SortedSet(primitiveComparator); - const set = empty - .add(0) - .add(2) - .add(5) - .add(10) - .add(12); + const set = empty.add(0).add(2).add(5).add(10).add(12); expect(set.firstAfterOrEqual(2)).to.equal(2); expect(set.firstAfterOrEqual(3)).to.equal(5); @@ -143,20 +134,9 @@ describe('SortedSet', () => { it('can unionWith another set.', () => { const empty = new SortedSet(primitiveComparator); - const set = empty - .add(0) - .add(1) - .add(2); - const set2 = empty - .add(2) - .add(3) - .add(4); - const expected = empty - .add(0) - .add(1) - .add(2) - .add(3) - .add(4); + const set = empty.add(0).add(1).add(2); + const set2 = empty.add(2).add(3).add(4); + const expected = empty.add(0).add(1).add(2).add(3).add(4); expect(set.unionWith(set2)).to.deep.equal(expected); }); diff --git a/packages/firestore/test/util/equality_matcher.ts b/packages/firestore/test/util/equality_matcher.ts index 3ba85f64d9c..46fbe71715a 100644 --- a/packages/firestore/test/util/equality_matcher.ts +++ b/packages/firestore/test/util/equality_matcher.ts @@ -123,7 +123,7 @@ export function addEqualityMatcher( // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const assertEql = (_super: (r: unknown, l: unknown) => boolean) => { originalFunction = originalFunction || _super; - return function( + return function ( this: Chai.Assertion, expected?: unknown, msg?: unknown From 3c8b6f51f8c7a7e0eeec36b1ea1b3e189f0c9d30 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 31 Jul 2020 13:42:21 -0700 Subject: [PATCH 09/10] Undo yarn update --- yarn.lock | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/yarn.lock b/yarn.lock index 345efa22872..6a34c9950f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1068,6 +1068,35 @@ unique-filename "^1.1.1" which "^1.3.1" +"@firebase/component@0.1.16": + version "0.1.16" + resolved "https://registry.npmjs.org/@firebase/component/-/component-0.1.16.tgz#7a0dbdfff1485d45b8485db87a982f053e68761a" + integrity sha512-FvffvFN0LWgv1H/FIyruTECOL69Dhy+JfwoTq+mV39V8Mz9lNpo41etonL5AOr7KmXxYJVbNwkx0L9Ei88i7JA== + dependencies: + "@firebase/util" "0.2.50" + tslib "^1.11.1" + +"@firebase/firestore@1.16.1": + version "1.16.1" + resolved "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.16.1.tgz#ddf4e357b4d847abe6a57a89d3e2d5b950339176" + integrity sha512-TGtvNIGHMEFFEuOSsRswou576GPZY39vXIsenn0B1Dqz9ACpyDtvAT9YdbG38srlPq7ZKwsP5x04LB43zZ6eAg== + dependencies: + "@firebase/component" "0.1.16" + "@firebase/firestore-types" "1.12.0" + "@firebase/logger" "0.2.6" + "@firebase/util" "0.2.50" + "@firebase/webchannel-wrapper" "0.2.41" + "@grpc/grpc-js" "^1.0.0" + "@grpc/proto-loader" "^0.5.0" + tslib "^1.11.1" + +"@firebase/util@0.2.50": + version "0.2.50" + resolved "https://registry.npmjs.org/@firebase/util/-/util-0.2.50.tgz#77666b845dcb49bc217650aa296a7a8986c06b44" + integrity sha512-vFE6+Jfc25u0ViSpFxxq0q5s+XmuJ/y7CL3ud79RQe+WLFFg+j0eH1t23k0yNSG9vZNM7h3uHRIXbV97sYLAyw== + dependencies: + tslib "^1.11.1" + "@google-cloud/paginator@^2.0.0": version "2.0.3" resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz#c7987ad05d1c3ebcef554381be80e9e8da4e4882" From c38f0fa32e7c54d692cc14f62d1785be7a0f53ad Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Mon, 3 Aug 2020 16:38:02 -0700 Subject: [PATCH 10/10] Review --- config/webpack.test.js | 3 +- packages/firestore/exp/test/bootstrap.ts | 2 - packages/firestore/karma.conf.js | 1 + packages/firestore/lite/test/bootstrap.ts | 2 - .../src/platform/browser_lite/connection.ts | 2 +- .../src/platform/node/grpc_connection.ts | 29 ++++++----- packages/firestore/src/remote/connection.ts | 4 ++ packages/firestore/src/remote/datastore.ts | 41 +++++++-------- .../firestore/src/remote/rest_connection.ts | 29 +++-------- .../test/integration/api/database.test.ts | 2 +- .../firestore/test/integration/bootstrap.ts | 2 - packages/firestore/test/unit/bootstrap.ts | 2 - .../test/unit/remote/rest_connection.test.ts | 50 +++++++------------ 13 files changed, 70 insertions(+), 99 deletions(-) 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/exp/test/bootstrap.ts b/packages/firestore/exp/test/bootstrap.ts index 29bdbb4870d..ba954f7c192 100644 --- a/packages/firestore/exp/test/bootstrap.ts +++ b/packages/firestore/exp/test/bootstrap.ts @@ -24,8 +24,6 @@ import '../register'; * https://github.com/webpack-contrib/karma-webpack#alternative-usage */ -process.env.TEST_PLATFORM = 'browser'; - // 'context()' definition requires additional dependency on webpack-env package. // eslint-disable-next-line @typescript-eslint/no-explicit-any const testsContext = (require as any).context( 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/lite/test/bootstrap.ts b/packages/firestore/lite/test/bootstrap.ts index effa47dbdc2..69812150818 100644 --- a/packages/firestore/lite/test/bootstrap.ts +++ b/packages/firestore/lite/test/bootstrap.ts @@ -24,8 +24,6 @@ import '../register'; * https://github.com/webpack-contrib/karma-webpack#alternative-usage */ -process.env.TEST_PLATFORM = 'browser-lite'; - // 'context()' definition requires additional dependency on webpack-env package. // eslint-disable-next-line @typescript-eslint/no-explicit-any const testsContext = (require as any).context('.', true, /^.*\.test.*$/); diff --git a/packages/firestore/src/platform/browser_lite/connection.ts b/packages/firestore/src/platform/browser_lite/connection.ts index 070bb5c53d3..1aca0a8ee9b 100644 --- a/packages/firestore/src/platform/browser_lite/connection.ts +++ b/packages/firestore/src/platform/browser_lite/connection.ts @@ -23,5 +23,5 @@ 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)); + return Promise.resolve(new FetchConnection(databaseInfo, fetch.bind(null))); } 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/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 index 8cb06af0f95..e39852707cc 100644 --- a/packages/firestore/src/remote/rest_connection.ts +++ b/packages/firestore/src/remote/rest_connection.ts @@ -23,7 +23,6 @@ import { logDebug, logWarn } from '../util/log'; import { FirestoreError } from '../util/error'; import { StringMap } from '../util/types'; import { debugAssert } from '../util/assert'; -import { Indexable } from '../util/misc'; const LOG_TAG = 'RestConnection'; @@ -64,29 +63,17 @@ export abstract class RestConnection implements Connection { invokeRPC( rpcName: string, + path: string, req: Req, token: Token | null ): Promise { - const url = this.makeUrl(rpcName, req); - - // The database and/or parent 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 = ({ ...req } as unknown) as Indexable; - delete jsonObj.parent; - delete jsonObj.database; - - logDebug(LOG_TAG, 'Sending: ', url, jsonObj); + const url = this.makeUrl(rpcName, path); + logDebug(LOG_TAG, 'Sending: ', url, req); const headers = {}; this.modifyHeadersForRequest(headers, token); - return this.performRPCRequest( - rpcName, - url, - headers, - jsonObj - ).then( + return this.performRPCRequest(rpcName, url, headers, req).then( response => { logDebug(LOG_TAG, 'Received: ', response); return response; @@ -95,7 +82,7 @@ export abstract class RestConnection implements Connection { logWarn( LOG_TAG, `${rpcName} failed with error: `, - err.message, + err, 'url: ', url, 'request:', @@ -108,12 +95,13 @@ export abstract class RestConnection implements Connection { 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, request, token); + return this.invokeRPC(rpcName, path, request, token); } abstract openStream( @@ -156,13 +144,12 @@ export abstract class RestConnection implements Connection { body: Req ): Promise; - private makeUrl(rpcName: string, req: Req): string { + private makeUrl(rpcName: string, path: string): string { const urlRpcName = RPC_NAME_URL_MAPPING[rpcName]; debugAssert( urlRpcName !== undefined, 'Unknown REST mapping for: ' + rpcName ); - const path = ((req as unknown) as Indexable).parent || this.databaseRoot; return `${this.baseUrl}/${RPC_URL_VERSION}/${path}:${urlRpcName}`; } } 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/bootstrap.ts b/packages/firestore/test/integration/bootstrap.ts index 131bf37dc36..a38829ab6b9 100644 --- a/packages/firestore/test/integration/bootstrap.ts +++ b/packages/firestore/test/integration/bootstrap.ts @@ -24,8 +24,6 @@ import '../../index'; * https://github.com/webpack-contrib/karma-webpack#alternative-usage */ -process.env.TEST_PLATFORM = 'browser'; - // 'context()' definition requires additional dependency on webpack-env package. // eslint-disable-next-line @typescript-eslint/no-explicit-any const testsContext = (require as any).context( diff --git a/packages/firestore/test/unit/bootstrap.ts b/packages/firestore/test/unit/bootstrap.ts index 2a195fcf0fd..9aff9cf328f 100644 --- a/packages/firestore/test/unit/bootstrap.ts +++ b/packages/firestore/test/unit/bootstrap.ts @@ -22,8 +22,6 @@ * https://github.com/webpack-contrib/karma-webpack#alternative-usage */ -process.env.TEST_PLATFORM = 'browser'; - // 'context()' definition requires additional dependency on webpack-env package. // eslint-disable-next-line @typescript-eslint/no-explicit-any const testsContext = (require as any).context( diff --git a/packages/firestore/test/unit/remote/rest_connection.test.ts b/packages/firestore/test/unit/remote/rest_connection.test.ts index eda6119ea36..a0153a1b0a5 100644 --- a/packages/firestore/test/unit/remote/rest_connection.test.ts +++ b/packages/firestore/test/unit/remote/rest_connection.test.ts @@ -64,46 +64,22 @@ describe('RestConnection', () => { ); const connection = new TestRestConnection(testDatabaseInfo); - it('url uses project ID and database ID', async () => { - await connection.invokeRPC('Commit', '{}', null); - expect(connection.lastUrl).to.equal( - 'http://example.com/v1/projects/testproject/' + - 'databases/(default)/documents:commit' - ); - }); - - it('url uses parent when provided', async () => { + it('url uses from path', async () => { await connection.invokeRPC( - 'RunQuery', - { - parent: 'projects/testproject/databases/(default)/documents/coll' - }, + 'Commit', + 'projects/testproject/databases/(default)/documents', + {}, null ); expect(connection.lastUrl).to.equal( - 'http://example.com/v1/projects/testproject/' + - 'databases/(default)/documents/coll:runQuery' + 'http://example.com/v1/projects/testproject/databases/(default)/documents:commit' ); }); - it('drops parent and database from request', async () => { - await connection.invokeRPC( - 'RunQuery', - { - foo: 'bar', - database: 'projects/testproject/databases/(default)/documents', - parent: 'projects/testproject/databases/(default)/documents/coll' - }, - null - ); - expect(connection.lastRequestBody).to.deep.equal({ - foo: 'bar' - }); - }); - it('merges headers', async () => { await connection.invokeRPC( 'RunQuery', + 'projects/testproject/databases/(default)/documents/foo', {}, { user: User.UNAUTHENTICATED, @@ -120,7 +96,12 @@ describe('RestConnection', () => { it('returns success', async () => { connection.nextResponse = Promise.resolve({ response: true }); - const response = await connection.invokeRPC('RunQuery', {}, null); + const response = await connection.invokeRPC( + 'RunQuery', + 'projects/testproject/databases/(default)/documents/coll', + {}, + null + ); expect(response).to.deep.equal({ response: true }); }); @@ -128,7 +109,12 @@ describe('RestConnection', () => { const error = new FirestoreError(Code.UNKNOWN, 'Test exception'); connection.nextResponse = Promise.reject(error); return expect( - connection.invokeRPC('RunQuery', {}, null) + connection.invokeRPC( + 'RunQuery', + 'projects/testproject/databases/(default)/documents/coll', + {}, + null + ) ).to.be.eventually.rejectedWith(error); }); });