Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MWPW-135873 Compress the caas hash strings #1401

Merged
merged 13 commits into from
Oct 18, 2023
Merged
78 changes: 65 additions & 13 deletions libs/blocks/caas-config/caas-config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable compat/compat */
/* eslint-disable react-hooks/exhaustive-deps */
/* global ClipboardItem */
import {
Expand All @@ -13,10 +14,16 @@
getConfig,
parseEncodedConfig,
loadStyle,
utf8ToB64,
} from '../../utils/utils.js';
import Accordion from '../../ui/controls/Accordion.js';
import { defaultState, initCaas, loadCaasFiles, loadCaasTags, loadStrings } from '../caas/utils.js';
import {
decodeCompressedString,
defaultState,
initCaas,
loadCaasFiles,
loadCaasTags,
loadStrings,
} from '../caas/utils.js';
import { Input as FormInput, Select as FormSelect } from '../../ui/controls/formControls.js';
import TagSelect from '../../ui/controls/TagSelector.js';
import MultiField from '../../ui/controls/MultiField.js';
Expand All @@ -36,13 +43,16 @@

const isValidUuid = (id) => /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(id);

const getHashConfig = () => {
const getHashConfig = async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see an await in this function, async needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getHashConfig now can either be async (for v2 hashes) or sync (for v1 hashes). I think for clarity, since it's possible that it will be async this function should be marked as async? Otherwise you have to just know. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, am updating the code to show why it's async.

const { hash } = window.location;
if (!hash) return null;
window.location.hash = '';

const encodedConfig = hash.startsWith('#') ? hash.substring(1) : hash;
return parseEncodedConfig(encodedConfig);
const config = encodedConfig.startsWith('~~')
? await decodeCompressedString(encodedConfig.substring(2))
: parseEncodedConfig(encodedConfig);
return config;
};

const caasFilesLoaded = loadCaasFiles();
Expand Down Expand Up @@ -700,14 +710,16 @@
return { ...state, [action.prop]: action.value };
case 'RESET_STATE':
return cloneObj(defaultState);
case 'SET_STATE':
return cloneObj(action.value);
/* c8 ignore next 2 */
default:
return state;
}
};

const getInitialState = () => {
let state = getHashConfig();
const getInitialState = async () => {
let state = await getHashConfig();
// /* c8 ignore next 2 */
if (!state) {
const lsState = localStorage.getItem(LS_KEY);
Expand Down Expand Up @@ -741,6 +753,34 @@
*/
const fgKeyReplacer = (key, value) => (key === 'fetchCardsFromFloodgateTree' ? undefined : value);

const getEncodedObject = async (obj, replacer = null) => {
if (!window.CompressionStream) {
await import('../../deps/compression-streams-pollyfill.js');
}

Check warning on line 759 in libs/blocks/caas-config/caas-config.js

View check run for this annotation

Codecov / codecov/patch

libs/blocks/caas-config/caas-config.js#L758-L759

Added lines #L758 - L759 were not covered by tests

const objToStream = (data) => new Blob(
[JSON.stringify(data, replacer)],
{ type: 'text/plain' },
).stream();

const compressStream = async (stream) => new Response(
// eslint-disable-next-line no-undef
stream.pipeThrough(new CompressionStream('gzip')),
);

const responseToBuffer = async (res) => {
const blob = await res.blob();
return blob.arrayBuffer();
};

const b64encode = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf)));

const stream = objToStream(obj);
const compressedResponse = await compressStream(stream);
const buffer = await responseToBuffer(compressedResponse);
return b64encode(buffer);
};

/* c8 ignore start */
const CopyBtn = () => {
const { state } = useContext(ConfiguratorContext);
Expand All @@ -756,22 +796,25 @@
}, 2000);
};

