UNPKG

firebase

Version:

Firebase JavaScript library for web and Node.js

1,379 lines (1,362 loc) 146 kB
import { getApp, _getProvider, _registerComponent, registerVersion, SDK_VERSION } from 'https://www.gstatic.com/firebasejs/9.7.0/firebase-app.js'; /** * @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. */ /** * @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 stringToByteArray$1 = function (str) { // TODO(user): Use native implementations if/when available const out = []; let p = 0; for (let i = 0; i < str.length; i++) { let c = str.charCodeAt(i); if (c < 128) { out[p++] = c; } else if (c < 2048) { out[p++] = (c >> 6) | 192; out[p++] = (c & 63) | 128; } else if ((c & 0xfc00) === 0xd800 && i + 1 < str.length && (str.charCodeAt(i + 1) & 0xfc00) === 0xdc00) { // Surrogate Pair c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff); out[p++] = (c >> 18) | 240; out[p++] = ((c >> 12) & 63) | 128; out[p++] = ((c >> 6) & 63) | 128; out[p++] = (c & 63) | 128; } else { out[p++] = (c >> 12) | 224; out[p++] = ((c >> 6) & 63) | 128; out[p++] = (c & 63) | 128; } } return out; }; /** * Turns an array of numbers into the string given by the concatenation of the * characters to which the numbers correspond. * @param bytes Array of numbers representing characters. * @return Stringification of the array. */ const byteArrayToString = function (bytes) { // TODO(user): Use native implementations if/when available const out = []; let pos = 0, c = 0; while (pos < bytes.length) { const c1 = bytes[pos++]; if (c1 < 128) { out[c++] = String.fromCharCode(c1); } else if (c1 > 191 && c1 < 224) { const c2 = bytes[pos++]; out[c++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); } else if (c1 > 239 && c1 < 365) { // Surrogate Pair const c2 = bytes[pos++]; const c3 = bytes[pos++]; const c4 = bytes[pos++]; const u = (((c1 & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63)) - 0x10000; out[c++] = String.fromCharCode(0xd800 + (u >> 10)); out[c++] = String.fromCharCode(0xdc00 + (u & 1023)); } else { const c2 = bytes[pos++]; const c3 = bytes[pos++]; out[c++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); } } return out.join(''); }; // We define it as an object literal instead of a class because a class compiled down to es5 can't // be treeshaked. https://github.com/rollup/rollup/issues/1691 // Static lookup maps, lazily populated by init_() const base64 = { /** * Maps bytes to characters. */ byteToCharMap_: null, /** * Maps characters to bytes. */ charToByteMap_: null, /** * Maps bytes to websafe characters. * @private */ byteToCharMapWebSafe_: null, /** * Maps websafe characters to bytes. * @private */ charToByteMapWebSafe_: null, /** * Our default alphabet, shared between * ENCODED_VALS and ENCODED_VALS_WEBSAFE */ ENCODED_VALS_BASE: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789', /** * Our default alphabet. Value 64 (=) is special; it means "nothing." */ get ENCODED_VALS() { return this.ENCODED_VALS_BASE + '+/='; }, /** * Our websafe alphabet. */ get ENCODED_VALS_WEBSAFE() { return this.ENCODED_VALS_BASE + '-_.'; }, /** * Whether this browser supports the atob and btoa functions. This extension * started at Mozilla but is now implemented by many browsers. We use the * ASSUME_* variables to avoid pulling in the full useragent detection library * but still allowing the standard per-browser compilations. * */ HAS_NATIVE_SUPPORT: typeof atob === 'function', /** * Base64-encode an array of bytes. * * @param input An array of bytes (numbers with * value in [0, 255]) to encode. * @param webSafe Boolean indicating we should use the * alternative alphabet. * @return The base64 encoded string. */ encodeByteArray(input, webSafe) { if (!Array.isArray(input)) { throw Error('encodeByteArray takes an array as a parameter'); } this.init_(); const byteToCharMap = webSafe ? this.byteToCharMapWebSafe_ : this.byteToCharMap_; const output = []; for (let i = 0; i < input.length; i += 3) { const byte1 = input[i]; const haveByte2 = i + 1 < input.length; const byte2 = haveByte2 ? input[i + 1] : 0; const haveByte3 = i + 2 < input.length; const byte3 = haveByte3 ? input[i + 2] : 0; const outByte1 = byte1 >> 2; const outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4); let outByte3 = ((byte2 & 0x0f) << 2) | (byte3 >> 6); let outByte4 = byte3 & 0x3f; if (!haveByte3) { outByte4 = 64; if (!haveByte2) { outByte3 = 64; } } output.push(byteToCharMap[outByte1], byteToCharMap[outByte2], byteToCharMap[outByte3], byteToCharMap[outByte4]); } return output.join(''); }, /** * Base64-encode a string. * * @param input A string to encode. * @param webSafe If true, we should use the * alternative alphabet. * @return The base64 encoded string. */ encodeString(input, webSafe) { // Shortcut for Mozilla browsers that implement // a native base64 encoder in the form of "btoa/atob" if (this.HAS_NATIVE_SUPPORT && !webSafe) { return btoa(input); } return this.encodeByteArray(stringToByteArray$1(input), webSafe); }, /** * Base64-decode a string. * * @param input to decode. * @param webSafe True if we should use the * alternative alphabet. * @return string representing the decoded value. */ decodeString(input, webSafe) { // Shortcut for Mozilla browsers that implement // a native base64 encoder in the form of "btoa/atob" if (this.HAS_NATIVE_SUPPORT && !webSafe) { return atob(input); } return byteArrayToString(this.decodeStringToByteArray(input, webSafe)); }, /** * Base64-decode a string. * * In base-64 decoding, groups of four characters are converted into three * bytes. If the encoder did not apply padding, the input length may not * be a multiple of 4. * * In this case, the last group will have fewer than 4 characters, and * padding will be inferred. If the group has one or two characters, it decodes * to one byte. If the group has three characters, it decodes to two bytes. * * @param input Input to decode. * @param webSafe True if we should use the web-safe alphabet. * @return bytes representing the decoded value. */ decodeStringToByteArray(input, webSafe) { this.init_(); const charToByteMap = webSafe ? this.charToByteMapWebSafe_ : this.charToByteMap_; const output = []; for (let i = 0; i < input.length;) { const byte1 = charToByteMap[input.charAt(i++)]; const haveByte2 = i < input.length; const byte2 = haveByte2 ? charToByteMap[input.charAt(i)] : 0; ++i; const haveByte3 = i < input.length; const byte3 = haveByte3 ? charToByteMap[input.charAt(i)] : 64; ++i; const haveByte4 = i < input.length; const byte4 = haveByte4 ? charToByteMap[input.charAt(i)] : 64; ++i; if (byte1 == null || byte2 == null || byte3 == null || byte4 == null) { throw Error(); } const outByte1 = (byte1 << 2) | (byte2 >> 4); output.push(outByte1); if (byte3 !== 64) { const outByte2 = ((byte2 << 4) & 0xf0) | (byte3 >> 2); output.push(outByte2); if (byte4 !== 64) { const outByte3 = ((byte3 << 6) & 0xc0) | byte4; output.push(outByte3); } } } return output; }, /** * Lazy static initialization function. Called before * accessing any of the static map variables. * @private */ init_() { if (!this.byteToCharMap_) { this.byteToCharMap_ = {}; this.charToByteMap_ = {}; this.byteToCharMapWebSafe_ = {}; this.charToByteMapWebSafe_ = {}; // We want quick mappings back and forth, so we precompute two maps. for (let i = 0; i < this.ENCODED_VALS.length; i++) { this.byteToCharMap_[i] = this.ENCODED_VALS.charAt(i); this.charToByteMap_[this.byteToCharMap_[i]] = i; this.byteToCharMapWebSafe_[i] = this.ENCODED_VALS_WEBSAFE.charAt(i); this.charToByteMapWebSafe_[this.byteToCharMapWebSafe_[i]] = i; // Be forgiving when decoding and correctly decode both encodings. if (i >= this.ENCODED_VALS_BASE.length) { this.charToByteMap_[this.ENCODED_VALS_WEBSAFE.charAt(i)] = i; this.charToByteMapWebSafe_[this.ENCODED_VALS.charAt(i)] = i; } } } } }; /** * URL-safe base64 encoding */ const base64Encode = function (str) { const utf8Bytes = stringToByteArray$1(str); return base64.encodeByteArray(utf8Bytes, true); }; /** * URL-safe base64 encoding (without "." padding in the end). * e.g. Used in JSON Web Token (JWT) parts. */ const base64urlEncodeWithoutPadding = function (str) { // Use base64url encoding and remove padding in the end (dot characters). return base64Encode(str).replace(/\./g, ''); }; /** * @license * Copyright 2021 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 createMockUserToken(token, projectId) { if (token.uid) { throw new Error('The "uid" field is no longer supported by mockUserToken. Please use "sub" instead for Firebase Auth User ID.'); } // Unsecured JWTs use "none" as the algorithm. const header = { alg: 'none', type: 'JWT' }; const project = projectId || 'demo-project'; const iat = token.iat || 0; const sub = token.sub || token.user_id; if (!sub) { throw new Error("mockUserToken must contain 'sub' or 'user_id' field!"); } const payload = Object.assign({ // Set all required fields to decent defaults iss: `https://securetoken.google.com/${project}`, aud: project, iat, exp: iat + 3600, auth_time: iat, sub, user_id: sub, firebase: { sign_in_provider: 'custom', identities: {} } }, token); // Unsecured JWTs use the empty string as a signature. const signature = ''; return [ base64urlEncodeWithoutPadding(JSON.stringify(header)), base64urlEncodeWithoutPadding(JSON.stringify(payload)), signature ].join('.'); } /** * @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. */ /** * @fileoverview Standardized Firebase Error. * * Usage: * * // Typescript string literals for type-safe codes * type Err = * 'unknown' | * 'object-not-found' * ; * * // Closure enum for type-safe error codes * // at-enum {string} * var Err = { * UNKNOWN: 'unknown', * OBJECT_NOT_FOUND: 'object-not-found', * } * * let errors: Map<Err, string> = { * 'generic-error': "Unknown error", * 'file-not-found': "Could not find file: {$file}", * }; * * // Type-safe function - must pass a valid error code as param. * let error = new ErrorFactory<Err>('service', 'Service', errors); * * ... * throw error.create(Err.GENERIC); * ... * throw error.create(Err.FILE_NOT_FOUND, {'file': fileName}); * ... * // Service: Could not file file: foo.txt (service/file-not-found). * * catch (e) { * assert(e.message === "Could not find file: foo.txt."); * if (e.code === 'service/file-not-found') { * console.log("Could not read file: " + e['file']); * } * } */ const ERROR_NAME = 'FirebaseError'; // Based on code from: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types class FirebaseError extends Error { constructor( /** The error code for this error. */ code, message, /** Custom data for this error. */ customData) { super(message); this.code = code; this.customData = customData; /** The custom name for all FirebaseErrors. */ this.name = ERROR_NAME; // Fix For ES5 // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work Object.setPrototypeOf(this, FirebaseError.prototype); // Maintains proper stack trace for where our error was thrown. // Only available on V8. if (Error.captureStackTrace) { Error.captureStackTrace(this, ErrorFactory.prototype.create); } } } class ErrorFactory { constructor(service, serviceName, errors) { this.service = service; this.serviceName = serviceName; this.errors = errors; } create(code, ...data) { const customData = data[0] || {}; const fullCode = `${this.service}/${code}`; const template = this.errors[code]; const message = template ? replaceTemplate(template, customData) : 'Error'; // Service Name: Error message (service/code). const fullMessage = `${this.serviceName}: ${message} (${fullCode}).`; const error = new FirebaseError(fullCode, fullMessage, customData); return error; } } function replaceTemplate(template, data) { return template.replace(PATTERN, (_, key) => { const value = data[key]; return value != null ? String(value) : `<${key}?>`; }); } const PATTERN = /\{\$([^}]+)}/g; /** * @license * Copyright 2021 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 getModularInstance(service) { if (service && service._delegate) { return service._delegate; } else { return service; } } /** * Component for service name T, e.g. `auth`, `auth-internal` */ class Component { /** * * @param name The public service name, e.g. app, auth, firestore, database * @param instanceFactory Service factory responsible for creating the public interface * @param type whether the service provided by the component is public or private */ constructor(name, instanceFactory, type) { this.name = name; this.instanceFactory = instanceFactory; this.type = type; this.multipleInstances = false; /** * Properties to be added to the service namespace */ this.serviceProps = {}; this.instantiationMode = "LAZY" /* LAZY */; this.onInstanceCreated = null; } setInstantiationMode(mode) { this.instantiationMode = mode; return this; } setMultipleInstances(multipleInstances) { this.multipleInstances = multipleInstances; return this; } setServiceProps(props) { this.serviceProps = props; return this; } setInstanceCreatedCallback(callback) { this.onInstanceCreated = callback; return this; } } /** * @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. */ /** * @fileoverview Constants used in the Firebase Storage library. */ /** * Domain name for firebase storage. */ const DEFAULT_HOST = 'firebasestorage.googleapis.com'; /** * The key in Firebase config json for the storage bucket. */ const CONFIG_STORAGE_BUCKET_KEY = 'storageBucket'; /** * 2 minutes * * The timeout for all operations except upload. */ const DEFAULT_MAX_OPERATION_RETRY_TIME = 2 * 60 * 1000; /** * 10 minutes * * The timeout for upload. */ const DEFAULT_MAX_UPLOAD_RETRY_TIME = 10 * 60 * 1000; /** * @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. */ /** * An error returned by the Firebase Storage SDK. * @public */ class StorageError extends FirebaseError { /** * @param code - A StorageErrorCode string to be prefixed with 'storage/' and * added to the end of the message. * @param message - Error message. */ constructor(code, message) { super(prependCode(code), `Firebase Storage: ${message} (${prependCode(code)})`); /** * Stores custom error data unque to StorageError. */ this.customData = { serverResponse: null }; this._baseMessage = this.message; // Without this, `instanceof StorageError`, in tests for example, // returns false. Object.setPrototypeOf(this, StorageError.prototype); } /** * Compares a StorageErrorCode against this error's code, filtering out the prefix. */ _codeEquals(code) { return prependCode(code) === this.code; } /** * Optional response message that was added by the server. */ get serverResponse() { return this.customData.serverResponse; } set serverResponse(serverResponse) { this.customData.serverResponse = serverResponse; if (this.customData.serverResponse) { this.message = `${this._baseMessage}\n${this.customData.serverResponse}`; } else { this.message = this._baseMessage; } } } function prependCode(code) { return 'storage/' + code; } function unknown() { const message = 'An unknown error occurred, please check the error payload for ' + 'server response.'; return new StorageError("unknown" /* UNKNOWN */, message); } function objectNotFound(path) { return new StorageError("object-not-found" /* OBJECT_NOT_FOUND */, "Object '" + path + "' does not exist."); } function quotaExceeded(bucket) { return new StorageError("quota-exceeded" /* QUOTA_EXCEEDED */, "Quota for bucket '" + bucket + "' exceeded, please view quota on " + 'https://firebase.google.com/pricing/.'); } function unauthenticated() { const message = 'User is not authenticated, please authenticate using Firebase ' + 'Authentication and try again.'; return new StorageError("unauthenticated" /* UNAUTHENTICATED */, message); } function unauthorizedApp() { return new StorageError("unauthorized-app" /* UNAUTHORIZED_APP */, 'This app does not have permission to access Firebase Storage on this project.'); } function unauthorized(path) { return new StorageError("unauthorized" /* UNAUTHORIZED */, "User does not have permission to access '" + path + "'."); } function retryLimitExceeded() { return new StorageError("retry-limit-exceeded" /* RETRY_LIMIT_EXCEEDED */, 'Max retry time for operation exceeded, please try again.'); } function canceled() { return new StorageError("canceled" /* CANCELED */, 'User canceled the upload/download.'); } function invalidUrl(url) { return new StorageError("invalid-url" /* INVALID_URL */, "Invalid URL '" + url + "'."); } function invalidDefaultBucket(bucket) { return new StorageError("invalid-default-bucket" /* INVALID_DEFAULT_BUCKET */, "Invalid default bucket '" + bucket + "'."); } function noDefaultBucket() { return new StorageError("no-default-bucket" /* NO_DEFAULT_BUCKET */, 'No default bucket ' + "found. Did you set the '" + CONFIG_STORAGE_BUCKET_KEY + "' property when initializing the app?"); } function cannotSliceBlob() { return new StorageError("cannot-slice-blob" /* CANNOT_SLICE_BLOB */, 'Cannot slice blob for upload. Please retry the upload.'); } function serverFileWrongSize() { return new StorageError("server-file-wrong-size" /* SERVER_FILE_WRONG_SIZE */, 'Server recorded incorrect upload file size, please retry the upload.'); } function noDownloadURL() { return new StorageError("no-download-url" /* NO_DOWNLOAD_URL */, 'The given file does not have any download URLs.'); } /** * @internal */ function invalidArgument(message) { return new StorageError("invalid-argument" /* INVALID_ARGUMENT */, message); } function appDeleted() { return new StorageError("app-deleted" /* APP_DELETED */, 'The Firebase app was deleted.'); } /** * @param name - The name of the operation that was invalid. * * @internal */ function invalidRootOperation(name) { return new StorageError("invalid-root-operation" /* INVALID_ROOT_OPERATION */, "The operation '" + name + "' cannot be performed on a root reference, create a non-root " + "reference using child, such as .child('file.png')."); } /** * @param format - The format that was not valid. * @param message - A message describing the format violation. */ function invalidFormat(format, message) { return new StorageError("invalid-format" /* INVALID_FORMAT */, "String does not match format '" + format + "': " + message); } /** * @param message - A message describing the internal error. */ function internalError(message) { throw new StorageError("internal-error" /* INTERNAL_ERROR */, 'Internal error: ' + 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. */ /** * Firebase Storage location data. * * @internal */ class Location { constructor(bucket, path) { this.bucket = bucket; this.path_ = path; } get path() { return this.path_; } get isRoot() { return this.path.length === 0; } fullServerUrl() { const encode = encodeURIComponent; return '/b/' + encode(this.bucket) + '/o/' + encode(this.path); } bucketOnlyServerUrl() { const encode = encodeURIComponent; return '/b/' + encode(this.bucket) + '/o'; } static makeFromBucketSpec(bucketString, host) { let bucketLocation; try { bucketLocation = Location.makeFromUrl(bucketString, host); } catch (e) { // Not valid URL, use as-is. This lets you put bare bucket names in // config. return new Location(bucketString, ''); } if (bucketLocation.path === '') { return bucketLocation; } else { throw invalidDefaultBucket(bucketString); } } static makeFromUrl(url, host) { let location = null; const bucketDomain = '([A-Za-z0-9.\\-_]+)'; function gsModify(loc) { if (loc.path.charAt(loc.path.length - 1) === '/') { loc.path_ = loc.path_.slice(0, -1); } } const gsPath = '(/(.*))?$'; const gsRegex = new RegExp('^gs://' + bucketDomain + gsPath, 'i'); const gsIndices = { bucket: 1, path: 3 }; function httpModify(loc) { loc.path_ = decodeURIComponent(loc.path); } const version = 'v[A-Za-z0-9_]+'; const firebaseStorageHost = host.replace(/[.]/g, '\\.'); const firebaseStoragePath = '(/([^?#]*).*)?$'; const firebaseStorageRegExp = new RegExp(`^https?://${firebaseStorageHost}/${version}/b/${bucketDomain}/o${firebaseStoragePath}`, 'i'); const firebaseStorageIndices = { bucket: 1, path: 3 }; const cloudStorageHost = host === DEFAULT_HOST ? '(?:storage.googleapis.com|storage.cloud.google.com)' : host; const cloudStoragePath = '([^?#]*)'; const cloudStorageRegExp = new RegExp(`^https?://${cloudStorageHost}/${bucketDomain}/${cloudStoragePath}`, 'i'); const cloudStorageIndices = { bucket: 1, path: 2 }; const groups = [ { regex: gsRegex, indices: gsIndices, postModify: gsModify }, { regex: firebaseStorageRegExp, indices: firebaseStorageIndices, postModify: httpModify }, { regex: cloudStorageRegExp, indices: cloudStorageIndices, postModify: httpModify } ]; for (let i = 0; i < groups.length; i++) { const group = groups[i]; const captures = group.regex.exec(url); if (captures) { const bucketValue = captures[group.indices.bucket]; let pathValue = captures[group.indices.path]; if (!pathValue) { pathValue = ''; } location = new Location(bucketValue, pathValue); group.postModify(location); break; } } if (location == null) { throw invalidUrl(url); } return location; } } /** * A request whose promise always fails. */ class FailRequest { constructor(error) { this.promise_ = Promise.reject(error); } /** @inheritDoc */ getPromise() { return this.promise_; } /** @inheritDoc */ cancel(_appDelete = false) { } } /** * @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. */ /** * @param f May be invoked * before the function returns. * @param callback Get all the arguments passed to the function * passed to f, including the initial boolean. */ function start(f, // eslint-disable-next-line @typescript-eslint/no-explicit-any callback, timeout) { // TODO(andysoto): make this code cleaner (probably refactor into an actual // type instead of a bunch of functions with state shared in the closure) let waitSeconds = 1; // Would type this as "number" but that doesn't work for Node so ¯\_(ツ)_/¯ // TODO: find a way to exclude Node type definition for storage because storage only works in browser // eslint-disable-next-line @typescript-eslint/no-explicit-any let retryTimeoutId = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any let globalTimeoutId = null; let hitTimeout = false; let cancelState = 0; function canceled() { return cancelState === 2; } let triggeredCallback = false; function triggerCallback(...args) { if (!triggeredCallback) { triggeredCallback = true; callback.apply(null, args); } } function callWithDelay(millis) { retryTimeoutId = setTimeout(() => { retryTimeoutId = null; f(handler, canceled()); }, millis); } function clearGlobalTimeout() { if (globalTimeoutId) { clearTimeout(globalTimeoutId); } } function handler(success, ...args) { if (triggeredCallback) { clearGlobalTimeout(); return; } if (success) { clearGlobalTimeout(); triggerCallback.call(null, success, ...args); return; } const mustStop = canceled() || hitTimeout; if (mustStop) { clearGlobalTimeout(); triggerCallback.call(null, success, ...args); return; } if (waitSeconds < 64) { /* TODO(andysoto): don't back off so quickly if we know we're offline. */ waitSeconds *= 2; } let waitMillis; if (cancelState === 1) { cancelState = 2; waitMillis = 0; } else { waitMillis = (waitSeconds + Math.random()) * 1000; } callWithDelay(waitMillis); } let stopped = false; function stop(wasTimeout) { if (stopped) { return; } stopped = true; clearGlobalTimeout(); if (triggeredCallback) { return; } if (retryTimeoutId !== null) { if (!wasTimeout) { cancelState = 2; } clearTimeout(retryTimeoutId); callWithDelay(0); } else { if (!wasTimeout) { cancelState = 1; } } } callWithDelay(0); globalTimeoutId = setTimeout(() => { hitTimeout = true; stop(true); }, timeout); return stop; } /** * Stops the retry loop from repeating. * If the function is currently "in between" retries, it is invoked immediately * with the second parameter as "true". Otherwise, it will be invoked once more * after the current invocation finishes iff the current invocation would have * triggered another retry. */ function stop(id) { id(false); } /** * @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 isJustDef(p) { return p !== void 0; } // eslint-disable-next-line @typescript-eslint/ban-types function isFunction(p) { return typeof p === 'function'; } function isNonArrayObject(p) { return typeof p === 'object' && !Array.isArray(p); } function isString(p) { return typeof p === 'string' || p instanceof String; } function isNativeBlob(p) { return isNativeBlobDefined() && p instanceof Blob; } function isNativeBlobDefined() { return typeof Blob !== 'undefined'; } function validateNumber(argument, minValue, maxValue, value) { if (value < minValue) { throw invalidArgument(`Invalid value for '${argument}'. Expected ${minValue} or greater.`); } if (value > maxValue) { throw invalidArgument(`Invalid value for '${argument}'. Expected ${maxValue} or less.`); } } /** * @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 makeUrl(urlPart, host, protocol) { let origin = host; if (protocol == null) { origin = `https://${host}`; } return `${protocol}://${origin}/v0${urlPart}`; } function makeQueryString(params) { const encode = encodeURIComponent; let queryPart = '?'; for (const key in params) { if (params.hasOwnProperty(key)) { const nextPart = encode(key) + '=' + encode(params[key]); queryPart = queryPart + nextPart + '&'; } } // Chop off the extra '&' or '?' on the end queryPart = queryPart.slice(0, -1); return queryPart; } /** * @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. */ /** * Error codes for requests made by the the XhrIo wrapper. */ var ErrorCode; (function (ErrorCode) { ErrorCode[ErrorCode["NO_ERROR"] = 0] = "NO_ERROR"; ErrorCode[ErrorCode["NETWORK_ERROR"] = 1] = "NETWORK_ERROR"; ErrorCode[ErrorCode["ABORT"] = 2] = "ABORT"; })(ErrorCode || (ErrorCode = {})); /** * @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. */ /** * Handles network logic for all Storage Requests, including error reporting and * retries with backoff. * * @param I - the type of the backend's network response. * @param - O the output type used by the rest of the SDK. The conversion * happens in the specified `callback_`. */ class NetworkRequest { constructor(url_, method_, headers_, body_, successCodes_, additionalRetryCodes_, callback_, errorCallback_, timeout_, progressCallback_, connectionFactory_) { this.url_ = url_; this.method_ = method_; this.headers_ = headers_; this.body_ = body_; this.successCodes_ = successCodes_; this.additionalRetryCodes_ = additionalRetryCodes_; this.callback_ = callback_; this.errorCallback_ = errorCallback_; this.timeout_ = timeout_; this.progressCallback_ = progressCallback_; this.connectionFactory_ = connectionFactory_; this.pendingConnection_ = null; this.backoffId_ = null; this.canceled_ = false; this.appDelete_ = false; this.promise_ = new Promise((resolve, reject) => { this.resolve_ = resolve; this.reject_ = reject; this.start_(); }); } /** * Actually starts the retry loop. */ start_() { const doTheRequest = (backoffCallback, canceled) => { if (canceled) { backoffCallback(false, new RequestEndStatus(false, null, true)); return; } const connection = this.connectionFactory_(); this.pendingConnection_ = connection; const progressListener = progressEvent => { const loaded = progressEvent.loaded; const total = progressEvent.lengthComputable ? progressEvent.total : -1; if (this.progressCallback_ !== null) { this.progressCallback_(loaded, total); } }; if (this.progressCallback_ !== null) { connection.addUploadProgressListener(progressListener); } // connection.send() never rejects, so we don't need to have a error handler or use catch on the returned promise. // eslint-disable-next-line @typescript-eslint/no-floating-promises connection .send(this.url_, this.method_, this.body_, this.headers_) .then(() => { if (this.progressCallback_ !== null) { connection.removeUploadProgressListener(progressListener); } this.pendingConnection_ = null; const hitServer = connection.getErrorCode() === ErrorCode.NO_ERROR; const status = connection.getStatus(); if (!hitServer || this.isRetryStatusCode_(status)) { const wasCanceled = connection.getErrorCode() === ErrorCode.ABORT; backoffCallback(false, new RequestEndStatus(false, null, wasCanceled)); return; } const successCode = this.successCodes_.indexOf(status) !== -1; backoffCallback(true, new RequestEndStatus(successCode, connection)); }); }; /** * @param requestWentThrough - True if the request eventually went * through, false if it hit the retry limit or was canceled. */ const backoffDone = (requestWentThrough, status) => { const resolve = this.resolve_; const reject = this.reject_; const connection = status.connection; if (status.wasSuccessCode) { try { const result = this.callback_(connection, connection.getResponse()); if (isJustDef(result)) { resolve(result); } else { resolve(); } } catch (e) { reject(e); } } else { if (connection !== null) { const err = unknown(); err.serverResponse = connection.getErrorText(); if (this.errorCallback_) { reject(this.errorCallback_(connection, err)); } else { reject(err); } } else { if (status.canceled) { const err = this.appDelete_ ? appDeleted() : canceled(); reject(err); } else { const err = retryLimitExceeded(); reject(err); } } } }; if (this.canceled_) { backoffDone(false, new RequestEndStatus(false, null, true)); } else { this.backoffId_ = start(doTheRequest, backoffDone, this.timeout_); } } /** @inheritDoc */ getPromise() { return this.promise_; } /** @inheritDoc */ cancel(appDelete) { this.canceled_ = true; this.appDelete_ = appDelete || false; if (this.backoffId_ !== null) { stop(this.backoffId_); } if (this.pendingConnection_ !== null) { this.pendingConnection_.abort(); } } isRetryStatusCode_(status) { // The codes for which to retry came from this page: // https://cloud.google.com/storage/docs/exponential-backoff const isFiveHundredCode = status >= 500 && status < 600; const extraRetryCodes = [ // Request Timeout: web server didn't receive full request in time. 408, // Too Many Requests: you're getting rate-limited, basically. 429 ]; const isExtraRetryCode = extraRetryCodes.indexOf(status) !== -1; const isRequestSpecificRetryCode = this.additionalRetryCodes_.indexOf(status) !== -1; return isFiveHundredCode || isExtraRetryCode || isRequestSpecificRetryCode; } } /** * A collection of information about the result of a network request. * @param opt_canceled - Defaults to false. */ class RequestEndStatus { constructor(wasSuccessCode, connection, canceled) { this.wasSuccessCode = wasSuccessCode; this.connection = connection; this.canceled = !!canceled; } } function addAuthHeader_(headers, authToken) { if (authToken !== null && authToken.length > 0) { headers['Authorization'] = 'Firebase ' + authToken; } } function addVersionHeader_(headers, firebaseVersion) { headers['X-Firebase-Storage-Version'] = 'webjs/' + (firebaseVersion !== null && firebaseVersion !== void 0 ? firebaseVersion : 'AppManager'); } function addGmpidHeader_(headers, appId) { if (appId) { headers['X-Firebase-GMPID'] = appId; } } function addAppCheckHeader_(headers, appCheckToken) { if (appCheckToken !== null) { headers['X-Firebase-AppCheck'] = appCheckToken; } } function makeRequest(requestInfo, appId, authToken, appCheckToken, requestFactory, firebaseVersion) { const queryPart = makeQueryString(requestInfo.urlParams); const url = requestInfo.url + queryPart; const headers = Object.assign({}, requestInfo.headers); addGmpidHeader_(headers, appId); addAuthHeader_(headers, authToken); addVersionHeader_(headers, firebaseVersion); addAppCheckHeader_(headers, appCheckToken); return new NetworkRequest(url, requestInfo.method, headers, requestInfo.body, requestInfo.successCodes, requestInfo.additionalRetryCodes, requestInfo.handler, requestInfo.errorHandler, requestInfo.timeout, requestInfo.progressCallback, requestFactory); } /** * @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 getBlobBuilder() { if (typeof BlobBuilder !== 'undefined') { return BlobBuilder; } else if (typeof WebKitBlobBuilder !== 'undefined') { return WebKitBlobBuilder; } else { return undefined; } } /** * Concatenates one or more values together and converts them to a Blob. * * @param args The values that will make up the resulting blob. * @return The blob. */ function getBlob$1(...args) { const BlobBuilder = getBlobBuilder(); if (BlobBuilder !== undefined) { const bb = new BlobBuilder(); for (let i = 0; i < args.length; i++) { bb.append(args[i]); } return bb.getBlob(); } else { if (isNativeBlobDefined()) { return new Blob(args); } else { throw new StorageError("unsupported-environment" /* UNSUPPORTED_ENVIRONMENT */, "This browser doesn't seem to support creating Blobs"); } } } /** * Slices the blob. The returned blob contains data from the start byte * (inclusive) till the end byte (exclusive). Negative indices cannot be used. * * @param blob The blob to be sliced. * @param start Index of the starting byte. * @param end Index of the ending byte. * @return The blob slice or null if not supported. */ function sliceBlob(blob, start, end) { if (blob.webkitSlice) { return blob.webkitSlice(start, end); } else if (blob.mozSlice) { return blob.mozSlice(start, end); } else if (blob.slice) { return blob.slice(start, end); } return null; } /** * @license * Copyright 2021 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 o