How to softcode firebase storage image url's - javascript

I am using Firebase Storage to create and send image url's to Firebase Database. I have this working well when I have both a 'currentUser' and a Firebase Storage child reference hardcoded. But, how do I grab the actual currentUser from Firebase Database and put a fluid name to the child reference/references? I've seen some possible answers to this in Java. But not Javascript. Here's my code:
// openImage() is the function that grabs a photo from the camera roll
// I'm using react-native btw, so some of this code is handling the
// blob.
openImage() {
this.setState({ loading: true });
const Blob = RNFetchBlob.polyfill.Blob;
const fs = RNFetchBlob.fs;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest
window.Blob = Blob
// if I use this code, it won't return the urls: const {currentUser}
// = firebase.auth()
// So, I've got the 'currentUser' hardcoded in as "12345"
const { currentUser } = "12345"
// ImagePicker.openPicker() is just a library to access the camera roll
ImagePicker.openPicker({
width: 400,
height: 300,
cropping: true,
mediaType: 'photo',
}).then(image => {
const imagePath = image.path;
let uploadBlob = null;
const storage = firebase.storage();
const storageRef = storage.ref(currentUser);
//I also have the .child reference name hardcoded as 'dp.jpg'
const imageRef = storageRef.child('dp.jpg');
const mime = 'image/jpg';
fs.readFile(imagePath, 'base64')
.then((data) => {
return Blob.build(data, { type: `${mime};BASE64` });
})
.then((blob) => {
uploadBlob = blob;
return imageRef.put(blob, { contentType: mime });
})
.then(() => {
uploadBlob.close();
return imageRef.getDownloadURL();
})
.then((url) => {
const { image } = this.props;
this.props.entryUpdate({ prop: 'image', value: url })
});
});
}
The image url is then passed to Firebase Database and sets it as a key of 'image' and a value of 'dp.jpg'. With the hardcoding, this works fine, but for only one image and one user (or folder in Firebase Storage) of course. I'm aware of how Firebase Realtime Database will assign it's own id number to an item as in:
const { currentUser } = firebase.auth();
const item = firebase.database().ref(`/users/${currentUser.uid}/items`)
.push();
return () => {
item.set({ make, model, year, uid: item.key, image })
where item.key is generated by Firebase, thus it doesn't have to be hardcoded in. Is it possible to achieve this in Firebase Storage for both the image child name and the 'currentUser'? And do I really need to assign each image to a 'currentUser' folder in Firebase storage since the mobile app only is fetching the url from the database?

#parohy Thanks for taking the time to search through the firebase docs. I think .once() only works with Firebase Realtime Database. And I'm looking for a unique name in Firebase Storage. However, your answer helped me come to my solution. I was trying to tap into the fact that Firebase Database already creates perfectly unique ids. However, I'm don't think I can access those in Firebase Storage. So I just did:
firebase.storage().ref().child(unique + '.jpg')
const unique = Date.now() + Math.random();
not perfectly unique, but close enough.

Related

Uploading an mp3 to Firebase Storage with React Native Expo

I am attempting to upload an mp3 to firebase storage using expo and react native. So far I've got the file into firebase storage, but it's only 9bytes large, so I'm doing something wrong. I've attempted this with blob as shown below with no success.
Here is a screenshot of the firebase storage folder showing the file uploaded but not the data of said file:
Any help is greatly appreciated, I feel like I'm missing a step to actually upload the data along with the file.
export default function SongPicker() {
const [song, setSong] = useState(null);
//Get current user through authentication
const user = auth.currentUser;
const pickDocument = async () => {
let result = await DocumentPicker.getDocumentAsync({});
// Fetch the photo with it's local URI
const response = fetch(result.uri);
alert(result.uri);
console.log(result);
const file = new Blob(
[response.value], {
type: 'audio/mpeg'
});
console.log('do we see this?');
try {
//Create the file reference
const storage = getStorage();
const storageRef = ref(storage, `songs/${user.uid}/${result.name}`);
// Upload Blob file to Firebase
const snapshot = uploadBytes(storageRef, file, 'blob').then((snapshot) => {
console.log('Uploaded a song to firebase storage!');
});
setSong(result.uri);
} catch (error) {
console.log(error);
}
}
The fetch() returns a Promise so you should add an await for that as well.
const response = await fetch(result.uri);
Then try using blob() method on the Response:
const file = await response.blob()
The third param in uploadBytes should be upload metadata object but you can skip that here:
const snapshot = await uploadBytes(storageRef, file).

Upload image to cloud storage from firebase cloud functions

I am trying so hard to upload one image from cloud functions
I am sending an image from the web to the cloud function using onRequest. I am sending a base64 string and the fileName. Now I was following different tutorials on the internet and couldn't seem to solve my problem.
Here is my code. I think I am doing something wrong with the service account json. Although i generated the json file and used it but still it didn't work.
I get the error of The caller does not have permission at Gaxios._request when i don't use service account json
And when i do use serviceAccount.json then i get this error The "path" argument must be of type string. Received an instance of Object which is from file.createWriteStream() i think
Anyway here is the code can anyone please help me with this
The projectId that I am using is shown in the picture below
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const projectId = functions.config().apikeys.projectid; // In the picture below
const stream = require("stream");
const cors = require("cors")({ origin: true });
const { Storage } = require("#google-cloud/storage");
// Enable Storage
const storage = new Storage({
projectId: projectId, // I did use serviceAccount json here but that wasn't working
});
// With serviceAccount.json code
// const storage = new Storage({
// projectId: projectId,
// keyFilename: serviceAccount,
// });
// This is giving the error of: The "path" argument must be of type string. Received an instance of Object
exports.storeUserProfileImage = functions.https.onRequest((req, res) => {
cors(req, res, async () => {
try {
const bucket = storage.bucket(`gs://${projectId}.appspot.com`);
let pictureURL;
const image = req.body.image;
const userId = req.body.userId;
const fileName = req.body.fileName;
const mimeType = image.match(
/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/
)[1];
//trim off the part of the payload that is not part of the base64 string
const base64EncodedImageString = image.replace(
/^data:image\/\w+;base64,/,
""
);
const imageBuffer = Buffer.from(base64EncodedImageString, "base64");
const bufferStream = new stream.PassThrough();
bufferStream.end(imageBuffer);
// Define file and fileName
const file = bucket.file("images/" + fileName);
bufferStream
.pipe(
file.createWriteStream({
metadata: {
contentType: mimeType,
},
public: true,
validation: "md5",
})
)
.on("error", function (err) {
console.log("error from image upload", err.message);
})
.on("finish", function () {
// The file upload is complete.
console.log("Image uploaded");
file
.getSignedUrl({
action: "read",
expires: "03-09-2491",
})
.then((signedUrls) => {
// signedUrls[0] contains the file's public URL
console.log("Signed urls", signedUrls[0]);
pictureURL = signedUrls[0];
});
});
console.log("image url", pictureURL);
res.status(200).send(pictureURL);
} catch (e) {
console.log(e);
return { success: false, error: e };
}
});
});
const storage = new Storage({
projectId: projectId
keyFilename: "" // <-- Path to a .json, .pem, or .p12 key file
});
keyFilename accepts path to where your service account is stored and the credentials themselves.
folder
|-index.js
|-credentials
|-serviceAccountKey.json
If your directory structure looks like about then the path should be like this:
const storage = new Storage({
projectId: projectId
keyFilename: "./credentials/serviceAccountKey.json"
});
Do note that if you are using Cloud functions then the SDK will use Application Default Credentials so you don't have to pass those params. Simply initialize as shown below:
const storage = new Storage()
So first of all I didn't give any serviceaccounts because I am using the firebase cloud functions as #Dharmaraj said in his answer
Secondly, this was a permission problem in the google cloud platform which can be solved by going through the following steps
Go to your project's Cloud Console (https://console.cloud.google.com/) > IAM & admin > IAM, Find the App Engine default service account then click on the pencil at far left > Click on add role > In the filter field enter Service Account Token Creator and click on it save and you are good to go
Found this solution from here
https://github.com/firebase/functions-samples/issues/782

How to change image name by timestamp in Database realtime after down url from storage

I already push the image to Firebase storage successfully, but I have a problem that. The images pushed onto Storage then get url to Firebase DB keep, it duplicating names, the new one overwrites the old. Now, I want to change it back to the current timestamp instead of the image name pushed onto it change the name image in DB to name image plus timestamp. To image not repeat. I mean, every time I push the image name then in DB will be replaced by the current time the image was pushed when they down URL
How can I do that in the code below
export const createProduct = async (productData, image) => {
const uploadTask = await firebaseStorage
.ref(`/images/${image.name}`)
.put(image);
if (uploadTask.state === "success") {
const url = await firebaseStorage
.ref("images")
.child(image.name)
.getDownloadURL();
const result = {
...productData,
image: url,
};
const postRef = firebaseDb.ref("products").push();
return postRef
.set(result)
.then(() => {
return getProducts();
})
.catch(() => ({ status: "error" }));
}
return { status: "failure" };
};
My code above is pushing the image first and then get the URL to the Firebase database. Then get status if is push success or not
Please, anyone can help me? thank you so much
If you want to name every image after the timestamp on the client that uploads it, that'd be:
const uploadTask = await firebaseStorage
.ref(`/images/${Date.now()}`)
.put(image);
To change the property name under which you store the URL in the database, you could do something like:
const url = await firebaseStorage
.ref("images")
.child(image.name)
.getDownloadURL();
const name = 'image_'+Date.now();
const result = {
...productData,
[name]: url,
};

How do I dynamically create a Firebase Storage reference based on previous reference value?

Here's a brief overview of the process that I need help with:
Client uploads images to Firebase Storage at users/displayName/uid/ - this step is clear and done. No help needed on this one.
Then, client presses "request a quote" button which saves the URLs of the images (the ones that were uploaded to Storage in step 1) to Firebase Firestore - this step is clear and done. No help needed on this one.
After the URLs are saved to the Firestore, I want those images from Step 1 to be moved within the same bucket of the Storage to users/displayName/uid/order1. So, basically, from users/displayName/uid/ to users/displayName/uid/order1. - I need help with writing out the right Javascript code for this action. I tried the code snippet from below but it does not seem to work, not sure what is wrong.
At some point in the future, the same client will need to upload more images thus repeating the first 3 steps above. However, on the 3rd step, I will need his images to be moved from users/displayName/uid/ to users/displayName/uid/order2. The problem lies in me not knowing how to render the last part of the directory as order2 and not the same order1 as in the 3rd step. The number after "order" will basically need to increase every time the client repeats the steps. I have no idea of how to write out the right code for this. Please help.
If it helps, here are my functions that upload images to Firebase Storage and Firebase Firestore:
// Upload to Storage
handleUpload = () => {
this.state.files.forEach((file) => {
const storageRef = firebase.storage().ref(`users/${this.state.displayName}/${this.state.uid}/${file.name}`);
var task = storageRef.put(file)
// Progress
task.on("state_changed", snapshot => {
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
this.setState({ progress });
},
// Error
error => {
console.log(error);
},
// Additional function to update state with all files uploaded
() => {
firebase.storage()
.ref(`users/${this.state.displayName}/${this.state.uid}`)
.child(file.name)
.getDownloadURL()
.then(url => {
this.setState(state => {
const urls = [...state.urls, url];
return {
urls
};
});
});
// Empty file upload
this.setState({ progress: 0 })
this.setState({ files: [] })
}
);
})
};
// Saving to Firestore - PRESS GET A QUOTE TO ACTIVATE
async saveToFirestore() {
// Getting it all from storage first
const listRef = firebase.storage().ref(`users/${this.state.displayName}/${this.state.uid}`)
const res = await listRef.listAll()
const urlPromises = res.items.map((itemRef) => {
return itemRef.getDownloadURL()
})
const urls = await Promise.all(urlPromises)
// Then, we save it all to Firestore
firebase.firestore().collection('Documents/').doc(this.state.displayName).set({
documents: urls,
quote: 'pending',
name: this.state.displayName,
email: this.state.email,
emailVerified: this.state.emailVerified,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
userId: this.state.uid
})
.then(() => {
const listRef = firebase.storage().ref(`users/${this.state.displayName}/${this.state.uid}`)
listRef.listAll().then((res) => {
console.log(res)
res.items.forEach(function (item) {
console.log(item)
firebase.storage().ref(`users/${this.state.displayName}/${this.state.uid}/order1/${item.name}`).put(item);
});
}).catch(function (error) {
// Uh-oh, an error occurred!
});
this.setState({ quote: "pending" })
firebase.firestore().collection('Documents/').doc(this.state.displayName).get().then((doc) => {
if (doc.exists) {
doc.data().documents.forEach(url => {
this.setState(state => {
const documents = [...state.documents, url];
return {
documents
};
});
})
this.setState({ createdAt: doc.data().createdAt.toDate().toString() })
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function (error) {
console.log("Error getting document:", error);
});
})
.catch((error) => {
console.error("Error writing document: ", error);
});
}
Please let me know if anything else is needed.
Thank you very much!
As far as I can tell, you have two parts to your question:
Determine the folder for the next order
Write all files from the root to this folder
I'll focus on the first step in this answer.
Determine the folder for the next order
To determine the next order? folder, we'll get a list of all files/folders from Storage, and then filter them to determine the highest number in there:
var ref = firebase.storage().ref("62849565");
ref.listAll().then(function(res) {
// Determine the next order folder
const orderFolders = res.prefixes.filter((folder) => folder.name.match(/^order(\d+)$/));
let highestOrderNumber = 0;
orderFolders.forEach((folder) => {
const match = folder.name.match(/^order(\d+)$/);
const number = parseInt(match[1]);
if (number > highestOrderNumber) {
highestOrderNumber = number;
}
})
const nextOrderFolderPrefix = "order" + (highestOrderNumber+1);
// Move the files from the root to the new folder
res.items.forEach(function(itemRef) {
// TODO: read file from root and write it to the next order folder
});
}).catch(function(error) {
console.error(error);
});
(Also see this jsbin where I created/tested this code)
In the above:
The res.prefixes.filter((folder) => folder.name.match(/^order(\d+)$/)) ensures we only consider folders following the order1 naming pattern.
We then loop over the folders to find the highest number.
We them determine the path for the next folder.
Write all files from the root to the new folder
The TODO in this code is to move the actual file. There is no operation in Cloud Storage (or its Firebase SDK) to move a file. So you will have to do a sequence of read the file, write it to its new location, and removing it from its original location. I recommend searching around a bit, as I'm pretty sure this must have been asked before.

Firebase Storage Task (Progress bar)

I am trying to do a content upload progress bar using Firebase storage, but I am having some problems returning the task from my function.
I have implemented a Firebase Singleton, using React Context API. In the Firebase component I have multiples functions, one of them called 'uploadContent'
Here is the code:
uploadContent = async (postInfo) => {
const { uri, description, location, tags } = postInfo;
// Post UUID
const postId = uuid();
// Upload to firestore
const data = {
id: postId,
description,
location,
tags,
time: firestore.Timestamp.fromDate(new Date()), // The time when the image is uploaded
likes: [], // At the first time, when a post is created, zero users has liked it
comments: [], // Also, there aren't any comments
};
await this.db
.collection("posts")
.doc(this.auth.currentUser.uid)
.collection("userPosts")
.add(data);
// Create a storage referece
const storageRef = firebase.storage().ref("photos").child(postId);
// Uri to Blob
const response = await fetch(uri);
const blob = await response.blob();
// Upload to storage
const task = storageRef.put(blob);
return task;
};
The thing is, that when I call this function from my uploader component, and try to use one of the returned object functions I get "[Unhandled promise rejection: TypeError: undefined is not a function (near '...task.on...')]", and I don't know how to solve this problem.
Pd: If I call this function inside the "uploadContent" method (where I create the task), it works fine, but I need to return the task...
Here is the code of the function where I call my firebase method:
const upload = async () => {
const { firebase, navigation } = props;
console.log("Uploading...");
// Prepare post information
const postInfo = {
uri: photo.uri,
description: descriptionInput.current.props.value,
location: locationName, // TODO - Object with the location coords too
tags: [], // TODO - users tagged
};
// Upload to firebase
const task = await firebase.uploadContent(postInfo);
task.on("state_changed", (taskSnapshot) => {
console.log(
`${taskSnapshot.bytesTransferred} transferred out of ${taskSnapshot.totalBytes}`
);
});
// navigation.navigate("Profile"); // TODO: route params -> task
};
Any ideas? Thanks.
I wasted so much time on a similar problem, but solved it!
In this part of the code, you are resolving the task (that is implemented with promise) into the value undefined.
// Upload to firebase
const task = await firebase.uploadContent(postInfo);
Just remove the await to use the task itself.

Categories