Cloudinary is a fantastic cloud service for storing, serving and transforming images and videos. However the documentation for uploading an image or video from the browser, in a secure fashion, are pretty poor. The various examples are scattered around the place, and none of them shows, in one place how to
- Sign a request on the server
- Use that signed request to upload a video
- Track the progress of the video upload
I had to patch it together for myself, and thought it’d be useful for you all.
// Run in the browser | |
// This function takes "someId" as a parameter, as an example that you | |
// may want to link the video upload to some object in your database. | |
// This is of course totally optional. | |
function uploadVideo( | |
someId: number, | |
file: File, | |
listeners: { | |
onProgress: (perc: number) => void; | |
onComplete: (url: string) => void; | |
onError: (str: string) => void; | |
} | |
): () => void { | |
let cancelableXhr = null; | |
fetch("/api/signCloudinaryUpload", { | |
method: "POST", | |
cache: "no-cache", | |
credentials: "include", | |
headers: { | |
"Content-Type": "application/json", | |
}, | |
body: JSON.stringify({ | |
someId | |
}), | |
}) | |
.then((res) => { | |
if (!res.ok) { | |
listeners.onError("Permission to upload denied"); | |
} else { | |
return res.json(); | |
} | |
}) | |
.then((signatureInfo) => { | |
cancelableXhr = runUpload( | |
signatureInfo.cloud_name, | |
signatureInfo.api_key, | |
signatureInfo.signature, | |
signatureInfo.public_id, | |
signatureInfo.timestamp | |
); | |
}); | |
function runUpload(cloudName, apiKey, signature, publicId, timestamp) { | |
const url = `https://api.cloudinary.com/v1_1/${cloudName}/upload`; | |
const xhr = new XMLHttpRequest(); | |
const fd = new FormData(); | |
xhr.open("POST", url, true); | |
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); | |
listeners.onProgress(0); | |
// Update progress (can be used to show progress indicator) | |
xhr.upload.addEventListener("progress", function(e) { | |
const progress = Math.round((e.loaded * 100.0) / e.total); | |
listeners.onProgress(progress); | |
console.log( | |
`fileuploadprogress data.loaded: ${e.loaded}, data.total: ${e.total}` | |
); | |
}); | |
xhr.onreadystatechange = function(e) { | |
if (xhr.readyState == 4 && xhr.status == 200) { | |
// File uploaded successfully | |
const response = JSON.parse(xhr.responseText); | |
console.log("response", response); | |
// Create a thumbnail of the uploaded image, with 150px width | |
listeners.onComplete(response.secure_url); | |
} | |
}; | |
fd.append("api_key", apiKey); | |
fd.append("public_id", publicId); | |
fd.append("timestamp", timestamp); | |
fd.append("signature", signature); | |
fd.append("file", file); | |
xhr.send(fd); | |
} | |
return () => { | |
cancelableXhr && cancelableXhr.abort(); | |
}; | |
} | |
// Run on the server | |
import { v2 as cloudinary } from "cloudinary"; | |
cloudinary.config({ | |
cloud_name: "", // Your cloud name | |
api_key: "", // your api key | |
api_secret: "", // your api secret | |
}); | |
function signCloudinaryRequest(publicId: string) { | |
const timestamp = Math.round(new Date().getTime() / 1000); | |
const apiSecret = (cloudinary.config("api_secret") as any) as string; | |
const signature = cloudinary.utils.api_sign_request( | |
{ | |
timestamp, | |
public_id: publicId, | |
}, | |
apiSecret | |
); | |
return { | |
api_key: (cloudinary.config("api_key") as any) as string, | |
signature, | |
cloud_name: (cloudinary.config("cloud_name") as any) as string, | |
timestamp, | |
}; | |
} | |
function apiHandler(request, response) { | |
// This assumes that you have a bodyParser set up | |
const someId = request.body.someId; | |
const publicId = `${someId}/video`; // use whatever path you like | |
const signatureInfo = signCloudinaryRequest(publicId); | |
response.status(200); | |
response.json({ | |
api_key: signatureInfo.api_key, | |
cloud_name: signatureInfo.cloud_name, | |
public_id: publicId, | |
signature: signatureInfo.signature, | |
timestamp: signatureInfo.timestamp, | |
}); | |
response.end(); | |
} |