@cloudinary/url-gen
Version:
You are invited to influence our new SDK [Click here to view github discussion](https://github.com/cloudinary/js-url-gen/discussions/602) =========================
246 lines (245 loc) • 8.96 kB
JavaScript
import { getUrlPrefix, getUrlVersion, handleAssetType, handleDeliveryType } from "../internal/url/cloudinaryURL.js";
import URLConfig from "../config/URLConfig.js";
import { getSDKAnalyticsSignature } from "../sdkAnalytics/getSDKAnalyticsSignature.js";
/**
* This const contains all the valid combination of asset/delivery for URL shortening purposes
* It's exported because it's used in a test, but it's not really shared enough to belong in a separate file
*/
export const SEO_TYPES = {
"image/upload": "images",
"image/private": "private_images",
"image/authenticated": "authenticated_images",
"raw/upload": "files",
"video/upload": "videos"
};
/**
* @description Cloudinary file without a transformation
* @summary SDK
* @memberOf SDK
*/
class CloudinaryFile {
constructor(publicID, cloudConfig = {}, urlConfig) {
this.setPublicID(publicID);
this.setCloudConfig(cloudConfig);
this.setURLConfig(urlConfig);
}
/**
* @description Sets the URL Config for this asset
* @param urlConfig
* @return {this}
*/
setURLConfig(urlConfig) {
this.urlConfig = new URLConfig(urlConfig);
return this;
}
/**
* @description Sets the Cloud Config for this asset
* @param urlConfig
* @return {this}
*/
setCloudConfig(cloudConfig) {
this.cloudName = cloudConfig.cloudName;
this.apiKey = cloudConfig.apiKey;
this.apiSecret = cloudConfig.apiSecret;
this.authToken = cloudConfig.authToken;
return this;
}
/**
* @description Sets the public ID of the asset.
* @param {string} publicID The public ID of the asset.
* @return {this}
*/
setPublicID(publicID) {
// PublicID must be a string!
this.publicID = publicID ? publicID.toString() : '';
return this;
}
/**
* @description Sets the delivery type of the asset.
* @param {DELIVERY_TYPE | string} newType The type of the asset.
* @return {this}
*/
setDeliveryType(newType) {
this.deliveryType = newType;
return this;
}
/**
* @description Sets the URL SEO suffix of the asset.
* @param {string} newSuffix The SEO suffix.
* @return {this}
*/
setSuffix(newSuffix) {
this.suffix = newSuffix;
return this;
}
/**
* @description Sets the signature of the asset.
* @param {string} signature The signature.
* @return {this}
*/
setSignature(signature) {
this.signature = signature;
return this;
}
/**
* @description Sets the version of the asset.
* @param {string} newVersion The version of the asset.
* @return {this}
*/
setVersion(newVersion) {
if (newVersion) {
this.version = newVersion;
}
return this;
}
/**
* @description Sets the asset type.
* @param {string} newType The type of the asset.
* @return {this}
*/
setAssetType(newType) {
if (newType) {
this.assetType = newType;
}
return this;
}
sign() {
return this;
}
/**
* @description Serializes to URL string
* @param overwriteOptions
*/
toURL(overwriteOptions = {}) {
return this.createCloudinaryURL(null, overwriteOptions.trackedAnalytics);
}
/**
* @description Validate various options before attempting to create a URL
* The function will throw in case a violation
* @throws Validation errors
*/
validateAssetForURLCreation() {
if (typeof this.cloudName === 'undefined') {
throw 'You must supply a cloudName when initializing the asset';
}
const suffixContainsDot = this.suffix && this.suffix.indexOf('.') >= 0;
const suffixContainsSlash = this.suffix && this.suffix.indexOf('/') >= 0;
if (suffixContainsDot || suffixContainsSlash) {
throw '`suffix`` should not include . or /';
}
}
/**
* @description return an SEO friendly name for a combination of asset/delivery, some examples:
* * image/upload -> images
* * video/upload -> videos
* If no match is found, return `{asset}/{delivery}`
*/
getResourceType() {
const assetType = handleAssetType(this.assetType);
const deliveryType = handleDeliveryType(this.deliveryType);
const hasSuffix = !!this.suffix;
const regularSEOType = `${assetType}/${deliveryType}`;
const shortSEOType = SEO_TYPES[`${assetType}/${deliveryType}`];
const useRootPath = this.urlConfig.useRootPath;
const shorten = this.urlConfig.shorten;
// Quick exit incase of useRootPath
if (useRootPath) {
if (regularSEOType === 'image/upload') {
return ''; // For image/upload we're done, just return nothing
}
else {
throw new Error(`useRootPath can only be used with assetType: 'image' and deliveryType: 'upload'. Provided: ${regularSEOType} instead`);
}
}
if (shorten && regularSEOType === 'image/upload') {
return 'iu';
}
if (hasSuffix) {
if (shortSEOType) {
return shortSEOType;
}
else {
throw new Error(`URL Suffix only supported for ${Object.keys(SEO_TYPES).join(', ')}, Provided: ${regularSEOType} instead`);
}
}
// If all else fails, return the regular image/upload combination (asset/delivery)
return regularSEOType;
}
getSignature() {
if (this.signature) {
return `s--${this.signature}--`;
}
else {
return '';
}
}
/**
*
* @description Creates a fully qualified CloudinaryURL
* @return {string} CloudinaryURL
* @throws Validation Errors
*/
createCloudinaryURL(transformation, trackedAnalytics) {
// In accordance with the existing implementation, if no publicID exists we should return nothing.
if (!this.publicID) {
return '';
}
// Throws if some options are mis-configured
// See the function for more information on when it throws
this.validateAssetForURLCreation();
const prefix = getUrlPrefix(this.cloudName, this.urlConfig);
const transformationString = transformation ? transformation.toString() : '';
const version = getUrlVersion(this.publicID, this.version, this.urlConfig.forceVersion);
const publicID = this.publicID;
if (typeof transformation === 'string') {
const url = [prefix, this.getResourceType(), this.getSignature(), transformationString, version, publicID.replace(/,/g, '%2C'), this.suffix]
.filter((a) => a)
.join('/');
return url;
}
else {
// Avoid applying encodeURI on entire string in case where we have transformations with comma (i.e. f_auto,q_auto)
// Since encodeURI does not encode commas we replace commas *only* on the publicID
const safeURL = [
encodeURI(prefix),
this.getResourceType(),
this.getSignature(),
encodeURI(transformationString),
version,
encodeURI(publicID).replace(/,/g, '%2C'),
this.suffix && encodeURI(this.suffix)
]
.filter((a) => a)
.join('/')
.replace(/\?/g, '%3F')
.replace(/=/g, '%3D');
const shouldAddAnalytics = this.urlConfig.analytics !== false && !(publicID.includes('?'));
let queryParamsString = '';
if (typeof (this.urlConfig.queryParams) === 'object') {
try {
const queryParams = new URLSearchParams(this.urlConfig.queryParams);
if (shouldAddAnalytics) {
queryParams.set("_a", getSDKAnalyticsSignature(trackedAnalytics));
}
queryParamsString = queryParams.toString();
}
catch (err) {
console.error('Error: URLSearchParams is not available so the queryParams object cannot be parsed, please try passing as an already parsed string');
}
}
else {
queryParamsString = this.urlConfig.queryParams || '';
if (shouldAddAnalytics) {
queryParamsString += `${(queryParamsString.length > 0 ? '&' : '')}_a=${getSDKAnalyticsSignature(trackedAnalytics)}`;
}
}
if (queryParamsString) {
return `${safeURL}?${queryParamsString}`;
}
else {
return safeURL;
}
}
}
}
export { CloudinaryFile };