Skip to content

Commit b1e23fc

Browse files
Feature/encode sdk version (#371)
* Add query param with SDK encoded suffix (_s=...) * Set default behaviour to false (instead of true), and add a test to enforce it
1 parent 96533e8 commit b1e23fc

28 files changed

+557
-24
lines changed

cloudinary.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
if (Number(process.versions.node.split('.')[0]) < 8) {
2-
console.warn('DEPRECATION NOTICE - Node 6 has been scheduled for removal from the Cloudinary SDK')
2+
console.warn('DEPRECATION NOTICE - Node 6 has been scheduled for removal from the Cloudinary SDK');
3+
// required polyfills for native ES6/7 functions (such as String.prototype.padStart)
4+
require('core-js');
35
module.exports = require('./lib-es5/cloudinary');
46
} else {
57
module.exports = require('./lib/cloudinary');
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
3+
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
4+
5+
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
6+
var num = 0;
7+
var map = {};
8+
9+
[].concat(_toConsumableArray(chars)).forEach(function (char) {
10+
var key = num.toString(2).padStart(6, '0');
11+
map[key] = char;
12+
num++;
13+
});
14+
15+
/**
16+
* Map of six-bit binary codes to Base64 characters
17+
*/
18+
module.exports = map;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
var base64Map = require('./base64Map');
4+
var reverseVersion = require('./revereseVersion');
5+
6+
/**
7+
* @description Encodes a semVer-like version string
8+
* @param {string} semVer Input can be either x.y.z or x.y
9+
* @return {string} A string built from 3 characters of the base64 table that encode the semVer
10+
*/
11+
function encodeVersion(semVer) {
12+
var strResult = '';
13+
14+
// support x.y or x.y.z by using 'parts' as a variable
15+
var parts = semVer.split('.').length;
16+
var paddedStringLength = parts * 6; // we pad to either 12 or 18 characters
17+
18+
// reverse (but don't mirror) the version. 1.5.15 -> 15.5.1
19+
// Pad to two spaces, 15.5.1 -> 15.05.01
20+
var paddedReversedSemver = reverseVersion(semVer);
21+
22+
// turn 15.05.01 to a string '150501' then to a number 150501
23+
var num = parseInt(paddedReversedSemver.split('.').join(''));
24+
25+
// Represent as binary, add left padding to 12 or 18 characters.
26+
// 150,501 -> 100100101111100101
27+
var paddedBinary = num.toString(2).padStart(paddedStringLength, '0');
28+
29+
// Stop in case an invalid version number was provided
30+
// paddedBinary must be built from sections of 6 bits
31+
if (paddedBinary.length % 6 !== 0) {
32+
throw 'Version must be smaller than 43.21.26)';
33+
}
34+
35+
// turn every 6 bits into a character using the base64Map
36+
paddedBinary.match(/.{1,6}/g).forEach(function (bitString) {
37+
// console.log(bitString);
38+
strResult += base64Map[bitString];
39+
});
40+
41+
return strResult;
42+
}
43+
44+
module.exports = encodeVersion;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
function getSDKFeatureCode() {
4+
var features = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
5+
6+
var defaultCode = '0';
7+
8+
if (features.responsive) {
9+
return 'A';
10+
}
11+
12+
return defaultCode;
13+
}
14+
15+
module.exports = getSDKFeatureCode;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
var fs = require('fs');
4+
var path = require('path');
5+
var encodeVersion = require('./encodeVersion');
6+
var getSDKFeatureCode = require('./getSDKFeatureCode');
7+
var SDKCode = 'M'; // Constant per SDK
8+
9+
10+
/**
11+
* @description Removes patch version from the semver if it exists
12+
* Turns x.y.z OR x.y into x.y
13+
* @param {'x.y.z' || 'x.y' || string} semVerStr
14+
*/
15+
function removePatchFromSemver(semVerStr) {
16+
var parts = semVerStr.split('.');
17+
18+
return `${parts[0]}.${parts[1]}`;
19+
}
20+
21+
/**
22+
* @description Gets the SDK signature by encoding the SDK version and node version
23+
* @param {{responsive:boolean}} features
24+
* @param {'default' | 'x.y.z' | 'x.y' | string} useSDKVersion Default uses package.json version
25+
* @param {'default' | 'x.y.z' | 'x.y' | string} useNodeVersion Default uses process.versions.node
26+
* @return {string} encodedSDK sdkVersionID
27+
*/
28+
function getSDKVersionID() {
29+
var features = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
30+
var useSDKVersion = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'default';
31+
var useNodeVersion = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'default';
32+
33+
try {
34+
// allow to pass a custom SDKVersion
35+
var pkgJSONFile = fs.readFileSync(path.join(__dirname, '../../../../package.json'), 'utf-8');
36+
37+
var sdkVersion = useSDKVersion === 'default' ? JSON.parse(pkgJSONFile).version : useSDKVersion;
38+
39+
// allow to pass a custom nodeVersion
40+
var nodeVersion = useNodeVersion === 'default' ? process.versions.node : useNodeVersion;
41+
42+
// Node version should always be in x.y format
43+
var twoPartNodeVersion = removePatchFromSemver(nodeVersion);
44+
var encodedSDKVersion = encodeVersion(sdkVersion);
45+
var encodedNodeVersion = encodeVersion(twoPartNodeVersion);
46+
var featureCode = getSDKFeatureCode(features);
47+
48+
return `${SDKCode}${encodedSDKVersion}${encodedNodeVersion}${featureCode}`;
49+
} catch (e) {
50+
// Either SDK or Node versions were unparsable
51+
return 'E';
52+
}
53+
}
54+
55+
module.exports = getSDKVersionID;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
/**
4+
* @description A semVer like string, x.y.z or x.y is allowed
5+
* Reverses the version positions, x.y.z turns to z.y.x
6+
* Pads each segment with '0' so they have length of 2
7+
* Example: 1.2.3 -> 03.02.01
8+
* @param {string} semVer Input can be either x.y.z or x.y
9+
* @return {string} in the form of zz.yy.xx (
10+
*/
11+
function reverseVersion(semVer) {
12+
if (semVer.split('.').length < 2) {
13+
throw new Error('invalid semVer, must have at least two segments');
14+
}
15+
16+
// Split by '.', reverse, create new array with padded values and concat it together
17+
return semVer.split('.').reverse().map(function (segment) {
18+
return segment.padStart(2, '0');
19+
}).join('.');
20+
}
21+
22+
module.exports = reverseVersion;

lib-es5/utils/ensureOption.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"use strict";
1+
'use strict';
22

33
/**
44
* Returns an ensureOption function that relies on the provided `defaultOptions` argument
@@ -9,10 +9,18 @@
99
*/
1010
function defaults(defaultOptions) {
1111
return function ensureOption(options, name, defaultValue) {
12-
var value = options[name] || defaultOptions[name] || defaultValue;
13-
if (value === undefined) {
12+
var value = void 0;
13+
14+
if (typeof options[name] !== 'undefined') {
15+
value = options[name];
16+
} else if (typeof defaultOptions[name] !== 'undefined') {
17+
value = defaultOptions[name];
18+
} else if (typeof defaultValue !== 'undefined') {
19+
value = defaultValue;
20+
} else {
1421
throw `Must supply ${name}`;
1522
}
23+
1624
return value;
1725
};
1826
}

lib-es5/utils/index.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ var ensurePresenceOf = require('./ensurePresenceOf');
5555
var ensureOption = require('./ensureOption').defaults(config());
5656
var entries = require('./entries');
5757
var isRemoteUrl = require('./isRemoteUrl');
58+
var getSDKVersionID = require('./encoding/sdkVersionID/getSDKVersionID');
5859

5960
exports = module.exports;
6061
var utils = module.exports;
@@ -776,6 +777,22 @@ function url(public_id) {
776777
var token = generate_token(auth_token);
777778
resultUrl += `?${token}`;
778779
}
780+
781+
var analytics = ensureOption(options, 'analytics', false);
782+
var responsive = ensureOption(options, 'responsive', false);
783+
784+
if (analytics === true) {
785+
var sdkVersionID = getSDKVersionID({
786+
responsive
787+
});
788+
// url might already have a '?' query param
789+
var appender = '?';
790+
if (resultUrl.indexOf('?') >= 0) {
791+
appender = '&';
792+
}
793+
resultUrl = `${resultUrl}${appender}_s=${sdkVersionID}`;
794+
}
795+
779796
return resultUrl;
780797
}
781798

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
2+
let num = 0;
3+
let map = {};
4+
5+
[...chars].forEach((char) => {
6+
let key = num.toString(2).padStart(6, '0');
7+
map[key] = char;
8+
num++;
9+
});
10+
11+
12+
/**
13+
* Map of six-bit binary codes to Base64 characters
14+
*/
15+
module.exports = map;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
let base64Map = require('./base64Map');
2+
let reverseVersion = require('./revereseVersion');
3+
4+
/**
5+
* @description Encodes a semVer-like version string
6+
* @param {string} semVer Input can be either x.y.z or x.y
7+
* @return {string} A string built from 3 characters of the base64 table that encode the semVer
8+
*/
9+
function encodeVersion(semVer) {
10+
let strResult = '';
11+
12+
// support x.y or x.y.z by using 'parts' as a variable
13+
let parts = semVer.split('.').length;
14+
let paddedStringLength = parts * 6; // we pad to either 12 or 18 characters
15+
16+
// reverse (but don't mirror) the version. 1.5.15 -> 15.5.1
17+
// Pad to two spaces, 15.5.1 -> 15.05.01
18+
let paddedReversedSemver = reverseVersion(semVer);
19+
20+
// turn 15.05.01 to a string '150501' then to a number 150501
21+
let num = parseInt(paddedReversedSemver.split('.').join(''));
22+
23+
// Represent as binary, add left padding to 12 or 18 characters.
24+
// 150,501 -> 100100101111100101
25+
let paddedBinary = num.toString(2).padStart(paddedStringLength, '0');
26+
27+
// Stop in case an invalid version number was provided
28+
// paddedBinary must be built from sections of 6 bits
29+
if (paddedBinary.length % 6 !== 0) {
30+
throw 'Version must be smaller than 43.21.26)';
31+
}
32+
33+
// turn every 6 bits into a character using the base64Map
34+
paddedBinary.match(/.{1,6}/g).forEach((bitString) => {
35+
// console.log(bitString);
36+
strResult += base64Map[bitString];
37+
});
38+
39+
return strResult;
40+
}
41+
42+
module.exports = encodeVersion;

0 commit comments

Comments
 (0)