From c5134d804a6651c4eec2a3c681a29ecbc066c382 Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Tue, 18 Jul 2023 17:16:22 +0200 Subject: [PATCH 1/7] feat: new method to_url added to support cached search feature --- lib-es5/utils/index.js | 45 +++++++++-- lib-es5/v2/search.js | 65 ++++++++++++++- lib/utils/index.js | 92 ++++++++++++++++------ lib/v2/search.js | 57 +++++++++++++- test/integration/api/search/search_spec.js | 69 +++++++++++++++- test/unit/url/video_url_spec.js | 3 +- types/index.d.ts | 6 ++ 7 files changed, 304 insertions(+), 33 deletions(-) diff --git a/lib-es5/utils/index.js b/lib-es5/utils/index.js index 25978c8c..eec3016e 100644 --- a/lib-es5/utils/index.js +++ b/lib-es5/utils/index.js @@ -466,6 +466,7 @@ function build_eager(transformations) { return format == null ? transformationString : `${transformationString}/${format}`; }).join('|'); } + /** * Build the custom headers for the request * @private @@ -791,6 +792,24 @@ function patchFetchFormat() { } } +function build_distribution_domain(options) { + var source = consumeOption(options, 'source', ''); + var cloud_name = consumeOption(options, 'cloud_name', config().cloud_name); + + if (!cloud_name) { + throw new Error('Must supply cloud_name in tag or in configuration'); + } + + var secure = consumeOption(options, 'secure', config().secure); + var private_cdn = consumeOption(options, 'private_cdn', config().private_cdn); + var cname = consumeOption(options, 'cname', config().cname); + var secure_distribution = consumeOption(options, 'secure_distribution', config().secure_distribution); + var cdn_subdomain = consumeOption(options, 'cdn_subdomain', config().cdn_subdomain); + var secure_cdn_subdomain = consumeOption(options, 'secure_cdn_subdomain', config().secure_cdn_subdomain); + + return unsigned_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution); +} + function url(public_id) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; @@ -886,7 +905,7 @@ function url(public_id) { } // eslint-disable-next-line no-empty } catch (error) {} - var hash = computeHash(to_sign + api_secret, signature_algorithm, 'base64'); + var hash = compute_hash(to_sign + api_secret, signature_algorithm, 'base64'); signature = hash.replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8); signature = `s--${signature}--`; } @@ -1002,6 +1021,7 @@ function finalize_resource_type(resource_type, type, url_suffix, use_root_path, } return [resource_type, type]; } + // cdn_subdomain and secure_cdn_subdomain // 1) Customers in shared distribution (e.g. res.cloudinary.com) // if cdn_domain is true uses res-[1-5].cloudinary.com for both http and https. @@ -1092,7 +1112,7 @@ function api_sign_request(params_to_sign, api_secret) { return `${k}=${toArray(v).join(",")}`; }).sort().join("&"); - return computeHash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex'); + return compute_hash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex'); } /** @@ -1103,7 +1123,7 @@ function api_sign_request(params_to_sign, api_secret) { * @param {string} encoding type of encoding * @return {string} computed hash value */ -function computeHash(input, signature_algorithm, encoding) { +function compute_hash(input, signature_algorithm, encoding) { if (!SUPPORTED_SIGNATURE_ALGORITHMS.includes(signature_algorithm)) { throw new Error(`Signature algorithm ${signature_algorithm} is not supported. Supported algorithms: ${SUPPORTED_SIGNATURE_ALGORITHMS.join(', ')}`); } @@ -1150,11 +1170,14 @@ function sign_request(params) { function webhook_signature(data, timestamp) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - ensurePresenceOf({ data, timestamp }); + ensurePresenceOf({ + data, + timestamp + }); var api_secret = ensureOption(options, 'api_secret'); var signature_algorithm = ensureOption(options, 'signature_algorithm', DEFAULT_SIGNATURE_ALGORITHM); - return computeHash(data + timestamp + api_secret, signature_algorithm, 'hex'); + return compute_hash(data + timestamp + api_secret, signature_algorithm, 'hex'); } /** @@ -1253,7 +1276,9 @@ function download_backedup_asset(asset_id, version_id) { * @param options */ function api_download_url(action, params, options) { - var download_params = _extends({}, params, { mode: "download" }); + var download_params = _extends({}, params, { + mode: "download" + }); var cloudinary_params = exports.sign_request(download_params, options); return exports.api_url(action, options) + "?" + hashToQuery(cloudinary_params); } @@ -1487,6 +1512,7 @@ function process_video_params(param) { return null; } } + /** * Returns a Hash of parameters used to create an archive * @private @@ -1530,7 +1556,10 @@ exports.create_source_tag = function create_source_tag(src, source_type) { var codecs_str = isArray(codecs) ? codecs.join(', ') : codecs; mime_type += `; codecs=${codecs_str}`; } - return ``; + return ``; }; function build_explicit_api_params(public_id) { @@ -1726,6 +1755,8 @@ exports.jsonArrayParam = jsonArrayParam; exports.download_folder = download_folder; exports.base_api_url = base_api_url; exports.download_backedup_asset = download_backedup_asset; +exports.compute_hash = compute_hash; +exports.build_distribution_domain = build_distribution_domain; // was exported before, so kept for backwards compatibility exports.DEFAULT_POSTER_OPTIONS = DEFAULT_POSTER_OPTIONS; diff --git a/lib-es5/v2/search.js b/lib-es5/v2/search.js index 248063ee..15dde73c 100644 --- a/lib-es5/v2/search.js +++ b/lib-es5/v2/search.js @@ -5,10 +5,26 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var api = require('./api'); +var config = require('../config'); +var ensureOption = require('../utils/ensureOption').defaults(config()); var _require = require('../utils'), isEmpty = _require.isEmpty, - isNumber = _require.isNumber; + isNumber = _require.isNumber, + api_sign_request = _require.api_sign_request, + compact_and_sort = _require.compact_and_sort, + compute_hash = _require.compute_hash, + build_distribution_domain = _require.build_distribution_domain, + clear_blank = _require.clear_blank; + +var _require2 = require("../utils/consts"), + DEFAULT_SIGNATURE_ALGORITHM = _require2.DEFAULT_SIGNATURE_ALGORITHM; + +var _require3 = require("crypto"), + sign = _require3.sign; + +var _require4 = require("../utils/encoding/base64Encode"), + base64Encode = _require4.base64Encode; var Search = function () { function Search() { @@ -19,6 +35,7 @@ var Search = function () { aggregate: [], with_field: [] }; + this._ttl = 300; } _createClass(Search, [{ @@ -89,6 +106,16 @@ var Search = function () { return this; } + }, { + key: 'ttl', + value: function ttl(newTtl) { + if (isNumber(newTtl)) { + this._ttl = newTtl; + return this; + } + + throw new Error('New TTL value has to be a Number.'); + } }, { key: 'to_query', value: function to_query() { @@ -111,6 +138,37 @@ var Search = function () { options = options || {}; return api.search(this.to_query(), options, callback); } + }, { + key: 'to_url', + value: function to_url(ttl, next_cursor) { + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + var apiSecret = 'api_secret' in options ? options.api_secret : config().api_secret; + if (!apiSecret) { + throw new Error('Must supply api_secret'); + } + + var signingAlgorithm = options.signature_algorithm || config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM; + + var urlTtl = ttl || this._ttl; + + var query = this.to_query(); + + var urlCursor = next_cursor; + if (query.next_cursor && !next_cursor) { + urlCursor = query.next_cursor; + delete query.next_cursor; + } + + var data = clear_blank(query); + var encodedQuery = base64Encode(JSON.stringify(data)); + + var urlPrefix = build_distribution_domain(options); + + var signature = compute_hash(`${urlTtl}${encodedQuery}${apiSecret}`, signingAlgorithm, 'hex'); + + return urlCursor ? `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}/${urlCursor}` : `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}/`; + } }], [{ key: 'instance', value: function instance() { @@ -148,6 +206,11 @@ var Search = function () { return this.instance().sort_by(field_name, dir); } + }, { + key: 'ttl', + value: function ttl(newTtl) { + return this.instance().ttl(newTtl); + } }]); return Search; diff --git a/lib/utils/index.js b/lib/utils/index.js index 9497baf3..c05ff090 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -33,7 +33,7 @@ const isUndefined = require("lodash/isUndefined"); const smart_escape = require("./encoding/smart_escape"); const consumeOption = require('./parsing/consumeOption'); const toArray = require('./parsing/toArray'); -let { base64EncodeURL } = require('./encoding/base64EncodeURL'); +let {base64EncodeURL} = require('./encoding/base64EncodeURL'); const encodeDoubleArray = require('./encoding/encodeDoubleArray'); const config = require("../config"); @@ -45,7 +45,10 @@ const ensureOption = require('./ensureOption').defaults(config()); const entries = require('./entries'); const isRemoteUrl = require('./isRemoteUrl'); const getSDKVersions = require('./encoding/sdkAnalytics/getSDKVersions'); -const {getAnalyticsOptions, getSDKAnalyticsSignature} = require('cloudinary-core').Util; +const { + getAnalyticsOptions, + getSDKAnalyticsSignature +} = require('cloudinary-core').Util; exports = module.exports; const utils = module.exports; @@ -373,7 +376,9 @@ function build_upload_params(options) { } function encode_key_value(arg) { - if (!isObject(arg)) { return arg; } + if (!isObject(arg)) { + return arg; + } return entries(arg).map(([k, v]) => `${k}=${v}`).join('|'); } @@ -403,7 +408,9 @@ function escapeMetadataValue(value) { * @return {string} */ function encode_context(metadataObj) { - if (!isObject(metadataObj)) { return metadataObj; } + if (!isObject(metadataObj)) { + return metadataObj; + } return entries(metadataObj).map(([key, value]) => { // if string, simply parse the value and move on @@ -434,6 +441,7 @@ function build_eager(transformations) { return format == null ? transformationString : `${transformationString}/${format}`; }).join('|'); } + /** * Build the custom headers for the request * @private @@ -488,7 +496,7 @@ function generate_transformation_string(options) { let named_transformation = []; if (base_transformations.some(isObject)) { base_transformations = base_transformations.map(tr => utils.generate_transformation_string( - isObject(tr) ? clone(tr) : { transformation: tr } + isObject(tr) ? clone(tr) : {transformation: tr} )); } else { named_transformation = base_transformations.join("."); @@ -655,13 +663,13 @@ function updateable_resource_params(options, params = {}) { if (options.quality_override != null) { params.quality_override = options.quality_override; } - if (options.asset_folder != null){ + if (options.asset_folder != null) { params.asset_folder = options.asset_folder; } - if (options.display_name != null){ + if (options.display_name != null) { params.display_name = options.display_name; } - if (options.unique_display_name != null){ + if (options.unique_display_name != null) { params.unique_display_name = options.unique_display_name; } return params; @@ -727,6 +735,24 @@ function patchFetchFormat(options = {}) { } } +function build_distribution_domain(options) { + const source = consumeOption(options, 'source', ''); + const cloud_name = consumeOption(options, 'cloud_name', config().cloud_name); + + if (!cloud_name) { + throw new Error('Must supply cloud_name in tag or in configuration'); + } + + const secure = consumeOption(options, 'secure', config().secure); + const private_cdn = consumeOption(options, 'private_cdn', config().private_cdn); + const cname = consumeOption(options, 'cname', config().cname); + const secure_distribution = consumeOption(options, 'secure_distribution', config().secure_distribution); + const cdn_subdomain = consumeOption(options, 'cdn_subdomain', config().cdn_subdomain); + const secure_cdn_subdomain = consumeOption(options, 'secure_cdn_subdomain', config().secure_cdn_subdomain); + + return unsigned_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution); +} + function url(public_id, options = {}) { let signature, source_to_sign; utils.patchFetchFormat(options); @@ -807,7 +833,7 @@ function url(public_id, options = {}) { // eslint-disable-next-line no-empty } catch (error) { } - let hash = computeHash(to_sign + api_secret, signature_algorithm, 'base64'); + let hash = compute_hash(to_sign + api_secret, signature_algorithm, 'base64'); signature = hash.replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8); signature = `s--${signature}--`; } @@ -833,7 +859,11 @@ function url(public_id, options = {}) { let urlAnalytics = ensureOption(options, 'urlAnalytics', false); if (urlAnalytics === true) { - let { sdkCode, sdkSemver, techVersion } = getSDKVersions(); + let { + sdkCode, + sdkSemver, + techVersion + } = getSDKVersions(); let sdkVersions = { sdkCode: ensureOption(options, 'sdkCode', sdkCode), sdkSemver: ensureOption(options, 'sdkSemver', sdkSemver), @@ -930,6 +960,7 @@ function finalize_resource_type(resource_type, type, url_suffix, use_root_path, } return [resource_type, type]; } + // cdn_subdomain and secure_cdn_subdomain // 1) Customers in shared distribution (e.g. res.cloudinary.com) // if cdn_domain is true uses res-[1-5].cloudinary.com for both http and https. @@ -986,7 +1017,7 @@ function unsigned_url_prefix( return prefix; } -function base_api_url(path= [], options = {}) { +function base_api_url(path = [], options = {}) { let cloudinary = ensureOption(options, "upload_prefix", UPLOAD_PREFIX); let cloud_name = ensureOption(options, "cloud_name"); let encode_path = unencoded_path => encodeURIComponent(unencoded_path).replace("'", '%27'); @@ -1013,7 +1044,7 @@ function api_sign_request(params_to_sign, api_secret) { ).map( ([k, v]) => `${k}=${toArray(v).join(",")}` ).sort().join("&"); - return computeHash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex'); + return compute_hash(to_sign + api_secret, config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM, 'hex'); } /** @@ -1024,7 +1055,7 @@ function api_sign_request(params_to_sign, api_secret) { * @param {string} encoding type of encoding * @return {string} computed hash value */ -function computeHash(input, signature_algorithm, encoding) { +function compute_hash(input, signature_algorithm, encoding) { if (!SUPPORTED_SIGNATURE_ALGORITHMS.includes(signature_algorithm)) { throw new Error(`Signature algorithm ${signature_algorithm} is not supported. Supported algorithms: ${SUPPORTED_SIGNATURE_ALGORITHMS.join(', ')}`); } @@ -1046,7 +1077,7 @@ function clear_blank(hash) { } function merge(hash1, hash2) { - return { ...hash1, ...hash2 }; + return {...hash1, ...hash2}; } function sign_request(params, options = {}) { @@ -1059,11 +1090,14 @@ function sign_request(params, options = {}) { } function webhook_signature(data, timestamp, options = {}) { - ensurePresenceOf({ data, timestamp }); + ensurePresenceOf({ + data, + timestamp + }); let api_secret = ensureOption(options, 'api_secret'); let signature_algorithm = ensureOption(options, 'signature_algorithm', DEFAULT_SIGNATURE_ALGORITHM); - return computeHash(data + timestamp + api_secret, signature_algorithm, 'hex'); + return compute_hash(data + timestamp + api_secret, signature_algorithm, 'hex'); } /** @@ -1154,7 +1188,10 @@ function download_backedup_asset(asset_id, version_id, options = {}) { * @param options */ function api_download_url(action, params, options) { - const download_params = {...params, mode: "download"} + const download_params = { + ...params, + mode: "download" + } let cloudinary_params = exports.sign_request(download_params, options); return exports.api_url(action, options) + "?" + hashToQuery(cloudinary_params); } @@ -1234,7 +1271,9 @@ function download_folder(folder_path, options = {}) { * @return {string} A string representing the HTML attribute */ function join_pair(key, value) { - if (!value) { return void 0; } + if (!value) { + return void 0; + } return value === true ? key : key + "='" + value + "'"; } @@ -1267,7 +1306,9 @@ function cloudinary_js_config() { } function v1_result_adapter(callback) { - if (callback == null) { return undefined; } + if (callback == null) { + return undefined; + } return function (result) { if (result.error != null) { return callback(result.error); @@ -1371,6 +1412,7 @@ function process_video_params(param) { return null; } } + /** * Returns a Hash of parameters used to create an archive * @private @@ -1410,11 +1452,14 @@ exports.create_source_tag = function create_source_tag(src, source_type, codecs let codecs_str = isArray(codecs) ? codecs.join(', ') : codecs; mime_type += `; codecs=${codecs_str}`; } - return ``; + return ``; }; function build_explicit_api_params(public_id, options = {}) { - return [exports.build_upload_params(extend({}, { public_id }, options))]; + return [exports.build_upload_params(extend({}, {public_id}, options))]; } function generate_responsive_breakpoints_string(breakpoints) { @@ -1537,7 +1582,8 @@ function jsonArrayParam(data, modifier) { * Empty function - do nothing * */ -exports.NOP = function () {}; +exports.NOP = function () { +}; exports.generate_auth_token = generate_auth_token; exports.getUserAgent = getUserAgent; exports.build_upload_params = build_upload_params; @@ -1588,6 +1634,8 @@ exports.jsonArrayParam = jsonArrayParam; exports.download_folder = download_folder; exports.base_api_url = base_api_url; exports.download_backedup_asset = download_backedup_asset; +exports.compute_hash = compute_hash; +exports.build_distribution_domain = build_distribution_domain; // was exported before, so kept for backwards compatibility exports.DEFAULT_POSTER_OPTIONS = DEFAULT_POSTER_OPTIONS; diff --git a/lib/v2/search.js b/lib/v2/search.js index 2ac64a39..0aef0090 100644 --- a/lib/v2/search.js +++ b/lib/v2/search.js @@ -1,5 +1,18 @@ const api = require('./api'); -const {isEmpty, isNumber} = require('../utils'); +const config = require('../config'); +const ensureOption = require('../utils/ensureOption').defaults(config()); +const { + isEmpty, + isNumber, + api_sign_request, + compact_and_sort, + compute_hash, + build_distribution_domain, + clear_blank +} = require('../utils'); +const {DEFAULT_SIGNATURE_ALGORITHM} = require("../utils/consts"); +const {sign} = require("crypto"); +const {base64Encode} = require("../utils/encoding/base64Encode"); const Search = class Search { constructor() { @@ -8,6 +21,7 @@ const Search = class Search { aggregate: [], with_field: [] }; + this._ttl = 300; } static instance() { @@ -38,6 +52,10 @@ const Search = class Search { return this.instance().sort_by(field_name, dir); } + static ttl(newTtl) { + return this.instance().ttl(newTtl); + } + expression(value) { this.query_hash.expression = value; return this; @@ -92,6 +110,15 @@ const Search = class Search { return this; } + ttl(newTtl) { + if (isNumber(newTtl)) { + this._ttl = newTtl; + return this; + } + + throw new Error('New TTL value has to be a Number.'); + } + to_query() { Object.keys(this.query_hash).forEach((k) => { let v = this.query_hash[k]; @@ -109,6 +136,34 @@ const Search = class Search { options = options || {}; return api.search(this.to_query(), options, callback); } + + to_url(ttl, next_cursor, options = {}) { + const apiSecret = 'api_secret' in options ? options.api_secret : config().api_secret; + if (!apiSecret) { + throw new Error('Must supply api_secret'); + } + + const signingAlgorithm = options.signature_algorithm || config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM; + + const urlTtl = ttl || this._ttl; + + const query = this.to_query(); + + let urlCursor = next_cursor; + if (query.next_cursor && !next_cursor) { + urlCursor = query.next_cursor; + delete query.next_cursor; + } + + let data = clear_blank(query); + const encodedQuery = base64Encode(JSON.stringify(data)); + + const urlPrefix = build_distribution_domain(options); + + const signature = compute_hash(`${urlTtl}${encodedQuery}${apiSecret}`, signingAlgorithm, 'hex'); + + return urlCursor ? `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}/${urlCursor}` : `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}/`; + } }; module.exports = Search; diff --git a/test/integration/api/search/search_spec.js b/test/integration/api/search/search_spec.js index 00394fea..9baf43e2 100644 --- a/test/integration/api/search/search_spec.js +++ b/test/integration/api/search/search_spec.js @@ -3,6 +3,8 @@ const cloudinary = require('../../../../cloudinary'); const helper = require("../../../spechelper"); const testConstants = require('../../../testUtils/testConstants'); const describe = require('../../../testUtils/suite'); +const exp = require("constants"); +const cluster = require("cluster"); const { TIMEOUT, TAGS, @@ -83,7 +85,72 @@ describe("search_api", function () { with_field: ['context', 'tags'] }); }); + + describe('to_url', () => { + const cloudName = 'test-cloud'; + + const commonUrlOptions = { + secure: true, + cloud_name: cloudName, + api_secret: 'test-secret', + api_key: 'test-key' + }; + + beforeEach(() => { + cloudinary.v2.config(commonUrlOptions); + }); + + const search = cloudinary.v2.search.expression('resource_type:image').sort_by('public_id', 'asc').max_results(10); + + const defaultSignature = 'b2eb2ae76343207c92c267130111b241c87c0246'; + const defaultTtl = '300'; + const encodedSearchPayload = 'eyJzb3J0X2J5IjpbeyJwdWJsaWNfaWQiOiJhc2MifV0sImV4cHJlc3Npb24iOiJyZXNvdXJjZV90eXBlOmltYWdlIiwibWF4X3Jlc3VsdHMiOjEwfQ=='; + + it('should build search url', () => { + const actual = search.to_url(); + + const expected = `https://res.cloudinary.com/${cloudName}/search/${defaultSignature}/${defaultTtl}/${encodedSearchPayload}/`; + expect(actual).to.eql(expected); + }); + + it('should build search url including next_cursor', () => { + const actual = search.to_url(null, 'next_cursor'); + const expected = `https://res.cloudinary.com/${cloudName}/search/${defaultSignature}/${defaultTtl}/${encodedSearchPayload}/next_cursor`; + expect(actual).to.eql(expected); + }); + + it('should build search url including next_cursor and ttl', () => { + const newTtl = 1000; + const actual = search.to_url(newTtl, 'next_cursor'); + const signature = '53314df951a294297d593b53d0b8e08f1bed5a81'; + const expected = `https://res.cloudinary.com/${cloudName}/search/${signature}/${newTtl}/${encodedSearchPayload}/next_cursor`; + expect(actual).to.eql(expected); + }); + + it('should build search url including next_cursor and ttl', () => { + const newTtl = 1000; + const actual = search.next_cursor('next_cursor').ttl(newTtl).to_url(); + const signature = '53314df951a294297d593b53d0b8e08f1bed5a81'; + const expected = `https://res.cloudinary.com/${cloudName}/search/${signature}/${newTtl}/${encodedSearchPayload}/next_cursor`; + expect(actual).to.eql(expected); + }); + + it('should build search url when private cdn configured', () => { + cloudinary.v2.config({ + secure: true, + cloud_name: cloudName, + private_cdn: true, + api_secret: 'secret', + api_key: 'key' + }); + const signature = '23154796f4aa4e81540ba36aece6b62fed911832'; + const actual = search.to_url(defaultTtl); + const expected = `https://${cloudName}-res.cloudinary.com/search/${signature}/${defaultTtl}/${encodedSearchPayload}/`; + expect(actual).to.eql(expected); + }); + }); }); + describe("integration", function () { this.timeout(TIMEOUT.LONG); before(function () { @@ -111,7 +178,7 @@ describe("search_api", function () { }) ]).delay(10000) .then((uploadResults) => { - uploadResults.forEach(({ value }) => { + uploadResults.forEach(({value}) => { ASSET_IDS.push(value.asset_id); }); }); diff --git a/test/unit/url/video_url_spec.js b/test/unit/url/video_url_spec.js index 541ad54b..6c97e63a 100644 --- a/test/unit/url/video_url_spec.js +++ b/test/unit/url/video_url_spec.js @@ -206,7 +206,8 @@ describe("Cloudinary::Utils for video", function () { var options, path, source; source = "movie_id"; options = createTestConfig({ - cloud_name: "test123" + cloud_name: "test123", + secure: false }); path = utils.video_thumbnail_url(source, options); it("should generate a cloudinary URI to the video thumbnail", function () { diff --git a/types/index.d.ts b/types/index.d.ts index cf4b414b..fe30ed52 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1196,10 +1196,14 @@ declare module 'cloudinary' { sort_by(key: string, value: 'asc' | 'desc'): search; + ttl(newTtl: number): search; + to_query(value?: string): search; with_field(value?: string): search; + to_url(newTtl: number | null, next_cursor: string | null, options: any): string; //todo: improve typing for options + static aggregate(args?: string): search; static expression(args?: string): search; @@ -1212,6 +1216,8 @@ declare module 'cloudinary' { static sort_by(key: string, value: 'asc' | 'desc'): search; + static ttl(newTtl: number): search; + static with_field(args?: string): search; } From 25a7c90ebebcc1680c07d958417a21282224e940 Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Tue, 18 Jul 2023 17:18:27 +0200 Subject: [PATCH 2/7] fix: improved formatting and typing for to_url options --- types/index.d.ts | 203 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 161 insertions(+), 42 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index fe30ed52..2cf8af97 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -59,7 +59,15 @@ declare module 'cloudinary' { | "auto:none" | "liquid" | "ocr_text"; - type Angle = number | (string & {}) | Array | "auto_right" | "auto_left" | "ignore" | "vflip" | "hflip"; + type Angle = + number + | (string & {}) + | Array + | "auto_right" + | "auto_left" + | "ignore" + | "vflip" + | "hflip"; type ImageEffect = | (string & {}) | "hue" @@ -112,7 +120,14 @@ declare module 'cloudinary' { | "gamma" | "improve"; - type VideoEffect = (string & {}) | "accelerate" | "reverse" | "boomerang" | "loop" | "make_transparent" | "transition"; + type VideoEffect = + (string & {}) + | "accelerate" + | "reverse" + | "boomerang" + | "loop" + | "make_transparent" + | "transition"; type AudioCodec = (string & {}) | "none" | "aac" | "vorbis" | "mp3"; type AudioFrequency = string @@ -510,7 +525,7 @@ declare module 'cloudinary' { public_id?: string; quality_analysis?: boolean; resource_type?: "image" | "video" | "raw" | "auto"; - responsive_breakpoints?: Record; + responsive_breakpoints?: Record; return_delete_token?: boolean timestamp?: number; transformation?: TransformationOptions; @@ -565,7 +580,15 @@ declare module 'cloudinary' { /****************************** API *************************************/ type Status = (string & {}) | "pending" | "approved" | "rejected"; - type StreamingProfiles = (string & {}) | "4k" | "full_hd" | "hd" | "sd" | "full_hd_wifi" | "full_hd_lean" | "hd_lean"; + type StreamingProfiles = + (string & {}) + | "4k" + | "full_hd" + | "hd" + | "sd" + | "full_hd_wifi" + | "full_hd_lean" + | "hd_lean"; type ModerationKind = (string & {}) | "manual" | "webpurify" | "aws_rek" | "metascan"; type AccessMode = (string & {}) | "public" | "authenticated"; type TargetArchiveFormat = (string & {}) | "zip" | "tgz"; @@ -800,7 +823,11 @@ declare module 'cloudinary' { namespace utils { - function sign_request(params_to_sign: SignApiOptions, options?: ConfigAndUrlOptions): { signature: string; api_key: string; [key:string]:any}; + function sign_request(params_to_sign: SignApiOptions, options?: ConfigAndUrlOptions): { + signature: string; + api_key: string; + [key: string]: any + }; function api_sign_request(params_to_sign: SignApiOptions, api_secret: string): string; @@ -822,13 +849,13 @@ declare module 'cloudinary' { function download_zip_url(options?: ArchiveApiOptions | ConfigAndUrlOptions): string; - function download_backedup_asset(asset_id?: string, version_id?: string, options?: ArchiveApiOptions | ConfigAndUrlOptions) : string + function download_backedup_asset(asset_id?: string, version_id?: string, options?: ArchiveApiOptions | ConfigAndUrlOptions): string function generate_auth_token(options?: AuthTokenApiOptions): string; function webhook_signature(data?: string, timestamp?: number, options?: ConfigOptions): string; - function private_download_url(publicID: string, format:string, options: Partial<{ + function private_download_url(publicID: string, format: string, options: Partial<{ resource_type: ResourceType; type: DeliveryType; expires_at: number; @@ -839,15 +866,26 @@ declare module 'cloudinary' { /****************************** Admin API V2 Methods *************************************/ namespace api { - function create_streaming_profile(name: string, options: AdminApiOptions | { display_name?: string, representations: TransformationOptions }, callback?: ResponseCallback): Promise; + function create_streaming_profile(name: string, options: AdminApiOptions | { + display_name?: string, + representations: TransformationOptions + }, callback?: ResponseCallback): Promise; function create_transformation(name: string, transformation: TransformationOptions, callback?: ResponseCallback): Promise; - function create_transformation(name: string, transformation: TransformationOptions, options?: AdminApiOptions | { allowed_for_strict?: boolean }, callback?: ResponseCallback): Promise; + function create_transformation(name: string, transformation: TransformationOptions, options?: AdminApiOptions | { + allowed_for_strict?: boolean + }, callback?: ResponseCallback): Promise; - function create_upload_mapping(folder: string, options: AdminApiOptions | { template: string }, callback?: ResponseCallback): Promise; + function create_upload_mapping(folder: string, options: AdminApiOptions | { + template: string + }, callback?: ResponseCallback): Promise; - function create_upload_preset(options?: AdminApiOptions | { name?: string, unsigned?: boolean, disallow_public_id?: boolean }, callback?: ResponseCallback): Promise; + function create_upload_preset(options?: AdminApiOptions | { + name?: string, + unsigned?: boolean, + disallow_public_id?: boolean + }, callback?: ResponseCallback): Promise; function delete_all_resources(value?: AdminAndResourceOptions, callback?: ResponseCallback): Promise; @@ -947,7 +985,10 @@ declare module 'cloudinary' { function resources_by_tag(tag: string, callback?: ResponseCallback): Promise; - function restore(public_ids: string[], options?: AdminApiOptions | { resource_type: ResourceType, type: DeliveryType }, callback?: ResponseCallback): Promise; + function restore(public_ids: string[], options?: AdminApiOptions | { + resource_type: ResourceType, + type: DeliveryType + }, callback?: ResponseCallback): Promise; function restore(public_ids: string[], callback?: ResponseCallback): Promise; @@ -965,13 +1006,25 @@ declare module 'cloudinary' { function search_folders(search_input: string, callback?: ResponseCallback): Promise; - function tags(options?: AdminApiOptions | { max_results?: number, next_cursor?: string, prefix?: string }, callback?: ResponseCallback): Promise; + function tags(options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string, + prefix?: string + }, callback?: ResponseCallback): Promise; - function transformation(transformation: TransformationOptions, options?: AdminApiOptions | { max_results?: number, next_cursor?: string, named?: boolean }, callback?: ResponseCallback): Promise; + function transformation(transformation: TransformationOptions, options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string, + named?: boolean + }, callback?: ResponseCallback): Promise; function transformation(transformation: TransformationOptions, callback?: ResponseCallback): Promise; - function transformations(options?: AdminApiOptions | { max_results?: number, next_cursor?: string, named?: boolean }, callback?: ResponseCallback): Promise; + function transformations(options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string, + named?: boolean + }, callback?: ResponseCallback): Promise; function transformations(callback?: ResponseCallback): Promise; @@ -991,15 +1044,23 @@ declare module 'cloudinary' { function update_resources_access_mode_by_tag(access_mode: AccessMode, tag: string, callback?: ResponseCallback): Promise; - function update_streaming_profile(name: string, options: { display_name?: string, representations: Array<{ transformation?: VideoTransformationOptions }> }, callback?: ResponseCallback): Promise; + function update_streaming_profile(name: string, options: { + display_name?: string, + representations: Array<{ transformation?: VideoTransformationOptions }> + }, callback?: ResponseCallback): Promise; function update_transformation(transformation_name: TransformationOptions, updates?: TransformationOptions, callback?: ResponseCallback): Promise; function update_transformation(transformation_name: TransformationOptions, callback?: ResponseCallback): Promise; - function update_upload_mapping(name: string, options: AdminApiOptions | { template: string }, callback?: ResponseCallback): Promise; + function update_upload_mapping(name: string, options: AdminApiOptions | { + template: string + }, callback?: ResponseCallback): Promise; - function update_upload_preset(name?: string, options?: AdminApiOptions | { unsigned?: boolean, disallow_public_id?: boolean }, callback?: ResponseCallback): Promise; + function update_upload_preset(name?: string, options?: AdminApiOptions | { + unsigned?: boolean, + disallow_public_id?: boolean + }, callback?: ResponseCallback): Promise; function update_upload_preset(name?: string, callback?: ResponseCallback): Promise; @@ -1007,7 +1068,10 @@ declare module 'cloudinary' { function upload_mapping(name?: string, callback?: ResponseCallback): Promise; - function upload_mappings(options?: AdminApiOptions | { max_results?: number, next_cursor?: string }, callback?: ResponseCallback): Promise; + function upload_mappings(options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string + }, callback?: ResponseCallback): Promise; function upload_mappings(callback?: ResponseCallback): Promise; @@ -1015,15 +1079,18 @@ declare module 'cloudinary' { function upload_preset(name?: string, callback?: ResponseCallback): Promise; - function upload_presets(options?: AdminApiOptions | { max_results?: number, next_cursor?: string }, callback?: ResponseCallback): Promise; + function upload_presets(options?: AdminApiOptions | { + max_results?: number, + next_cursor?: string + }, callback?: ResponseCallback): Promise; function usage(callback?: ResponseCallback, options?: AdminApiOptions): Promise; function usage(options?: AdminApiOptions): Promise; - function create_folder(path:string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; + function create_folder(path: string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; - function delete_folder(path:string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; + function delete_folder(path: string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; /****************************** Structured Metadata API V2 Methods *************************************/ @@ -1039,9 +1106,9 @@ declare module 'cloudinary' { function delete_metadata_field(field_external_id: string, callback?: ResponseCallback): Promise; - function metadata_field_by_field_id(external_id:string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; + function metadata_field_by_field_id(external_id: string, options?: AdminApiOptions, callback?: ResponseCallback): Promise; - function metadata_field_by_field_id(external_id:string, callback?: ResponseCallback): Promise; + function metadata_field_by_field_id(external_id: string, callback?: ResponseCallback): Promise; function update_metadata_field(external_id: string, field: MetadataFieldApiOptions, options?: AdminApiOptions, callback?: ResponseCallback): Promise; @@ -1081,11 +1148,17 @@ declare module 'cloudinary' { /****************************** Upload API V2 Methods *************************************/ namespace uploader { - function add_context(context: string, public_ids: string[], options?: { type?: DeliveryType, resource_type?: ResourceType }, callback?: ResponseCallback): Promise; + function add_context(context: string, public_ids: string[], options?: { + type?: DeliveryType, + resource_type?: ResourceType + }, callback?: ResponseCallback): Promise; function add_context(context: string, public_ids: string[], callback?: ResponseCallback): Promise; - function add_tag(tag: string, public_ids: string[], options?: { type?: DeliveryType, resource_type?: ResourceType }, callback?: ResponseCallback): Promise; + function add_tag(tag: string, public_ids: string[], options?: { + type?: DeliveryType, + resource_type?: ResourceType + }, callback?: ResponseCallback): Promise; function add_tag(tag: string, public_ids: string[], callback?: ResponseCallback): Promise; @@ -1093,7 +1166,11 @@ declare module 'cloudinary' { function create_zip(options?: ArchiveApiOptions, callback?: ResponseCallback): Promise; - function destroy(public_id: string, options?: { resource_type?: ResourceType, type?: DeliveryType, invalidate?: boolean }, callback?: ResponseCallback,): Promise; + function destroy(public_id: string, options?: { + resource_type?: ResourceType, + type?: DeliveryType, + invalidate?: boolean + }, callback?: ResponseCallback,): Promise; function destroy(public_id: string, callback?: ResponseCallback,): Promise; @@ -1101,41 +1178,80 @@ declare module 'cloudinary' { function explicit(public_id: string, callback?: ResponseCallback): Promise; - function explode(public_id: string, options?: { page?: 'all', type?: DeliveryType, format?: ImageAndVideoFormatOptions, notification_url?: string, transformations?: TransformationOptions }, callback?: ResponseCallback): Promise + function explode(public_id: string, options?: { + page?: 'all', + type?: DeliveryType, + format?: ImageAndVideoFormatOptions, + notification_url?: string, + transformations?: TransformationOptions + }, callback?: ResponseCallback): Promise function explode(public_id: string, callback?: ResponseCallback): Promise - function generate_sprite(tag: string, options?: { transformation?: TransformationOptions, format?: ImageAndVideoFormatOptions, notification_url?: string, async?: boolean }, callback?: ResponseCallback): Promise; + function generate_sprite(tag: string, options?: { + transformation?: TransformationOptions, + format?: ImageAndVideoFormatOptions, + notification_url?: string, + async?: boolean + }, callback?: ResponseCallback): Promise; function generate_sprite(tag: string, callback?: ResponseCallback): Promise; function image_upload_tag(field?: string, options?: UploadApiOptions): Promise; - function multi(tag: string, options?: { transformation?: TransformationOptions, async?: boolean, format?: ImageAndVideoFormatOptions, notification_url?: string }, callback?: ResponseCallback): Promise; + function multi(tag: string, options?: { + transformation?: TransformationOptions, + async?: boolean, + format?: ImageAndVideoFormatOptions, + notification_url?: string + }, callback?: ResponseCallback): Promise; function multi(tag: string, callback?: ResponseCallback): Promise; - function remove_all_context(public_ids: string[], options?: { context?: string, resource_type?: ResourceType, type?: DeliveryType }, callback?: ResponseCallback): Promise; + function remove_all_context(public_ids: string[], options?: { + context?: string, + resource_type?: ResourceType, + type?: DeliveryType + }, callback?: ResponseCallback): Promise; function remove_all_context(public_ids: string[], callback?: ResponseCallback): Promise; - function remove_all_tags(public_ids: string[], options?: { tag?: string, resource_type?: ResourceType, type?: DeliveryType }, callback?: ResponseCallback): Promise; + function remove_all_tags(public_ids: string[], options?: { + tag?: string, + resource_type?: ResourceType, + type?: DeliveryType + }, callback?: ResponseCallback): Promise; function remove_all_tags(public_ids: string[], callback?: ResponseCallback): Promise; - function remove_tag(tag: string, public_ids: string[], options?: { tag?: string, resource_type?: ResourceType, type?: DeliveryType }, callback?: ResponseCallback): Promise; + function remove_tag(tag: string, public_ids: string[], options?: { + tag?: string, + resource_type?: ResourceType, + type?: DeliveryType + }, callback?: ResponseCallback): Promise; function remove_tag(tag: string, public_ids: string[], callback?: ResponseCallback): Promise; - function rename(from_public_id: string, to_public_id: string, options?: { resource_type?: ResourceType, type?: DeliveryType, to_type?: DeliveryType, overwrite?: boolean, invalidate?: boolean }, callback?: ResponseCallback): Promise; + function rename(from_public_id: string, to_public_id: string, options?: { + resource_type?: ResourceType, + type?: DeliveryType, + to_type?: DeliveryType, + overwrite?: boolean, + invalidate?: boolean + }, callback?: ResponseCallback): Promise; function rename(from_public_id: string, to_public_id: string, callback?: ResponseCallback): Promise; - function replace_tag(tag: string, public_ids: string[], options?: { resource_type?: ResourceType, type?: DeliveryType }, callback?: ResponseCallback): Promise; + function replace_tag(tag: string, public_ids: string[], options?: { + resource_type?: ResourceType, + type?: DeliveryType + }, callback?: ResponseCallback): Promise; function replace_tag(tag: string, public_ids: string[], callback?: ResponseCallback): Promise; - function text(text: string, options?: TextStyleOptions | { public_id?: string }, callback?: ResponseCallback): Promise; + function text(text: string, options?: TextStyleOptions | { + public_id?: string + }, callback?: ResponseCallback): Promise; function text(text: string, callback?: ResponseCallback): Promise; @@ -1171,13 +1287,16 @@ declare module 'cloudinary' { function upload_url(options?: ConfigOptions): Promise; - function create_slideshow(options?: ConfigOptions & { manifest_transformation?: TransformationOptions, manifest_json?: Record}, callback?: UploadResponseCallback): Promise; + function create_slideshow(options?: ConfigOptions & { + manifest_transformation?: TransformationOptions, + manifest_json?: Record + }, callback?: UploadResponseCallback): Promise; /****************************** Structured Metadata API V2 Methods *************************************/ - function update_metadata(metadata: string | Record, public_ids: string[], options?:UploadApiOptions, callback?: ResponseCallback): Promise; + function update_metadata(metadata: string | Record, public_ids: string[], options?: UploadApiOptions, callback?: ResponseCallback): Promise; - function update_metadata(metadata: string| Record, public_ids: string[], callback?: ResponseCallback): Promise; + function update_metadata(metadata: string | Record, public_ids: string[], callback?: ResponseCallback): Promise; } /****************************** Search API *************************************/ @@ -1202,7 +1321,7 @@ declare module 'cloudinary' { with_field(value?: string): search; - to_url(newTtl: number | null, next_cursor: string | null, options: any): string; //todo: improve typing for options + to_url(newTtl: number | null, next_cursor: string | null, options: ConfigOptions): string; static aggregate(args?: string): search; @@ -1229,11 +1348,11 @@ declare module 'cloudinary' { function sub_account(subAccountId: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; - function create_sub_account(name: string, cloudName: string, customAttributes?: Record, enabled?: boolean, baseAccount?: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; + function create_sub_account(name: string, cloudName: string, customAttributes?: Record, enabled?: boolean, baseAccount?: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; function delete_sub_account(subAccountId: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; - function update_sub_account(subAccountId: string, name?: string, cloudName?: string, customAttributes?: Record, enabled?: boolean, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; + function update_sub_account(subAccountId: string, name?: string, cloudName?: string, customAttributes?: Record, enabled?: boolean, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; function user(userId: string, options?: ProvisioningApiOptions, callback?: ResponseCallback): Promise; From e7b1835a89b235a621703d1c3888e5ade7517ef3 Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Wed, 19 Jul 2023 15:40:07 +0200 Subject: [PATCH 3/7] chore: improved types and added type tests --- types/cloudinary_ts_spec.ts | 11 +++++++++++ types/index.d.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/types/cloudinary_ts_spec.ts b/types/cloudinary_ts_spec.ts index 9b551a9d..c2255c01 100644 --- a/types/cloudinary_ts_spec.ts +++ b/types/cloudinary_ts_spec.ts @@ -881,6 +881,7 @@ cloudinary.v2.search .expression('cat -tags:kitten') .sort_by('public_id', 'desc') .aggregate('format') + .ttl(10) .execute().then(result => console.log(result)); // $ExpectType Promise @@ -889,6 +890,7 @@ cloudinary.v2.search .with_field('context') .with_field('tags') .max_results(10) + .ttl(10) .execute().then(result => console.log(result)); // $ExpectType Promise @@ -896,8 +898,17 @@ cloudinary.v2.search .expression('resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m') .sort_by('public_id', 'desc') .max_results(30) + .ttl(10) .execute().then(result => console.log(result)); +// $ExpectType string +cloudinary.v2.search + .expression('resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m') + .sort_by('public_id', 'desc') + .max_results(30) + .ttl(10) + .to_url(); + // $ExpectType string let test2 = cloudinary.v2.url("sample.jpg", { sign_url: true, diff --git a/types/index.d.ts b/types/index.d.ts index 2cf8af97..3851b2e8 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1321,7 +1321,7 @@ declare module 'cloudinary' { with_field(value?: string): search; - to_url(newTtl: number | null, next_cursor: string | null, options: ConfigOptions): string; + to_url(newTtl?: number, next_cursor?: string, options?: ConfigOptions): string; static aggregate(args?: string): search; From 29ab3022947a41880008f135dfeb37e11b88d725 Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Wed, 19 Jul 2023 15:40:48 +0200 Subject: [PATCH 4/7] fix: pr fixes, better code organisation --- lib-es5/utils/index.js | 8 ++ lib-es5/v2/search.js | 29 ++--- lib/utils/index.js | 8 ++ lib/v2/search.js | 23 ++-- test/integration/api/search/search_spec.js | 125 ------------------- test/unit/search/search_spec.js | 133 +++++++++++++++++++++ test/utils/utils_spec.js | 20 +++- 7 files changed, 187 insertions(+), 159 deletions(-) create mode 100644 test/unit/search/search_spec.js diff --git a/lib-es5/utils/index.js b/lib-es5/utils/index.js index eec3016e..49a41614 100644 --- a/lib-es5/utils/index.js +++ b/lib-es5/utils/index.js @@ -1152,6 +1152,13 @@ function clear_blank(hash) { return filtered_hash; } +function sort_object_by_key(object) { + return Object.keys(object).sort().reduce(function (obj, key) { + obj[key] = object[key]; + return obj; + }, {}); +} + function merge(hash1, hash2) { return _extends({}, hash1, hash2); } @@ -1757,6 +1764,7 @@ exports.base_api_url = base_api_url; exports.download_backedup_asset = download_backedup_asset; exports.compute_hash = compute_hash; exports.build_distribution_domain = build_distribution_domain; +exports.sort_object_by_key = sort_object_by_key; // was exported before, so kept for backwards compatibility exports.DEFAULT_POSTER_OPTIONS = DEFAULT_POSTER_OPTIONS; diff --git a/lib-es5/v2/search.js b/lib-es5/v2/search.js index 15dde73c..de7832a4 100644 --- a/lib-es5/v2/search.js +++ b/lib-es5/v2/search.js @@ -6,25 +6,17 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons var api = require('./api'); var config = require('../config'); -var ensureOption = require('../utils/ensureOption').defaults(config()); var _require = require('../utils'), isEmpty = _require.isEmpty, isNumber = _require.isNumber, - api_sign_request = _require.api_sign_request, - compact_and_sort = _require.compact_and_sort, compute_hash = _require.compute_hash, build_distribution_domain = _require.build_distribution_domain, - clear_blank = _require.clear_blank; + clear_blank = _require.clear_blank, + sort_object_by_key = _require.sort_object_by_key; -var _require2 = require("../utils/consts"), - DEFAULT_SIGNATURE_ALGORITHM = _require2.DEFAULT_SIGNATURE_ALGORITHM; - -var _require3 = require("crypto"), - sign = _require3.sign; - -var _require4 = require("../utils/encoding/base64Encode"), - base64Encode = _require4.base64Encode; +var _require2 = require('../utils/encoding/base64Encode'), + base64Encode = _require2.base64Encode; var Search = function () { function Search() { @@ -148,8 +140,6 @@ var Search = function () { throw new Error('Must supply api_secret'); } - var signingAlgorithm = options.signature_algorithm || config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM; - var urlTtl = ttl || this._ttl; var query = this.to_query(); @@ -157,17 +147,18 @@ var Search = function () { var urlCursor = next_cursor; if (query.next_cursor && !next_cursor) { urlCursor = query.next_cursor; - delete query.next_cursor; } + delete query.next_cursor; - var data = clear_blank(query); - var encodedQuery = base64Encode(JSON.stringify(data)); + var dataOrderedByKey = sort_object_by_key(clear_blank(query)); + var encodedQuery = base64Encode(JSON.stringify(dataOrderedByKey)); var urlPrefix = build_distribution_domain(options); - var signature = compute_hash(`${urlTtl}${encodedQuery}${apiSecret}`, signingAlgorithm, 'hex'); + var signature = compute_hash(`${urlTtl}${encodedQuery}${apiSecret}`, 'sha256', 'hex'); - return urlCursor ? `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}/${urlCursor}` : `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}/`; + var urlWithoutCursor = `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}`; + return urlCursor ? `${urlWithoutCursor}/${urlCursor}` : urlWithoutCursor; } }], [{ key: 'instance', diff --git a/lib/utils/index.js b/lib/utils/index.js index c05ff090..babccac3 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1076,6 +1076,13 @@ function clear_blank(hash) { return filtered_hash; } +function sort_object_by_key(object) { + return Object.keys(object).sort().reduce((obj, key) => { + obj[key] = object[key]; + return obj; + }, {}); +} + function merge(hash1, hash2) { return {...hash1, ...hash2}; } @@ -1636,6 +1643,7 @@ exports.base_api_url = base_api_url; exports.download_backedup_asset = download_backedup_asset; exports.compute_hash = compute_hash; exports.build_distribution_domain = build_distribution_domain; +exports.sort_object_by_key = sort_object_by_key; // was exported before, so kept for backwards compatibility exports.DEFAULT_POSTER_OPTIONS = DEFAULT_POSTER_OPTIONS; diff --git a/lib/v2/search.js b/lib/v2/search.js index 0aef0090..dfde4958 100644 --- a/lib/v2/search.js +++ b/lib/v2/search.js @@ -1,18 +1,14 @@ const api = require('./api'); const config = require('../config'); -const ensureOption = require('../utils/ensureOption').defaults(config()); const { isEmpty, isNumber, - api_sign_request, - compact_and_sort, compute_hash, build_distribution_domain, - clear_blank + clear_blank, + sort_object_by_key } = require('../utils'); -const {DEFAULT_SIGNATURE_ALGORITHM} = require("../utils/consts"); -const {sign} = require("crypto"); -const {base64Encode} = require("../utils/encoding/base64Encode"); +const {base64Encode} = require('../utils/encoding/base64Encode'); const Search = class Search { constructor() { @@ -143,8 +139,6 @@ const Search = class Search { throw new Error('Must supply api_secret'); } - const signingAlgorithm = options.signature_algorithm || config().signature_algorithm || DEFAULT_SIGNATURE_ALGORITHM; - const urlTtl = ttl || this._ttl; const query = this.to_query(); @@ -152,17 +146,18 @@ const Search = class Search { let urlCursor = next_cursor; if (query.next_cursor && !next_cursor) { urlCursor = query.next_cursor; - delete query.next_cursor; } + delete query.next_cursor; - let data = clear_blank(query); - const encodedQuery = base64Encode(JSON.stringify(data)); + const dataOrderedByKey = sort_object_by_key(clear_blank(query)); + const encodedQuery = base64Encode(JSON.stringify(dataOrderedByKey)); const urlPrefix = build_distribution_domain(options); - const signature = compute_hash(`${urlTtl}${encodedQuery}${apiSecret}`, signingAlgorithm, 'hex'); + const signature = compute_hash(`${urlTtl}${encodedQuery}${apiSecret}`, 'sha256', 'hex'); - return urlCursor ? `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}/${urlCursor}` : `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}/`; + const urlWithoutCursor = `${urlPrefix}/search/${signature}/${urlTtl}/${encodedQuery}`; + return urlCursor ? `${urlWithoutCursor}/${urlCursor}` : urlWithoutCursor; } }; diff --git a/test/integration/api/search/search_spec.js b/test/integration/api/search/search_spec.js index 9baf43e2..ef05ca7e 100644 --- a/test/integration/api/search/search_spec.js +++ b/test/integration/api/search/search_spec.js @@ -26,131 +26,6 @@ const SEARCH_TAG = 'npm_advanced_search_' + UNIQUE_JOB_SUFFIX_ID; const ASSET_IDS = []; describe("search_api", function () { - describe("unit", function () { - it('should create empty json', function () { - var query_hash = cloudinary.v2.search.instance().to_query(); - expect(query_hash).to.eql({}); - }); - it('should always return same object in fluent interface', function () { - let instance = cloudinary.v2.search.instance(); - [ - 'expression', - 'sort_by', - 'max_results', - 'next_cursor', - 'aggregate', - 'with_field' - ].forEach(method => expect(instance).to.eql(instance[method]('emptyarg'))); - }); - it('should add expression to query', function () { - var query = cloudinary.v2.search.expression('format:jpg').to_query(); - expect(query).to.eql({ - expression: 'format:jpg' - }); - }); - it('should add sort_by to query', function () { - var query = cloudinary.v2.search.sort_by('created_at', 'asc').sort_by('updated_at', 'desc').to_query(); - expect(query).to.eql({ - sort_by: [ - { - created_at: 'asc' - }, - { - updated_at: 'desc' - } - ] - }); - }); - it('should add max_results to query', function () { - var query = cloudinary.v2.search.max_results('format:jpg').to_query(); - expect(query).to.eql({ - max_results: 'format:jpg' - }); - }); - it('should add next_cursor to query', function () { - var query = cloudinary.v2.search.next_cursor('format:jpg').to_query(); - expect(query).to.eql({ - next_cursor: 'format:jpg' - }); - }); - it('should add aggregate arguments as array to query', function () { - var query = cloudinary.v2.search.aggregate('format').aggregate('size_category').to_query(); - expect(query).to.eql({ - aggregate: ['format', 'size_category'] - }); - }); - it('should add with_field to query', function () { - var query = cloudinary.v2.search.with_field('context').with_field('tags').to_query(); - expect(query).to.eql({ - with_field: ['context', 'tags'] - }); - }); - - describe('to_url', () => { - const cloudName = 'test-cloud'; - - const commonUrlOptions = { - secure: true, - cloud_name: cloudName, - api_secret: 'test-secret', - api_key: 'test-key' - }; - - beforeEach(() => { - cloudinary.v2.config(commonUrlOptions); - }); - - const search = cloudinary.v2.search.expression('resource_type:image').sort_by('public_id', 'asc').max_results(10); - - const defaultSignature = 'b2eb2ae76343207c92c267130111b241c87c0246'; - const defaultTtl = '300'; - const encodedSearchPayload = 'eyJzb3J0X2J5IjpbeyJwdWJsaWNfaWQiOiJhc2MifV0sImV4cHJlc3Npb24iOiJyZXNvdXJjZV90eXBlOmltYWdlIiwibWF4X3Jlc3VsdHMiOjEwfQ=='; - - it('should build search url', () => { - const actual = search.to_url(); - - const expected = `https://res.cloudinary.com/${cloudName}/search/${defaultSignature}/${defaultTtl}/${encodedSearchPayload}/`; - expect(actual).to.eql(expected); - }); - - it('should build search url including next_cursor', () => { - const actual = search.to_url(null, 'next_cursor'); - const expected = `https://res.cloudinary.com/${cloudName}/search/${defaultSignature}/${defaultTtl}/${encodedSearchPayload}/next_cursor`; - expect(actual).to.eql(expected); - }); - - it('should build search url including next_cursor and ttl', () => { - const newTtl = 1000; - const actual = search.to_url(newTtl, 'next_cursor'); - const signature = '53314df951a294297d593b53d0b8e08f1bed5a81'; - const expected = `https://res.cloudinary.com/${cloudName}/search/${signature}/${newTtl}/${encodedSearchPayload}/next_cursor`; - expect(actual).to.eql(expected); - }); - - it('should build search url including next_cursor and ttl', () => { - const newTtl = 1000; - const actual = search.next_cursor('next_cursor').ttl(newTtl).to_url(); - const signature = '53314df951a294297d593b53d0b8e08f1bed5a81'; - const expected = `https://res.cloudinary.com/${cloudName}/search/${signature}/${newTtl}/${encodedSearchPayload}/next_cursor`; - expect(actual).to.eql(expected); - }); - - it('should build search url when private cdn configured', () => { - cloudinary.v2.config({ - secure: true, - cloud_name: cloudName, - private_cdn: true, - api_secret: 'secret', - api_key: 'key' - }); - const signature = '23154796f4aa4e81540ba36aece6b62fed911832'; - const actual = search.to_url(defaultTtl); - const expected = `https://${cloudName}-res.cloudinary.com/search/${signature}/${defaultTtl}/${encodedSearchPayload}/`; - expect(actual).to.eql(expected); - }); - }); - }); - describe("integration", function () { this.timeout(TIMEOUT.LONG); before(function () { diff --git a/test/unit/search/search_spec.js b/test/unit/search/search_spec.js new file mode 100644 index 00000000..d038ea45 --- /dev/null +++ b/test/unit/search/search_spec.js @@ -0,0 +1,133 @@ +const cloudinary = require('../../../cloudinary'); + +describe('Search', () => { + it('should create empty json', function () { + var query_hash = cloudinary.v2.search.instance().to_query(); + expect(query_hash).to.eql({}); + }); + + it('should always return same object in fluent interface', function () { + let instance = cloudinary.v2.search.instance(); + [ + 'expression', + 'sort_by', + 'max_results', + 'next_cursor', + 'aggregate', + 'with_field' + ].forEach(method => expect(instance).to.eql(instance[method]('emptyarg'))); + }); + + it('should add expression to query', function () { + var query = cloudinary.v2.search.expression('format:jpg').to_query(); + expect(query).to.eql({ + expression: 'format:jpg' + }); + }); + + it('should add sort_by to query', function () { + var query = cloudinary.v2.search.sort_by('created_at', 'asc').sort_by('updated_at', 'desc').to_query(); + expect(query).to.eql({ + sort_by: [ + { + created_at: 'asc' + }, + { + updated_at: 'desc' + } + ] + }); + }); + + it('should add max_results to query', function () { + var query = cloudinary.v2.search.max_results('format:jpg').to_query(); + expect(query).to.eql({ + max_results: 'format:jpg' + }); + }); + + it('should add next_cursor to query', function () { + var query = cloudinary.v2.search.next_cursor('format:jpg').to_query(); + expect(query).to.eql({ + next_cursor: 'format:jpg' + }); + }); + + it('should add aggregate arguments as array to query', function () { + var query = cloudinary.v2.search.aggregate('format').aggregate('size_category').to_query(); + expect(query).to.eql({ + aggregate: ['format', 'size_category'] + }); + }); + + it('should add with_field to query', function () { + var query = cloudinary.v2.search.with_field('context').with_field('tags').to_query(); + expect(query).to.eql({ + with_field: ['context', 'tags'] + }); + }); + + describe('to_url', () => { + const cloudName = 'test-cloud'; + + const commonUrlOptions = { + secure: true, + cloud_name: cloudName, + api_secret: 'test-secret', + api_key: 'test-key' + }; + + beforeEach(() => { + cloudinary.v2.config(commonUrlOptions); + }); + + const expression = 'resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m'; + const search = cloudinary.v2.search.expression(expression).sort_by('public_id', 'desc').max_results('30'); + + const encodedSearchPayload = 'eyJleHByZXNzaW9uIjoicmVzb3VyY2VfdHlwZTppbWFnZSBBTkQgdGFncz1raXR0ZW4gQU5EIH' + + 'VwbG9hZGVkX2F0PjFkIEFORCBieXRlcz4xbSIsIm1heF9yZXN1bHRzIjoiMzAiLCJzb3J0X2J5IjpbeyJwdWJsaWNfaWQiOiJkZXNjIn1dfQ=='; + const ttl300Sig = '4a239e6a6461eebb9ee48617245a1efb01d362dd8263f42861f92611491b47eb'; + const ttl1000Sig = '4bdf0749f71b875bea640ed037a0f809dd7498db1ef8d6e4231887eec88d9a2c'; + const defaultTtl = 300; + const newTtl = 1000; + + it('should build cached search url', () => { + const actual = search.to_url(); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl300Sig}/${defaultTtl}/${encodedSearchPayload}`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url including ttl', () => { + const actual = search.to_url(newTtl); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url including next_cursor', () => { + const actual = search.to_url(undefined, 'next_cursor'); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl300Sig}/${defaultTtl}/${encodedSearchPayload}/next_cursor`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url including next_cursor and ttl', () => { + const actual = search.to_url(newTtl, 'next_cursor'); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}/next_cursor`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url including next_cursor and ttl set on instance', () => { + const actual = search.next_cursor('next_cursor').ttl(newTtl).to_url(); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}/next_cursor`; + expect(actual).to.eql(expected); + }); + + it('should build cached search url when private cdn configured', () => { + cloudinary.v2.config({ + private_cdn: true + }); + const actual = search.to_url(defaultTtl); + const expected = `https://${cloudName}-res.cloudinary.com/search/${ttl300Sig}/${defaultTtl}/${encodedSearchPayload}`; + expect(actual).to.eql(expected); + }); + }); +}); diff --git a/test/utils/utils_spec.js b/test/utils/utils_spec.js index e78fb591..e93fb031 100644 --- a/test/utils/utils_spec.js +++ b/test/utils/utils_spec.js @@ -28,7 +28,10 @@ const { const TEST_TAG = helper.TEST_TAG; const createTestConfig = require('../testUtils/createTestConfig'); -const {clear_blank} = require("../../lib/utils"); +const { + clear_blank, + sort_object_by_key +} = require("../../lib/utils"); // Defined globals var cloud_name = ''; @@ -1752,4 +1755,19 @@ describe("utils", function () { }); }); }) + + describe('sort_object_by_keys', () => { + it("should sort object's properties alphabetically", () => { + const unordered = { + z: 'irrelevant', + m: 'irrelevant', + a: 'irrelevant' + }; + + assert.deepStrictEqual(Object.keys(unordered), ['z', 'm', 'a']); + + const ordered = sort_object_by_key(unordered); + assert.deepStrictEqual(Object.keys(ordered), ['a', 'm', 'z']) + }); + }); }); From 7d2b8835c6b8ff9a82088ac4cbe7d9f5eec6b05b Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Thu, 20 Jul 2023 09:23:11 +0200 Subject: [PATCH 5/7] fix: pr fixes --- test/unit/search/search_spec.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/test/unit/search/search_spec.js b/test/unit/search/search_spec.js index d038ea45..5785f8c7 100644 --- a/test/unit/search/search_spec.js +++ b/test/unit/search/search_spec.js @@ -68,13 +68,13 @@ describe('Search', () => { }); describe('to_url', () => { - const cloudName = 'test-cloud'; + const cloudName = 'test123'; const commonUrlOptions = { secure: true, cloud_name: cloudName, - api_secret: 'test-secret', - api_key: 'test-key' + api_secret: 'secret', + api_key: 'key' }; beforeEach(() => { @@ -82,14 +82,15 @@ describe('Search', () => { }); const expression = 'resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m'; - const search = cloudinary.v2.search.expression(expression).sort_by('public_id', 'desc').max_results('30'); + const search = cloudinary.v2.search.expression(expression).sort_by('public_id', 'desc').max_results(30); - const encodedSearchPayload = 'eyJleHByZXNzaW9uIjoicmVzb3VyY2VfdHlwZTppbWFnZSBBTkQgdGFncz1raXR0ZW4gQU5EIH' + - 'VwbG9hZGVkX2F0PjFkIEFORCBieXRlcz4xbSIsIm1heF9yZXN1bHRzIjoiMzAiLCJzb3J0X2J5IjpbeyJwdWJsaWNfaWQiOiJkZXNjIn1dfQ=='; - const ttl300Sig = '4a239e6a6461eebb9ee48617245a1efb01d362dd8263f42861f92611491b47eb'; - const ttl1000Sig = '4bdf0749f71b875bea640ed037a0f809dd7498db1ef8d6e4231887eec88d9a2c'; + const encodedSearchPayload = 'eyJleHByZXNzaW9uIjoicmVzb3VyY2VfdHlwZTppbWFnZSBBTkQgdGFncz1raXR0ZW4gQU5EIHVw' + + 'bG9hZGVkX2F0PjFkIEFORCBieXRlcz4xbSIsIm1heF9yZXN1bHRzIjozMCwic29ydF9ieSI6W3sicHVibGljX2lkIjoiZGVzYyJ9XX0='; + const ttl300Sig = '431454b74cefa342e2f03e2d589b2e901babb8db6e6b149abf25bc0dd7ab20b7'; + const ttl1000Sig = '25b91426a37d4f633a9b34383c63889ff8952e7ffecef29a17d600eeb3db0db7'; const defaultTtl = 300; const newTtl = 1000; + const nextCursor = 'db27cfb02b3f69cb39049969c23ca430c6d33d5a3a7c3ad1d870c54e1a54ee0faa5acdd9f6d288666986001711759d10'; it('should build cached search url', () => { const actual = search.to_url(); @@ -104,20 +105,20 @@ describe('Search', () => { }); it('should build cached search url including next_cursor', () => { - const actual = search.to_url(undefined, 'next_cursor'); - const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl300Sig}/${defaultTtl}/${encodedSearchPayload}/next_cursor`; + const actual = search.to_url(undefined, nextCursor); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl300Sig}/${defaultTtl}/${encodedSearchPayload}/${nextCursor}`; expect(actual).to.eql(expected); }); it('should build cached search url including next_cursor and ttl', () => { - const actual = search.to_url(newTtl, 'next_cursor'); - const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}/next_cursor`; + const actual = search.to_url(newTtl, nextCursor); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}/${nextCursor}`; expect(actual).to.eql(expected); }); it('should build cached search url including next_cursor and ttl set on instance', () => { - const actual = search.next_cursor('next_cursor').ttl(newTtl).to_url(); - const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}/next_cursor`; + const actual = search.next_cursor(nextCursor).ttl(newTtl).to_url(); + const expected = `https://res.cloudinary.com/${cloudName}/search/${ttl1000Sig}/${newTtl}/${encodedSearchPayload}/${nextCursor}`; expect(actual).to.eql(expected); }); From 3b00b293759fca73729cb78c39e02782ee8cf718 Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Thu, 20 Jul 2023 10:42:24 +0200 Subject: [PATCH 6/7] chore: reusing existing function --- lib/utils/index.js | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index babccac3..45fd84fc 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -735,15 +735,18 @@ function patchFetchFormat(options = {}) { } } -function build_distribution_domain(options) { - const source = consumeOption(options, 'source', ''); +function build_distribution_domain(source, options) { const cloud_name = consumeOption(options, 'cloud_name', config().cloud_name); - if (!cloud_name) { throw new Error('Must supply cloud_name in tag or in configuration'); } - const secure = consumeOption(options, 'secure', config().secure); + const ssl_detected = consumeOption(options, "ssl_detected", config().ssl_detected); + let secure = consumeOption(options, 'secure', config().secure); + if (secure === null) { + secure = ssl_detected || config().secure; + } + const private_cdn = consumeOption(options, 'private_cdn', config().private_cdn); const cname = consumeOption(options, 'cname', config().cname); const secure_distribution = consumeOption(options, 'secure_distribution', config().secure_distribution); @@ -767,20 +770,6 @@ function url(public_id, options = {}) { } let long_url_signature = !!consumeOption(options, "long_url_signature", config().long_url_signature); let format = consumeOption(options, "format"); - let cloud_name = consumeOption(options, "cloud_name", config().cloud_name); - if (!cloud_name) { - throw "Unknown cloud_name"; - } - let private_cdn = consumeOption(options, "private_cdn", config().private_cdn); - let secure_distribution = consumeOption(options, "secure_distribution", config().secure_distribution); - let secure = consumeOption(options, "secure", null); - let ssl_detected = consumeOption(options, "ssl_detected", config().ssl_detected); - if (secure === null) { - secure = ssl_detected || config().secure; - } - let cdn_subdomain = consumeOption(options, "cdn_subdomain", config().cdn_subdomain); - let secure_cdn_subdomain = consumeOption(options, "secure_cdn_subdomain", config().secure_cdn_subdomain); - let cname = consumeOption(options, "cname", config().cname); let shorten = consumeOption(options, "shorten", config().shorten); let sign_url = consumeOption(options, "sign_url", config().sign_url); let api_secret = consumeOption(options, "api_secret", config().api_secret); @@ -837,16 +826,7 @@ function url(public_id, options = {}) { signature = hash.replace(/\//g, '_').replace(/\+/g, '-').substring(0, long_url_signature ? 32 : 8); signature = `s--${signature}--`; } - let prefix = unsigned_url_prefix( - public_id, - cloud_name, - private_cdn, - cdn_subdomain, - secure_cdn_subdomain, - cname, - secure, - secure_distribution - ); + let prefix = build_distribution_domain(public_id, options); let resultUrl = [prefix, resource_type, type, signature, transformation, version, public_id].filter(function (part) { return (part != null) && part !== ''; }).join('/').replace(/ /g, '%20'); From 78b3da1d4e8b2cc27e040a4f740cc9d9b26faf6b Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Thu, 20 Jul 2023 12:25:01 +0200 Subject: [PATCH 7/7] fix: ssl_detected used from options if not configured before --- lib/utils/index.js | 4 ++-- lib/v2/search.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index 45fd84fc..63d5dbfc 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -741,8 +741,8 @@ function build_distribution_domain(source, options) { throw new Error('Must supply cloud_name in tag or in configuration'); } - const ssl_detected = consumeOption(options, "ssl_detected", config().ssl_detected); - let secure = consumeOption(options, 'secure', config().secure); + let secure = consumeOption(options, 'secure', null); + const ssl_detected = consumeOption(options, 'ssl_detected', config().ssl_detected); if (secure === null) { secure = ssl_detected || config().secure; } diff --git a/lib/v2/search.js b/lib/v2/search.js index dfde4958..87b1ae55 100644 --- a/lib/v2/search.js +++ b/lib/v2/search.js @@ -152,7 +152,7 @@ const Search = class Search { const dataOrderedByKey = sort_object_by_key(clear_blank(query)); const encodedQuery = base64Encode(JSON.stringify(dataOrderedByKey)); - const urlPrefix = build_distribution_domain(options); + const urlPrefix = build_distribution_domain(options.source, options); const signature = compute_hash(`${urlTtl}${encodedQuery}${apiSecret}`, 'sha256', 'hex');