Skip to content

Instantly share code, notes, and snippets.

@sanrai
Created October 2, 2024 01:11
Show Gist options
  • Save sanrai/128cf8091b8659dd883eac5e5d9a86fa to your computer and use it in GitHub Desktop.
Save sanrai/128cf8091b8659dd883eac5e5d9a86fa to your computer and use it in GitHub Desktop.
// URL-safe character set for Base85 encoding (now with 85 characters)
const URL_SAFE_CHARS = [
// Digits (10)
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
// Uppercase letters (26)
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
// Lowercase letters (26, including 'z')
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
// Unreserved punctuation (4)
'-', '.', '_', '~',
// Sub-delimiters and additional characters (19)
'!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=', ':', '@',
'/', '?', '[', ']', '`', '^' // Added '^' to reach 85 characters
];
// Mapping from character to its index in URL_SAFE_CHARS
const URL_SAFE_CHARS_MAP = {};
for (let i = 0; i < URL_SAFE_CHARS.length; i++) {
URL_SAFE_CHARS_MAP[URL_SAFE_CHARS[i]] = i;
}
// Base85 encoding function using BigInt and URL-safe characters
function base85Encode(bytes) {
let output = '';
let i = 0;
while (i < bytes.length) {
let chunkSize = Math.min(4, bytes.length - i);
let value = BigInt(0);
for (let j = 0; j < chunkSize; j++) {
value = (value << BigInt(8)) | BigInt(bytes[i + j]);
}
// Pad value if chunkSize < 4
if (chunkSize < 4) {
value <<= BigInt((4 - chunkSize) * 8);
}
// Generate the encoded string
let encoded = '';
for (let j = 0; j < 5; j++) {
let index = Number(value % BigInt(85));
let char = URL_SAFE_CHARS[index];
encoded = char + encoded;
value = value / BigInt(85);
}
if (chunkSize < 4) {
// Since 'encoded' is built from right to left, extract from the start
encoded = encoded.substring(0, chunkSize + 1);
}
output += encoded;
i += chunkSize;
}
return output;
}
// Base85 decoding function using BigInt and URL-safe characters
function base85Decode(input) {
input = input.replace(/\s+/g, '');
const outputBytes = [];
let value = BigInt(0);
let count = 0;
for (let i = 0; i < input.length; i++) {
const c = input[i];
if (!(c in URL_SAFE_CHARS_MAP)) {
throw new Error(`Invalid character '${c}' in Base85 encoding.`);
}
value = value * BigInt(85) + BigInt(URL_SAFE_CHARS_MAP[c]);
count++;
if (count === 5) {
const bytes = [];
for (let j = 3; j >= 0; j--) {
bytes[j] = Number(value & BigInt(0xFF));
value >>= BigInt(8);
}
outputBytes.push(...bytes);
value = BigInt(0);
count = 0;
}
}
// Handle remaining characters
if (count > 0) {
for (let i = count; i < 5; i++) {
value = value * BigInt(85) + BigInt(84); // Padding with the highest value
}
const bytes = [];
for (let j = 3; j >= 0; j--) {
bytes[j] = Number(value & BigInt(0xFF));
value >>= BigInt(8);
}
const bytesToExtract = count - 1;
outputBytes.push(...bytes.slice(0, bytesToExtract));
}
return new Uint8Array(outputBytes);
}
// Base64 encoding function (unchanged)
function base64Encode(uint8Array) {
let binary = '';
const len = uint8Array.length;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(uint8Array[i]);
}
return btoa(binary);
}
// Base64 decoding function (unchanged)
function base64Decode(str) {
const binaryString = atob(str);
const len = binaryString.length;
const uint8Array = new Uint8Array(len);
for (let i = 0; i < len; i++) {
uint8Array[i] = binaryString.charCodeAt(i);
}
return uint8Array;
}
// Compression function using CompressionStream (unchanged)
async function compressString(inputStr) {
const encoder = new TextEncoder();
const inputBytes = encoder.encode(inputStr);
const compressedStream = new Blob([inputBytes]).stream().pipeThrough(new CompressionStream('gzip'));
const compressedArrayBuffer = await new Response(compressedStream).arrayBuffer();
const compressedBytes = new Uint8Array(compressedArrayBuffer);
return compressedBytes;
}
// Decompression function using DecompressionStream (unchanged)
async function decompressBytes(compressedBytes) {
const decompressedStream = new Blob([compressedBytes]).stream().pipeThrough(new DecompressionStream('gzip'));
const decompressedArrayBuffer = await new Response(decompressedStream).arrayBuffer();
const decoder = new TextDecoder();
const decompressedStr = decoder.decode(decompressedArrayBuffer);
return decompressedStr;
}
// The configuration string
let configStr = '{"andLogicTags":[{"intraTagLogic":"OR","andTags":["caas:content-type/report","caas:content-type/video","caas:content-type/webinar","caas:content-type/infographic","caas:content-type/ebook","caas:content-type/guide","caas:content-type/demo","caas:content-type/blog","caas:content-type/customer-story","caas:content-type/event-session","caas:content-type/demos-and-videos","caas:content-type/podcast","caas:content-type/customer-blog"]}],"cardStyle":"1:2","endpoint":"14257-chimera.adobeioruntime.net/api/v1/web/chimera-0.0.1/collection","excludeTags":["caas:events/max","caas:content-type/product"],"excludedCards":[{"contentId":"64c7ad2e-7bf1-5e7f-8619-c66f27371445"},{"contentId":"0dfd22b4-56d1-5ce5-ad6e-b58c5ef608d5"},{"contentId":"d69bc403-58d5-5fcf-9fad-4d006f6b96d9"},{"contentId":"bfc32f06-6e6a-5f99-b252-48c1410be5a3"},{"contentId":"c80d9edd-8c6d-5fc2-97f1-445f53e49188"},{"contentId":"97380bce-0678-5e02-900e-82957e2c4afe"}],"layoutType":"3up","loadMoreBtnStyle":"over-background","paginationEnabled":true,"paginationQuantityShown":true,"paginationUseTheme3":true,"placeholderUrl":"https://main--bacom--adobecom.hlx.live/caas/mappings","resultsPerPage":"9","searchFields":["overlays.banner.description","contentArea.description","contentArea.detailText","contentArea.title","overlays.label.description"],"setCardBorders":true,"showFilters":true,"showSearch":true,"showTotalResults":true,"sortEnablePopup":true,"source":["bacom"],"totalCardsToShow":"4500","filters":[{"filterTag":["caas:products"],"openedOnLoad":"","icon":"","excludeTags":["caas:products/magento-commerce"]},{"filterTag":["caas:content-type"],"openedOnLoad":"","icon":"","excludeTags":["caas:content-type/customer-story"]},{"filterTag":["caas:topic"],"openedOnLoad":"","icon":"","excludeTags":["caas:topic/enterprise-work-management"]},{"filterTag":["caas:industry"],"openedOnLoad":"","icon":"","excludeTags":[]}],"sortDateAsc":true,"sortDateDesc":true,"sortTitleAsc":true,"sortTitleDesc":true,"collectionName":"All resources","doNotLazyLoad":true,"autoCountryLang":true,"showIds":true,"filterBuildPanel":"custom","filtersCustom":[{"filterTag":"","openedOnLoad":"","icon":"","excludeTags":"","group":"{customGroupA}","filtersCustomItems":[{"filtersCustomLabel":"Advertising","customFilterTag":["caas:products/adobe-advertising-cloud"]},{"filtersCustomLabel":"Analytics","customFilterTag":["caas:products/adobe-analytics"]},{"filtersCustomLabel":"Audience Manager","customFilterTag":["caas:products/adobe-audience-manager"]},{"filtersCustomLabel":"Campaign","customFilterTag":["caas:products/adobe-campaign"]},{"filtersCustomLabel":"Commerce","customFilterTag":["caas:products/adobe-commerce"]},{"filtersCustomLabel":"Customer Journey Analytics","customFilterTag":["caas:products/customer-journey-analytics"]},{"filtersCustomLabel":"Experience Manager Assets","customFilterTag":["caas:products/adobe-experience-manager-assets"]},{"filtersCustomLabel":"Experience Manager Forms","customFilterTag":["caas:products/adobe-experience-manager-forms"]},{"filtersCustomLabel":"Experience Manager Guides","customFilterTag":["caas:products/experience-manager-guides"]},{"filtersCustomLabel":"Experience Manager Sites","customFilterTag":["caas:products/adobe-experience-manager-sites"]},{"filtersCustomLabel":"GenStudio","customFilterTag":["caas:products/genstudio"]},{"filtersCustomLabel":"Journey Optimizer","customFilterTag":["caas:products/adobe-journey-optimizer"]},{"filtersCustomLabel":"Journey Optimizer B2B Edition","customFilterTag":["caas:products/adobe-journey-optimizer-b2b-edition"]},{"filtersCustomLabel":"Learning Manager","customFilterTag":["caas:products/learning-manager"]},{"filtersCustomLabel":"Marketo Engage","customFilterTag":["caas:products/marketo-engage-bizible"]},{"filtersCustomLabel":"Mix Modeler","customFilterTag":["caas:products/adobe-mix-modeler"]},{"filtersCustomLabel":"Product Analytics","customFilterTag":["caas:products/adobe-product-analytics"]},{"filtersCustomLabel":"Real-Time CDP","customFilterTag":["caas:products/adobe-real-time-cdp"]},{"filtersCustomLabel":"Target","customFilterTag":["caas:products/adobe-target"]},{"filtersCustomLabel":"Workfront","customFilterTag":["caas:products/workfront"]}]},{"filterTag":"","openedOnLoad":"","icon":"","excludeTags":"","group":"{customGroupB}","filtersCustomItems":[{"filtersCustomLabel":"{customFilterB1}","customFilterTag":["caas:content-type/blog"]},{"filtersCustomLabel":"{customFilterB2}","customFilterTag":["caas:content-type/customer-story"]},{"filtersCustomLabel":"{customFilterB3}","customFilterTag":["caas:content-type/demo"]},{"filtersCustomLabel":"{customFilterB4}","customFilterTag":["caas:content-type/demos-and-videos"]},{"filtersCustomLabel":"{customFilterB5}","customFilterTag":["caas:content-type/ebook"]},{"filtersCustomLabel":"{customFilterB6}","customFilterTag":["caas:content-type/event-session"]},{"filtersCustomLabel":"{customFilterB7}","customFilterTag":["caas:content-type/guide"]},{"filtersCustomLabel":"{customFilterB8}","customFilterTag":["caas:content-type/infographic"]},{"filtersCustomLabel":"{customFilterB9}","customFilterTag":["caas:content-type/podcast"]},{"filtersCustomLabel":"{customFilterB10}","customFilterTag":["caas:content-type/report"]},{"filtersCustomLabel":"{customFilterB11}","customFilterTag":["caas:content-type/video"]},{"filtersCustomLabel":"{customFilterB12}","customFilterTag":["caas:content-type/webinar"]}]},{"group":"{customGroupC}","filtersCustomItems":[{"filtersCustomLabel":"{customFilterC1}","customFilterTag":["caas:industry/automotive-&-mobility"]},{"filtersCustomLabel":"{customFilterC2}","customFilterTag":["caas:industry/consumer-goods"]},{"filtersCustomLabel":"{customFilterC3}","customFilterTag":["caas:industry/financial-services"]},{"filtersCustomLabel":"{customFilterC4}","customFilterTag":["caas:industry/government"]},{"filtersCustomLabel":"{customFilterC5}","customFilterTag":["caas:industry/healthcare"]},{"filtersCustomLabel":"{customFilterC6}","customFilterTag":["caas:industry/high-tech"]},{"filtersCustomLabel":"{customFilterC7}","customFilterTag":["caas:industry/manufacturing"]},{"filtersCustomLabel":"{customFilterC8}","customFilterTag":["caas:industry/media-and-entertainment"]},{"filtersCustomLabel":"{customFilterC9}","customFilterTag":["caas:industry/public-sector"]},{"filtersCustomLabel":"{customFilterC10}","customFilterTag":["caas:industry/retail"]},{"filtersCustomLabel":"{customFilterC11}","customFilterTag":["caas:industry/telecommunications"]},{"filtersCustomLabel":"{customFilterC12}","customFilterTag":["caas:industry/travel-and-hospitality"]}],"openedOnLoad":""}],"hideDateInterval":true}';
// Test function for Base85 encoding and decoding
async function testBase85Encoding() {
// Compress the input string
const compressedBytes = await compressString(configStr);
console.log('compressedBytes:', compressedBytes);
// Encode using Base85 with URL-safe characters
const base85Encoded = base85Encode(compressedBytes);
console.log(`Base85 encoded length: ${base85Encoded.length} characters`);
console.log(`Base85 encoded string: ${base85Encoded}`);
// Decode using Base85
const decodedBytes = base85Decode(base85Encoded);
// Verify that the decoded bytes match the original compressed bytes
const arraysMatch = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
const bytesMatch = arraysMatch(compressedBytes, decodedBytes);
console.log(`Base85 encoding and decoding bytes match: ${bytesMatch}`);
if (!bytesMatch) {
console.error('Mismatch between compressed bytes and decoded bytes.');
return;
}
// Decompress the decoded bytes
const decompressedStr = await decompressBytes(decodedBytes);
// Verify that the decompressed string matches the original string
const success = configStr === decompressedStr;
console.log(`Base85 decoding and decompression successful: ${success}`);
}
// The below configStr test cases have been verified and working.
// Commented them out so sample code focuses on BACOM specific use case.
// let configStr = `{
// "title": "多言語テスト",
// "description": "This is a test with multiple languages: English, 中文, العربية, हिन्दी, Español.",
// "emojiTest": "Here are some emojis: 😀🚀🌟🍕",
// "specialChars": "Special characters: ~!@#$%^&*()_+-=[]{}|;':\",./<>?",
// "nested": {
// "chinese": "汉字测试",
// "japanese": "日本語のテスト",
// "arabic": "اختبار باللغة العربية",
// "hindi": "हिंदी में परीक्षण",
// "spanish": "Prueba en español"
// }
// }`;
// let configStr = `{
// "surrogatePairs": "𝌆𠀋𠁧", // Characters outside the BMP (Basic Multilingual Plane)
// "controlChars": "Text with control chars:\u0000\u0001\u0002\u0003",
// "nullByteTest": "String with null byte\0in the middle"
// }`;
// Run the updated Base85 encoding test
testBase85Encoding();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment