224 lines
8.0 KiB
JavaScript
224 lines
8.0 KiB
JavaScript
/**
|
|
* FixIt decryptor for encrypted pages and fixit-encryptor shortcode
|
|
* @param {Object} options
|
|
* @param {Function} [options.decrypted] [Lifecycle Hooks] handler after decrypting
|
|
* @param {Function} [options.reset] [Lifecycle Hooks] handler after encrypting again
|
|
* @param {Number} [options.duration=86400] number of seconds to cache decryption statistics. unit: s
|
|
* @author @Lruihao https://lruihao.cn
|
|
* @since v0.2.15
|
|
*/
|
|
FixItDecryptor = function (options = {}) {
|
|
var _proto = FixItDecryptor.prototype;
|
|
this.options = options || {};
|
|
this.options.duration = this.options.duration || 24 * 60 * 60; // default cache one day
|
|
this.decryptedEventSet = new Set();
|
|
this.resetEventSet = new Set();
|
|
this.$el = document.querySelector('.fixit-decryptor-container');
|
|
|
|
/**
|
|
* decrypt content
|
|
* @param {String} base64EncodeContent encrypted content
|
|
*/
|
|
var _decryptContent = (base64EncodeContent) => {
|
|
try {
|
|
this.$el.querySelector('.fixit-decryptor-loading').classList.add('d-none');
|
|
this.$el.querySelector('#fixit-decryptor-input').classList.add('d-none');
|
|
this.$el.querySelector('.fixit-encryptor-btn').classList.remove('d-none');
|
|
document.querySelector('#content').insertAdjacentHTML(
|
|
'afterbegin',
|
|
CryptoJS.enc.Base64.parse(base64EncodeContent).toString(CryptoJS.enc.Utf8)
|
|
);
|
|
} catch (err) {
|
|
return console.error(err);
|
|
}
|
|
// decrypted hook
|
|
console.log(this.decryptedEventSet)
|
|
for (const event of this.decryptedEventSet) {
|
|
event();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* initialize FixIt decryptor
|
|
*/
|
|
_proto.init = () => {
|
|
this.addEventListener('decrypted', this.options?.decrypted);
|
|
this.addEventListener('reset', this.options?.reset);
|
|
this.validateCache();
|
|
|
|
const _decryptor = this;
|
|
this.$el.querySelector('#fixit-decryptor-input')?.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
const $content = document.querySelector('#content');
|
|
const password = $content.getAttribute('data-password');
|
|
const input = this.value.trim();
|
|
const saltLen = input.length % 2 ? input.length : input.length + 1;
|
|
const inputMd5 = CryptoJS.MD5(input).toString();
|
|
const inputSha256 = CryptoJS.SHA256(input).toString();
|
|
|
|
this.value = '';
|
|
this.blur();
|
|
if (!input) {
|
|
alert('Please enter the correct password!');
|
|
return console.warn('Please enter the correct password!');
|
|
}
|
|
if (inputMd5 !== password) {
|
|
alert(`Password error: ${input} not the correct password!`);
|
|
return console.warn(`Password error: ${input} not the correct password!`);
|
|
}
|
|
// cache decryption statistics
|
|
window.localStorage?.setItem(
|
|
`fixit-decryptor/#${location.pathname}`,
|
|
JSON.stringify({
|
|
expiration: Math.ceil(Date.now() / 1000) + _decryptor.options.duration,
|
|
md5: inputMd5,
|
|
sha256: inputSha256.slice(saltLen)
|
|
})
|
|
);
|
|
_decryptContent($content.getAttribute('data-content').replace(inputSha256.slice(saltLen), ''));
|
|
}
|
|
});
|
|
|
|
this.$el.querySelector('.fixit-encryptor-btn')?.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
this.classList.add('d-none')
|
|
_decryptor.$el.querySelector('#fixit-decryptor-input').classList.remove('d-none');
|
|
document.querySelector('#content').innerHTML = '';
|
|
document.querySelector('#content').insertAdjacentElement(
|
|
'afterbegin',
|
|
_decryptor.$el
|
|
);
|
|
window.localStorage?.removeItem(`fixit-decryptor/#${location.pathname}`);
|
|
// reset hook
|
|
for (const event of _decryptor.resetEventSet) {
|
|
event();
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* initialize fixit-encryptor shortcodes
|
|
*/
|
|
_proto.initShortcodes = () => {
|
|
// TODO TODO shortcode decrypted event
|
|
// this.addEventListener('decrypted', this.options?.decrypted);
|
|
const _decryptor = this;
|
|
const $shortcodes = document.querySelectorAll('fixit-encryptor:not(.decrypted)');
|
|
|
|
$shortcodes.forEach($shortcode => {
|
|
$shortcode.querySelector('.fixit-decryptor-input')?.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
const $decryptor = this.parentElement.parentElement;
|
|
const $content = $decryptor.nextElementSibling;
|
|
const password = $content.getAttribute('data-password');
|
|
const input = this.value.trim();
|
|
const saltLen = input.length % 2 ? input.length : input.length + 1;
|
|
const inputMd5 = CryptoJS.MD5(input).toString();
|
|
const inputSha256 = CryptoJS.SHA256(input).toString();
|
|
|
|
this.value = '';
|
|
this.blur();
|
|
if (!input) {
|
|
alert('Please enter the correct password!');
|
|
return console.warn('Please enter the correct password!');
|
|
}
|
|
if (inputMd5 !== password) {
|
|
alert(`Password error: ${input} not the correct password!`);
|
|
return console.warn(`Password error: ${input} not the correct password!`);
|
|
}
|
|
try {
|
|
const base64EncodeContent = $content.getAttribute('data-content').replace(inputSha256.slice(saltLen), '');
|
|
$decryptor.querySelector('.fixit-decryptor-input').classList.add('d-none');
|
|
$content.insertAdjacentHTML(
|
|
'afterbegin',
|
|
CryptoJS.enc.Base64.parse(base64EncodeContent).toString(CryptoJS.enc.Utf8)
|
|
);
|
|
$decryptor.parentElement.classList.add('decrypted');
|
|
} catch (err) {
|
|
return console.error(err);
|
|
}
|
|
// TODO shortcode decrypted hook
|
|
// for (const event of _decryptor.decryptedEventSet) {
|
|
// event();
|
|
// }
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* validate the cached decryption statistics in localStorage
|
|
* @returns {FixItDecryptor}
|
|
*/
|
|
_proto.validateCache = () => {
|
|
const $content = document.querySelector('#content');
|
|
const password = $content.getAttribute('data-password');
|
|
const cachedStat = JSON.parse(window.localStorage?.getItem(`fixit-decryptor/#${location.pathname}`));
|
|
|
|
if (!cachedStat) {
|
|
this.$el.querySelector('.fixit-decryptor-loading').classList.add('d-none');
|
|
this.$el.querySelector('#fixit-decryptor-input').classList.remove('d-none');
|
|
return this;
|
|
}
|
|
if (cachedStat?.md5 !== password || Number(cachedStat?.expiration) < Math.ceil(Date.now() / 1000)) {
|
|
this.$el.querySelector('.fixit-decryptor-loading').classList.add('d-none');
|
|
this.$el.querySelector('#fixit-decryptor-input').classList.remove('d-none');
|
|
window.localStorage?.removeItem(`fixit-decryptor/#${location.pathname}`);
|
|
console.warn('The password has expired, please re-enter!');
|
|
return this;
|
|
}
|
|
_decryptContent($content.getAttribute('data-content').replace(cachedStat.sha256, ''));
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* add event listener for FixIt Decryptor
|
|
* @param {String} event event name
|
|
* @param {Function} listener event handler
|
|
* @returns {FixItDecryptor}
|
|
*/
|
|
_proto.addEventListener = (event, listener) => {
|
|
if (typeof listener !== 'function') {
|
|
return this;
|
|
}
|
|
switch (event) {
|
|
case 'decrypted':
|
|
this.decryptedEventSet.add(listener);
|
|
break;
|
|
case 'reset':
|
|
this.resetEventSet.add(listener);
|
|
break;
|
|
default:
|
|
console.warn(`Event ${event} not found in FixIt Decryptor!`);
|
|
break;
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* remove event listener for FixIt Decryptor
|
|
* @param {String} event event name
|
|
* @param {Function} listener event handler
|
|
* @returns {FixItDecryptor}
|
|
*/
|
|
_proto.removeEventListener = (event, listener) => {
|
|
if (typeof listener !== 'function') {
|
|
return this;
|
|
}
|
|
switch (event) {
|
|
case 'decrypted':
|
|
this.decryptedEventSet.delete(listener);
|
|
break;
|
|
case 'reset':
|
|
this.resetEventSet.delete(listener);
|
|
break;
|
|
default:
|
|
console.warn(`Event ${event} not found in FixIt Decryptor!`);
|
|
break;
|
|
}
|
|
return this;
|
|
};
|
|
};
|