diff --git a/common/api-review/storage.api.md b/common/api-review/storage.api.md index 92906ced407..3908f2db229 100644 --- a/common/api-review/storage.api.md +++ b/common/api-review/storage.api.md @@ -10,7 +10,7 @@ import { EmulatorMockTokenOptions } from '@firebase/util'; import { FirebaseApp } from '@firebase/app'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { FirebaseError } from '@firebase/util'; -import { _FirebaseService } from '@firebase/app'; +import { _FirebaseService } from '@firebase/app-exp'; import { NextFn } from '@firebase/util'; import { Provider } from '@firebase/component'; import { Subscribe } from '@firebase/util'; @@ -21,6 +21,11 @@ export function connectStorageEmulator(storage: FirebaseStorage, host: string, p mockUserToken?: EmulatorMockTokenOptions | string; }): void; +// Warning: (ae-forgotten-export) The symbol "StringData" needs to be exported by the entry point index.d.ts +// +// @internal (undocumented) +export function _dataFromString(format: StringFormat, stringData: string): StringData; + // @public export function deleteObject(ref: StorageReference): Promise; @@ -51,6 +56,55 @@ export interface FirebaseStorageError extends FirebaseError { serverResponse: string | null; } +// @public +export class _FirebaseStorageImpl implements FirebaseStorage { + constructor( + app: FirebaseApp, _authProvider: Provider, + _appCheckProvider: Provider, + _pool: ConnectionPool, _url?: string | undefined, _firebaseVersion?: string | undefined); + readonly app: FirebaseApp; + // @internal (undocumented) + readonly _appCheckProvider: Provider; + // (undocumented) + protected readonly _appId: string | null; + // (undocumented) + readonly _authProvider: Provider; + // Warning: (ae-incompatible-release-tags) The symbol "_bucket" is marked as @public, but its signature references "Location" which is marked as @internal + // + // (undocumented) + _bucket: _Location | null; + _delete(): Promise; + // (undocumented) + readonly _firebaseVersion?: string | undefined; + // (undocumented) + _getAppCheckToken(): Promise; + // (undocumented) + _getAuthToken(): Promise; + // (undocumented) + get host(): string; + set host(host: string); + // Warning: (ae-forgotten-export) The symbol "RequestInfo" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "Request" needs to be exported by the entry point index.d.ts + // + // (undocumented) + _makeRequest(requestInfo: RequestInfo_2, authToken: string | null, appCheckToken: string | null): Request_2; + // (undocumented) + makeRequestWithTokens(requestInfo: RequestInfo_2): Promise>; + // Warning: (ae-incompatible-release-tags) The symbol "_makeStorageReference" is marked as @public, but its signature references "Location" which is marked as @internal + // Warning: (ae-incompatible-release-tags) The symbol "_makeStorageReference" is marked as @public, but its signature references "Reference" which is marked as @internal + _makeStorageReference(loc: _Location): _Reference; + get maxOperationRetryTime(): number; + set maxOperationRetryTime(time: number); + get maxUploadRetryTime(): number; + set maxUploadRetryTime(time: number); + // Warning: (ae-forgotten-export) The symbol "ConnectionPool" needs to be exported by the entry point index.d.ts + // + // @internal (undocumented) + readonly _pool: ConnectionPool; + // (undocumented) + readonly _url?: string | undefined; +} + // @public export interface FullMetadata extends UploadMetadata { bucket: string; @@ -77,6 +131,14 @@ export function getMetadata(ref: StorageReference): Promise; // @public export function getStorage(app?: FirebaseApp, bucketUrl?: string): FirebaseStorage; +// Warning: (ae-forgotten-export) The symbol "FirebaseStorageError" needs to be exported by the entry point index.d.ts +// +// @internal (undocumented) +export function _invalidArgument(message: string): FirebaseStorageError_2; + +// @internal (undocumented) +export function _invalidRootOperation(name: string): FirebaseStorageError_2; + // @public export function list(ref: StorageReference, options?: ListOptions): Promise; @@ -123,18 +185,17 @@ export function ref(storageOrRef: FirebaseStorage | StorageReference, path?: str // @internal export class _Reference { - // Warning: (ae-forgotten-export) The symbol "FirebaseStorageImpl" needs to be exported by the entry point index.d.ts - constructor(_service: FirebaseStorageImpl, location: string | _Location); + constructor(_service: _FirebaseStorageImpl, location: string | _Location); get bucket(): string; get fullPath(): string; // (undocumented) _location: _Location; get name(): string; // (undocumented) - protected _newRef(service: FirebaseStorageImpl, location: _Location): _Reference; + protected _newRef(service: _FirebaseStorageImpl, location: _Location): _Reference; get parent(): _Reference | null; get root(): _Reference; - get storage(): FirebaseStorageImpl; + get storage(): _FirebaseStorageImpl; _throwIfRoot(name: string): void; // @override toString(): string; @@ -187,9 +248,29 @@ export const StringFormat: { // @public export type TaskEvent = 'state_changed'; +// @public +export type _TaskEvent = string; + +// @public +export const _TaskEvent: { + STATE_CHANGED: string; +}; + // @public export type TaskState = 'running' | 'paused' | 'success' | 'canceled' | 'error'; +// @public +export type _TaskState = typeof _TaskState[keyof typeof _TaskState]; + +// @public +export const _TaskState: { + readonly RUNNING: "running"; + readonly PAUSED: "paused"; + readonly SUCCESS: "success"; + readonly CANCELED: "canceled"; + readonly ERROR: "error"; +}; + // @public export function updateMetadata(ref: StorageReference, metadata: SettableMetadata): Promise; @@ -232,21 +313,15 @@ export class _UploadTask { catch(onRejected: (p1: FirebaseStorageError_2) => T | Promise): Promise; // Warning: (ae-forgotten-export) The symbol "Metadata" needs to be exported by the entry point index.d.ts _metadata: Metadata | null; - // Warning: (ae-forgotten-export) The symbol "TaskEvent" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "StorageObserver" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "ErrorFn" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "CompleteFn" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Unsubscribe" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Subscribe" needs to be exported by the entry point index.d.ts - on(type: TaskEvent_2, nextOrObserver?: StorageObserver_2 | ((a: UploadTaskSnapshot_2) => unknown), error?: ErrorFn, completed?: CompleteFn_2): Unsubscribe_2 | Subscribe_2; + on(type: _TaskEvent, nextOrObserver?: StorageObserver | null | ((snapshot: UploadTaskSnapshot) => unknown), error?: ((a: FirebaseStorageError_2) => unknown) | null, completed?: Unsubscribe_2 | null): Unsubscribe_2 | Subscribe_2; pause(): boolean; resume(): boolean; - // Warning: (ae-forgotten-export) The symbol "UploadTaskSnapshot" needs to be exported by the entry point index.d.ts - get snapshot(): UploadTaskSnapshot_2; + get snapshot(): UploadTaskSnapshot; // Warning: (ae-forgotten-export) The symbol "InternalTaskState" needs to be exported by the entry point index.d.ts _state: InternalTaskState; - // Warning: (ae-forgotten-export) The symbol "FirebaseStorageError" needs to be exported by the entry point index.d.ts - then(onFulfilled?: ((value: UploadTaskSnapshot_2) => U | Promise) | null, onRejected?: ((error: FirebaseStorageError_2) => U | Promise) | null): Promise; + then(onFulfilled?: ((value: UploadTaskSnapshot) => U | Promise) | null, onRejected?: ((error: FirebaseStorageError_2) => U | Promise) | null): Promise; _transferred: number; } diff --git a/packages-exp/app-exp/src/constants.ts b/packages-exp/app-exp/src/constants.ts index f00a93f0da9..34feb88f791 100644 --- a/packages-exp/app-exp/src/constants.ts +++ b/packages-exp/app-exp/src/constants.ts @@ -36,7 +36,7 @@ import { name as performanceCompatName } from '../../../packages-exp/performance import { name as remoteConfigName } from '../../../packages-exp/remote-config-exp/package.json'; import { name as remoteConfigCompatName } from '../../../packages-exp/remote-config-compat/package.json'; import { name as storageName } from '../../../packages/storage/package.json'; -import { name as storageCompatName } from '../../../packages/storage/compat/package.json'; +import { name as storageCompatName } from '../../../packages/storage-compat/package.json'; import { name as firestoreName } from '../../../packages/firestore/package.json'; import { name as firestoreCompatName } from '../../../packages/firestore/compat/package.json'; import { name as packageName } from '../../../packages-exp/firebase-exp/package.json'; diff --git a/packages/rules-unit-testing/src/api/index.ts b/packages/rules-unit-testing/src/api/index.ts index 51d16209187..9cf12e24af9 100644 --- a/packages/rules-unit-testing/src/api/index.ts +++ b/packages/rules-unit-testing/src/api/index.ts @@ -160,7 +160,7 @@ export type FirebaseEmulatorOptions = { function trimmedBase64Encode(val: string): string { // Use base64url encoding and remove padding in the end (dot characters). - return base64Encode(val).replace(/\./g, ""); + return base64Encode(val).replace(/\./g, ''); } function createUnsecuredJwt(token: TokenOptions, projectId?: string): string { @@ -498,7 +498,7 @@ function initializeApp( ComponentType.PRIVATE ); - ((app as unknown) as _FirebaseApp)._addOrOverwriteComponent( + (app as unknown as _FirebaseApp)._addOrOverwriteComponent( mockAuthComponent ); } @@ -703,7 +703,7 @@ export function assertFails(pr: Promise): any { errCode === 'permission-denied' || errCode === 'permission_denied' || errMessage.indexOf('permission_denied') >= 0 || - errMessage.indexOf('permission denied') >= 0 || + errMessage.indexOf('permission denied') >= 0 || // Storage permission errors contain message: (storage/unauthorized) errMessage.indexOf('unauthorized') >= 0; diff --git a/packages/storage/rollup.shared.js b/packages/storage-compat/.eslintrc.js similarity index 55% rename from packages/storage/rollup.shared.js rename to packages/storage-compat/.eslintrc.js index 59562480d49..ffe0e481071 100644 --- a/packages/storage/rollup.shared.js +++ b/packages/storage-compat/.eslintrc.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2021 Google LLC + * 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. @@ -15,20 +15,23 @@ * limitations under the License. */ -/** - * Returns an replacement configuration for `@rollup/plugin-alias` that replaces - * references to platform-specific files with implementations for the provided - * target platform. - */ -function generateAliasConfig(platform) { - return { - entries: [ +const path = require('path'); + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + }, + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', { - find: /^(.*)\/platform\/([^.\/]*)(\.ts)?$/, - replacement: `$1\/platform/${platform}/$2.ts` + varsIgnorePattern: '^_', + args: 'none' } ] - }; -} - -exports.generateAliasConfig = generateAliasConfig; + } +}; diff --git a/packages/storage-compat/README.md b/packages/storage-compat/README.md new file mode 100644 index 00000000000..7c1c82bd424 --- /dev/null +++ b/packages/storage-compat/README.md @@ -0,0 +1,5 @@ +# @firebase/storage + +This is the Cloud Storage component of the Firebase JS SDK. + +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages/storage-compat/karma.conf.js b/packages/storage-compat/karma.conf.js new file mode 100644 index 00000000000..3b5d7e3f39b --- /dev/null +++ b/packages/storage-compat/karma.conf.js @@ -0,0 +1,46 @@ +/** + * @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 karmaBase = require('../../config/karma.base'); +const { argv } = require('yargs'); + +module.exports = function (config) { + const karmaConfig = Object.assign({}, karmaBase, { + // files to load into karma + files: getTestFiles(argv), + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha'] + }); + + config.set(karmaConfig); +}; + +function getTestFiles(argv) { + let unitTestFiles = ['test/unit/*']; + let integrationTestFiles = ['test/integration/*']; + + if (argv.unit) { + return unitTestFiles; + } else if (argv.integration) { + return integrationTestFiles; + } else { + return [...unitTestFiles, ...integrationTestFiles]; + } +} + +module.exports.files = getTestFiles(argv); diff --git a/packages/storage-compat/package.json b/packages/storage-compat/package.json new file mode 100644 index 00000000000..b629bf8298a --- /dev/null +++ b/packages/storage-compat/package.json @@ -0,0 +1,49 @@ +{ + "name": "@firebase/storage-compat", + "version": "0.0.900", + "description": "The Firebase Firestore compatibility package", + "author": "Firebase (https://firebase.google.com/)", + "main": "./dist/index.cjs.js", + "browser": "./dist/index.esm2017.js", + "module": "./dist/index.esm2017.js", + "esm5": "./dist/index.esm5.js", + "license": "Apache-2.0", + "typings": "./dist/src/index.d.ts", + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c rollup.config.js && yarn add-compat-overloads", + "build:deps": "lerna run --scope @firebase/storage-compat --include-dependencies build", + "dev": "rollup -c -w", + "test": "run-p test:browser test:node lint", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser test:node", + "test:browser:unit": "karma start --single-run --unit", + "test:browser:integration": "karma start --single-run --integration", + "test:browser": "karma start --single-run", + "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file src/index.ts --config ../../config/mocharc.node.js", + "test:debug": "karma start --browser=Chrome", + "prettier": "prettier --write 'src/**/*.ts' 'test/**/*.ts'", + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../storage/dist/storage-public.d.ts -o dist/src/index.d.ts -a -r FirebaseStorage:types.FirebaseStorage -r StorageReference:types.Reference -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/storage" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "dependencies": { + "@firebase/storage": "0.7.0", + "@firebase/storage-types": "0.5.0", + "@firebase/util": "1.3.0", + "@firebase/component": "0.5.6", + "tslib": "^2.1.0" + }, + "devDependencies": { + "@firebase/app-compat": "0.x", + "@firebase/auth-compat": "0.x", + "rollup": "2.52.2", + "@rollup/plugin-json": "4.1.0", + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2" + }, + "files": [ + "dist" + ] +} \ No newline at end of file diff --git a/packages/storage/rollup.config.compat.js b/packages/storage-compat/rollup.config.js similarity index 59% rename from packages/storage/rollup.config.compat.js rename to packages/storage-compat/rollup.config.js index a1b6321157d..c220eb88893 100644 --- a/packages/storage/rollup.config.compat.js +++ b/packages/storage-compat/rollup.config.js @@ -18,55 +18,39 @@ import json from '@rollup/plugin-json'; import typescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; -import alias from '@rollup/plugin-alias'; import pkg from './package.json'; -import { getImportPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -const { generateAliasConfig } = require('./rollup.shared'); - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/storage' -]; +const deps = Object.keys( + Object.assign({}, pkg.peerDependencies, pkg.dependencies) +); /** * ES5 Builds */ const es5BuildPlugins = [ typescriptPlugin({ typescript, - abortOnError: false, - transformers: [ - getImportPathTransformer({ - // ../exp/index - pattern: /^.*exp\/api$/, - template: ['@firebase/storage'] - }) - ] + abortOnError: false }), json() ]; const es5Builds = [ { - input: './compat/index.ts', + input: './src/index.ts', output: [ { - dir: 'dist/compat/cjs', + file: pkg.main, format: 'cjs', sourcemap: true }, { - dir: 'dist/compat/esm5', + file: pkg.esm5, format: 'es', sourcemap: true } ], - plugins: [alias(generateAliasConfig('browser')), ...es5BuildPlugins], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } + plugins: [...es5BuildPlugins], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) } ]; @@ -77,13 +61,6 @@ const es2017BuildPlugins = [ typescriptPlugin({ typescript, abortOnError: false, - transformers: [ - getImportPathTransformer({ - // ../exp/index - pattern: /^.*exp\/api$/, - template: ['@firebase/storage'] - }) - ], tsconfigOverride: { compilerOptions: { target: 'es2017' @@ -95,17 +72,14 @@ const es2017BuildPlugins = [ const es2017Builds = [ { - input: './compat/index.ts', + input: './src/index.ts', output: { - dir: 'dist/compat/esm2017', + file: pkg.browser, format: 'es', sourcemap: true }, - plugins: [alias(generateAliasConfig('browser')), ...es2017BuildPlugins], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } + plugins: [...es2017BuildPlugins], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) } ]; diff --git a/packages/storage/compat/index.ts b/packages/storage-compat/src/index.ts similarity index 90% rename from packages/storage/compat/index.ts rename to packages/storage-compat/src/index.ts index 88ac166e1f9..a4d7e913f3d 100644 --- a/packages/storage/compat/index.ts +++ b/packages/storage-compat/src/index.ts @@ -18,8 +18,11 @@ // eslint-disable-next-line import/no-extraneous-dependencies import firebase from '@firebase/app-compat'; import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { StringFormat } from '../src/implementation/string'; -import { TaskEvent, TaskState } from '../src/implementation/taskenums'; +import { + StringFormat, + _TaskEvent as TaskEvent, + _TaskState as TaskState +} from '@firebase/storage'; import { ReferenceCompat } from './reference'; import { StorageServiceCompat } from './service'; @@ -31,12 +34,12 @@ import { InstanceFactoryOptions } from '@firebase/component'; -import { name, version } from './package.json'; +import { name, version } from '../package.json'; /** * Type constant for Firebase Storage. */ -const STORAGE_TYPE = 'storage'; +const STORAGE_TYPE = 'storage-compat'; function factory( container: ComponentContainer, @@ -45,7 +48,7 @@ function factory( // Dependencies const app = container.getProvider('app-compat').getImmediate(); const storageExp = container - .getProvider('storage-exp') + .getProvider('storage') .getImmediate({ identifier: url }); const storageServiceCompat: StorageServiceCompat = new StorageServiceCompat( @@ -73,7 +76,7 @@ export function registerStorage(instance: _FirebaseNamespace): void { instance.registerVersion(name, version); } -registerStorage((firebase as unknown) as _FirebaseNamespace); +registerStorage(firebase as unknown as _FirebaseNamespace); /** * Define extension behavior for `registerStorage` diff --git a/packages/storage/compat/list.ts b/packages/storage-compat/src/list.ts similarity index 96% rename from packages/storage/compat/list.ts rename to packages/storage-compat/src/list.ts index da394d20849..60c6a3b34e1 100644 --- a/packages/storage/compat/list.ts +++ b/packages/storage-compat/src/list.ts @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ListResult } from '../exp/api'; +import { ListResult } from '@firebase/storage'; import * as types from '@firebase/storage-types'; import { ReferenceCompat } from './reference'; import { StorageServiceCompat } from './service'; diff --git a/packages/storage/compat/reference.ts b/packages/storage-compat/src/reference.ts similarity index 92% rename from packages/storage/compat/reference.ts rename to packages/storage-compat/src/reference.ts index d882642d6bf..ea2af1004fd 100644 --- a/packages/storage/compat/reference.ts +++ b/packages/storage-compat/src/reference.ts @@ -26,20 +26,22 @@ import { deleteObject, UploadTask, StringFormat, + UploadMetadata, + FullMetadata, + SettableMetadata, _UploadTask, _getChild, _Reference, - _FbsBlob -} from '../exp/api'; // import from the exp public API + _FbsBlob, + _dataFromString, + _invalidRootOperation +} from '@firebase/storage'; import { UploadTaskCompat } from './task'; import { ListResultCompat } from './list'; import { StorageServiceCompat } from './service'; import * as types from '@firebase/storage-types'; -import { Metadata } from '../src/metadata'; -import { dataFromString } from '../src/implementation/string'; -import { invalidRootOperation } from '../src/implementation/error'; import { Compat } from '@firebase/util'; export class ReferenceCompat @@ -104,7 +106,7 @@ export class ReferenceCompat ): types.UploadTask { this._throwIfRoot('put'); return new UploadTaskCompat( - uploadBytesResumable(this._delegate, data, metadata as Metadata), + uploadBytesResumable(this._delegate, data, metadata as UploadMetadata), this ); } @@ -119,11 +121,11 @@ export class ReferenceCompat putString( value: string, format: StringFormat = StringFormat.RAW, - metadata?: Metadata + metadata?: types.UploadMetadata ): types.UploadTask { this._throwIfRoot('putString'); - const data = dataFromString(format, value); - const metadataClone = { ...metadata } as Metadata; + const data = _dataFromString(format, value); + const metadataClone = { ...metadata }; if (metadataClone['contentType'] == null && data.contentType != null) { metadataClone['contentType'] = data.contentType; } @@ -131,7 +133,7 @@ export class ReferenceCompat new _UploadTask( this._delegate as _Reference, new _FbsBlob(data.data, true), - metadataClone + metadataClone as FullMetadata & { [k: string]: string } ) as UploadTask, this ); @@ -208,7 +210,7 @@ export class ReferenceCompat ): Promise { return updateMetadata( this._delegate, - metadata as Metadata + metadata as SettableMetadata ) as Promise; } @@ -231,7 +233,7 @@ export class ReferenceCompat private _throwIfRoot(name: string): void { if ((this._delegate as _Reference)._location.path === '') { - throw invalidRootOperation(name); + throw _invalidRootOperation(name); } } } diff --git a/packages/storage/compat/service.ts b/packages/storage-compat/src/service.ts similarity index 86% rename from packages/storage/compat/service.ts rename to packages/storage-compat/src/service.ts index 17a4cb952d4..a3e122efb5d 100644 --- a/packages/storage/compat/service.ts +++ b/packages/storage-compat/src/service.ts @@ -20,13 +20,13 @@ import { FirebaseApp } from '@firebase/app-types'; import { ref, - _Location, connectStorageEmulator, - FirebaseStorage -} from '../exp/api'; // import from the exp public API + FirebaseStorage, + _Location, + _invalidArgument, + _FirebaseStorageImpl +} from '@firebase/storage'; // import from the exp public API import { ReferenceCompat } from './reference'; -import { isUrl, FirebaseStorageImpl } from '../src/service'; -import { invalidArgument } from '../src/implementation/error'; import { Compat, EmulatorMockTokenOptions } from '@firebase/util'; /** @@ -52,7 +52,7 @@ export class StorageServiceCompat */ ref(path?: string): types.Reference { if (isUrl(path)) { - throw invalidArgument( + throw _invalidArgument( 'ref() expected a child path but got a URL, use refFromURL instead.' ); } @@ -65,14 +65,14 @@ export class StorageServiceCompat */ refFromURL(url: string): types.Reference { if (!isUrl(url)) { - throw invalidArgument( + throw _invalidArgument( 'refFromURL() expected a full URL but got a child path, use ref() instead.' ); } try { - _Location.makeFromUrl(url, (this._delegate as FirebaseStorageImpl).host); + _Location.makeFromUrl(url, (this._delegate as _FirebaseStorageImpl).host); } catch (e) { - throw invalidArgument( + throw _invalidArgument( 'refFromUrl() expected a valid full URL but got an invalid one.' ); } @@ -97,3 +97,7 @@ export class StorageServiceCompat connectStorageEmulator(this._delegate, host, port, options); } } + +function isUrl(path?: string): boolean { + return /^[A-Za-z]+:\/\//.test(path as string); +} diff --git a/packages/storage/compat/task.ts b/packages/storage-compat/src/task.ts similarity index 99% rename from packages/storage/compat/task.ts rename to packages/storage-compat/src/task.ts index a358c47c40a..13fcc6e8d03 100644 --- a/packages/storage/compat/task.ts +++ b/packages/storage-compat/src/task.ts @@ -21,7 +21,7 @@ import { UploadTaskSnapshot, TaskEvent, StorageObserver -} from '../exp/api'; +} from '@firebase/storage'; import { UploadTaskSnapshotCompat } from './tasksnapshot'; import { ReferenceCompat } from './reference'; import * as types from '@firebase/storage-types'; diff --git a/packages/storage/compat/tasksnapshot.ts b/packages/storage-compat/src/tasksnapshot.ts similarity index 95% rename from packages/storage/compat/tasksnapshot.ts rename to packages/storage-compat/src/tasksnapshot.ts index 4d092994199..9faaa3485e9 100644 --- a/packages/storage/compat/tasksnapshot.ts +++ b/packages/storage-compat/src/tasksnapshot.ts @@ -15,14 +15,15 @@ * limitations under the License. */ -import { UploadTaskSnapshot } from '../exp/api'; +import { UploadTaskSnapshot } from '@firebase/storage'; import { ReferenceCompat } from './reference'; import { UploadTaskCompat } from './task'; import * as types from '@firebase/storage-types'; import { Compat } from '@firebase/util'; export class UploadTaskSnapshotCompat - implements types.UploadTaskSnapshot, Compat { + implements types.UploadTaskSnapshot, Compat +{ constructor( readonly _delegate: UploadTaskSnapshot, readonly task: UploadTaskCompat, diff --git a/packages/storage/test/integration/integration.compat.test.ts b/packages/storage-compat/test/integration/integration.test.ts similarity index 96% rename from packages/storage/test/integration/integration.compat.test.ts rename to packages/storage-compat/test/integration/integration.test.ts index e2b5c6f8508..047410c65d0 100644 --- a/packages/storage/test/integration/integration.compat.test.ts +++ b/packages/storage-compat/test/integration/integration.test.ts @@ -15,15 +15,16 @@ * limitations under the License. */ -import firebase from '@firebase/app'; -import '@firebase/auth'; +import firebase from '@firebase/app-compat'; +// eslint-disable-next-line import/no-extraneous-dependencies +import '@firebase/auth-compat'; // See https://github.com/typescript-eslint/typescript-eslint/issues/363 // eslint-disable-next-line @typescript-eslint/no-unused-vars import * as storage from '@firebase/storage-types'; import { expect } from 'chai'; -import '../../index'; +import '../../src/index'; // eslint-disable-next-line @typescript-eslint/no-require-imports const PROJECT_CONFIG = require('../../../../config/project.json'); diff --git a/packages/storage-compat/test/setup.ts b/packages/storage-compat/test/setup.ts new file mode 100644 index 00000000000..22ed9b0d112 --- /dev/null +++ b/packages/storage-compat/test/setup.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2019 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 { use } from 'chai'; +import { restore } from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +use(chaiAsPromised); +use(sinonChai); + +afterEach(async () => { + restore(); +}); diff --git a/packages/storage/test/unit/index.compat.test.ts b/packages/storage-compat/test/unit/index.test.ts similarity index 84% rename from packages/storage/test/unit/index.compat.test.ts rename to packages/storage-compat/test/unit/index.test.ts index 451821ab2b5..d048407a0b8 100644 --- a/packages/storage/test/unit/index.compat.test.ts +++ b/packages/storage-compat/test/unit/index.test.ts @@ -14,12 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import '../setup'; import { expect } from 'chai'; -import '../../index'; -import firebase from '@firebase/app'; +import '../../src/index'; +import firebase from '@firebase/app-compat'; // eslint-disable-next-line import/no-extraneous-dependencies -import { StorageServiceCompat } from '../../compat/service'; -import { FirebaseStorageImpl } from '../../src/service'; +import { StorageServiceCompat } from '../../src/service'; +import { _FirebaseStorageImpl } from '@firebase/storage'; // eslint-disable-next-line @typescript-eslint/no-require-imports const PROJECT_CONFIG = require('../../../../config/project.json'); @@ -39,7 +40,7 @@ describe('Firebase Storage > API', () => { }); const storage = firebase.storage!(); expect( - ((storage as StorageServiceCompat)._delegate as FirebaseStorageImpl) + ((storage as StorageServiceCompat)._delegate as _FirebaseStorageImpl) ._bucket?.bucket ).to.equal(STORAGE_BUCKET); await app.delete(); @@ -53,7 +54,7 @@ describe('Firebase Storage > API', () => { }); const storage = firebase.storage!(app, 'gs://foo-bar.appspot.com'); expect( - ((storage as StorageServiceCompat)._delegate as FirebaseStorageImpl) + ((storage as StorageServiceCompat)._delegate as _FirebaseStorageImpl) ._bucket?.bucket ).to.equal(STORAGE_BUCKET); await app.delete(); diff --git a/packages/storage-compat/test/unit/reference.test.ts b/packages/storage-compat/test/unit/reference.test.ts new file mode 100644 index 00000000000..0d809a3c823 --- /dev/null +++ b/packages/storage-compat/test/unit/reference.test.ts @@ -0,0 +1,214 @@ +/** + * @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 '../setup'; +import { expect } from 'chai'; +import { ReferenceCompat } from '../../src/reference'; +import { StorageServiceCompat } from '../../src/service'; +import { makeTestCompatStorage, fakeApp, fakeStorage } from '../utils'; +import firebase from '@firebase/app-compat'; +import { + StorageReference, + getStorage, + FirebaseStorage +} from '@firebase/storage'; +import { fake } from 'sinon'; +import { FirebaseApp } from '@firebase/app-types'; +import { Reference } from '@firebase/storage-types'; + +describe('Firebase Storage > Reference', () => { + let testCompatApp: FirebaseApp; + let testModularStorage: FirebaseStorage; + let service: StorageServiceCompat; + before(() => { + testCompatApp = firebase.initializeApp({}); + testModularStorage = getStorage(testCompatApp); + service = makeTestCompatStorage(testCompatApp, testModularStorage); + }); + + after(() => { + return testCompatApp.delete(); + }); + describe('toString', () => { + it('delegates to the modular Reference.toString()', () => { + const fakeToString = fake.returns('test123'); + const ref = new ReferenceCompat( + { + toString: fakeToString + } as unknown as StorageReference, + makeTestCompatStorage(fakeApp, fakeStorage) + ); + + expect(ref.toString()).to.equal('test123'); + expect(fakeToString).to.have.been.calledOnceWithExactly(); + }); + }); + + describe('parent', () => { + it('Returns null at root', () => { + const root = service.refFromURL('gs://test-bucket'); + expect(root.parent).to.be.null; + }); + it('Returns root one level down', () => { + const child = service.refFromURL('gs://test-bucket/hello'); + expect(child.parent!.toString()).to.equal('gs://test-bucket/'); + }); + it('Works correctly with empty levels', () => { + const s = service.refFromURL('gs://test-bucket/a///'); + expect(s.parent!.toString()).to.equal('gs://test-bucket/a/'); + }); + }); + + describe('root', () => { + it('Returns self at root', () => { + const root = service.refFromURL('gs://test-bucket'); + expect(root.root.toString()).to.equal('gs://test-bucket/'); + }); + + it('Returns root multiple levels down', () => { + const s = service.refFromURL('gs://test-bucket/a/b/c/d'); + expect(s.root.toString()).to.equal('gs://test-bucket/'); + }); + }); + + describe('bucket', () => { + it('Returns bucket name', () => { + const root = service.refFromURL('gs://test-bucket'); + expect(root.bucket).to.equal('test-bucket'); + }); + }); + + describe('fullPath', () => { + it('Returns full path without leading slash', () => { + const s = service.refFromURL('gs://test-bucket/full/path'); + expect(s.fullPath).to.equal('full/path'); + }); + }); + + describe('name', () => { + it('Works at top level', () => { + const s = service.refFromURL('gs://test-bucket/toplevel.txt'); + expect(s.name).to.equal('toplevel.txt'); + }); + + it('Works at not the top level', () => { + const s = service.refFromURL('gs://test-bucket/not/toplevel.txt'); + expect('toplevel.txt').to.equal(s.name); + }); + }); + + describe('child', () => { + let root: Reference; + before(() => { + root = service.refFromURL('gs://test-bucket'); + }); + it('works with a simple string', () => { + expect(root.child('a').toString()).to.equal('gs://test-bucket/a'); + }); + it('drops a trailing slash', () => { + expect(root.child('ab/').toString()).to.equal('gs://test-bucket/ab'); + }); + it('compresses repeated slashes', () => { + expect(root.child('//a///b/////').toString()).to.equal( + 'gs://test-bucket/a/b' + ); + }); + it('works chained multiple times with leading slashes', () => { + expect( + root.child('a').child('/b').child('c').child('d/e').toString() + ).to.equal('gs://test-bucket/a/b/c/d/e'); + }); + }); + + describe('putString', () => { + let child: Reference; + before(() => { + child = service.refFromURL('gs://test-bucket/hello'); + }); + it('Uses metadata.contentType for RAW format', () => { + // Regression test for b/30989476 + const task = child.putString('hello', 'raw', { + contentType: 'lol/wut' + }); + expect(task.snapshot.metadata!.contentType).to.equal('lol/wut'); + task.cancel(); + }); + it('Uses embedded content type in DATA_URL format', () => { + const task = child.putString('data:lol/wat;base64,aaaa', 'data_url'); + expect(task.snapshot.metadata!.contentType).to.equal('lol/wat'); + task.cancel(); + }); + it('Lets metadata.contentType override embedded content type in DATA_URL format', () => { + const task = child.putString('data:ignore/me;base64,aaaa', 'data_url', { + contentType: 'tomato/soup' + }); + expect(task.snapshot.metadata!.contentType).to.equal('tomato/soup'); + task.cancel(); + }); + }); + + describe('Argument verification', () => { + describe('list', () => { + it('throws on invalid maxResults', async () => { + const child = service.refFromURL('gs://test-bucket/hello'); + await expect(child.list({ maxResults: 0 })).to.be.rejectedWith( + 'storage/invalid-argument' + ); + await expect(child.list({ maxResults: -4 })).to.be.rejectedWith( + 'storage/invalid-argument' + ); + await expect(child.list({ maxResults: 1001 })).to.be.rejectedWith( + 'storage/invalid-argument' + ); + }); + }); + }); + + describe('root operations', () => { + let root: Reference; + before(() => { + root = service.refFromURL('gs://test-bucket'); + }); + it('put throws', () => { + expect(() => root.put(new Uint8Array())).to.throw( + 'storage/invalid-root-operation' + ); + }); + it('putString throws', () => { + expect(() => root.putString('raw', 'raw')).to.throw( + 'storage/invalid-root-operation' + ); + }); + it('delete throws', () => { + expect(() => root.delete()).to.throw('storage/invalid-root-operation'); + }); + it('getMetadata throws', async () => { + await expect(root.getMetadata()).to.be.rejectedWith( + 'storage/invalid-root-operation' + ); + }); + it('updateMetadata throws', async () => { + await expect(root.updateMetadata({})).to.be.rejectedWith( + 'storage/invalid-root-operation' + ); + }); + it('getDownloadURL throws', async () => { + await expect(root.getDownloadURL()).to.be.rejectedWith( + 'storage/invalid-root-operation' + ); + }); + }); +}); diff --git a/packages/storage-compat/test/unit/service.test.ts b/packages/storage-compat/test/unit/service.test.ts new file mode 100644 index 00000000000..5059052218b --- /dev/null +++ b/packages/storage-compat/test/unit/service.test.ts @@ -0,0 +1,234 @@ +/** + * @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 '../setup'; +import { expect } from 'chai'; +import { stub } from 'sinon'; +import * as modularStorage from '@firebase/storage'; +import { makeTestCompatStorage, fakeApp, fakeStorage } from '../utils'; +import { + FirebaseStorage, + getStorage, + FirebaseStorageError +} from '@firebase/storage'; +import firebase from '@firebase/app-compat'; +import { StorageServiceCompat } from '../../src/service'; +import { FirebaseApp } from '@firebase/app-types'; + +const DEFAULT_HOST = 'firebasestorage.googleapis.com'; + +describe('Firebase Storage > Service', () => { + describe('useEmulator(host, port)', () => { + it('calls connectStorageEmulator() correctly', () => { + const connectStorageEmulatorStub = stub( + modularStorage, + 'connectStorageEmulator' + ).callsFake(() => {}); + const service = makeTestCompatStorage(fakeApp, fakeStorage); + service.useEmulator('test.host.org', 1234); + + expect(connectStorageEmulatorStub).to.have.been.calledWithExactly( + fakeStorage, + 'test.host.org', + 1234, + {} + ); + }); + }); + + describe('refFromURL', () => { + let service: StorageServiceCompat; + let testCompatApp: FirebaseApp; + let testModularStorage: FirebaseStorage; + before(() => { + testCompatApp = firebase.initializeApp({}); + testModularStorage = getStorage(testCompatApp); + service = makeTestCompatStorage(testCompatApp, testModularStorage); + }); + + after(() => { + return testCompatApp.delete(); + }); + + it('Works with gs:// URLs', () => { + const ref = service.refFromURL('gs://mybucket/child/path/image.png'); + expect(ref.toString()).to.equal('gs://mybucket/child/path/image.png'); + }); + it('Works with http:// URLs', () => { + const ref = service.refFromURL( + `http://${DEFAULT_HOST}/v0/b/` + + 'mybucket/o/child%2Fpath%2Fimage.png?downloadToken=hello' + ); + expect(ref.toString()).to.equal('gs://mybucket/child/path/image.png'); + }); + it('Works with https:// URLs', () => { + const ref = service.refFromURL( + `https://${DEFAULT_HOST}/v0/b/` + + 'mybucket/o/child%2Fpath%2Fimage.png?downloadToken=hello' + ); + expect(ref.toString()).to.equal('gs://mybucket/child/path/image.png'); + }); + it('Works with storage.googleapis.com URLs', () => { + const ref = service.refFromURL( + `https://storage.googleapis.com/mybucket/path%20with%20space/image.png` + ); + expect(ref.toString()).to.equal( + 'gs://mybucket/path with space/image.png' + ); + }); + it('Works with storage.googleapis.com URLs with query params', () => { + const ref = service.refFromURL( + `https://storage.googleapis.com/mybucket/path%20with%20space/image.png?X-Goog-Algorithm= +GOOG4-RSA-SHA256` + ); + expect(ref.toString()).to.equal( + 'gs://mybucket/path with space/image.png' + ); + }); + it('Works with storage.cloud.google.com URLs', () => { + const ref = service.refFromURL( + `https://storage.cloud.google.com/mybucket/path%20with%20space/image.png` + ); + expect(ref.toString()).to.equal( + 'gs://mybucket/path with space/image.png' + ); + }); + it('Works with storage.cloud.google.com URLs and escaped slash', () => { + const ref = service.refFromURL( + `https://storage.cloud.google.com/mybucket/path%20with%20space%2Fimage.png` + ); + expect(ref.toString()).to.equal( + 'gs://mybucket/path with space/image.png' + ); + }); + }); + + describe('Argument verification', () => { + let service: StorageServiceCompat; + let testCompatApp: FirebaseApp; + let testModularStorage: FirebaseStorage; + before(() => { + testCompatApp = firebase.initializeApp({}); + testModularStorage = getStorage(testCompatApp); + service = makeTestCompatStorage(testCompatApp, testModularStorage); + }); + + after(() => { + return testCompatApp.delete(); + }); + + describe('ref', () => { + it('Throws on gs:// argument', () => { + expect(() => service.ref('gs://yo')).to.throw( + 'storage/invalid-argument' + ); + }); + }); + + describe('refFromURL', () => { + it('Throws with a non-URL string arg', () => { + expect(() => { + service.refFromURL('child'); + }).to.throw( + /expected a full URL but got a child path.*storage\/invalid-argument/i + ); + }); + it('Throws with an invalid URL arg', () => { + expect(() => { + service.refFromURL('notlegit://url'); + }).to.throw('storage/invalid-argument'); + }); + }); + + describe('MaxUploadRetryTime', () => { + const modularStorage = {} as FirebaseStorage; + const service = makeTestCompatStorage(fakeApp, modularStorage); + it('reads from the modular instance', () => { + modularStorage.maxUploadRetryTime = 999; + expect(service.maxUploadRetryTime).to.equal(999); + }); + + it('sets value on the modular instance', () => { + service.setMaxUploadRetryTime(888); + expect(modularStorage.maxUploadRetryTime).to.equal(888); + }); + }); + describe('MaxOperationRetryTime', () => { + const modularStorage = {} as FirebaseStorage; + const service = makeTestCompatStorage(fakeApp, modularStorage); + it('reads from the modular instance', () => { + modularStorage.maxOperationRetryTime = 999; + expect(service.maxOperationRetryTime).to.equal(999); + }); + + it('sets value on the modular instance', () => { + service.setMaxOperationRetryTime(888); + expect(modularStorage.maxOperationRetryTime).to.equal(888); + }); + }); + }); + + describe('Deletion', () => { + let service: StorageServiceCompat; + let testCompatApp: FirebaseApp; + let testModularStorage: FirebaseStorage; + before(() => { + testCompatApp = firebase.initializeApp({}); + testModularStorage = getStorage(testCompatApp); + service = makeTestCompatStorage(testCompatApp, testModularStorage); + }); + + after(() => { + return testCompatApp.delete(); + }); + + it('In-flight requests are canceled when the service is deleted', async () => { + const ref = service.refFromURL('gs://mybucket/image.jpg'); + const metadataPromise = ref.getMetadata(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + service._delegate._delete(); + await expect(metadataPromise).to.be.rejectedWith('storage/app-deleted'); + }); + it('Requests fail when started after the service is deleted', async () => { + const ref = service.refFromURL('gs://mybucket/image.jpg'); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + service._delegate._delete(); + + await expect(ref.getMetadata()).to.be.rejectedWith('storage/app-deleted'); + }); + it('Running uploads fail when the service is deleted', () => { + const ref = service.refFromURL('gs://mybucket/image.jpg'); + const toReturn = new Promise((resolve, reject) => { + ref.put(new Uint8Array([97])).on( + 'state_changed', + null, + (err: FirebaseStorageError | Error) => { + expect((err as FirebaseStorageError).code).to.equal( + 'storage/app-deleted' + ); + resolve(); + }, + () => { + reject('Upload completed, should have been canceled'); + } + ); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + service._delegate._delete(); + }); + return toReturn; + }); + }); +}); diff --git a/packages/storage-compat/test/utils.ts b/packages/storage-compat/test/utils.ts new file mode 100644 index 00000000000..18da96ea996 --- /dev/null +++ b/packages/storage-compat/test/utils.ts @@ -0,0 +1,34 @@ +/** + * @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. + */ + +import { FirebaseApp } from '@firebase/app-types'; +import { FirebaseStorage } from '@firebase/storage'; +import { StorageServiceCompat } from '../src/service'; + +export function makeTestCompatStorage( + app: FirebaseApp, + storage: FirebaseStorage +): StorageServiceCompat { + const storageServiceCompat: StorageServiceCompat = new StorageServiceCompat( + app, + storage + ); + return storageServiceCompat; +} + +export const fakeApp = {} as FirebaseApp; +export const fakeStorage = {} as FirebaseStorage; diff --git a/packages/storage-compat/tsconfig.json b/packages/storage-compat/tsconfig.json new file mode 100644 index 00000000000..a06ed9a374c --- /dev/null +++ b/packages/storage-compat/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist" + }, + "exclude": [ + "dist/**/*" + ] +} \ No newline at end of file diff --git a/packages/storage-types/index.d.ts b/packages/storage-types/index.d.ts index 7707043777b..c6a8fc7178d 100644 --- a/packages/storage-types/index.d.ts +++ b/packages/storage-types/index.d.ts @@ -153,6 +153,6 @@ export class FirebaseStorage { declare module '@firebase/component' { interface NameServiceMapping { - 'storage': FirebaseStorage; + 'storage-compat': FirebaseStorage; } } diff --git a/packages/storage/.eslintrc.js b/packages/storage/.eslintrc.js index 00bf88df31c..ffe0e481071 100644 --- a/packages/storage/.eslintrc.js +++ b/packages/storage/.eslintrc.js @@ -26,23 +26,12 @@ module.exports = { tsconfigRootDir: __dirname }, rules: { - 'no-throw-literal': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { varsIgnorePattern: '^_', args: 'none' } - ], - 'import/no-extraneous-dependencies': [ - 'error', - { - 'packageDir': [ - path.resolve(__dirname, '../../'), - __dirname, - path.resolve(__dirname, 'exp') - ] - } ] } }; diff --git a/packages/storage/.npmignore b/packages/storage/.npmignore deleted file mode 100644 index 682c8f74a52..00000000000 --- a/packages/storage/.npmignore +++ /dev/null @@ -1,9 +0,0 @@ -# Directories not needed by end users -/src -test - -# Files not needed by end users -gulpfile.js -index.ts -karma.conf.js -tsconfig.json \ No newline at end of file diff --git a/packages/storage/api-extractor.json b/packages/storage/api-extractor.json index c85607441f0..8a3c6cb251e 100644 --- a/packages/storage/api-extractor.json +++ b/packages/storage/api-extractor.json @@ -1,10 +1,10 @@ { "extends": "../../config/api-extractor.json", // Point it to your entry point d.ts file. - "mainEntryPointFilePath": "/exp/dist/exp/index.d.ts", + "mainEntryPointFilePath": "/dist/src/index.d.ts", "dtsRollup": { "enabled": true, - "untrimmedFilePath": "/exp/dist/.d.ts", - "publicTrimmedFilePath": "/exp/dist/-public.d.ts" + "untrimmedFilePath": "/dist/.d.ts", + "publicTrimmedFilePath": "/dist/-public.d.ts" } } \ No newline at end of file diff --git a/packages/storage/compat/package.json b/packages/storage/compat/package.json deleted file mode 100644 index 2c149a59ccb..00000000000 --- a/packages/storage/compat/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@firebase/storage-compat", - "version": "0.0.900", - "description": "The Cloud Storage component of the Firebase JS SDK.", - "author": "Firebase (https://firebase.google.com/)", - "main": "../dist/compat/cjs/index.js", - "browser": "../dist/compat/esm2017/index.js", - "module": "../dist/compat/esm2017/index.js", - "esm5": "../dist/compat/esm5/index.js", - "license": "Apache-2.0", - "typings": "../dist/compat/esm2017/compat/index.d.ts" - } - \ No newline at end of file diff --git a/packages/storage/exp/package.json b/packages/storage/exp/package.json deleted file mode 100644 index 896db7ac0c1..00000000000 --- a/packages/storage/exp/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@firebase/storage-exp", - "description": "A tree-shakeable version of the Storage SDK", - "main": "./dist/index.node.cjs.js", - "module": "./dist/index.browser.esm2017.js", - "browser": "./dist/index.browser.esm2017.js", - "esm5": "./dist/index.browser.esm5.js", - "typings": "./dist/storage-public.d.ts", - "private": true -} diff --git a/packages/storage/index.ts b/packages/storage/index.ts deleted file mode 100644 index 8128907d79b..00000000000 --- a/packages/storage/index.ts +++ /dev/null @@ -1,88 +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 firebase from '@firebase/app'; -import { _FirebaseNamespace } from '@firebase/app-types/private'; -import { StringFormat } from './src/implementation/string'; -import { TaskEvent, TaskState } from './src/implementation/taskenums'; - -import { ConnectionPool } from './src/implementation/connectionPool'; -import { ReferenceCompat } from './compat/reference'; -import { StorageServiceCompat } from './compat/service'; -import { FirebaseStorageImpl } from './src/service'; -import * as types from '@firebase/storage-types'; -import { - Component, - ComponentType, - ComponentContainer, - InstanceFactoryOptions -} from '@firebase/component'; - -import { name, version } from './package.json'; - -import './register-module'; - -/** - * Type constant for Firebase Storage. - */ -const STORAGE_TYPE = 'storage'; - -function factory( - container: ComponentContainer, - { instanceIdentifier: url }: InstanceFactoryOptions -): types.FirebaseStorage { - // Dependencies - // TODO: This should eventually be 'app-compat' - const app = container.getProvider('app').getImmediate(); - const authProvider = container.getProvider('auth-internal'); - const appCheckProvider = container.getProvider('app-check-internal'); - - // TODO: get StorageService instance from component framework instead - // of creating a new one. - const storageServiceCompat: StorageServiceCompat = new StorageServiceCompat( - app, - new FirebaseStorageImpl( - app, - authProvider, - appCheckProvider, - new ConnectionPool(), - url, - firebase.SDK_VERSION - ) - ); - return storageServiceCompat; -} - -export function registerStorage(instance: _FirebaseNamespace): void { - const namespaceExports = { - // no-inline - TaskState, - TaskEvent, - StringFormat, - Storage: FirebaseStorageImpl, - Reference: ReferenceCompat - }; - instance.INTERNAL.registerComponent( - new Component(STORAGE_TYPE, factory, ComponentType.PUBLIC) - .setServiceProps(namespaceExports) - .setMultipleInstances(true) - ); - - instance.registerVersion(name, version); -} - -registerStorage(firebase as _FirebaseNamespace); diff --git a/packages/storage/karma.conf.js b/packages/storage/karma.conf.js index ab37877fc20..3b5d7e3f39b 100644 --- a/packages/storage/karma.conf.js +++ b/packages/storage/karma.conf.js @@ -32,20 +32,8 @@ module.exports = function (config) { function getTestFiles(argv) { let unitTestFiles = ['test/unit/*']; - let integrationTestFiles = []; - if (argv.exp) { - unitTestFiles = unitTestFiles.filter( - filename => !filename.includes('.compat.') - ); - integrationTestFiles = ['test/integration/*exp*']; - } else if (argv.compat) { - unitTestFiles = unitTestFiles.filter( - filename => !filename.includes('.exp.') - ); - integrationTestFiles = ['test/integration/*compat*']; - } else { - integrationTestFiles = ['test/integration/*']; - } + let integrationTestFiles = ['test/integration/*']; + if (argv.unit) { return unitTestFiles; } else if (argv.integration) { diff --git a/packages/storage/package.json b/packages/storage/package.json index 1a58efaba03..6b64a3c3eb5 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -3,50 +3,39 @@ "version": "0.7.0", "description": "", "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.node.cjs.js", - "module": "dist/index.browser.esm.js", - "browser": "dist/index.browser.esm.js", - "esm2017": "dist/index.browser.esm2017.js", + "main": "dist/index.cjs.js", + "module": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "esm5": "dist/index.esm5.js", "files": [ - "dist", - "exp/dist" + "dist" ], "scripts": { "bundle": "rollup -c", "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "run-p 'bundle rollup.config.js' build:exp && yarn build:compat", - "build:exp": "rollup -c rollup.config.exp.js ; yarn api-report", - "build:compat": "rollup -c rollup.config.compat.js && yarn add-compat-overloads", - "build:exp:release": "yarn build:exp && yarn build:compat", + "build": "rollup -c rollup.config.js && yarn api-report", "build:deps": "lerna run --scope @firebase/storage --include-dependencies build", "dev": "rollup -c -w", "test": "run-p test:browser test:node lint", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser test:node", - "test:browser:compat:unit": "karma start --single-run --compat --unit", - "test:browser:exp:unit": "karma start --single-run --exp --unit", - "test:browser:compat:integration": "karma start --single-run --compat --integration", - "test:browser:exp:integration": "karma start --single-run --exp --integration", - "test:browser:compat": "karma start --single-run --compat", - "test:browser:exp": "karma start --single-run --exp", + "test:browser:unit": "karma start --single-run --unit", + "test:browser:integration": "karma start --single-run --integration", "test:browser": "karma start --single-run", - "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file index.ts --config ../../config/mocharc.node.js", + "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file src/index.ts --config ../../config/mocharc.node.js", "test:debug": "karma start --browser=Chrome", "prettier": "prettier --write 'src/**/*.ts' 'test/**/*.ts'", - "api-report": "api-extractor run --local --verbose && ts-node-script ../../repo-scripts/prune-dts/prune-dts.ts --input exp/dist/storage-public.d.ts --output exp/dist/storage-public.d.ts", - "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i exp/dist/storage-public.d.ts -o dist/compat/esm2017/compat/index.d.ts -a -r StorageService:types.FirebaseStorage -r StorageReference:types.Reference -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/storage" + "api-report": "api-extractor run --local --verbose && ts-node-script ../../repo-scripts/prune-dts/prune-dts.ts --input dist/storage-public.d.ts --output dist/storage-public.d.ts" }, "license": "Apache-2.0", "dependencies": { - "@firebase/storage-types": "0.5.0", "@firebase/util": "1.3.0", "@firebase/component": "0.5.6", "node-fetch": "2.6.1", "tslib": "^2.1.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" }, "devDependencies": { "@firebase/app": "0.6.30", @@ -65,5 +54,5 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/index.d.ts" + "typings": "dist/src/index.d.ts" } diff --git a/packages/storage/register-module.ts b/packages/storage/register-module.ts deleted file mode 100644 index 23d09759347..00000000000 --- a/packages/storage/register-module.ts +++ /dev/null @@ -1,50 +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 * as types from '@firebase/storage-types'; - -/** - * Define extension behavior for `registerStorage` - */ -declare module '@firebase/app-types' { - interface FirebaseNamespace { - storage?: { - (app?: FirebaseApp, url?: string): types.FirebaseStorage; - Storage: typeof types.FirebaseStorage; - - StringFormat: { - BASE64: types.StringFormat; - BASE64URL: types.StringFormat; - DATA_URL: types.StringFormat; - RAW: types.StringFormat; - }; - TaskEvent: { - STATE_CHANGED: types.TaskEvent; - }; - TaskState: { - CANCELED: types.TaskState; - ERROR: types.TaskState; - PAUSED: types.TaskState; - RUNNING: types.TaskState; - SUCCESS: types.TaskState; - }; - }; - } - interface FirebaseApp { - storage?(storageBucket?: string): types.FirebaseStorage; - } -} diff --git a/packages/storage/rollup.config.exp.js b/packages/storage/rollup.config.exp.js deleted file mode 100644 index 0a6c310656a..00000000000 --- a/packages/storage/rollup.config.exp.js +++ /dev/null @@ -1,110 +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 json from '@rollup/plugin-json'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import pkgExp from './exp/package.json'; -import alias from '@rollup/plugin-alias'; -import pkg from './package.json'; -import path from 'path'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; - -const { generateAliasConfig } = require('./rollup.shared'); - -const deps = [ - ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), - '@firebase/app' -]; - -const nodeDeps = [...deps, 'util']; - -const es5Plugins = [ - typescriptPlugin({ - typescript, - abortOnError: false, - transformers: [importPathTransformer] - }), - json() -]; - -const es5Builds = [ - // Browser - { - input: './exp/index.ts', - output: { - file: path.resolve('./exp', pkgExp.esm5), - format: 'es', - sourcemap: true - }, - plugins: [alias(generateAliasConfig('browser')), ...es5Plugins], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } - } -]; - -const es2017Plugins = [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - abortOnError: false, - transformers: [importPathTransformer] - }), - json({ preferConst: true }) -]; - -const es2017Builds = [ - // Node - { - input: './exp/index.ts', - output: { - file: path.resolve('./exp', pkgExp.main), - format: 'cjs', - sourcemap: true - }, - plugins: [alias(generateAliasConfig('node')), ...es2017Plugins], - external: id => - nodeDeps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } - }, - - // Browser - { - input: './exp/index.ts', - output: { - file: path.resolve('./exp', pkgExp.browser), - format: 'es', - sourcemap: true - }, - plugins: [alias(generateAliasConfig('browser')), ...es2017Plugins], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - treeshake: { - moduleSideEffects: false - } - } -]; - -// eslint-disable-next-line import/no-default-export -export default [...es5Builds, ...es2017Builds]; diff --git a/packages/storage/rollup.config.js b/packages/storage/rollup.config.js index fcb28940b0c..f9c66fc7432 100644 --- a/packages/storage/rollup.config.js +++ b/packages/storage/rollup.config.js @@ -21,7 +21,16 @@ import typescript from 'typescript'; import alias from '@rollup/plugin-alias'; import pkg from './package.json'; -const { generateAliasConfig } = require('./rollup.shared'); +function generateAliasConfig(platform) { + return { + entries: [ + { + find: /^(.*)\/platform\/([^.\/]*)(\.ts)?$/, + replacement: `$1\/platform/${platform}/$2.ts` + } + ] + }; +} const deps = Object.keys( Object.assign({}, pkg.peerDependencies, pkg.dependencies) @@ -29,24 +38,23 @@ const deps = Object.keys( const nodeDeps = [...deps, 'util']; -/** - * ES5 Builds - */ -const es5BuildPlugins = [ +const es5Plugins = [ typescriptPlugin({ - typescript + typescript, + abortOnError: false }), json() ]; const es5Builds = [ + // Browser { - input: './index.ts', + input: './src/index.ts', output: [ { file: 'dist/index.browser.cjs.js', format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true } + { file: pkg.esm5, format: 'es', sourcemap: true } ], - plugins: [alias(generateAliasConfig('browser')), ...es5BuildPlugins], + plugins: [alias(generateAliasConfig('browser')), ...es5Plugins], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), treeshake: { moduleSideEffects: false @@ -54,17 +62,15 @@ const es5Builds = [ } ]; -/** - * ES2017 Builds - */ -const es2017BuildPlugins = [ +const es2017Plugins = [ typescriptPlugin({ typescript, tsconfigOverride: { compilerOptions: { target: 'es2017' } - } + }, + abortOnError: false }), json({ preferConst: true }) ]; @@ -72,28 +78,29 @@ const es2017BuildPlugins = [ const es2017Builds = [ // Node { - input: './index.ts', + input: './src/index.ts', output: { file: pkg.main, format: 'cjs', sourcemap: true }, - plugins: [alias(generateAliasConfig('node')), ...es2017BuildPlugins], + plugins: [alias(generateAliasConfig('node')), ...es2017Plugins], external: id => nodeDeps.some(dep => id === dep || id.startsWith(`${dep}/`)), treeshake: { moduleSideEffects: false } }, + // Browser { - input: './index.ts', + input: './src/index.ts', output: { - file: pkg.esm2017, + file: pkg.browser, format: 'es', sourcemap: true }, - plugins: [alias(generateAliasConfig('browser')), ...es2017BuildPlugins], + plugins: [alias(generateAliasConfig('browser')), ...es2017Plugins], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), treeshake: { moduleSideEffects: false diff --git a/packages/storage/exp/api.ts b/packages/storage/src/api.ts similarity index 91% rename from packages/storage/exp/api.ts rename to packages/storage/src/api.ts index 171d7641e19..b729946c7a3 100644 --- a/packages/storage/exp/api.ts +++ b/packages/storage/src/api.ts @@ -21,7 +21,7 @@ import { ref as refInternal, FirebaseStorageImpl, connectStorageEmulator as connectEmulatorInternal -} from '../src/service'; +} from './service'; import { Provider } from '@firebase/component'; import { @@ -35,7 +35,7 @@ import { UploadMetadata, FullMetadata } from './public-types'; -import { Metadata as MetadataInternal } from '../src/metadata'; +import { Metadata as MetadataInternal } from './metadata'; import { uploadBytes as uploadBytesInternal, uploadBytesResumable as uploadBytesResumableInternal, @@ -48,7 +48,7 @@ import { deleteObject as deleteObjectInternal, Reference, _getChild as _getChildInternal -} from '../src/reference'; +} from './reference'; import { STORAGE_TYPE } from './constants'; import { EmulatorMockTokenOptions, getModularInstance } from '@firebase/util'; @@ -57,10 +57,20 @@ import { EmulatorMockTokenOptions, getModularInstance } from '@firebase/util'; */ export * from './public-types'; -export { Location as _Location } from '../src/implementation/location'; -export { UploadTask as _UploadTask } from '../src/task'; -export type { Reference as _Reference } from '../src/reference'; -export { FbsBlob as _FbsBlob } from '../src/implementation/blob'; +export { Location as _Location } from './implementation/location'; +export { UploadTask as _UploadTask } from './task'; +export type { Reference as _Reference } from './reference'; +export type { FirebaseStorageImpl as _FirebaseStorageImpl } from './service'; +export { FbsBlob as _FbsBlob } from './implementation/blob'; +export { dataFromString as _dataFromString } from './implementation/string'; +export { + invalidRootOperation as _invalidRootOperation, + invalidArgument as _invalidArgument +} from './implementation/error'; +export { + TaskEvent as _TaskEvent, + TaskState as _TaskState +} from './implementation/taskenums'; /** * Uploads data to this object's location. @@ -277,7 +287,7 @@ export function _getChild(ref: StorageReference, childPath: string): Reference { return _getChildInternal(ref as Reference, childPath); } -export { StringFormat } from '../src/implementation/string'; +export { StringFormat } from './implementation/string'; /** * Gets a {@link FirebaseStorage} instance for the given Firebase app. @@ -292,10 +302,7 @@ export function getStorage( bucketUrl?: string ): FirebaseStorage { app = getModularInstance(app); - const storageProvider: Provider<'storage-exp'> = _getProvider( - app, - STORAGE_TYPE - ); + const storageProvider: Provider<'storage'> = _getProvider(app, STORAGE_TYPE); const storageInstance = storageProvider.getImmediate({ identifier: bucketUrl }); diff --git a/packages/storage/exp/constants.ts b/packages/storage/src/constants.ts similarity index 93% rename from packages/storage/exp/constants.ts rename to packages/storage/src/constants.ts index a14f489490e..21ea91291b3 100644 --- a/packages/storage/exp/constants.ts +++ b/packages/storage/src/constants.ts @@ -18,4 +18,4 @@ /** * Type constant for Firebase Storage. */ -export const STORAGE_TYPE = 'storage-exp'; +export const STORAGE_TYPE = 'storage'; diff --git a/packages/storage/src/implementation/error.ts b/packages/storage/src/implementation/error.ts index 41a29fd5dff..a603237b455 100644 --- a/packages/storage/src/implementation/error.ts +++ b/packages/storage/src/implementation/error.ts @@ -250,6 +250,9 @@ export function noDownloadURL(): FirebaseStorageError { ); } +/** + * @internal + */ export function invalidArgument(message: string): FirebaseStorageError { return new FirebaseStorageError(StorageErrorCode.INVALID_ARGUMENT, message); } @@ -292,6 +295,8 @@ export function appDeleted(): FirebaseStorageError { /** * @param name - The name of the operation that was invalid. + * + * @internal */ export function invalidRootOperation(name: string): FirebaseStorageError { return new FirebaseStorageError( diff --git a/packages/storage/src/implementation/observer.ts b/packages/storage/src/implementation/observer.ts index 5c480758ca3..d1ab432610d 100644 --- a/packages/storage/src/implementation/observer.ts +++ b/packages/storage/src/implementation/observer.ts @@ -81,8 +81,8 @@ export class Observer implements StorageObserver { isFunction(nextOrObserver) || error != null || complete != null; if (asFunctions) { this.next = nextOrObserver as NextFn; - this.error = error; - this.complete = complete; + this.error = error ?? undefined; + this.complete = complete ?? undefined; } else { const observer = nextOrObserver as { next?: NextFn; diff --git a/packages/storage/src/implementation/string.ts b/packages/storage/src/implementation/string.ts index ea9ba2dc275..8497190389f 100644 --- a/packages/storage/src/implementation/string.ts +++ b/packages/storage/src/implementation/string.ts @@ -70,6 +70,9 @@ export class StringData { } } +/** + * @internal + */ export function dataFromString( format: StringFormat, stringData: string diff --git a/packages/storage/src/implementation/taskenums.ts b/packages/storage/src/implementation/taskenums.ts index 735474ff974..c57c58a9f45 100644 --- a/packages/storage/src/implementation/taskenums.ts +++ b/packages/storage/src/implementation/taskenums.ts @@ -59,8 +59,9 @@ export const enum InternalTaskState { /** * Represents the current state of a running upload. */ -export type TaskState = string; +export type TaskState = typeof TaskState[keyof typeof TaskState]; +// type keys = keyof TaskState /** * Represents the current state of a running upload. */ @@ -79,7 +80,7 @@ export const TaskState = { /** The task failed with an error. */ ERROR: 'error' -}; +} as const; export function taskStateFromInternalTaskState( state: InternalTaskState diff --git a/packages/storage/exp/index.ts b/packages/storage/src/index.ts similarity index 100% rename from packages/storage/exp/index.ts rename to packages/storage/src/index.ts diff --git a/packages/storage/src/metadata.ts b/packages/storage/src/metadata.ts index 61af339068f..f91fd73a519 100644 --- a/packages/storage/src/metadata.ts +++ b/packages/storage/src/metadata.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FullMetadata } from '../exp/public-types'; +import { FullMetadata } from './public-types'; /** * @fileoverview Documentation for the metadata format. diff --git a/packages/storage/exp/public-types.ts b/packages/storage/src/public-types.ts similarity index 99% rename from packages/storage/exp/public-types.ts rename to packages/storage/src/public-types.ts index 210e096d23e..39b6276ccda 100644 --- a/packages/storage/exp/public-types.ts +++ b/packages/storage/src/public-types.ts @@ -487,6 +487,6 @@ export interface UploadResult { declare module '@firebase/component' { interface NameServiceMapping { - 'storage-exp': FirebaseStorage; + 'storage': FirebaseStorage; } } diff --git a/packages/storage/src/reference.ts b/packages/storage/src/reference.ts index cd237acd80c..d925ba2091a 100644 --- a/packages/storage/src/reference.ts +++ b/packages/storage/src/reference.ts @@ -31,7 +31,7 @@ import { deleteObject as requestsDeleteObject, multipartUpload } from './implementation/requests'; -import { ListOptions } from '../exp/public-types'; +import { ListOptions, UploadResult } from './public-types'; import { StringFormat, dataFromString } from './implementation/string'; import { Metadata } from './metadata'; import { FirebaseStorageImpl } from './service'; @@ -39,7 +39,6 @@ import { ListResult } from './list'; import { UploadTask } from './task'; import { invalidRootOperation, noDownloadURL } from './implementation/error'; import { validateNumber } from './implementation/type'; -import { UploadResult } from './tasksnapshot'; /** * Provides methods to interact with a bucket in the Firebase Storage service. diff --git a/packages/storage/src/service.ts b/packages/storage/src/service.ts index af3e3df2f73..0848a2262b3 100644 --- a/packages/storage/src/service.ts +++ b/packages/storage/src/service.ts @@ -38,7 +38,7 @@ import { noDefaultBucket } from './implementation/error'; import { validateNumber } from './implementation/type'; -import { FirebaseStorage } from '../exp/public-types'; +import { FirebaseStorage } from './public-types'; import { createMockUserToken, EmulatorMockTokenOptions } from '@firebase/util'; export function isUrl(path?: string): boolean { @@ -148,7 +148,6 @@ export function connectStorageEmulator( /** * A service that provides Firebase Storage Reference instances. - * @public * @param opt_url - gs:// url to a custom Storage Bucket */ export class FirebaseStorageImpl implements FirebaseStorage { diff --git a/packages/storage/src/task.ts b/packages/storage/src/task.ts index e7edbc4e076..04c58d4ca1f 100644 --- a/packages/storage/src/task.ts +++ b/packages/storage/src/task.ts @@ -32,15 +32,14 @@ import { } from './implementation/taskenums'; import { Metadata } from './metadata'; import { - CompleteFn, - ErrorFn, Observer, - StorageObserver, Subscribe, - Unsubscribe + Unsubscribe, + StorageObserver as StorageObserverInternal, + NextFn } from './implementation/observer'; import { Request } from './implementation/request'; -import { UploadTaskSnapshot } from './tasksnapshot'; +import { UploadTaskSnapshot, StorageObserver } from './public-types'; import { async as fbsAsync } from './implementation/async'; import { Mappings, getMappings } from './implementation/metadata'; import { @@ -76,7 +75,7 @@ export class UploadTask { _transferred: number = 0; private _needToFetchStatus: boolean = false; private _needToFetchMetadata: boolean = false; - private _observers: Array> = []; + private _observers: Array> = []; private _resumable: boolean; /** * Upload state. @@ -483,11 +482,18 @@ export class UploadTask { type: TaskEvent, nextOrObserver?: | StorageObserver - | ((a: UploadTaskSnapshot) => unknown), - error?: ErrorFn, - completed?: CompleteFn + | null + | ((snapshot: UploadTaskSnapshot) => unknown), + error?: ((a: FirebaseStorageError) => unknown) | null, + completed?: Unsubscribe | null ): Unsubscribe | Subscribe { - const observer = new Observer(nextOrObserver, error, completed); + const observer = new Observer( + (nextOrObserver as + | StorageObserverInternal + | NextFn) || undefined, + error || undefined, + completed || undefined + ); this._addObserver(observer); return () => { this._removeObserver(observer); diff --git a/packages/storage/src/tasksnapshot.ts b/packages/storage/src/tasksnapshot.ts deleted file mode 100644 index 01341bf5df6..00000000000 --- a/packages/storage/src/tasksnapshot.ts +++ /dev/null @@ -1,73 +0,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. - */ -import { TaskState } from './implementation/taskenums'; -import { Metadata } from './metadata'; -import { Reference } from './reference'; -import { UploadTask } from './task'; - -/** - * Result returned from a non-resumable upload. - * @public - */ -export interface UploadResult { - /** - * Contains the metadata sent back from the server. - */ - readonly metadata: Metadata; - - /** - * The reference that spawned this upload. - */ - readonly ref: Reference; -} - -/** - * Holds data about the current state of the upload task. - * @public - */ -export interface UploadTaskSnapshot { - /** - * The number of bytes that have been successfully uploaded so far. - */ - readonly bytesTransferred: number; - - /** - * The total number of bytes to be uploaded. - */ - readonly totalBytes: number; - - /** - * The current state of the task. - */ - readonly state: TaskState; - - /** - * Before the upload completes, contains the metadata sent to the server. - * After the upload completes, contains the metadata sent back from the server. - */ - readonly metadata: Metadata; - - /** - * The task of which this is a snapshot. - */ - readonly task: UploadTask; - - /** - * The reference that spawned this snapshot's upload task. - */ - readonly ref: Reference; -} diff --git a/packages/storage/test/integration/integration.exp.test.ts b/packages/storage/test/integration/integration.test.ts similarity index 97% rename from packages/storage/test/integration/integration.exp.test.ts rename to packages/storage/test/integration/integration.test.ts index 282ee9d6860..63b1c2c3cc9 100644 --- a/packages/storage/test/integration/integration.exp.test.ts +++ b/packages/storage/test/integration/integration.test.ts @@ -30,11 +30,11 @@ import { getMetadata, updateMetadata, listAll -} from '../../exp/index'; +} from '../../src/index'; import { use, expect } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as types from '../../exp/public-types'; +import * as types from '../../src/public-types'; use(chaiAsPromised); @@ -46,7 +46,7 @@ export const STORAGE_BUCKET = PROJECT_CONFIG.storageBucket; export const API_KEY = PROJECT_CONFIG.apiKey; export const AUTH_DOMAIN = PROJECT_CONFIG.authDomain; -describe('FirebaseStorage Exp', () => { +describe('FirebaseStorage Integration tests', () => { let app: FirebaseApp; let storage: types.FirebaseStorage; diff --git a/packages/storage/test/unit/index.exp.test.ts b/packages/storage/test/unit/index.test.ts similarity index 97% rename from packages/storage/test/unit/index.exp.test.ts rename to packages/storage/test/unit/index.test.ts index 9c57c2583eb..8f2a6b37785 100644 --- a/packages/storage/test/unit/index.exp.test.ts +++ b/packages/storage/test/unit/index.test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ import { expect } from 'chai'; -import { getStorage } from '../../exp/index'; +import { getStorage } from '../../src/index'; import { FirebaseStorageImpl } from '../../src/service'; // eslint-disable-next-line import/no-extraneous-dependencies import { initializeApp, deleteApp } from '@firebase/app-exp'; diff --git a/packages/storage/test/unit/reference.compat.test.ts b/packages/storage/test/unit/reference.compat.test.ts deleted file mode 100644 index bc725558d08..00000000000 --- a/packages/storage/test/unit/reference.compat.test.ts +++ /dev/null @@ -1,315 +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 { expect } from 'chai'; -import { FirebaseApp } from '@firebase/app-types'; -import { StringFormat } from '../../src/implementation/string'; -import { Headers } from '../../src/implementation/connection'; -import { Metadata } from '../../src/metadata'; -import { ReferenceCompat } from '../../compat/reference'; -import { StorageServiceCompat } from '../../compat/service'; -import * as testShared from './testshared'; -import { SendHook, TestingConnection } from './connection'; -import { DEFAULT_HOST } from '../../src/implementation/constants'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { Provider } from '@firebase/component'; -import { FirebaseStorageImpl } from '../../src/service'; -import { Reference } from '../../src/reference'; -import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; - -/* eslint-disable @typescript-eslint/no-floating-promises */ -function makeFakeService( - app: FirebaseApp, - authProvider: Provider, - appCheckProvider: Provider, - sendHook: SendHook -): StorageServiceCompat { - const storageServiceCompat: StorageServiceCompat = new StorageServiceCompat( - app, - new FirebaseStorageImpl( - app, - authProvider, - appCheckProvider, - testShared.makePool(sendHook) - ) - ); - return storageServiceCompat; -} - -function makeStorage(url: string): ReferenceCompat { - const service = new FirebaseStorageImpl( - {} as FirebaseApp, - testShared.emptyAuthProvider, - testShared.fakeAppCheckTokenProvider, - testShared.makePool(null) - ); - const storageServiceCompat: StorageServiceCompat = new StorageServiceCompat( - {} as FirebaseApp, - service - ); - return new ReferenceCompat(new Reference(service, url), storageServiceCompat); -} - -describe('Firebase Storage > Reference', () => { - const root = makeStorage('gs://test-bucket/'); - const child = makeStorage('gs://test-bucket/hello'); - describe('Path constructor', () => { - it('root', () => { - expect(root.toString()).to.equal('gs://test-bucket/'); - }); - it('keeps characters after ? on a gs:// string', () => { - const s = makeStorage('gs://test-bucket/this/ismyobject?hello'); - expect(s.toString()).to.equal('gs://test-bucket/this/ismyobject?hello'); - }); - it("doesn't URL-decode on a gs:// string", () => { - const s = makeStorage('gs://test-bucket/%3F'); - expect(s.toString()).to.equal('gs://test-bucket/%3F'); - }); - it('ignores URL params and fragments on an http URL', () => { - const s = makeStorage( - `http://${DEFAULT_HOST}/v0/b/test-bucket/o/my/object.txt` + - '?ignoreme#please' - ); - expect(s.toString()).to.equal('gs://test-bucket/my/object.txt'); - }); - it('URL-decodes and ignores fragment on an http URL', () => { - const s = makeStorage( - `http://${DEFAULT_HOST}/v0/b/test-bucket/o/%3F?ignore` - ); - expect(s.toString()).to.equal('gs://test-bucket/?'); - }); - - it('ignores URL params and fragments on an https URL', () => { - const s = makeStorage( - `https://${DEFAULT_HOST}/v0/b/test-bucket/o/my/object.txt` + - '?ignoreme#please' - ); - expect(s.toString()).to.equal('gs://test-bucket/my/object.txt'); - }); - - it('URL-decodes and ignores fragment on an https URL', () => { - const s = makeStorage( - `https://${DEFAULT_HOST}/v0/b/test-bucket/o/%3F?ignore` - ); - expect(s.toString()).to.equal('gs://test-bucket/?'); - }); - }); - - describe('toString', () => { - it("Doesn't add trailing slash", () => { - const s = makeStorage('gs://test-bucket/foo'); - expect(s.toString()).to.equal('gs://test-bucket/foo'); - }); - it('Strips trailing slash', () => { - const s = makeStorage('gs://test-bucket/foo/'); - expect(s.toString()).to.equal('gs://test-bucket/foo'); - }); - }); - - describe('parent', () => { - it('Returns null at root', () => { - expect(root.parent).to.be.null; - }); - it('Returns root one level down', () => { - expect(child.parent!.toString()).to.equal('gs://test-bucket/'); - }); - it('Works correctly with empty levels', () => { - const s = makeStorage('gs://test-bucket/a///'); - expect(s.parent!.toString()).to.equal('gs://test-bucket/a/'); - }); - }); - - describe('root', () => { - it('Returns self at root', () => { - expect(root.root.toString()).to.equal('gs://test-bucket/'); - }); - - it('Returns root multiple levels down', () => { - const s = makeStorage('gs://test-bucket/a/b/c/d'); - expect(s.root.toString()).to.equal('gs://test-bucket/'); - }); - }); - - describe('bucket', () => { - it('Returns bucket name', () => { - expect(root.bucket).to.equal('test-bucket'); - }); - }); - - describe('fullPath', () => { - it('Returns full path without leading slash', () => { - const s = makeStorage('gs://test-bucket/full/path'); - expect(s.fullPath).to.equal('full/path'); - }); - }); - - describe('name', () => { - it('Works at top level', () => { - const s = makeStorage('gs://test-bucket/toplevel.txt'); - expect(s.name).to.equal('toplevel.txt'); - }); - - it('Works at not the top level', () => { - const s = makeStorage('gs://test-bucket/not/toplevel.txt'); - expect('toplevel.txt').to.equal(s.name); - }); - }); - - describe('child', () => { - it('works with a simple string', () => { - expect(root.child('a').toString()).to.equal('gs://test-bucket/a'); - }); - it('drops a trailing slash', () => { - expect(root.child('ab/').toString()).to.equal('gs://test-bucket/ab'); - }); - it('compresses repeated slashes', () => { - expect(root.child('//a///b/////').toString()).to.equal( - 'gs://test-bucket/a/b' - ); - }); - it('works chained multiple times with leading slashes', () => { - expect( - root.child('a').child('/b').child('c').child('d/e').toString() - ).to.equal('gs://test-bucket/a/b/c/d/e'); - }); - }); - - it("Doesn't send Authorization on null auth token", done => { - function newSend( - connection: TestingConnection, - url: string, - method: string, - body?: ArrayBufferView | Blob | string | null, - headers?: Headers - ): void { - expect(headers).to.not.be.undefined; - expect(headers!['Authorization']).to.be.undefined; - done(); - } - - const service = makeFakeService( - testShared.fakeApp, - testShared.emptyAuthProvider, - testShared.fakeAppCheckTokenProvider, - newSend - ); - const ref = service.refFromURL('gs://test-bucket'); - ref.child('foo').getMetadata(); - }); - - it('Works if the user logs in before creating the storage reference', done => { - // Regression test for b/27227221 - function newSend( - connection: TestingConnection, - url: string, - method: string, - body?: ArrayBufferView | Blob | string | null, - headers?: Headers - ): void { - expect(headers).to.not.be.undefined; - expect(headers!['Authorization']).to.equal( - 'Firebase ' + testShared.authToken - ); - done(); - } - - const service = makeFakeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - newSend - ); - const ref = service.refFromURL('gs://test-bucket'); - ref.child('foo').getMetadata(); - }); - - describe('putString', () => { - it('Uses metadata.contentType for RAW format', () => { - // Regression test for b/30989476 - const task = child.putString('hello', StringFormat.RAW, { - contentType: 'lol/wut' - } as Metadata); - expect(task.snapshot.metadata!.contentType).to.equal('lol/wut'); - task.cancel(); - }); - it('Uses embedded content type in DATA_URL format', () => { - const task = child.putString( - 'data:lol/wat;base64,aaaa', - StringFormat.DATA_URL - ); - expect(task.snapshot.metadata!.contentType).to.equal('lol/wat'); - task.cancel(); - }); - it('Lets metadata.contentType override embedded content type in DATA_URL format', () => { - const task = child.putString( - 'data:ignore/me;base64,aaaa', - StringFormat.DATA_URL, - { contentType: 'tomato/soup' } as Metadata - ); - expect(task.snapshot.metadata!.contentType).to.equal('tomato/soup'); - task.cancel(); - }); - }); - - describe('Argument verification', () => { - describe('list', () => { - it('throws on invalid maxResults', () => { - it('throws on invalid maxResults', async () => { - await expect(child.list({ maxResults: 0 })).to.be.rejectedWith( - 'storage/invalid-argument' - ); - await expect(child.list({ maxResults: -4 })).to.be.rejectedWith( - 'storage/invalid-argument' - ); - await expect(child.list({ maxResults: 1001 })).to.be.rejectedWith( - 'storage/invalid-argument' - ); - }); - }); - }); - }); - - describe('root operations', () => { - it('put throws', () => { - expect(() => root.put(new Uint8Array())).to.throw( - 'storage/invalid-root-operation' - ); - }); - it('putString throws', () => { - expect(() => root.putString('raw', StringFormat.RAW)).to.throw( - 'storage/invalid-root-operation' - ); - }); - it('delete throws', () => { - expect(() => root.delete()).to.throw('storage/invalid-root-operation'); - }); - it('getMetadata throws', async () => { - await expect(root.getMetadata()).to.be.rejectedWith( - 'storage/invalid-root-operation' - ); - }); - it('updateMetadata throws', async () => { - await expect(root.updateMetadata({} as Metadata)).to.be.rejectedWith( - 'storage/invalid-root-operation' - ); - }); - it('getDownloadURL throws', async () => { - await expect(root.getDownloadURL()).to.be.rejectedWith( - 'storage/invalid-root-operation' - ); - }); - }); -}); diff --git a/packages/storage/test/unit/reference.exp.test.ts b/packages/storage/test/unit/reference.test.ts similarity index 100% rename from packages/storage/test/unit/reference.exp.test.ts rename to packages/storage/test/unit/reference.test.ts diff --git a/packages/storage/test/unit/service.compat.test.ts b/packages/storage/test/unit/service.compat.test.ts deleted file mode 100644 index f6796e3de05..00000000000 --- a/packages/storage/test/unit/service.compat.test.ts +++ /dev/null @@ -1,370 +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 { expect } from 'chai'; -import { TaskEvent } from '../../src/implementation/taskenums'; -import { Headers } from '../../src/implementation/connection'; -import { ConnectionPool } from '../../src/implementation/connectionPool'; -import { StorageServiceCompat } from '../../compat/service'; -import * as testShared from './testshared'; -import { DEFAULT_HOST } from '../../src/implementation/constants'; -import { FirebaseStorageError } from '../../src/implementation/error'; -import { FirebaseStorageImpl } from '../../src/service'; -import { FirebaseApp } from '@firebase/app-types'; -import { Provider } from '@firebase/component'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; -import { TestingConnection } from './connection'; - -const fakeAppGs = testShared.makeFakeApp('gs://mybucket'); -const fakeAppGsEndingSlash = testShared.makeFakeApp('gs://mybucket/'); -const fakeAppInvalidGs = testShared.makeFakeApp('gs://mybucket/hello'); -const connectionPool = new ConnectionPool(); - -function makeGsUrl(child: string = ''): string { - return 'gs://' + testShared.bucket + '/' + child; -} - -function makeService( - app: FirebaseApp, - authProvider: Provider, - appCheckProvider: Provider, - pool: ConnectionPool, - url?: string -): StorageServiceCompat { - const storageServiceCompat: StorageServiceCompat = new StorageServiceCompat( - app, - new FirebaseStorageImpl(app, authProvider, appCheckProvider, pool, url) - ); - return storageServiceCompat; -} - -describe('Firebase Storage > Service', () => { - describe('simple constructor', () => { - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool - ); - it('Root refs point to the right place', () => { - const ref = service.ref(); - expect(ref.toString()).to.equal(makeGsUrl()); - }); - it('Child refs point to the right place', () => { - const ref = service.ref('path/to/child'); - expect(ref.toString()).to.equal(makeGsUrl('path/to/child')); - }); - it('Throws calling ref with a gs:// URL', () => { - const error = testShared.assertThrows(() => { - service.ref('gs://bucket/object'); - }, 'storage/invalid-argument'); - expect(error.message).to.match(/refFromURL/); - }); - it('Throws calling ref with an http:// URL', () => { - const error = testShared.assertThrows(() => { - service.ref(`http://${DEFAULT_HOST}/etc`); - }, 'storage/invalid-argument'); - expect(error.message).to.match(/refFromURL/); - }); - it('Throws calling ref with an https:// URL', () => { - const error = testShared.assertThrows(() => { - service.ref(`https://${DEFAULT_HOST}/etc`); - }, 'storage/invalid-argument'); - expect(error.message).to.match(/refFromURL/); - }); - }); - describe('custom bucket constructor', () => { - it('gs:// custom bucket constructor refs point to the right place', () => { - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool, - 'gs://foo-bar.appspot.com' - ); - const ref = service.ref(); - expect(ref.toString()).to.equal('gs://foo-bar.appspot.com/'); - }); - it('http:// custom bucket constructor refs point to the right place', () => { - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool, - `http://${DEFAULT_HOST}/v1/b/foo-bar.appspot.com/o` - ); - const ref = service.ref(); - expect(ref.toString()).to.equal('gs://foo-bar.appspot.com/'); - }); - it('https:// custom bucket constructor refs point to the right place', () => { - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool, - `https://${DEFAULT_HOST}/v1/b/foo-bar.appspot.com/o` - ); - const ref = service.ref(); - expect(ref.toString()).to.equal('gs://foo-bar.appspot.com/'); - }); - - it('Bare bucket name constructor refs point to the right place', () => { - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool, - 'foo-bar.appspot.com' - ); - const ref = service.ref(); - expect(ref.toString()).to.equal('gs://foo-bar.appspot.com/'); - }); - it('Child refs point to the right place', () => { - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool, - 'foo-bar.appspot.com' - ); - const ref = service.ref('path/to/child'); - expect(ref.toString()).to.equal('gs://foo-bar.appspot.com/path/to/child'); - }); - it('Throws trying to construct with a gs:// URL containing an object path', () => { - const error = testShared.assertThrows(() => { - makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool, - 'gs://bucket/object/' - ); - }, 'storage/invalid-default-bucket'); - expect(error.message).to.match(/Invalid default bucket/); - }); - }); - describe('default bucket config', () => { - it('gs:// works without ending slash', () => { - const service = makeService( - fakeAppGs, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool - ); - expect(service.ref().toString()).to.equal('gs://mybucket/'); - }); - it('gs:// works with ending slash', () => { - const service = makeService( - fakeAppGsEndingSlash, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool - ); - expect(service.ref().toString()).to.equal('gs://mybucket/'); - }); - it('Throws when config bucket is gs:// with an object path', () => { - testShared.assertThrows(() => { - makeService( - fakeAppInvalidGs, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool - ); - }, 'storage/invalid-default-bucket'); - }); - }); - describe('connectStorageEmulator(service, host, port)', () => { - it('sets emulator host correctly', done => { - function newSend( - connection: TestingConnection, - url: string, - method: string, - body?: ArrayBufferView | Blob | string | null, - headers?: Headers - ): void { - // Expect emulator host to be in url of storage operations requests, - // in this case getDownloadURL. - expect(url).to.match(/^http:\/\/test\.host\.org:1234.+/); - connection.abort(); - done(); - } - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - testShared.makePool(newSend) - ); - service.useEmulator('test.host.org', 1234); - expect((service._delegate as FirebaseStorageImpl).host).to.equal( - 'http://test.host.org:1234' - ); - void service.ref('test.png').getDownloadURL(); - }); - }); - describe('refFromURL', () => { - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool - ); - it('Works with gs:// URLs', () => { - const ref = service.refFromURL('gs://mybucket/child/path/image.png'); - expect(ref.toString()).to.equal('gs://mybucket/child/path/image.png'); - }); - it('Works with http:// URLs', () => { - const ref = service.refFromURL( - `http://${DEFAULT_HOST}/v0/b/` + - 'mybucket/o/child%2Fpath%2Fimage.png?downloadToken=hello' - ); - expect(ref.toString()).to.equal('gs://mybucket/child/path/image.png'); - }); - it('Works with https:// URLs', () => { - const ref = service.refFromURL( - `https://${DEFAULT_HOST}/v0/b/` + - 'mybucket/o/child%2Fpath%2Fimage.png?downloadToken=hello' - ); - expect(ref.toString()).to.equal('gs://mybucket/child/path/image.png'); - }); - it('Works with storage.googleapis.com URLs', () => { - const ref = service.refFromURL( - `https://storage.googleapis.com/mybucket/path%20with%20space/image.png` - ); - expect(ref.toString()).to.equal( - 'gs://mybucket/path with space/image.png' - ); - }); - it('Works with storage.googleapis.com URLs with query params', () => { - const ref = service.refFromURL( - `https://storage.googleapis.com/mybucket/path%20with%20space/image.png?X-Goog-Algorithm= -GOOG4-RSA-SHA256` - ); - expect(ref.toString()).to.equal( - 'gs://mybucket/path with space/image.png' - ); - }); - it('Works with storage.cloud.google.com URLs', () => { - const ref = service.refFromURL( - `https://storage.cloud.google.com/mybucket/path%20with%20space/image.png` - ); - expect(ref.toString()).to.equal( - 'gs://mybucket/path with space/image.png' - ); - }); - it('Works with storage.cloud.google.com URLs and escaped slash', () => { - const ref = service.refFromURL( - `https://storage.cloud.google.com/mybucket/path%20with%20space%2Fimage.png` - ); - expect(ref.toString()).to.equal( - 'gs://mybucket/path with space/image.png' - ); - }); - }); - describe('Argument verification', () => { - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool - ); - describe('ref', () => { - it('Throws on gs:// argument', () => { - testShared.assertThrows( - testShared.bind(service.ref, service, 'gs://yo'), - 'storage/invalid-argument' - ); - }); - }); - describe('refFromURL', () => { - it('Throws with a non-URL string arg', () => { - const error = testShared.assertThrows( - testShared.bind(service.refFromURL, service, 'child'), - 'storage/invalid-argument' - ); - expect(error.message).to.match( - /expected a full URL but got a child path/i - ); - }); - it('Throws with an invalid URL arg', () => { - testShared.assertThrows( - testShared.bind(service.refFromURL, service, 'notlegit://url'), - 'storage/invalid-argument' - ); - }); - }); - describe('setMaxUploadRetryTime', () => { - it('Throws on negative arg', () => { - testShared.assertThrows( - testShared.bind(service.setMaxUploadRetryTime, service, -10), - 'storage/invalid-argument' - ); - }); - }); - describe('setMaxOperationRetryTime', () => { - it('Throws on negative arg', () => { - testShared.assertThrows( - testShared.bind(service.setMaxOperationRetryTime, service, -10), - 'storage/invalid-argument' - ); - }); - }); - }); - - describe('Deletion', () => { - const service = makeService( - testShared.fakeApp, - testShared.fakeAuthProvider, - testShared.fakeAppCheckTokenProvider, - connectionPool - ); - it('In-flight requests are canceled when the service is deleted', async () => { - const ref = service.refFromURL('gs://mybucket/image.jpg'); - const metadataPromise = ref.getMetadata(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - service._delegate._delete(); - await expect(metadataPromise).to.be.rejectedWith('storage/app-deleted'); - }); - it('Requests fail when started after the service is deleted', async () => { - const ref = service.refFromURL('gs://mybucket/image.jpg'); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - service._delegate._delete(); - - await expect(ref.getMetadata()).to.be.rejectedWith('storage/app-deleted'); - }); - it('Running uploads fail when the service is deleted', () => { - const ref = service.refFromURL('gs://mybucket/image.jpg'); - const toReturn = new Promise((resolve, reject) => { - ref.put(new Uint8Array([97])).on( - TaskEvent.STATE_CHANGED, - null, - (err: FirebaseStorageError | Error) => { - expect((err as FirebaseStorageError).code).to.equal( - 'storage/app-deleted' - ); - resolve(); - }, - () => { - reject('Upload completed, should have been canceled'); - } - ); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - service._delegate._delete(); - }); - return toReturn; - }); - }); -}); diff --git a/packages/storage/test/unit/service.exp.test.ts b/packages/storage/test/unit/service.test.ts similarity index 95% rename from packages/storage/test/unit/service.exp.test.ts rename to packages/storage/test/unit/service.test.ts index a68ea1bea85..423eb732f8a 100644 --- a/packages/storage/test/unit/service.exp.test.ts +++ b/packages/storage/test/unit/service.test.ts @@ -418,4 +418,29 @@ GOOG4-RSA-SHA256` return toReturn; }); }); + + describe('Argument verification', () => { + const service = new FirebaseStorageImpl( + testShared.fakeApp, + testShared.fakeAuthProvider, + testShared.fakeAppCheckTokenProvider, + connectionPool + ); + describe('setMaxUploadRetryTime', () => { + it('Throws on negative arg', () => { + testShared.assertThrows( + () => (service.maxOperationRetryTime = -10), + 'storage/invalid-argument' + ); + }); + }); + describe('setMaxOperationRetryTime', () => { + it('Throws on negative arg', () => { + testShared.assertThrows( + () => (service.maxOperationRetryTime = -10), + 'storage/invalid-argument' + ); + }); + }); + }); });