-
Notifications
You must be signed in to change notification settings - Fork 373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(storage): Add getDownloadUrl
method to the Storage API
#2036
Conversation
src/storage/storage.ts
Outdated
if (!metadata?.metadata?.firebaseStorageDownloadTokens) { | ||
throw new FirebaseError({ | ||
code: 'storage/no-download-token', | ||
message: 'No download token available. Please create one in the Firebase Console.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we actually do have a token API that the console uses. We would have to implement it here as well but perhaps we could point users to that method instead of the console
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Maneesh for putting it together. I really think our customers would appreciate it.
However our download url design is a hot mess right now and I'm hoping we can clean it up in 2023. This means getDownloadUrl() in the current form will either be deprecated or at least discouraged.
Does it make sense to add it here if we are planning to deprecate it a few months later?
src/storage/cloud-extensions.ts
Outdated
// Gets metadata from firebase backend instead of GCS | ||
getFirebaseMetadata(): Promise<FirebaseMetadata> { | ||
// We need to talk to the firebase storage endpoints instead of the google cloud bucket endpoint | ||
const endpoint = "https://firebasestorage.googleapis.com/v0"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can configure this for testing against our internal testing endpoints or emulator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expectation is that you call getStorage(app, 'myendpoint')
or getStorage(undefined, 'myEndpoint')
src/storage/cloud-extensions.ts
Outdated
constructor( | ||
bucket: FirebaseStorageBucket, | ||
name: string, | ||
private endpoint: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious how other SDKs handle env overrides. Could you confirm this is commonly how this is implemented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Firestore just uses the google cloud package and does something like this:
const settings: Settings = {
host: "localhost:8080",
ssl: false
};
// Initialize Firestore
const firestore = new Firestore(settings);
For RTDB, we ask them to override the databaseUrl:
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://movie-picker-729bb-default-rtdb.firebaseio.com",
});
So I don't think there's a best practices way to get this done. My recommendation is to do something like:
getStorage({ host: 'myhosturl' });
So that we can allow for more options to be customized down the road. WDYT?
EDIT: Introducing a new ENV var seems cleaner than piping through a bunch of classes. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, I don't have a strong sense of what design is right here, env var sounds fine.
Eventually these patterns should be consolidated imo, but that can happen async.
src/storage/cloud-extensions.ts
Outdated
* Gets metadata from firebase backend instead of GCS | ||
* @returns {FirebaseMetadata} | ||
*/ | ||
getFirebaseMetadata(): Promise<FirebaseMetadata> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be private?
src/storage/cloud-extensions.ts
Outdated
}); | ||
} | ||
const [token] = downloadTokens.split(","); | ||
return `${this.endpoint}/v0/b/${this.bucket.name}/o/${encodeURIComponent( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems like there's a bug here, getFirebaseMetadata above assumes this.endpoint
has the /v0
url part.
src/storage/cloud-extensions.ts
Outdated
this.endpoint = endpoint; | ||
} | ||
/** | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you want me to add more information about this?
src/storage/cloud-extensions.ts
Outdated
super(bucket, name, options); | ||
} | ||
/** | ||
* Gets metadata from firebase backend instead of GCS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: Firebase
src/storage/storage.ts
Outdated
@@ -57,10 +60,12 @@ export class Storage { | |||
|
|||
process.env.STORAGE_EMULATOR_HOST = `http://${process.env.FIREBASE_STORAGE_EMULATOR_HOST}`; | |||
} | |||
this.endpoint = (userEndpoint || process.env.STORAGE_EMULATOR_HOST || 'https://firebasestorage.googleapis.com') + '/v0'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we already have one for the emulator host, does it make sense to use another env var for the endpoint override?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nits but overall lgtm
src/storage/cloud-extensions.ts
Outdated
uri, | ||
}, | ||
(err, body) => { | ||
console.log(body); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove
src/storage/cloud-extensions.ts
Outdated
* @returns {FirebaseMetadata} | ||
*/ | ||
private getFirebaseMetadata(): Promise<FirebaseMetadata> { | ||
// Build any custom headers based on the defined interceptors on the parent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this comment still accurate?
src/storage/cloud-extensions.ts
Outdated
} | ||
|
||
/** | ||
* Gets the download URL for a given file. Will throw a `FirebaseError` if there are no download tokens available. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/storage/cloud-extensions.ts
Outdated
|
||
/** | ||
* Gets the download URL for a given file. Will throw a `FirebaseError` if there are no download tokens available. | ||
* @returns {Promise<string>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please update with a description of the result or remove as this is redundant with the method signature
src/storage/cloud-extensions.ts
Outdated
export class FirebaseStorageClient extends StorageClient { | ||
/** | ||
* | ||
* @param bucketName |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update?
src/storage/cloud-extensions.ts
Outdated
*/ | ||
export class FirebaseStorageBucket extends Bucket { | ||
/** | ||
* @param name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall direction lgtm, small nits, can you go through the whole PR to make sure its up to date?
src/storage/index.ts
Outdated
} | ||
/** | ||
* Gets metadata from Firebase backend instead of GCS | ||
* @returns {FirebaseMetadata} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
small nit: jsdoc clauses unnecessary unless they add context that cant be inferred from the method signature
go/java-practices/javadoc#param
src/storage/index.ts
Outdated
|
||
/** | ||
* Gets the download URL for a given file. Will throw a `FirebaseError` if there are no download tokens available. | ||
* @returns {Promise<string>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment
src/storage/index.ts
Outdated
endpoint: string, | ||
file: File | ||
): Promise<FirebaseMetadata> { | ||
// Build any custom headers based on the defined interceptors on the parent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this comment still up to date?
etc/firebase-admin.storage.api.md
Outdated
@@ -17,7 +19,8 @@ export function getStorage(app?: App): Storage; | |||
// @public | |||
export class Storage { | |||
get app(): App; | |||
bucket(name?: string): Bucket; | |||
// Warning: (ae-forgotten-export) The symbol "FirebaseStorageBucket" needs to be exported by the entry point index.d.ts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update?
test/integration/storage.spec.ts
Outdated
@@ -38,6 +37,12 @@ describe('admin.storage', () => { | |||
return verifyBucket(bucket, 'storage().bucket(string)') | |||
.should.eventually.be.fulfilled; | |||
}); | |||
it('bucket(string) creates a download token', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update tests
Bumping this. cc @maneesht Edit: In the meantime, I got it to work by using the code from this PR directly in my service, thanks again @maneesht !! utils: export interface FirebaseMetadata {
name: string
bucket: string
generation: string
metageneration: string
contentType: string
timeCreated: string
updated: string
storageClass: string
size: string
md5Hash: string
contentEncoding: string
contentDisposition: string
crc32c: string
etag: string
downloadTokens?: string
}
export function getFirebaseMetadata(
endpoint: string,
file: File,
): Promise<FirebaseMetadata> {
const uri = `${endpoint}/b/${file.bucket.name}/o/${encodeURIComponent(
file.name,
)}`
return new Promise((resolve, reject) => {
file.storage.makeAuthenticatedRequest(
{
method: 'GET',
uri,
},
(err, body) => {
if (err) {
reject(err)
} else {
resolve(body)
}
},
)
})
}
export async function getDownloadUrl(file: File): Promise<string> {
const endpoint =
(process.env.STORAGE_EMULATOR_HOST ||
'https://firebasestorage.googleapis.com') + '/v0'
const { downloadTokens } = await getFirebaseMetadata(endpoint, file)
if (!downloadTokens) {
throw new Error(
'No download token available. Please create one in the Firebase Console.',
)
}
const [token] = downloadTokens.split(',')
return `${endpoint}/b/${file.bucket.name}/o/${encodeURIComponent(
file.name,
)}?alt=media&token=${token}`
} service generateDownloadUrl(filePath: string) {
const bucket = firebaseAdmin
.storage()
.bucket(this.configService.get('FIREBASE_STORAGE_BUCKET'))
const file = bucket.file(filePath)
return getDownloadUrl(file)
} |
Hi, we expect this change to rollout this week. You'll be able to follow along with the release at https://firebase.google.com/support/release-notes/admin/node |
This feature is now available in https://github.com/firebase/firebase-admin-node/releases/tag/v11.10.0 🥳 |
Congrats and thanks, @maneesht! |
Fixes #1352