cloudinary-core
Version:
Cloudinary Client Side JS library. Cloudinary streamlines your web application’s image manipulation needs. Cloudinary's cloud-based servers automate image uploading, resizing, cropping, optimizing, sprite generation and more.
386 lines (346 loc) • 12.2 kB
JavaScript
import Transformation from './transformation';
import {
ACCESSIBILITY_MODES,
DEFAULT_IMAGE_PARAMS,
OLD_AKAMAI_SHARED_CDN,
PLACEHOLDER_IMAGE_MODES,
SHARED_CDN,
SEO_TYPES
} from './constants';
import {
defaults,
compact,
isPlainObject
} from './util';
import crc32 from './crc32';
import getSDKAnalyticsSignature from "./sdkAnalytics/getSDKAnalyticsSignature";
import getAnalyticsOptions from "./sdkAnalytics/getAnalyticsOptions";
/**
* Adds protocol, host, pathname prefixes to given string
* @param str
* @returns {string}
*/
function makeUrl(str) {
let prefix = document.location.protocol + '//' + document.location.host;
if (str[0] === '?') {
prefix += document.location.pathname;
} else if (str[0] !== '/') {
prefix += document.location.pathname.replace(/\/[^\/]*$/, '/');
}
return prefix + str;
}
/**
* Check is given string is a url
* @param str
* @returns {boolean}
*/
function isUrl(str){
return str ? !!str.match(/^https?:\//) : false;
}
// Produce a number between 1 and 5 to be used for cdn sub domains designation
function cdnSubdomainNumber(publicId) {
return crc32(publicId) % 5 + 1;
}
/**
* Removes signature from options and returns the signature
* Makes sure signature is empty or of this format: s--signature--
* @param {object} options
* @returns {string} the formatted signature
*/
function handleSignature(options) {
const {signature} = options;
const isFormatted = !signature || (signature.indexOf('s--') === 0 && signature.substr(-2) === '--');
delete options.signature;
return isFormatted ? signature : `s--${signature}--`;
}
/**
* Create the URL prefix for Cloudinary resources.
* @param {string} publicId the resource public ID
* @param {object} options additional options
* @param {string} options.cloud_name - the cloud name.
* @param {boolean} [options.cdn_subdomain=false] - Whether to automatically build URLs with
* multiple CDN sub-domains.
* @param {string} [options.private_cdn] - Boolean (default: false). Should be set to true for Advanced plan's users
* that have a private CDN distribution.
* @param {string} [options.protocol="http://"] - the URI protocol to use. If options.secure is true,
* the value is overridden to "https://"
* @param {string} [options.secure_distribution] - The domain name of the CDN distribution to use for building HTTPS URLs.
* Relevant only for Advanced plan's users that have a private CDN distribution.
* @param {string} [options.cname] - Custom domain name to use for building HTTP URLs.
* Relevant only for Advanced plan's users that have a private CDN distribution and a custom CNAME.
* @param {boolean} [options.secure_cdn_subdomain=true] - When options.secure is true and this parameter is false,
* the subdomain is set to "res".
* @param {boolean} [options.secure=false] - Force HTTPS URLs of images even if embedded in non-secure HTTP pages.
* When this value is true, options.secure_distribution will be used as host if provided, and options.protocol is set
* to "https://".
* @returns {string} the URL prefix for the resource.
* @private
*/
function handlePrefix(publicId, options) {
if (options.cloud_name && options.cloud_name[0] === '/') {
return '/res' + options.cloud_name;
}
// defaults
let protocol = "http://";
let cdnPart = "";
let subdomain = "res";
let host = ".cloudinary.com";
let path = "/" + options.cloud_name;
// modifications
if (options.protocol) {
protocol = options.protocol + '//';
}
if (options.private_cdn) {
cdnPart = options.cloud_name + "-";
path = "";
}
if (options.cdn_subdomain) {
subdomain = "res-" + cdnSubdomainNumber(publicId);
}
if (options.secure) {
protocol = "https://";
if (options.secure_cdn_subdomain === false) {
subdomain = "res";
}
if ((options.secure_distribution != null) && options.secure_distribution !== OLD_AKAMAI_SHARED_CDN && options.secure_distribution !== SHARED_CDN) {
cdnPart = "";
subdomain = "";
host = options.secure_distribution;
}
} else if (options.cname) {
protocol = "http://";
cdnPart = "";
subdomain = options.cdn_subdomain ? 'a' + ((crc32(publicId) % 5) + 1) + '.' : '';
host = options.cname;
}
return [protocol, cdnPart, subdomain, host, path].join("");
}
/**
* Return the resource type and action type based on the given configuration
* @function Cloudinary#handleResourceType
* @param {Object|string} resource_type
* @param {string} [type='upload']
* @param {string} [url_suffix]
* @param {boolean} [use_root_path]
* @param {boolean} [shorten]
* @returns {string} resource_type/type
* @ignore
*/
function handleResourceType({resource_type = "image", type = "upload", url_suffix, use_root_path, shorten}) {
let options, resourceType = resource_type;
if (isPlainObject(resourceType)) {
options = resourceType;
resourceType = options.resource_type;
type = options.type;
shorten = options.shorten;
}
if (type == null) {
type = 'upload';
}
if (url_suffix != null) {
resourceType = SEO_TYPES[`${resourceType}/${type}`];
type = null;
if (resourceType == null) {
throw new Error(`URL Suffix only supported for ${Object.keys(SEO_TYPES).join(', ')}`);
}
}
if (use_root_path) {
if (resourceType === 'image' && type === 'upload' || resourceType === "images") {
resourceType = null;
type = null;
} else {
throw new Error("Root path only supported for image/upload");
}
}
if (shorten && resourceType === 'image' && type === 'upload') {
resourceType = 'iu';
type = null;
}
return [resourceType, type].join("/");
}
/**
* Encode publicId
* @param publicId
* @returns {string} encoded publicId
*/
function encodePublicId(publicId) {
return encodeURIComponent(publicId).replace(/%3A/g, ':').replace(/%2F/g, '/');
}
/**
* Encode and format publicId
* @param publicId
* @param options
* @returns {string} publicId
*/
function formatPublicId(publicId, options) {
if (isUrl(publicId)){
publicId = encodePublicId(publicId);
} else {
try {
// Make sure publicId is URI encoded.
publicId = decodeURIComponent(publicId);
} catch (error) {}
publicId = encodePublicId(publicId);
if (options.url_suffix) {
publicId = publicId + '/' + options.url_suffix;
}
if (options.format) {
if (!options.trust_public_id) {
publicId = publicId.replace(/\.(jpg|png|gif|webp)$/, '');
}
publicId = publicId + '.' + options.format;
}
}
return publicId;
}
/**
* Get any error with url options
* @param options
* @returns {string} if error, otherwise return undefined
*/
function validate(options) {
const {cloud_name, url_suffix} = options;
if (!cloud_name) {
return 'Unknown cloud_name';
}
if (url_suffix && url_suffix.match(/[\.\/]/)) {
return 'url_suffix should not include . or /';
}
}
/**
* Get version part of the url
* @param publicId
* @param options
* @returns {string}
*/
function handleVersion(publicId, options) {
// force_version param means to make sure there is a version in the url (Default is true)
const isForceVersion = (options.force_version || typeof options.force_version === 'undefined');
// Is version included in publicId or in options, or publicId is a url (doesn't need version)
const isVersionExist = (publicId.indexOf('/') < 0 || publicId.match(/^v[0-9]+/) || isUrl(publicId)) || options.version;
if (isForceVersion && !isVersionExist) {
options.version = 1;
}
return options.version ? `v${options.version}` : '';
}
/**
* Get final transformation component for url string
* @param options
* @returns {string}
*/
function handleTransformation(options) {
let {placeholder, accessibility, ...otherOptions} = options || {};
const result = new Transformation(otherOptions);
// Append accessibility transformations
if (accessibility && ACCESSIBILITY_MODES[accessibility]) {
result.chain().effect(ACCESSIBILITY_MODES[accessibility]);
}
// Append placeholder transformations
if (placeholder) {
if (placeholder === "predominant-color" && result.getValue('width') && result.getValue('height')) {
placeholder += '-pixel';
}
const placeholderTransformations = PLACEHOLDER_IMAGE_MODES[placeholder] || PLACEHOLDER_IMAGE_MODES.blur;
placeholderTransformations.forEach(t => result.chain().transformation(t));
}
return result.serialize();
}
/**
* If type is 'fetch', update publicId to be a url
* @param publicId
* @param type
* @returns {string}
*/
function preparePublicId(publicId, {type}){
return (!isUrl(publicId) && type === 'fetch') ? makeUrl(publicId) : publicId;
}
/**
* Generate url string
* @param publicId
* @param options
* @returns {string} final url
*/
function urlString(publicId, options) {
if (isUrl(publicId) && (options.type === 'upload' || options.type === 'asset')) {
return publicId;
}
const version = handleVersion(publicId, options);
const transformationString = handleTransformation(options);
const prefix = handlePrefix(publicId, options);
const signature = handleSignature(options);
const resourceType = handleResourceType(options);
publicId = formatPublicId(publicId, options);
return compact([prefix, resourceType, signature, transformationString, version, publicId])
.join('/')
.replace(/([^:])\/+/g, '$1/') // replace '///' with '//'
.replace(' ', '%20');
}
/**
* Merge options and config with defaults
* update options fetch_format according to 'type' param
* @param options
* @param config
* @returns {*} updated options
*/
function prepareOptions(options, config) {
if (options instanceof Transformation) {
options = options.toOptions();
}
options = defaults({}, options, config, DEFAULT_IMAGE_PARAMS);
if (options.type === 'fetch') {
options.fetch_format = options.fetch_format || options.format;
}
return options;
}
/**
* Generates a URL for any asset in your Media library.
* @function url
* @ignore
* @param {string} publicId - The public ID of the media asset.
* @param {Object} [options={}] - The {@link Transformation} parameters to include in the URL.
* @param {object} [config={}] - URL configuration parameters
* @param {type} [options.type='upload'] - The asset's storage type.
* For details on all fetch types, see
* <a href="https://cloudinary.com/documentation/image_transformations#fetching_images_from_remote_locations"
* target="_blank">Fetch types</a>.
* @param {Object} [options.resource_type='image'] - The type of asset. <p>Possible values:<br/>
* - `image`<br/>
* - `video`<br/>
* - `raw`
* @param {signature} [options.signature='s--12345678--'] - The signature component of a
* signed delivery URL of the format: /s--SIGNATURE--/.
* For details on signatures, see
* <a href="https://cloudinary.com/documentation/signatures" target="_blank">Signatures</a>.
* @return {string} The media asset URL.
* @see <a href="https://cloudinary.com/documentation/image_transformation_reference" target="_blank">
* Available image transformations</a>
* @see <a href="https://cloudinary.com/documentation/video_transformation_reference" target="_blank">
* Available video transformations</a>
*/
export default function url(publicId, options = {}, config = {}) {
if (!publicId) {
return publicId;
}
options = prepareOptions(options, config);
publicId = preparePublicId(publicId, options);
const error = validate(options);
if (error) {
throw error;
}
let resultUrl = urlString(publicId, options);
if(options.urlAnalytics) {
let analyticsOptions = getAnalyticsOptions(options);
let sdkAnalyticsSignature = getSDKAnalyticsSignature(analyticsOptions);
// url might already have a '?' query param
let appender = '?';
if (resultUrl.indexOf('?') >= 0) {
appender = '&';
}
resultUrl = `${resultUrl}${appender}_a=${sdkAnalyticsSignature}`;
}
if (options.auth_token) {
let appender = resultUrl.indexOf('?') >= 0 ? '&' : '?';
resultUrl = `${resultUrl}${appender}__cld_token__=${options.auth_token}`;
}
return resultUrl;
};