diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 3017438249..ec1830df25 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -748,15 +748,14 @@ export interface ServiceAccount { } // @public -export function storage(app?: app.App): storage.Storage; +export function storage(app?: App): storage.Storage; // @public (undocumented) export namespace storage { - export interface Storage { - app: app.App; - // (undocumented) - bucket(name?: string): Bucket; - } + // Warning: (ae-forgotten-export) The symbol "Storage" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type Storage = Storage; } diff --git a/etc/firebase-admin.storage.api.md b/etc/firebase-admin.storage.api.md new file mode 100644 index 0000000000..d13887d9df --- /dev/null +++ b/etc/firebase-admin.storage.api.md @@ -0,0 +1,34 @@ +## API Report File for "firebase-admin.storage" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; +import { Bucket } from '@google-cloud/storage'; + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function getStorage(app?: App): Storage; + +// @public +export class Storage { + get app(): App; + // (undocumented) + bucket(name?: string): Bucket; + } + +// @public +export function storage(app?: App): storage.Storage; + +// @public (undocumented) +export namespace storage { + // (undocumented) + export type Storage = Storage; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js index aaa4094818..8f31443727 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -42,6 +42,7 @@ const entryPoints = { 'firebase-admin/messaging': './lib/messaging/index.d.ts', 'firebase-admin/project-management': './lib/project-management/index.d.ts', 'firebase-admin/security-rules': './lib/security-rules/index.d.ts', + 'firebase-admin/storage': './lib/storage/index.d.ts', 'firebase-admin/remote-config': './lib/remote-config/index.d.ts', }; diff --git a/gulpfile.js b/gulpfile.js index 413c530e79..042b44560f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -95,6 +95,7 @@ gulp.task('compile', function() { 'lib/messaging/*.d.ts', 'lib/project-management/*.d.ts', 'lib/security-rules/*.d.ts', + 'lib/storage/*.d.ts', 'lib/remote-config/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 95ca40c2bc..5bbcf4ad1b 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -26,7 +26,7 @@ import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Auth } from '../auth/index'; import { MachineLearning } from '../machine-learning/machine-learning'; import { Messaging } from '../messaging/index'; -import { Storage } from '../storage/storage'; +import { Storage } from '../storage/index'; import { Database } from '../database/index'; import { Firestore } from '../firestore/index'; import { InstanceId } from '../instance-id/index'; @@ -303,10 +303,8 @@ export class FirebaseApp implements app.App { * @return The Storage service instance of this app. */ public storage(): Storage { - return this.ensureService_('storage', () => { - const storageService: typeof Storage = require('../storage/storage').Storage; - return new storageService(this); - }); + const fn = require('../storage/index').getStorage; + return fn(this); } public firestore(): Firestore { diff --git a/src/storage/index.ts b/src/storage/index.ts index 109f54431d..3cc1e82c12 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -14,8 +14,22 @@ * limitations under the License. */ -import { Bucket } from '@google-cloud/storage'; -import { app } from '../firebase-namespace-api'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { Storage } from './storage'; + +export { Storage } from './storage'; + +export function getStorage(app?: App): Storage { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('storage', (app) => new Storage(app)); +} + +import { Storage as TStorage } from './storage'; /** * Gets the {@link storage.Storage `Storage`} service for the @@ -39,25 +53,9 @@ import { app } from '../firebase-namespace-api'; * var otherStorage = admin.storage(otherApp); * ``` */ -export declare function storage(app?: app.App): storage.Storage; +export declare function storage(app?: App): storage.Storage; /* eslint-disable @typescript-eslint/no-namespace */ export namespace storage { - /** - * The default `Storage` service if no - * app is provided or the `Storage` service associated with the provided - * app. - */ - export interface Storage { - /** - * Optional app whose `Storage` service to - * return. If not provided, the default `Storage` service will be returned. - */ - app: app.App; - /** - * @returns A [Bucket](https://cloud.google.com/nodejs/docs/reference/storage/latest/Bucket) - * instance as defined in the `@google-cloud/storage` package. - */ - bucket(name?: string): Bucket; - } + export type Storage = TStorage; } diff --git a/src/storage/storage.ts b/src/storage/storage.ts index 94989db24e..d3511dc052 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -15,32 +15,29 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app'; import { FirebaseError } from '../utils/error'; import { ServiceAccountCredential, isApplicationDefault } from '../app/credential-internal'; import { Bucket, Storage as StorageClient } from '@google-cloud/storage'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { storage } from './index'; - -import StorageInterface = storage.Storage; /** * The default `Storage` service if no * app is provided or the `Storage` service associated with the provided * app. */ -export class Storage implements StorageInterface { +export class Storage { - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; private readonly storageClient: StorageClient; /** - * @param {FirebaseApp} app The app for this Storage service. + * @param app The app for this Storage service. * @constructor * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseError({ code: 'storage/invalid-argument', @@ -109,7 +106,7 @@ export class Storage implements StorageInterface { /** * @return The app associated with this Storage instance. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } } diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 7448610fd1..8c8862c826 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -56,6 +56,7 @@ import './machine-learning/machine-learning-api-client.spec'; // Storage import './storage/storage.spec'; +import './storage/index.spec'; // Firestore import './firestore/firestore.spec'; diff --git a/test/unit/storage/index.spec.ts b/test/unit/storage/index.spec.ts new file mode 100644 index 0000000000..8251207677 --- /dev/null +++ b/test/unit/storage/index.spec.ts @@ -0,0 +1,73 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getStorage, Storage } from '../../../src/storage/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Storage', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to initialize Google Cloud Storage client with the ' + + 'available credential. Must initialize the SDK with a certificate credential or ' + + 'application default credentials to use Cloud Storage API.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getStorage()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getStorage(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + expect(() => getStorage(mockCredentialApp)).to.throw(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getStorage(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const storage1: Storage = getStorage(mockApp); + const storage2: Storage = getStorage(mockApp); + expect(storage1).to.equal(storage2); + }); + }); +});