@firebase/firestore
Version:
The Cloud Firestore component of the Firebase JS SDK.
1,388 lines (1,368 loc) • 1.29 MB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var app = require('@firebase/app');
var component = require('@firebase/component');
var logger = require('@firebase/logger');
var util$1 = require('util');
var util = require('@firebase/util');
var crypto = require('crypto');
var bloomBlob = require('@firebase/webchannel-wrapper/bloom-blob');
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
const name = "@firebase/firestore";
const version$1 = "4.7.17";
/**
* @license
* Copyright 2017 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.
*/
/**
* Simple wrapper around a nullable UID. Mostly exists to make code more
* readable.
*/
class User {
constructor(uid) {
this.uid = uid;
}
isAuthenticated() {
return this.uid != null;
}
/**
* Returns a key representing this user, suitable for inclusion in a
* dictionary.
*/
toKey() {
if (this.isAuthenticated()) {
return 'uid:' + this.uid;
}
else {
return 'anonymous-user';
}
}
isEqual(otherUser) {
return otherUser.uid === this.uid;
}
}
/** A user with a null UID. */
User.UNAUTHENTICATED = new User(null);
// TODO(mikelehen): Look into getting a proper uid-equivalent for
// non-FirebaseAuth providers.
User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
User.FIRST_PARTY = new User('first-party-uid');
User.MOCK_USER = new User('mock-user');
const version = "11.9.0";
/**
* @license
* Copyright 2017 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.
*/
let SDK_VERSION = version;
function setSDKVersion(version) {
SDK_VERSION = version;
}
/**
* @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.
*/
/** Formats an object as a JSON string, suitable for logging. */
function formatJSON(value) {
// util.inspect() results in much more readable output than JSON.stringify()
return util$1.inspect(value, { depth: 100 });
}
/**
* @license
* Copyright 2017 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.
*/
const logClient = new logger.Logger('@firebase/firestore');
// Helper methods are needed because variables can't be exported as read/write
function getLogLevel() {
return logClient.logLevel;
}
/**
* Sets the verbosity of Cloud Firestore logs (debug, error, or silent).
*
* @param logLevel - The verbosity you set for activity and error logging. Can
* be any of the following values:
*
* <ul>
* <li>`debug` for the most verbose logging level, primarily for
* debugging.</li>
* <li>`error` to log errors only.</li>
* <li><code>`silent` to turn off logging.</li>
* </ul>
*/
function setLogLevel(logLevel) {
logClient.setLogLevel(logLevel);
}
function logDebug(msg, ...obj) {
if (logClient.logLevel <= logger.LogLevel.DEBUG) {
const args = obj.map(argToString);
logClient.debug(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
}
}
function logError(msg, ...obj) {
if (logClient.logLevel <= logger.LogLevel.ERROR) {
const args = obj.map(argToString);
logClient.error(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
}
}
/**
* @internal
*/
function logWarn(msg, ...obj) {
if (logClient.logLevel <= logger.LogLevel.WARN) {
const args = obj.map(argToString);
logClient.warn(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
}
}
/**
* Converts an additional log parameter to a string representation.
*/
function argToString(obj) {
if (typeof obj === 'string') {
return obj;
}
else {
try {
return formatJSON(obj);
}
catch (e) {
// Converting to JSON failed, just log the object directly
return obj;
}
}
}
/**
* @license
* Copyright 2017 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.
*/
function fail(id, messageOrContext, context) {
let message = 'Unexpected state';
if (typeof messageOrContext === 'string') {
message = messageOrContext;
}
else {
context = messageOrContext;
}
_fail(id, message, context);
}
function _fail(id, failure, context) {
// Log the failure in addition to throw an exception, just in case the
// exception is swallowed.
let message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ${failure} (ID: ${id.toString(16)})`;
if (context !== undefined) {
try {
const stringContext = JSON.stringify(context);
message += ' CONTEXT: ' + stringContext;
}
catch (e) {
message += ' CONTEXT: ' + context;
}
}
logError(message);
// NOTE: We don't use FirestoreError here because these are internal failures
// that cannot be handled by the user. (Also it would create a circular
// dependency between the error and assert modules which doesn't work.)
throw new Error(message);
}
function hardAssert(assertion, id, messageOrContext, context) {
let message = 'Unexpected state';
if (typeof messageOrContext === 'string') {
message = messageOrContext;
}
else {
context = messageOrContext;
}
if (!assertion) {
_fail(id, message, context);
}
}
/**
* Fails if the given assertion condition is false, throwing an Error with the
* given message if it did.
*
* The code of callsites invoking this function are stripped out in production
* builds. Any side-effects of code within the debugAssert() invocation will not
* happen in this case.
*
* @internal
*/
function debugAssert(assertion, message) {
if (!assertion) {
fail(0xdeb6, message);
}
}
/**
* Casts `obj` to `T`. In non-production builds, verifies that `obj` is an
* instance of `T` before casting.
*/
function debugCast(obj,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor) {
return obj;
}
/**
* @license
* Copyright 2017 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.
*/
const Code = {
// Causes are copied from:
// https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h
/** Not an error; returned on success. */
OK: 'ok',
/** The operation was cancelled (typically by the caller). */
CANCELLED: 'cancelled',
/** Unknown error or an error from a different error domain. */
UNKNOWN: 'unknown',
/**
* Client specified an invalid argument. Note that this differs from
* FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
* problematic regardless of the state of the system (e.g., a malformed file
* name).
*/
INVALID_ARGUMENT: 'invalid-argument',
/**
* Deadline expired before operation could complete. For operations that
* change the state of the system, this error may be returned even if the
* operation has completed successfully. For example, a successful response
* from a server could have been delayed long enough for the deadline to
* expire.
*/
DEADLINE_EXCEEDED: 'deadline-exceeded',
/** Some requested entity (e.g., file or directory) was not found. */
NOT_FOUND: 'not-found',
/**
* Some entity that we attempted to create (e.g., file or directory) already
* exists.
*/
ALREADY_EXISTS: 'already-exists',
/**
* The caller does not have permission to execute the specified operation.
* PERMISSION_DENIED must not be used for rejections caused by exhausting
* some resource (use RESOURCE_EXHAUSTED instead for those errors).
* PERMISSION_DENIED must not be used if the caller cannot be identified
* (use UNAUTHENTICATED instead for those errors).
*/
PERMISSION_DENIED: 'permission-denied',
/**
* The request does not have valid authentication credentials for the
* operation.
*/
UNAUTHENTICATED: 'unauthenticated',
/**
* Some resource has been exhausted, perhaps a per-user quota, or perhaps the
* entire file system is out of space.
*/
RESOURCE_EXHAUSTED: 'resource-exhausted',
/**
* Operation was rejected because the system is not in a state required for
* the operation's execution. For example, directory to be deleted may be
* non-empty, an rmdir operation is applied to a non-directory, etc.
*
* A litmus test that may help a service implementor in deciding
* between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
* (a) Use UNAVAILABLE if the client can retry just the failing call.
* (b) Use ABORTED if the client should retry at a higher-level
* (e.g., restarting a read-modify-write sequence).
* (c) Use FAILED_PRECONDITION if the client should not retry until
* the system state has been explicitly fixed. E.g., if an "rmdir"
* fails because the directory is non-empty, FAILED_PRECONDITION
* should be returned since the client should not retry unless
* they have first fixed up the directory by deleting files from it.
* (d) Use FAILED_PRECONDITION if the client performs conditional
* REST Get/Update/Delete on a resource and the resource on the
* server does not match the condition. E.g., conflicting
* read-modify-write on the same resource.
*/
FAILED_PRECONDITION: 'failed-precondition',
/**
* The operation was aborted, typically due to a concurrency issue like
* sequencer check failures, transaction aborts, etc.
*
* See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
* and UNAVAILABLE.
*/
ABORTED: 'aborted',
/**
* Operation was attempted past the valid range. E.g., seeking or reading
* past end of file.
*
* Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed
* if the system state changes. For example, a 32-bit file system will
* generate INVALID_ARGUMENT if asked to read at an offset that is not in the
* range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from
* an offset past the current file size.
*
* There is a fair bit of overlap between FAILED_PRECONDITION and
* OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
* when it applies so that callers who are iterating through a space can
* easily look for an OUT_OF_RANGE error to detect when they are done.
*/
OUT_OF_RANGE: 'out-of-range',
/** Operation is not implemented or not supported/enabled in this service. */
UNIMPLEMENTED: 'unimplemented',
/**
* Internal errors. Means some invariants expected by underlying System has
* been broken. If you see one of these errors, Something is very broken.
*/
INTERNAL: 'internal',
/**
* The service is currently unavailable. This is a most likely a transient
* condition and may be corrected by retrying with a backoff.
*
* See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
* and UNAVAILABLE.
*/
UNAVAILABLE: 'unavailable',
/** Unrecoverable data loss or corruption. */
DATA_LOSS: 'data-loss'
};
/** An error returned by a Firestore operation. */
class FirestoreError extends util.FirebaseError {
/** @hideconstructor */
constructor(
/**
* The backend error code associated with this error.
*/
code,
/**
* A custom error description.
*/
message) {
super(code, message);
this.code = code;
this.message = message;
// HACK: We write a toString property directly because Error is not a real
// class and so inheritance does not work correctly. We could alternatively
// do the same "back-door inheritance" trick that FirebaseError does.
this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`;
}
}
/**
* @license
* Copyright 2017 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.
*/
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
/**
* @license
* Copyright 2017 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.
*/
class OAuthToken {
constructor(value, user) {
this.user = user;
this.type = 'OAuth';
this.headers = new Map();
this.headers.set('Authorization', `Bearer ${value}`);
}
}
/**
* A CredentialsProvider that always yields an empty token.
* @internal
*/
class EmptyAuthCredentialsProvider {
getToken() {
return Promise.resolve(null);
}
invalidateToken() { }
start(asyncQueue, changeListener) {
// Fire with initial user.
asyncQueue.enqueueRetryable(() => changeListener(User.UNAUTHENTICATED));
}
shutdown() { }
}
/**
* A CredentialsProvider that always returns a constant token. Used for
* emulator token mocking.
*/
class EmulatorAuthCredentialsProvider {
constructor(token) {
this.token = token;
/**
* Stores the listener registered with setChangeListener()
* This isn't actually necessary since the UID never changes, but we use this
* to verify the listen contract is adhered to in tests.
*/
this.changeListener = null;
}
getToken() {
return Promise.resolve(this.token);
}
invalidateToken() { }
start(asyncQueue, changeListener) {
this.changeListener = changeListener;
// Fire with initial user.
asyncQueue.enqueueRetryable(() => changeListener(this.token.user));
}
shutdown() {
this.changeListener = null;
}
}
class FirebaseAuthCredentialsProvider {
constructor(authProvider) {
this.authProvider = authProvider;
/** Tracks the current User. */
this.currentUser = User.UNAUTHENTICATED;
/**
* Counter used to detect if the token changed while a getToken request was
* outstanding.
*/
this.tokenCounter = 0;
this.forceRefresh = false;
this.auth = null;
}
start(asyncQueue, changeListener) {
hardAssert(this.tokenListener === undefined, 0xa540);
let lastTokenId = this.tokenCounter;
// A change listener that prevents double-firing for the same token change.
const guardedChangeListener = user => {
if (this.tokenCounter !== lastTokenId) {
lastTokenId = this.tokenCounter;
return changeListener(user);
}
else {
return Promise.resolve();
}
};
// A promise that can be waited on to block on the next token change.
// This promise is re-created after each change.
let nextToken = new Deferred();
this.tokenListener = () => {
this.tokenCounter++;
this.currentUser = this.getUser();
nextToken.resolve();
nextToken = new Deferred();
asyncQueue.enqueueRetryable(() => guardedChangeListener(this.currentUser));
};
const awaitNextToken = () => {
const currentTokenAttempt = nextToken;
asyncQueue.enqueueRetryable(async () => {
await currentTokenAttempt.promise;
await guardedChangeListener(this.currentUser);
});
};
const registerAuth = (auth) => {
logDebug('FirebaseAuthCredentialsProvider', 'Auth detected');
this.auth = auth;
if (this.tokenListener) {
this.auth.addAuthTokenListener(this.tokenListener);
awaitNextToken();
}
};
this.authProvider.onInit(auth => registerAuth(auth));
// Our users can initialize Auth right after Firestore, so we give it
// a chance to register itself with the component framework before we
// determine whether to start up in unauthenticated mode.
setTimeout(() => {
if (!this.auth) {
const auth = this.authProvider.getImmediate({ optional: true });
if (auth) {
registerAuth(auth);
}
else {
// If auth is still not available, proceed with `null` user
logDebug('FirebaseAuthCredentialsProvider', 'Auth not yet detected');
nextToken.resolve();
nextToken = new Deferred();
}
}
}, 0);
awaitNextToken();
}
getToken() {
// Take note of the current value of the tokenCounter so that this method
// can fail (with an ABORTED error) if there is a token change while the
// request is outstanding.
const initialTokenCounter = this.tokenCounter;
const forceRefresh = this.forceRefresh;
this.forceRefresh = false;
if (!this.auth) {
return Promise.resolve(null);
}
return this.auth.getToken(forceRefresh).then(tokenData => {
// Cancel the request since the token changed while the request was
// outstanding so the response is potentially for a previous user (which
// user, we can't be sure).
if (this.tokenCounter !== initialTokenCounter) {
logDebug('FirebaseAuthCredentialsProvider', 'getToken aborted due to token change.');
return this.getToken();
}
else {
if (tokenData) {
hardAssert(typeof tokenData.accessToken === 'string', 0x7c5d, { tokenData });
return new OAuthToken(tokenData.accessToken, this.currentUser);
}
else {
return null;
}
}
});
}
invalidateToken() {
this.forceRefresh = true;
}
shutdown() {
if (this.auth && this.tokenListener) {
this.auth.removeAuthTokenListener(this.tokenListener);
}
this.tokenListener = undefined;
}
// Auth.getUid() can return null even with a user logged in. It is because
// getUid() is synchronous, but the auth code populating Uid is asynchronous.
// This method should only be called in the AuthTokenListener callback
// to guarantee to get the actual user.
getUser() {
const currentUid = this.auth && this.auth.getUid();
hardAssert(currentUid === null || typeof currentUid === 'string', 0x0807, { currentUid });
return new User(currentUid);
}
}
/*
* FirstPartyToken provides a fresh token each time its value
* is requested, because if the token is too old, requests will be rejected.
* Technically this may no longer be necessary since the SDK should gracefully
* recover from unauthenticated errors (see b/33147818 for context), but it's
* safer to keep the implementation as-is.
*/
class FirstPartyToken {
constructor(sessionIndex, iamToken, authTokenFactory) {
this.sessionIndex = sessionIndex;
this.iamToken = iamToken;
this.authTokenFactory = authTokenFactory;
this.type = 'FirstParty';
this.user = User.FIRST_PARTY;
this._headers = new Map();
}
/**
* Gets an authorization token, using a provided factory function, or return
* null.
*/
getAuthToken() {
if (this.authTokenFactory) {
return this.authTokenFactory();
}
else {
return null;
}
}
get headers() {
this._headers.set('X-Goog-AuthUser', this.sessionIndex);
// Use array notation to prevent minification
const authHeaderTokenValue = this.getAuthToken();
if (authHeaderTokenValue) {
this._headers.set('Authorization', authHeaderTokenValue);
}
if (this.iamToken) {
this._headers.set('X-Goog-Iam-Authorization-Token', this.iamToken);
}
return this._headers;
}
}
/*
* Provides user credentials required for the Firestore JavaScript SDK
* to authenticate the user, using technique that is only available
* to applications hosted by Google.
*/
class FirstPartyAuthCredentialsProvider {
constructor(sessionIndex, iamToken, authTokenFactory) {
this.sessionIndex = sessionIndex;
this.iamToken = iamToken;
this.authTokenFactory = authTokenFactory;
}
getToken() {
return Promise.resolve(new FirstPartyToken(this.sessionIndex, this.iamToken, this.authTokenFactory));
}
start(asyncQueue, changeListener) {
// Fire with initial uid.
asyncQueue.enqueueRetryable(() => changeListener(User.FIRST_PARTY));
}
shutdown() { }
invalidateToken() { }
}
class AppCheckToken {
constructor(value) {
this.value = value;
this.type = 'AppCheck';
this.headers = new Map();
if (value && value.length > 0) {
this.headers.set('x-firebase-appcheck', this.value);
}
}
}
class FirebaseAppCheckTokenProvider {
constructor(app$1, appCheckProvider) {
this.appCheckProvider = appCheckProvider;
this.forceRefresh = false;
this.appCheck = null;
this.latestAppCheckToken = null;
this.serverAppAppCheckToken = null;
if (app._isFirebaseServerApp(app$1) && app$1.settings.appCheckToken) {
this.serverAppAppCheckToken = app$1.settings.appCheckToken;
}
}
start(asyncQueue, changeListener) {
hardAssert(this.tokenListener === undefined, 0x0db8);
const onTokenChanged = tokenResult => {
if (tokenResult.error != null) {
logDebug('FirebaseAppCheckTokenProvider', `Error getting App Check token; using placeholder token instead. Error: ${tokenResult.error.message}`);
}
const tokenUpdated = tokenResult.token !== this.latestAppCheckToken;
this.latestAppCheckToken = tokenResult.token;
logDebug('FirebaseAppCheckTokenProvider', `Received ${tokenUpdated ? 'new' : 'existing'} token.`);
return tokenUpdated
? changeListener(tokenResult.token)
: Promise.resolve();
};
this.tokenListener = (tokenResult) => {
asyncQueue.enqueueRetryable(() => onTokenChanged(tokenResult));
};
const registerAppCheck = (appCheck) => {
logDebug('FirebaseAppCheckTokenProvider', 'AppCheck detected');
this.appCheck = appCheck;
if (this.tokenListener) {
this.appCheck.addTokenListener(this.tokenListener);
}
};
this.appCheckProvider.onInit(appCheck => registerAppCheck(appCheck));
// Our users can initialize AppCheck after Firestore, so we give it
// a chance to register itself with the component framework.
setTimeout(() => {
if (!this.appCheck) {
const appCheck = this.appCheckProvider.getImmediate({ optional: true });
if (appCheck) {
registerAppCheck(appCheck);
}
else {
// If AppCheck is still not available, proceed without it.
logDebug('FirebaseAppCheckTokenProvider', 'AppCheck not yet detected');
}
}
}, 0);
}
getToken() {
if (this.serverAppAppCheckToken) {
return Promise.resolve(new AppCheckToken(this.serverAppAppCheckToken));
}
const forceRefresh = this.forceRefresh;
this.forceRefresh = false;
if (!this.appCheck) {
return Promise.resolve(null);
}
return this.appCheck.getToken(forceRefresh).then(tokenResult => {
if (tokenResult) {
hardAssert(typeof tokenResult.token === 'string', 0xae0e, { tokenResult });
this.latestAppCheckToken = tokenResult.token;
return new AppCheckToken(tokenResult.token);
}
else {
return null;
}
});
}
invalidateToken() {
this.forceRefresh = true;
}
shutdown() {
if (this.appCheck && this.tokenListener) {
this.appCheck.removeTokenListener(this.tokenListener);
}
this.tokenListener = undefined;
}
}
/**
* An AppCheck token provider that always yields an empty token.
* @internal
*/
class EmptyAppCheckTokenProvider {
getToken() {
return Promise.resolve(new AppCheckToken(''));
}
invalidateToken() { }
start(asyncQueue, changeListener) { }
shutdown() { }
}
/**
* Builds a CredentialsProvider depending on the type of
* the credentials passed in.
*/
function makeAuthCredentialsProvider(credentials) {
if (!credentials) {
return new EmptyAuthCredentialsProvider();
}
switch (credentials['type']) {
case 'firstParty':
return new FirstPartyAuthCredentialsProvider(credentials['sessionIndex'] || '0', credentials['iamToken'] || null, credentials['authTokenFactory'] || null);
case 'provider':
return credentials['client'];
default:
throw new FirestoreError(Code.INVALID_ARGUMENT, 'makeAuthCredentialsProvider failed due to invalid credential type');
}
}
/**
* @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.
*/
/**
* Generates `nBytes` of random bytes.
*
* If `nBytes < 0` , an error will be thrown.
*/
function randomBytes(nBytes) {
return crypto.randomBytes(nBytes);
}
/**
* @license
* Copyright 2023 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.
*/
/**
* An instance of the Platform's 'TextEncoder' implementation.
*/
function newTextEncoder() {
return new util$1.TextEncoder();
}
/**
* An instance of the Platform's 'TextDecoder' implementation.
*/
function newTextDecoder() {
return new util$1.TextDecoder('utf-8');
}
/**
* @license
* Copyright 2017 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.
*/
/**
* A utility class for generating unique alphanumeric IDs of a specified length.
*
* @internal
* Exported internally for testing purposes.
*/
class AutoId {
static newId() {
// Alphanumeric characters
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
// The largest byte value that is a multiple of `char.length`.
const maxMultiple = Math.floor(256 / chars.length) * chars.length;
let autoId = '';
const targetLength = 20;
while (autoId.length < targetLength) {
const bytes = randomBytes(40);
for (let i = 0; i < bytes.length; ++i) {
// Only accept values that are [0, maxMultiple), this ensures they can
// be evenly mapped to indices of `chars` via a modulo operation.
if (autoId.length < targetLength && bytes[i] < maxMultiple) {
autoId += chars.charAt(bytes[i] % chars.length);
}
}
}
return autoId;
}
}
function primitiveComparator(left, right) {
if (left < right) {
return -1;
}
if (left > right) {
return 1;
}
return 0;
}
/** Compare strings in UTF-8 encoded byte order */
function compareUtf8Strings(left, right) {
let i = 0;
while (i < left.length && i < right.length) {
const leftCodePoint = left.codePointAt(i);
const rightCodePoint = right.codePointAt(i);
if (leftCodePoint !== rightCodePoint) {
if (leftCodePoint < 128 && rightCodePoint < 128) {
// ASCII comparison
return primitiveComparator(leftCodePoint, rightCodePoint);
}
else {
// Lazy instantiate TextEncoder
const encoder = newTextEncoder();
// UTF-8 encode the character at index i for byte comparison.
const leftBytes = encoder.encode(getUtf8SafeSubstring(left, i));
const rightBytes = encoder.encode(getUtf8SafeSubstring(right, i));
const comp = compareByteArrays$1(leftBytes, rightBytes);
if (comp !== 0) {
return comp;
}
else {
// EXTREMELY RARE CASE: Code points differ, but their UTF-8 byte
// representations are identical. This can happen with malformed input
// (invalid surrogate pairs). The backend also actively prevents invalid
// surrogates as INVALID_ARGUMENT errors, so we almost never receive
// invalid strings from backend.
// Fallback to code point comparison for graceful handling.
return primitiveComparator(leftCodePoint, rightCodePoint);
}
}
}
// Increment by 2 for surrogate pairs, 1 otherwise
i += leftCodePoint > 0xffff ? 2 : 1;
}
// Compare lengths if all characters are equal
return primitiveComparator(left.length, right.length);
}
function getUtf8SafeSubstring(str, index) {
const firstCodePoint = str.codePointAt(index);
if (firstCodePoint > 0xffff) {
// It's a surrogate pair, return the whole pair
return str.substring(index, index + 2);
}
else {
// It's a single code point, return it
return str.substring(index, index + 1);
}
}
function compareByteArrays$1(left, right) {
for (let i = 0; i < left.length && i < right.length; ++i) {
if (left[i] !== right[i]) {
return primitiveComparator(left[i], right[i]);
}
}
return primitiveComparator(left.length, right.length);
}
/** Helper to compare arrays using isEqual(). */
function arrayEquals(left, right, comparator) {
if (left.length !== right.length) {
return false;
}
return left.every((value, index) => comparator(value, right[index]));
}
/**
* Returns the immediate lexicographically-following string. This is useful to
* construct an inclusive range for indexeddb iterators.
*/
function immediateSuccessor(s) {
// Return the input string, with an additional NUL byte appended.
return s + '\0';
}
/**
* @license
* Copyright 2017 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.
*/
// The earliest date supported by Firestore timestamps (0001-01-01T00:00:00Z).
const MIN_SECONDS = -62135596800;
// Number of nanoseconds in a millisecond.
const MS_TO_NANOS = 1e6;
/**
* A `Timestamp` represents a point in time independent of any time zone or
* calendar, represented as seconds and fractions of seconds at nanosecond
* resolution in UTC Epoch time.
*
* It is encoded using the Proleptic Gregorian Calendar which extends the
* Gregorian calendar backwards to year one. It is encoded assuming all minutes
* are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
* table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
* 9999-12-31T23:59:59.999999999Z.
*
* For examples and further specifications, refer to the
* {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}.
*/
class Timestamp {
/**
* Creates a new timestamp with the current date, with millisecond precision.
*
* @returns a new timestamp representing the current date.
*/
static now() {
return Timestamp.fromMillis(Date.now());
}
/**
* Creates a new timestamp from the given date.
*
* @param date - The date to initialize the `Timestamp` from.
* @returns A new `Timestamp` representing the same point in time as the given
* date.
*/
static fromDate(date) {
return Timestamp.fromMillis(date.getTime());
}
/**
* Creates a new timestamp from the given number of milliseconds.
*
* @param milliseconds - Number of milliseconds since Unix epoch
* 1970-01-01T00:00:00Z.
* @returns A new `Timestamp` representing the same point in time as the given
* number of milliseconds.
*/
static fromMillis(milliseconds) {
const seconds = Math.floor(milliseconds / 1000);
const nanos = Math.floor((milliseconds - seconds * 1000) * MS_TO_NANOS);
return new Timestamp(seconds, nanos);
}
/**
* Creates a new timestamp.
*
* @param seconds - The number of seconds of UTC time since Unix epoch
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
* 9999-12-31T23:59:59Z inclusive.
* @param nanoseconds - The non-negative fractions of a second at nanosecond
* resolution. Negative second values with fractions must still have
* non-negative nanoseconds values that count forward in time. Must be
* from 0 to 999,999,999 inclusive.
*/
constructor(
/**
* The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
*/
seconds,
/**
* The fractions of a second at nanosecond resolution.*
*/
nanoseconds) {
this.seconds = seconds;
this.nanoseconds = nanoseconds;
if (nanoseconds < 0) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
}
if (nanoseconds >= 1e9) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
}
if (seconds < MIN_SECONDS) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
}
// This will break in the year 10,000.
if (seconds >= 253402300800) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
}
}
/**
* Converts a `Timestamp` to a JavaScript `Date` object. This conversion
* causes a loss of precision since `Date` objects only support millisecond
* precision.
*
* @returns JavaScript `Date` object representing the same point in time as
* this `Timestamp`, with millisecond precision.
*/
toDate() {
return new Date(this.toMillis());
}
/**
* Converts a `Timestamp` to a numeric timestamp (in milliseconds since
* epoch). This operation causes a loss of precision.
*
* @returns The point in time corresponding to this timestamp, represented as
* the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
*/
toMillis() {
return this.seconds * 1000 + this.nanoseconds / MS_TO_NANOS;
}
_compareTo(other) {
if (this.seconds === other.seconds) {
return primitiveComparator(this.nanoseconds, other.nanoseconds);
}
return primitiveComparator(this.seconds, other.seconds);
}
/**
* Returns true if this `Timestamp` is equal to the provided one.
*
* @param other - The `Timestamp` to compare against.
* @returns true if this `Timestamp` is equal to the provided one.
*/
isEqual(other) {
return (other.seconds === this.seconds && other.nanoseconds === this.nanoseconds);
}
/** Returns a textual representation of this `Timestamp`. */
toString() {
return ('Timestamp(seconds=' +
this.seconds +
', nanoseconds=' +
this.nanoseconds +
')');
}
/** Returns a JSON-serializable representation of this `Timestamp`. */
toJSON() {
return { seconds: this.seconds, nanoseconds: this.nanoseconds };
}
/**
* Converts this object to a primitive string, which allows `Timestamp` objects
* to be compared using the `>`, `<=`, `>=` and `>` operators.
*/
valueOf() {
// This method returns a string of the form <seconds>.<nanoseconds> where
// <seconds> is translated to have a non-negative value and both <seconds>
// and <nanoseconds> are left-padded with zeroes to be a consistent length.
// Strings with this format then have a lexicographical ordering that matches
// the expected ordering. The <seconds> translation is done to avoid having
// a leading negative sign (i.e. a leading '-' character) in its string
// representation, which would affect its lexicographical ordering.
const adjustedSeconds = this.seconds - MIN_SECONDS;
// Note: Up to 12 decimal digits are required to represent all valid
// 'seconds' values.
const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
return formattedSeconds + '.' + formattedNanoseconds;
}
}
/**
* @license
* Copyright 2017 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.
*/
/**
* A version of a document in Firestore. This corresponds to the version
* timestamp, such as update_time or read_time.
*/
class SnapshotVersion {
static fromTimestamp(value) {
return new SnapshotVersion(value);
}
static min() {
return new SnapshotVersion(new Timestamp(0, 0));
}
static max() {
return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
}
constructor(timestamp) {
this.timestamp = timestamp;
}
compareTo(other) {
return this.timestamp._compareTo(other.timestamp);
}
isEqual(other) {
return this.timestamp.isEqual(other.timestamp);
}
/** Returns a number representation of the version for use in spec tests. */
toMicroseconds() {
// Convert to microseconds.
return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;
}
toString() {
return 'SnapshotVersion(' + this.timestamp.toString() + ')';
}
toTimestamp() {
return this.timestamp;
}
}
/**
* @license
* Copyright 2017 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.
*/
const DOCUMENT_KEY_NAME = '__name__';
/**
* Path represents an ordered sequence of string segments.
*/
class BasePath {
constructor(segments, offset, length) {
if (offset === undefined) {
offset = 0;
}
else if (offset > segments.length) {
fail(0x027d, {
offset,
range: segments.length
});
}
if (length === undefined) {
length = segments.length - offset;
}
else if (length > segments.length - offset) {
fail(0x06d2, {
length,
range: segments.length - offset
});
}
this.segments = segments;
this.offset = offset;
this.len = length;
}
get length() {
return this.len;
}
isEqual(other) {
return BasePath.comparator(this, other) === 0;
}
child(nameOrPath) {
const segments = this.segments.slice(this.offset, this.limit());
if (nameOrPath instanceof BasePath) {
nameOrPath.forEach(segment => {
segments.push(segment);
});
}
else {
segments.push(nameOrPath);
}
return this.construct(segments);
}
/** The index of one past the last segment of the path. */
limit() {
return this.offset + this.length;
}
popFirst(size) {
size = size === undefined ? 1 : size;
return this.construct(this.segments, this.offset + size, this.length - size);
}
popLast() {
return this.construct(this.segments, this.offset, this.length - 1);
}
firstSegment() {
return this.segments[this.offset];
}
lastSegment() {
return this.get(this.length - 1);
}
get(index) {
return this.segments[this.offset + index];
}
isEmpty() {
return this.length === 0;
}
isPrefixOf(other) {
if (other.length < this.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this.get(i) !== other.get(i)) {
return false;
}
}
return true;
}
isImmediateParentOf(potentialChild) {
if (this.length + 1 !== potentialChild.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this.get(i) !== potentialChild.get(i)) {
return false;
}
}
return true;
}
forEach(fn) {
for (let i = this.offset, end = this.limit(); i < end; i++) {
fn(this.segments[i]);
}
}
toArray() {
return this.segments.slice(this.offset, this.limit());
}
/**
* Compare 2 paths segment by segment, prioritizing numeric IDs
* (e.g., "__id123__") in numeric ascending order, followed by string
* segments in lexicographical order.
*/
static comparator(p1, p2) {
const len = Math.min(p1.length, p2.length);
for (let i = 0; i < len; i++) {
const comparison = BasePath.compareSegments(p1.get(i), p2.get(i));
if (comparison !== 0) {
return comparison;
}
}
return primitiveComparator(p1.length, p2.length);
}
static compareSegments(lhs, rhs) {
const isLhsNumeric = BasePath.isNumericId(lhs);
const isRhsNumeric = BasePath.isNumericId(rhs);
if (isLhsNumeric && !isRhsNumeric) {
// Only lhs is numeric
return -1;
}
else if (!isLhsNumeric && isRhsNumeric) {
// Only rhs is numeric
return 1;
}
else if (isLhsNumeric && isRhsNumeric) {
// both numeric
return BasePath.extractNumericId(lhs).compare(BasePath.extractNumericId(rhs));
}
else {
// both non-numeric
return compareUtf8Strings(lhs, rhs);
}
}
// Checks if a segment is a numeric ID (starts with "__id" and ends with "__").
static isNumeric