Uploading a signed video to Cloudinary: a code example

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();
}