const getUrl = () => {
const getUrl = async () => {
const url = new URL(window.location.href);
url.search = '';
url.hash = utf8ToB64(JSON.stringify(state, fgKeyReplacer));
const hashStr = await getEncodedObject(state, fgKeyReplacer);
// starts with ~~ to differentiate from old hash format
url.hash = `~~${hashStr}`;
return url.href;
};

const copyConfig = () => {
setConfigUrl(getUrl());
const copyConfig = async () => {
const url = await getUrl();
setConfigUrl(url);
if (!navigator?.clipboard) {
setTempStatus(setIsError);
return;
}

const link = document.createElement('a');
link.href = getUrl();
link.href = url;
const dateStr = new Date().toLocaleString('us-EN', {
weekday: 'long',
year: 'numeric',
Expand All @@ -782,7 +825,7 @@
hour12: false,
});
const collectionName = state.collectionName ? `- ${state.collectionName} ` : '';
link.textContent = `Content as a Service ${collectionName}- ${dateStr}${state.doNotLazyLoad ? ' (no-lazy)' : ''}`;
link.textContent = `Content as a Service v2 ${collectionName}- ${dateStr}${state.doNotLazyLoad ? ' (no-lazy)' : ''}`;

const blob = new Blob([link.outerHTML], { type: 'text/html' });
const data = [new ClipboardItem({ [blob.type]: blob })];
Expand Down Expand Up @@ -899,7 +942,7 @@
};

const Configurator = ({ rootEl }) => {
const [state, dispatch] = useReducer(reducer, getInitialState() || cloneObj(defaultState));
const [state, dispatch] = useReducer(reducer, {});
const [isCaasLoaded, setIsCaasLoaded] = useState(false);
const [strings, setStrings] = useState();
const [panels, setPanels] = useState([]);
Expand All @@ -919,6 +962,15 @@
});
}, []);

useEffect(() => {
const setInitialState = async () => {
const initialState = await getInitialState();
dispatch({ type: 'SET_STATE', value: initialState });
};

setInitialState();
}, []);

