Skip to content

Commit

Permalink
Add protocol as a separate property for FirebaseStorage service (#5453)
Browse files Browse the repository at this point in the history
  • Loading branch information
hsubox76 committed Sep 8, 2021
1 parent d9808aa commit 66d4a1e
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 51 deletions.
5 changes: 5 additions & 0 deletions .changeset/tender-pumpkins-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@firebase/storage': patch
---

Store protocol and host separately on Storage service instance. Fixes a bug when generating url strings.
5 changes: 3 additions & 2 deletions common/api-review/storage.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export class _FirebaseStorageImpl implements FirebaseStorage {
_getAppCheckToken(): Promise<string | null>;
// (undocumented)
_getAuthToken(): Promise<string | null>;
// (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
Expand All @@ -97,6 +96,8 @@ export class _FirebaseStorageImpl implements FirebaseStorage {
// (undocumented)
readonly _pool: ConnectionPool;
// (undocumented)
_protocol: string;
// (undocumented)
readonly _url?: string | undefined;
}

Expand Down Expand Up @@ -292,7 +293,7 @@ export interface UploadResult {
}

// @public
export function uploadString(ref: StorageReference, value: string, format?: string, metadata?: UploadMetadata): Promise<UploadResult>;
export function uploadString(ref: StorageReference, value: string, format?: StringFormat, metadata?: UploadMetadata): Promise<UploadResult>;

// @public
export interface UploadTask {
Expand Down
12 changes: 7 additions & 5 deletions packages/storage/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
} from './reference';
import { STORAGE_TYPE } from './constants';
import { EmulatorMockTokenOptions, getModularInstance } from '@firebase/util';
import { StringFormat } from './implementation/string';

export { EmulatorMockTokenOptions } from '@firebase/util';

Expand Down Expand Up @@ -78,7 +79,7 @@ export {
* Uploads data to this object's location.
* The upload is not resumable.
* @public
* @param ref - StorageReference where data should be uploaded.
* @param ref - {@link StorageReference} where data should be uploaded.
* @param data - The data to upload.
* @param metadata - Metadata for the data to upload.
* @returns A Promise containing an UploadResult
Expand All @@ -100,7 +101,7 @@ export function uploadBytes(
* Uploads a string to this object's location.
* The upload is not resumable.
* @public
* @param ref - StorageReference where string should be uploaded.
* @param ref - {@link StorageReference} where string should be uploaded.
* @param value - The string to upload.
* @param format - The format of the string to upload.
* @param metadata - Metadata for the string to upload.
Expand All @@ -109,7 +110,7 @@ export function uploadBytes(
export function uploadString(
ref: StorageReference,
value: string,
format?: string,
format?: StringFormat,
metadata?: UploadMetadata
): Promise<UploadResult> {
ref = getModularInstance(ref);
Expand All @@ -125,7 +126,7 @@ export function uploadString(
* Uploads data to this object's location.
* The upload can be paused and resumed, and exposes progress updates.
* @public
* @param ref - StorageReference where data should be uploaded.
* @param ref - {@link StorageReference} where data should be uploaded.
* @param data - The data to upload.
* @param metadata - Metadata for the data to upload.
* @returns An UploadTask
Expand Down Expand Up @@ -317,7 +318,8 @@ export function getStorage(
* @param storage - The {@link FirebaseStorage} instance
* @param host - The emulator host (ex: localhost)
* @param port - The emulator port (ex: 5001)
* @param options.mockUserToken - the mock auth token to use for unit testing Security Rules.
* @param options - Emulator options. `options.mockUserToken` is the mock auth
* token to use for unit testing Security Rules.
* @public
*/
export function connectStorageEmulator(
Expand Down
5 changes: 3 additions & 2 deletions packages/storage/src/implementation/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ export function fromResourceString(
export function downloadUrlFromResourceString(
metadata: Metadata,
resourceString: string,
host: string
host: string,
protocol: string
): string | null {
const obj = jsonObjectOrNull(resourceString);
if (obj === null) {
Expand All @@ -177,7 +178,7 @@ export function downloadUrlFromResourceString(
const bucket: string = metadata['bucket'] as string;
const path: string = metadata['fullPath'] as string;
const urlPart = '/b/' + encode(bucket) + '/o/' + encode(path);
const base = makeUrl(urlPart, host);
const base = makeUrl(urlPart, host, protocol);
const queryString = makeQueryString({
alt: 'media',
token
Expand Down
27 changes: 11 additions & 16 deletions packages/storage/src/implementation/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export function downloadUrlHandler(
return downloadUrlFromResourceString(
metadata as Metadata,
text,
service.host
service.host,
service._protocol
);
}
return handler;
Expand All @@ -99,10 +100,7 @@ export function downloadUrlHandler(
export function sharedErrorHandler(
location: Location
): (p1: Connection, p2: StorageError) => StorageError {
function errorHandler(
xhr: Connection,
err: StorageError
): StorageError {
function errorHandler(xhr: Connection, err: StorageError): StorageError {
let newErr;
if (xhr.getStatus() === 401) {
if (
Expand Down Expand Up @@ -136,10 +134,7 @@ export function objectErrorHandler(
): (p1: Connection, p2: StorageError) => StorageError {
const shared = sharedErrorHandler(location);

function errorHandler(
xhr: Connection,
err: StorageError
): StorageError {
function errorHandler(xhr: Connection, err: StorageError): StorageError {
let newErr = shared(xhr, err);
if (xhr.getStatus() === 404) {
newErr = objectNotFound(location.path);
Expand All @@ -156,7 +151,7 @@ export function getMetadata(
mappings: Mappings
): RequestInfo<Metadata> {
const urlPart = location.fullServerUrl();
const url = makeUrl(urlPart, service.host);
const url = makeUrl(urlPart, service.host, service._protocol);
const method = 'GET';
const timeout = service.maxOperationRetryTime;
const requestInfo = new RequestInfo(
Expand Down Expand Up @@ -192,7 +187,7 @@ export function list(
urlParams['maxResults'] = maxResults;
}
const urlPart = location.bucketOnlyServerUrl();
const url = makeUrl(urlPart, service.host);
const url = makeUrl(urlPart, service.host, service._protocol);
const method = 'GET';
const timeout = service.maxOperationRetryTime;
const requestInfo = new RequestInfo(
Expand All @@ -212,7 +207,7 @@ export function getDownloadUrl(
mappings: Mappings
): RequestInfo<string | null> {
const urlPart = location.fullServerUrl();
const url = makeUrl(urlPart, service.host);
const url = makeUrl(urlPart, service.host, service._protocol);
const method = 'GET';
const timeout = service.maxOperationRetryTime;
const requestInfo = new RequestInfo(
Expand All @@ -232,7 +227,7 @@ export function updateMetadata(
mappings: Mappings
): RequestInfo<Metadata> {
const urlPart = location.fullServerUrl();
const url = makeUrl(urlPart, service.host);
const url = makeUrl(urlPart, service.host, service._protocol);
const method = 'PATCH';
const body = toResourceString(metadata, mappings);
const headers = { 'Content-Type': 'application/json; charset=utf-8' };
Expand All @@ -254,7 +249,7 @@ export function deleteObject(
location: Location
): RequestInfo<void> {
const urlPart = location.fullServerUrl();
const url = makeUrl(urlPart, service.host);
const url = makeUrl(urlPart, service.host, service._protocol);
const method = 'DELETE';
const timeout = service.maxOperationRetryTime;

Expand Down Expand Up @@ -334,7 +329,7 @@ export function multipartUpload(
throw cannotSliceBlob();
}
const urlParams: UrlParams = { name: metadata_['fullPath']! };
const url = makeUrl(urlPart, service.host);
const url = makeUrl(urlPart, service.host, service._protocol);
const method = 'POST';
const timeout = service.maxUploadRetryTime;
const requestInfo = new RequestInfo(
Expand Down Expand Up @@ -397,7 +392,7 @@ export function createResumableUpload(
const urlPart = location.bucketOnlyServerUrl();
const metadataForUpload = metadataForUpload_(location, blob, metadata);
const urlParams: UrlParams = { name: metadataForUpload['fullPath']! };
const url = makeUrl(urlPart, service.host);
const url = makeUrl(urlPart, service.host, service._protocol);
const method = 'POST';
const headers = {
'X-Goog-Upload-Protocol': 'resumable',
Expand Down
10 changes: 6 additions & 4 deletions packages/storage/src/implementation/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@
*/
import { UrlParams } from './requestinfo';

export function makeUrl(urlPart: string, host: string): string {
const protocolMatch = host.match(/^(\w+):\/\/.+/);
const protocol = protocolMatch?.[1];
export function makeUrl(
urlPart: string,
host: string,
protocol: string
): string {
let origin = host;
if (protocol == null) {
origin = `https://${host}`;
}
return `${origin}/v0${urlPart}`;
return `${protocol}://${origin}/v0${urlPart}`;
}

export function makeQueryString(params: UrlParams): string {
Expand Down
16 changes: 8 additions & 8 deletions packages/storage/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ export function connectStorageEmulator(
mockUserToken?: EmulatorMockTokenOptions | string;
} = {}
): void {
storage.host = `http://${host}:${port}`;
storage.host = `${host}:${port}`;
storage._protocol = 'http';
const { mockUserToken } = options;
if (mockUserToken) {
storage._overrideAuthToken =
Expand All @@ -149,7 +150,7 @@ export function connectStorageEmulator(
/**
* A service that provides Firebase Storage Reference instances.
* @param opt_url - gs:// url to a custom Storage Bucket
*
*
* @internal
*/
export class FirebaseStorageImpl implements FirebaseStorage {
Expand All @@ -158,9 +159,9 @@ export class FirebaseStorageImpl implements FirebaseStorage {
* This string can be in the formats:
* - host
* - host:port
* - protocol://host:port
*/
private _host: string = DEFAULT_HOST;
_protocol: string = 'https';
protected readonly _appId: string | null = null;
private readonly _requests: Set<Request<unknown>>;
private _deleted: boolean = false;
Expand Down Expand Up @@ -195,15 +196,14 @@ export class FirebaseStorageImpl implements FirebaseStorage {
}
}

/**
* The host string for this service, in the form of `host` or
* `host:port`.
*/
get host(): string {
return this._host;
}

/**
* Set host string for this service.
* @param host - host string in the form of host, host:port,
* or protocol://host:port
*/
set host(host: string) {
this._host = host;
if (this._url != null) {
Expand Down
50 changes: 50 additions & 0 deletions packages/storage/test/unit/location.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @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 { expect } from 'chai';
import { DEFAULT_HOST } from '../../src/implementation/constants';
import { Location } from '../../src/implementation/location';

describe('Firebase Storage > Location', () => {
it('makeFromUrl handles an emulator url correctly', () => {
const loc = Location.makeFromUrl(
'http://localhost:3001/v0/b/abcdefg.appspot.com/o/abcde.txt',
'localhost:3001'
);
expect(loc.bucket).to.equal('abcdefg.appspot.com');
});
it('makeFromUrl handles a Firebase Storage url correctly', () => {
const loc = Location.makeFromUrl(
'https://firebasestorage.googleapis.com/v0/b/abcdefgh.appspot.com/o/abcde.txt',
DEFAULT_HOST
);
expect(loc.bucket).to.equal('abcdefgh.appspot.com');
});
it('makeFromUrl handles a gs url correctly', () => {
const loc = Location.makeFromUrl(
'gs://mybucket/child/path/abcde.txt',
DEFAULT_HOST
);
expect(loc.bucket).to.equal('mybucket');
});
it('makeFromUrl handles a Cloud Storage url correctly', () => {
const loc = Location.makeFromUrl(
'https://storage.googleapis.com/mybucket/abcde.txt',
DEFAULT_HOST
);
expect(loc.bucket).to.equal('mybucket');
});
});

0 comments on commit 66d4a1e

Please sign in to comment.