useEffect(() => {
if (state.showIds && !cardMutationObsv) {
/* c8 ignore next */
Expand Down
21 changes: 18 additions & 3 deletions libs/blocks/caas/caas.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { initCaas, loadCaasFiles, loadStrings, fgHeaderValue } from './utils.js';
import { parseEncodedConfig, createIntersectionObserver, getMetadata, getConfig, b64ToUtf8 } from '../../utils/utils.js';
import {
decodeCompressedString,
fgHeaderValue,
initCaas,
loadCaasFiles,
loadStrings,
} from './utils.js';
import {
b64ToUtf8,
createIntersectionObserver,
getConfig,
getMetadata,
parseEncodedConfig,
} from '../../utils/utils.js';

const ROOT_MARGIN = 1000;
const P_CAAS_AIO = b64ToUtf8('MTQyNTctY2hpbWVyYS5hZG9iZWlvcnVudGltZS5uZXQvYXBpL3YxL3dlYi9jaGltZXJhLTAuMC4xL2NvbGxlY3Rpb24=');
Expand All @@ -15,7 +27,10 @@ const getCaasStrings = (placeholderUrl) => new Promise((resolve) => {

const loadCaas = async (a) => {
const encodedConfig = a.href.split('#')[1];
const state = parseEncodedConfig(encodedConfig);
// ~~ indicates the new compressed string format
const state = encodedConfig.startsWith('~~')
? await decodeCompressedString(encodedConfig.substring(2))
: parseEncodedConfig(encodedConfig);

const [caasStrs] = await Promise.all([
getCaasStrings(state.placeholderUrl),
Expand Down
30 changes: 30 additions & 0 deletions libs/blocks/caas/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable compat/compat */
/* eslint-disable no-underscore-dangle */
import { loadScript, loadStyle, getConfig as pageConfigHelper } from '../../utils/utils.js';
import { fetchWithTimeout } from '../utils/utils.js';
Expand Down Expand Up @@ -56,6 +57,35 @@
}
};

export const decodeCompressedString = async (txt) => {
if (!window.DecompressionStream) {
await import('../../deps/compression-streams-pollyfill.js');
}

Check warning on line 63 in libs/blocks/caas/utils.js

View check run for this annotation

Codecov / codecov/patch

libs/blocks/caas/utils.js#L62-L63

Added lines #L62 - L63 were not covered by tests
const b64decode = (str) => {
const binaryStr = window.atob(str);
const bytes = new Uint8Array(new ArrayBuffer(binaryStr.length));
binaryStr?.split('')
.forEach((c, i) => (bytes[i] = c.charCodeAt(0)));
return bytes;
};

const b64toStream = (b64) => new Blob([b64decode(b64)], { type: 'text/plain' }).stream();

const decompressStream = async (stream) => new Response(
// eslint-disable-next-line no-undef
stream.pipeThrough(new DecompressionStream('gzip')),
);

const responseToJSON = async (response) => {
const blob = await response.blob();
return JSON.parse(await blob.text());
};

const stream = b64toStream(txt);
const resp = await decompressStream(stream);
return responseToJSON(resp);
};

export const loadCaasFiles = async () => {
const version = new URL(document.location.href)?.searchParams?.get('caasver') || 'stable';

Expand Down
1 change: 1 addition & 0 deletions libs/deps/compression-streams-pollyfill.js

Large diffs are not rendered by default.

45 changes: 38 additions & 7 deletions test/blocks/caas-config/caas-config.test.html
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,16 @@
const tagUrlInput = findByLabel('Tags Url', configPanelEl);
tagUrlInput.value = 'https://not.the.right.url/tags';
tagUrlInput.dispatchEvent(new Event('change'));
const errorEl = await waitForElement('.tool-error');
expect(errorEl).to.not.be.null;
expect(errorEl.textContent).to.be.equal('Unable to fetch tags, loading backup tags. Please check tags url in the Advanced Panel');

const toolErrorEl = await waitForElement('.tool-error');
expect(toolErrorEl).to.not.be.null;

// Due to a race condition, the error message may be one of two things
let correctErrorMsg = false;
if (toolErrorEl.textContent === 'Tags url is not defined in the Advanced Panel'
|| toolErrorEl.textContent === 'Unable to fetch tags, loading backup tags. Please check tags url in the Advanced Panel') {
correctErrorMsg = true;
}
expect(correctErrorMsg).to.be.true;
const expectedConfig = cloneObj(defaultConfig);
expect(config).to.eql(expectedConfig);
});
Expand Down Expand Up @@ -285,7 +291,24 @@
copyBtn.click();
await delay(50);
const copyTextArea = document.querySelector('.copy-text');
expect(copyTextArea.value.split('#')[1]).to.equal('eyJhZGRpdGlvbmFsUmVxdWVzdFBhcmFtcyI6W10sImFuYWx5dGljc0NvbGxlY3Rpb25OYW1lIjoiIiwiYW5hbHl0aWNzVHJhY2tJbXByZXNzaW9uIjpmYWxzZSwiYW5kTG9naWNUYWdzIjpbXSwiYXV0b0NvdW50cnlMYW5nIjpmYWxzZSwiYm9va21hcmtJY29uU2VsZWN0IjoiIiwiYm9va21hcmtJY29uVW5zZWxlY3QiOiIiLCJjYXJkU3R5bGUiOiJoYWxmLWhlaWdodCIsImNhcmRUaXRsZUFjY2Vzc2liaWxpdHlMZXZlbCI6NiwiY29sbGVjdGlvbkJ0blN0eWxlIjoicHJpbWFyeSIsImNvbGxlY3Rpb25OYW1lIjoiIiwiY29sbGVjdGlvblRpdGxlIjoiIiwiY29sbGVjdGlvblNpemUiOiIiLCJjb250YWluZXIiOiIxMjAwTWF4V2lkdGgiLCJjb250ZW50VHlwZVRhZ3MiOltdLCJjb3VudHJ5IjoiY2Fhczpjb3VudHJ5L3VzIiwiY3VzdG9tQ2FyZCI6IiIsImN0YUFjdGlvbiI6Il9zZWxmIiwiZG9Ob3RMYXp5TG9hZCI6ZmFsc2UsImRpc2FibGVCYW5uZXJzIjpmYWxzZSwiZHJhZnREYiI6ZmFsc2UsImVuZHBvaW50Ijoid3d3LmFkb2JlLmNvbS9jaGltZXJhLWFwaS9jb2xsZWN0aW9uIiwiZW52aXJvbm1lbnQiOiIiLCJleGNsdWRlZENhcmRzIjpbXSwiZXhjbHVkZVRhZ3MiOltdLCJmYWxsYmFja0VuZHBvaW50IjoiIiwiZmVhdHVyZWRDYXJkcyI6W10sImZpbHRlckV2ZW50IjoiIiwiZmlsdGVyQnVpbGRQYW5lbCI6ImF1dG9tYXRpYyIsImZpbHRlckxvY2F0aW9uIjoibGVmdCIsImZpbHRlckxvZ2ljIjoib3IiLCJmaWx0ZXJzIjpbXSwiZmlsdGVyc0N1c3RvbSI6W10sImZpbHRlcnNTaG93RW1wdHkiOmZhbHNlLCJndXR0ZXIiOiI0eCIsImhlYWRlcnMiOltdLCJoaWRlQ3RhSWRzIjpbXSwiaGlkZUN0YVRhZ3MiOltdLCJpbmNsdWRlVGFncyI6W10sImxhbmd1YWdlIjoiY2FhczpsYW5ndWFnZS9lbiIsImxheW91dFR5cGUiOiI0dXAiLCJsb2FkTW9yZUJ0blN0eWxlIjoicHJpbWFyeSIsIm5vdExvZ2ljVGFncyI6W10sIm9ubHlTaG93Qm9va21hcmtlZENhcmRzIjpmYWxzZSwib3JMb2dpY1RhZ3MiOltdLCJwYWdpbmF0aW9uQW5pbWF0aW9uU3R5bGUiOiJwYWdlZCIsInBhZ2luYXRpb25FbmFibGVkIjpmYWxzZSwicGFnaW5hdGlvblF1YW50aXR5U2hvd24iOmZhbHNlLCJwYWdpbmF0aW9uVHlwZSI6InBhZ2luYXRvciIsInBhZ2luYXRpb25Vc2VUaGVtZTMiOmZhbHNlLCJwbGFjZWhvbGRlclVybCI6IiIsInJlc3VsdHNQZXJQYWdlIjo1LCJzZWFyY2hGaWVsZHMiOltdLCJzZXRDYXJkQm9yZGVycyI6ZmFsc2UsInNob3dCb29rbWFya3NGaWx0ZXIiOmZhbHNlLCJzaG93Qm9va21hcmtzT25DYXJkcyI6ZmFsc2UsInNob3dGaWx0ZXJzIjpmYWxzZSwic2hvd1NlYXJjaCI6ZmFsc2UsInNob3dUb3RhbFJlc3VsdHMiOmZhbHNlLCJzb3J0RGF0ZUFzYyI6ZmFsc2UsInNvcnREYXRlRGVzYyI6ZmFsc2UsInNvcnREYXRlTW9kaWZpZWQiOmZhbHNlLCJzb3J0RGVmYXVsdCI6ImRhdGVEZXNjIiwic29ydEVuYWJsZVBvcHVwIjpmYWxzZSwic29ydEVuYWJsZVJhbmRvbVNhbXBsaW5nIjpmYWxzZSwic29ydEV2ZW50U29ydCI6ZmFsc2UsInNvcnRGZWF0dXJlZCI6ZmFsc2UsInNvcnRNb2RpZmllZEFzYyI6ZmFsc2UsInNvcnRNb2RpZmllZERlc2MiOmZhbHNlLCJzb3J0UmFuZG9tIjpmYWxzZSwic29ydFJlc2Vydm9pclBvb2wiOjEwMDAsInNvcnRSZXNlcnZvaXJTYW1wbGUiOjMsInNvcnRUaXRsZUFzYyI6ZmFsc2UsInNvcnRUaXRsZURlc2MiOmZhbHNlLCJzb3VyY2UiOlsiaGF3a3MiXSwidGFnc1VybCI6Ind3dy5hZG9iZS5jb20vY2hpbWVyYS1hcGkvdGFncyIsInRhcmdldEFjdGl2aXR5IjoiIiwidGFyZ2V0RW5hYmxlZCI6ZmFsc2UsInRoZW1lIjoibGlnaHRlc3QiLCJkZXRhaWxzVGV4dE9wdGlvbiI6ImRlZmF1bHQiLCJ0aXRsZUhlYWRpbmdMZXZlbCI6ImgzIiwidG90YWxDYXJkc1RvU2hvdyI6MTAsInVzZUxpZ2h0VGV4dCI6ZmFsc2UsInVzZU92ZXJsYXlMaW5rcyI6ZmFsc2UsImNvbGxlY3Rpb25CdXR0b25TdHlsZSI6InByaW1hcnkiLCJ1c2VySW5mbyI6W119')

// Windows/Linux encodes the hash very slightly differently than OSX
// This is due to different header values in the zip output
// Note the start of each hash:
// Win: ~~H4sIAAAAAAAAA3
// Osx: ~~H4sIAAAAAAAAE3

// This does not affect the decoded hash value,
// so either of these are valid hashes for the same content

const windowsHash = '~~H4sIAAAAAAAAA3VVTXPbNhD9Lzw7sVK3PehmyfbEM3KsRvL0kOl0VsBSxAjEsgAomen0v3cXpPihKDfiLfbr7cPy3wy0NtGQA/sV/6kxxDV4KEM2//bXTQYMN9GosCRrUcm9L1BiNs+ykXHrQR2ey8pjCHwjm+dgA8oFvaK9UVvYn+PVkZZUu+ibFbh9f3NHdCjBH54VuQ1KpjbFGH9zYWRR4PUmNlZqKcDmHwo0+yJ2lq2JFu+VkoJ2xprYrPCINpv/zva+lUV05xCVN5ynycbmodMBS4EvwY353mMugnHo+fjpl9nsBd7/NDoWnQld3DYVDnyolgu+rQDCvDve1kEc6hCpXHI3XewI9ykdH/9mKnLGNH2huILvzYpA92xqE2BncQGOKwkD7CGPD7v+jE5XZJwQejqdPoKmHX5UVN6qwpTo4QNU5nboMhOPo/HkSnTdFPBd2Vqjliq7ljpo6JGz2R0L5HFIx545Qqz9xDM3NqJ/PPbRW2BRG6vX4GR8SUAlsOh684oUdKxYzOMIZ+UxSL6HJnnCMvE7gTYFnR7LKjY9R/s6xjTNX985TIGg+zCF0biM8Kyn56Fx4y6YsCz5GvZ4Hvf5fItCroWG6qQPSVdXAvFUX8jjVaE6nvz0dZGzjbSw6F5Nz27XDPkLhwr2xiX27p0p00efh+vS2fjKoxNRDSobLH/U4CK/Mcntrti7njogDWQwvgXcFlji3eBoQWFBlql+87aVAq+W2sawRr9OBP52kwUEr4ong/Y8gYBRGl6Q12PdhxEl4SlN+rrt1U35EuPTWTkjbJMyT6AtRVmgqcjBQD4+QMT7oH7AHvAK+ELa5GbEcTJgDhyWedBnv9bQDmRNFWtl7NDiX3n7UrmBsrJmtGnTBXliG/6YoE/di5yA54ouWzjjP7TRpp1CGNAfyfg1EY/z02w2uzCkKnmqdy3ebu+LwAm8SFd7xV7f+AdwOoSMFRBZ2q1mfr7R5E4mV/0eo2zUo5EHn/XYpdCjyFPWi/xg+Acpexd5zduwxff4WnXbR3dzYgcp9TPvCia+++9kxZ0YRCVJY1uSxyJk3GR1wJWElmh9UgZfj+h5KayMOwyaGv29eDXRlb3Anv7Z5SRv4r//Ac4t/CbgBwAA';
const osxHash = '~~H4sIAAAAAAAAE3VVTXPbNhD9Lzw7sVK3PehmyfbEM3KsRvL0kOl0VsBSxAjEsgAomen0v3cXpPihKDfiLfbr7cPy3wy0NtGQA/sV/6kxxDV4KEM2//bXTQYMN9GosCRrUcm9L1BiNs+ykXHrQR2ey8pjCHwjm+dgA8oFvaK9UVvYn+PVkZZUu+ibFbh9f3NHdCjBH54VuQ1KpjbFGH9zYWRR4PUmNlZqKcDmHwo0+yJ2lq2JFu+VkoJ2xprYrPCINpv/zva+lUV05xCVN5ynycbmodMBS4EvwY353mMugnHo+fjpl9nsBd7/NDoWnQld3DYVDnyolgu+rQDCvDve1kEc6hCpXHI3XewI9ykdH/9mKnLGNH2huILvzYpA92xqE2BncQGOKwkD7CGPD7v+jE5XZJwQejqdPoKmHX5UVN6qwpTo4QNU5nboMhOPo/HkSnTdFPBd2Vqjliq7ljpo6JGz2R0L5HFIx545Qqz9xDM3NqJ/PPbRW2BRG6vX4GR8SUAlsOh684oUdKxYzOMIZ+UxSL6HJnnCMvE7gTYFnR7LKjY9R/s6xjTNX985TIGg+zCF0biM8Kyn56Fx4y6YsCz5GvZ4Hvf5fItCroWG6qQPSVdXAvFUX8jjVaE6nvz0dZGzjbSw6F5Nz27XDPkLhwr2xiX27p0p00efh+vS2fjKoxNRDSobLH/U4CK/Mcntrti7njogDWQwvgXcFlji3eBoQWFBlql+87aVAq+W2sawRr9OBP52kwUEr4ong/Y8gYBRGl6Q12PdhxEl4SlN+rrt1U35EuPTWTkjbJMyT6AtRVmgqcjBQD4+QMT7oH7AHvAK+ELa5GbEcTJgDhyWedBnv9bQDmRNFWtl7NDiX3n7UrmBsrJmtGnTBXliG/6YoE/di5yA54ouWzjjP7TRpp1CGNAfyfg1EY/z02w2uzCkKnmqdy3ebu+LwAm8SFd7xV7f+AdwOoSMFRBZ2q1mfr7R5E4mV/0eo2zUo5EHn/XYpdCjyFPWi/xg+Acpexd5zduwxff4WnXbR3dzYgcp9TPvCia+++9kxZ0YRCVJY1uSxyJk3GR1wJWElmh9UgZfj+h5KayMOwyaGv29eDXRlb3Anv7Z5SRv4r//Ac4t/CbgBwAA';
let isCorrectHash = false;
const hash = copyTextArea.value.split('#')[1];
if (hash === windowsHash || hash === osxHash) {
isCorrectHash = true;
}
expect(isCorrectHash).to.be.true;
});

it('Clones an object', () => {
Expand All @@ -305,13 +328,21 @@
expect(updateObj({ a: 'blah', d: 1234 }, allKeys)).to.eql({ a: 'blah', b: 2, c: [6, 7, 8], d: 1234 });
});

it('Converts Base 64 to UTF-8', () => {
it('Converts Verson1 Hash Base 64 to UTF-8', async () => {
const hash = 'eyJhZGRpdGlvbmFsUmVxdWVzdFBhcmFtcyI6W10sImFuYWx5dGljc0NvbGxlY3Rpb25OYW1lIjoiIiwiYW5hbHl0aWNzVHJhY2tJbXByZXNzaW9uIjpmYWxzZSwiYW5kTG9naWNUYWdzIjpbXSwiYXV0b0NvdW50cnlMYW5nIjpmYWxzZSwiYm9va21hcmtJY29uU2VsZWN0IjoiIiwiYm9va21hcmtJY29uVW5zZWxlY3QiOiIiLCJjYXJkU3R5bGUiOiJoYWxmLWhlaWdodCIsImNhcmRUaXRsZUFjY2Vzc2liaWxpdHlMZXZlbCI6NiwiY29sbGVjdGlvbkJ0blN0eWxlIjoicHJpbWFyeSIsImNvbGxlY3Rpb25OYW1lIjoiIiwiY29sbGVjdGlvblRpdGxlIjoiIiwiY29sbGVjdGlvblNpemUiOiIiLCJjb250YWluZXIiOiIxMjAwTWF4V2lkdGgiLCJjb250ZW50VHlwZVRhZ3MiOltdLCJjb3VudHJ5IjoiY2Fhczpjb3VudHJ5L3VzIiwiY3VzdG9tQ2FyZCI6IiIsImN0YUFjdGlvbiI6Il9ibGFuayIsImRvTm90TGF6eUxvYWQiOmZhbHNlLCJkaXNhYmxlQmFubmVycyI6ZmFsc2UsImRyYWZ0RGIiOmZhbHNlLCJlbmRwb2ludCI6Ind3dy5hZG9iZS5jb20vY2hpbWVyYS1hcGkvY29sbGVjdGlvbiIsImVudmlyb25tZW50IjoiIiwiZXhjbHVkZWRDYXJkcyI6W10sImV4Y2x1ZGVUYWdzIjpbXSwiZmFsbGJhY2tFbmRwb2ludCI6IiIsImZlYXR1cmVkQ2FyZHMiOltdLCJmaWx0ZXJFdmVudCI6IiIsImZpbHRlckJ1aWxkUGFuZWwiOiJhdXRvbWF0aWMiLCJmaWx0ZXJMb2NhdGlvbiI6ImxlZnQiLCJmaWx0ZXJMb2dpYyI6Im9yIiwiZmlsdGVycyI6W10sImZpbHRlcnNDdXN0b20iOltdLCJmaWx0ZXJzU2hvd0VtcHR5IjpmYWxzZSwiZ3V0dGVyIjoiNHgiLCJoZWFkZXJzIjpbXSwiaGVhZGVycyI6W10sImhpZGVDdGFJZHMiOltdLCJpbmNsdWRlVGFncyI6W10sImxhbmd1YWdlIjoiY2FhczpsYW5ndWFnZS9lbiIsImxheW91dFR5cGUiOiI0dXAiLCJsb2FkTW9yZUJ0blN0eWxlIjoicHJpbWFyeSIsIm9ubHlTaG93Qm9va21hcmtlZENhcmRzIjpmYWxzZSwib3JMb2dpY1RhZ3MiOltdLCJwYWdpbmF0aW9uQW5pbWF0aW9uU3R5bGUiOiJwYWdlZCIsInBhZ2luYXRpb25FbmFibGVkIjpmYWxzZSwicGFnaW5hdGlvblF1YW50aXR5U2hvd24iOmZhbHNlLCJwYWdpbmF0aW9uVHlwZSI6InBhZ2luYXRvciIsInBhZ2luYXRpb25Vc2VUaGVtZTMiOmZhbHNlLCJwbGFjZWhvbGRlclVybCI6IiIsInJlc3VsdHNQZXJQYWdlIjo1LCJzZWFyY2hGaWVsZHMiOltdLCJzZXRDYXJkQm9yZGVycyI6ZmFsc2UsInNob3dCb29rbWFya3NGaWx0ZXIiOmZhbHNlLCJzaG93Qm9va21hcmtzT25DYXJkcyI6ZmFsc2UsInNob3dGaWx0ZXJzIjpmYWxzZSwic2hvd1NlYXJjaCI6ZmFsc2UsInNob3dUb3RhbFJlc3VsdHMiOmZhbHNlLCJzb3J0RGF0ZUFzYyI6ZmFsc2UsInNvcnREYXRlRGVzYyI6ZmFsc2UsInNvcnREYXRlTW9kaWZpZWQiOmZhbHNlLCJzb3J0RGVmYXVsdCI6ImRhdGVEZXNjIiwic29ydEVuYWJsZVBvcHVwIjpmYWxzZSwic29ydEVuYWJsZVJhbmRvbVNhbXBsaW5nIjpmYWxzZSwic29ydEV2ZW50U29ydCI6ZmFsc2UsInNvcnRGZWF0dXJlZCI6ZmFsc2UsInNvcnRNb2RpZmllZEFzYyI6ZmFsc2UsInNvcnRNb2RpZmllZERlc2MiOmZhbHNlLCJzb3J0UmFuZG9tIjpmYWxzZSwic29ydFJlc2Vydm9pclBvb2wiOjEwMDAsInNvcnRSZXNlcnZvaXJTYW1wbGUiOjMsInNvcnRUaXRsZUFzYyI6ZmFsc2UsInNvcnRUaXRsZURlc2MiOmZhbHNlLCJzb3VyY2UiOlsiaGF3a3MiXSwidGFnc1VybCI6Ind3dy5hZG9iZS5jb20vY2hpbWVyYS1hcGkvdGFncyIsInRhcmdldEFjdGl2aXR5IjoiIiwidGFyZ2V0RW5hYmxlZCI6ZmFsc2UsInRoZW1lIjoibGlnaHRlc3QiLCJkZXRhaWxzVGV4dE9wdGlvbiI6ImRlZmF1bHQiLCJ0aXRsZUhlYWRpbmdMZXZlbCI6ImgzIiwidG90YWxDYXJkc1RvU2hvdyI6MTAsInVzZUxpZ2h0VGV4dCI6ZmFsc2UsInVzZU92ZXJsYXlMaW5rcyI6ZmFsc2UsImNvbGxlY3Rpb25CdXR0b25TdHlsZSI6InByaW1hcnkiLCJ1c2VySW5mbyI6W119';
window.location.hash = hash;
const hashConfig = getHashConfig();
const hashConfig = await getHashConfig();
expect(hashConfig.cardStyle).to.equal('half-height');
});

it('Converts Verson2 Gzipped Hash Base 64 to UTF-8', async () => {
const hash = '~~H4sIAAAAAAAAE3VVTXPbNhD9Lzw7sVK3PehmyfbEM3KsRvL0kOl0VsBSxAjEsgAomen0v3cX/JSi3Ii32K+3D8t/M9DaREMO7Ff8p8YQ1+ChDNn82183GTDcRKPCkqxFJfe+QInZPMsmxq0HdXguK48h8I1snoMNKBf0ivZGbWHfx6sjLal20TcrcPvh5o7oUII/PCtyG5RMbYop/ubCxKLA601srNSS19Z+EKDDtyZavFdKytkZa2KzwiPabP4724dGFtH1ASpvOEuTTc1jnyOWAl+CG/N9wFwE49Dz8dMvs9kLvP9pdCw6E7q4bSoc2VAtE3xbAYR5d7ytgzjUIVK5lKba2BHuUzo+/s1E5Ixp+kJxBd+bFYEeuNQmwM7iAhxXEkbYQx4fdsMZna7IOKHzdDp9BE07/KiovFWFKdHDB6jM7dhlJh5H48mV6LoZ4LuytUYtVXYtddDYI2ezO5bH45iOPXOEWPszz9zYiP7xOERvgUVtrF6Dk/El+ZTAkhvMK1LQsWIxjxOcdccg+QE6yxOWid8zaFPQ6bGsYjNwtK9jTNP89Z3DFAh6CFMYjcsIz/r8PDZu3AUTlgVfwx77cffnWxRyLTRUJ31IuroSiKf6Qh6vCpWcbaTgRfdCBi670slfPL0K9sYlru6dKdPHEJWrkLczXnl0IqFRU6Pljxpc5Bclud0Ve9dBByT6R+NbwG2BJd6NjhYUFmSZ2Ddv28HzGqltDGv060TXbzdZQPCqeDJoe74DRml4QV5PVR4mlISnNNfrtld3zpcYn3qdTLBNynwGbSnKskxFjgby8QEi3gf1A/aAV8AX0iY3E46TAXPgsMyD7v1aQzuQNVWsjKlDi3/lTUvlBsrKmslWTRfkQW344wx96t7fGdhXdNlCj//QRpv2HMKA/kjGr4l4nJ9ms9mFIVXJU71r8XZXXwRO4EW62iv2+pYVcDqEjBUQWdqtZn6+v+ROJlf9HqPsz6OR550N2KXQo8hTlonZF5F/hrJlkZe6DVt8j69Vt2t0Nyd2kFI/82Zg4ru/TFbciUFUkjS2JXksQsZNVgdcSWiJNiRl8PWInlfAyrjDqKnJv4oXEV3ZAuzpn11O7Ztw/DeYPvr//gcuiyqz3gcAAA==';
window.location.hash = hash;
// v2 getHashConfig is async
const hashConfig = await getHashConfig();
expect(hashConfig.cardStyle).to.equal('full-card');
});

});
</script>
</html>
Loading
Loading