Delete public/rammerhead.js
This commit is contained in:
parent
029331aeb5
commit
585f937b81
1 changed files with 0 additions and 497 deletions
|
|
@ -1,497 +0,0 @@
|
|||
(function () {
|
||||
var hammerhead = window['%hammerhead%'];
|
||||
if (!hammerhead) throw new Error('hammerhead not loaded yet');
|
||||
if (hammerhead.settings._settings.sessionId) {
|
||||
// task.js already loaded. this will likely never happen though since this file loads before task.js
|
||||
console.warn('unexpected task.js to load before rammerhead.js. url shuffling cannot be used');
|
||||
main();
|
||||
} else {
|
||||
// wait for task.js to load
|
||||
hookHammerheadStartOnce(main);
|
||||
// before task.js, we need to add url shuffling
|
||||
addUrlShuffling();
|
||||
}
|
||||
|
||||
function main() {
|
||||
fixUrlRewrite();
|
||||
fixElementGetter();
|
||||
fixCrossWindowLocalStorage();
|
||||
|
||||
delete window.overrideGetProxyUrl;
|
||||
delete window.overrideParseProxyUrl;
|
||||
delete window.overrideIsCrossDomainWindows;
|
||||
|
||||
// other code if they want to also hook onto hammerhead start //
|
||||
if (window.rammerheadStartListeners) {
|
||||
for (const eachListener of window.rammerheadStartListeners) {
|
||||
try {
|
||||
eachListener();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
delete window.rammerheadStartListeners;
|
||||
}
|
||||
|
||||
// sync localStorage code //
|
||||
// disable if other code wants to implement their own localStorage site wrapper
|
||||
if (window.rammerheadDisableLocalStorageImplementation) {
|
||||
delete window.rammerheadDisableLocalStorageImplementation;
|
||||
return;
|
||||
}
|
||||
// consts
|
||||
var timestampKey = 'rammerhead_synctimestamp';
|
||||
var updateInterval = 5000;
|
||||
var isSyncing = false;
|
||||
|
||||
var proxiedLocalStorage = localStorage;
|
||||
var realLocalStorage = proxiedLocalStorage.internal.nativeStorage;
|
||||
var sessionId = hammerhead.settings._settings.sessionId;
|
||||
var origin = window.__get$(window, 'location').origin;
|
||||
var keyChanges = [];
|
||||
|
||||
try {
|
||||
syncLocalStorage();
|
||||
} catch (e) {
|
||||
if (e.message !== 'server wants to disable localStorage syncing') {
|
||||
throw e;
|
||||
}
|
||||
return;
|
||||
}
|
||||
proxiedLocalStorage.addChangeEventListener(function (event) {
|
||||
if (isSyncing) return;
|
||||
if (keyChanges.indexOf(event.key) === -1) keyChanges.push(event.key);
|
||||
});
|
||||
setInterval(function () {
|
||||
var update = compileUpdate();
|
||||
if (!update) return;
|
||||
localStorageRequest({ type: 'update', updateData: update }, function (data) {
|
||||
updateTimestamp(data.timestamp);
|
||||
});
|
||||
|
||||
keyChanges = [];
|
||||
}, updateInterval);
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
var update = compileUpdate();
|
||||
if (update) {
|
||||
// even though we'll never get the timestamp, it's fine. this way,
|
||||
// the data is safer
|
||||
hammerhead.nativeMethods.sendBeacon.call(
|
||||
window.navigator,
|
||||
getSyncStorageEndpoint(),
|
||||
JSON.stringify({
|
||||
type: 'update',
|
||||
updateData: update
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function syncLocalStorage() {
|
||||
isSyncing = true;
|
||||
var timestamp = getTimestamp();
|
||||
var response;
|
||||
if (!timestamp) {
|
||||
// first time syncing
|
||||
response = localStorageRequest({ type: 'sync', fetch: true });
|
||||
if (response.timestamp) {
|
||||
updateTimestamp(response.timestamp);
|
||||
overwriteLocalStorage(response.data);
|
||||
}
|
||||
} else {
|
||||
// resync
|
||||
response = localStorageRequest({ type: 'sync', timestamp: timestamp, data: proxiedLocalStorage });
|
||||
if (response.timestamp) {
|
||||
updateTimestamp(response.timestamp);
|
||||
overwriteLocalStorage(response.data);
|
||||
}
|
||||
}
|
||||
isSyncing = false;
|
||||
|
||||
function overwriteLocalStorage(data) {
|
||||
if (!data || typeof data !== 'object') throw new TypeError('data must be an object');
|
||||
proxiedLocalStorage.clear();
|
||||
for (var prop in data) {
|
||||
proxiedLocalStorage[prop] = data[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateTimestamp(timestamp) {
|
||||
if (!timestamp) throw new TypeError('timestamp must be defined');
|
||||
if (isNaN(parseInt(timestamp))) throw new TypeError('timestamp must be a number. received' + timestamp);
|
||||
realLocalStorage[timestampKey] = timestamp;
|
||||
}
|
||||
function getTimestamp() {
|
||||
var rawTimestamp = realLocalStorage[timestampKey];
|
||||
var timestamp = parseInt(rawTimestamp);
|
||||
if (isNaN(timestamp)) {
|
||||
if (rawTimestamp) {
|
||||
console.warn('invalid timestamp retrieved from storage: ' + rawTimestamp);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return timestamp;
|
||||
}
|
||||
function getSyncStorageEndpoint() {
|
||||
return (
|
||||
'/syncLocalStorage?sessionId=' + encodeURIComponent(sessionId) + '&origin=' + encodeURIComponent(origin)
|
||||
);
|
||||
}
|
||||
function localStorageRequest(data, callback) {
|
||||
if (!data || typeof data !== 'object') throw new TypeError('data must be an object');
|
||||
|
||||
var request = hammerhead.createNativeXHR();
|
||||
// make synchronous if there is no callback
|
||||
request.open('POST', getSyncStorageEndpoint(), !!callback);
|
||||
request.setRequestHeader('content-type', 'application/json');
|
||||
request.send(JSON.stringify(data));
|
||||
function check() {
|
||||
if (request.status === 404) {
|
||||
throw new Error('server wants to disable localStorage syncing');
|
||||
}
|
||||
if (request.status !== 200)
|
||||
throw new Error(
|
||||
'server sent a non 200 code. got ' + request.status + '. Response: ' + request.responseText
|
||||
);
|
||||
}
|
||||
if (!callback) {
|
||||
check();
|
||||
return JSON.parse(request.responseText);
|
||||
} else {
|
||||
request.onload = function () {
|
||||
check();
|
||||
callback(JSON.parse(request.responseText));
|
||||
};
|
||||
}
|
||||
}
|
||||
function compileUpdate() {
|
||||
if (!keyChanges.length) return null;
|
||||
|
||||
var updates = {};
|
||||
for (var i = 0; i < keyChanges.length; i++) {
|
||||
updates[keyChanges[i]] = proxiedLocalStorage[keyChanges[i]];
|
||||
}
|
||||
|
||||
keyChanges = [];
|
||||
return updates;
|
||||
}
|
||||
}
|
||||
|
||||
var noShuffling = false;
|
||||
function addUrlShuffling() {
|
||||
const request = new XMLHttpRequest();
|
||||
const sessionId = (location.pathname.slice(1).match(/^[a-z0-9]+/i) || [])[0];
|
||||
if (!sessionId) {
|
||||
console.warn('cannot get session id from url');
|
||||
return;
|
||||
}
|
||||
request.open('GET', '/api/shuffleDict?id=' + sessionId, false);
|
||||
request.send();
|
||||
if (request.status !== 200) {
|
||||
console.warn(
|
||||
`received a non 200 status code while trying to fetch shuffleDict:\nstatus: ${request.status}\nresponse: ${request.responseText}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const shuffleDict = JSON.parse(request.responseText);
|
||||
if (!shuffleDict) return;
|
||||
|
||||
// pasting entire thing here "because lazy" - m28
|
||||
const mod = (n, m) => ((n % m) + m) % m;
|
||||
const baseDictionary = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-';
|
||||
const shuffledIndicator = '_rhs';
|
||||
const generateDictionary = function () {
|
||||
let str = '';
|
||||
const split = baseDictionary.split('');
|
||||
while (split.length > 0) {
|
||||
str += split.splice(Math.floor(Math.random() * split.length), 1)[0];
|
||||
}
|
||||
return str;
|
||||
};
|
||||
class StrShuffler {
|
||||
constructor(dictionary = generateDictionary()) {
|
||||
this.dictionary = dictionary;
|
||||
}
|
||||
shuffle(str) {
|
||||
if (str.startsWith(shuffledIndicator)) {
|
||||
return str;
|
||||
}
|
||||
let shuffledStr = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charAt(i);
|
||||
const idx = baseDictionary.indexOf(char);
|
||||
if (char === '%' && str.length - i >= 3) {
|
||||
shuffledStr += char;
|
||||
shuffledStr += str.charAt(++i);
|
||||
shuffledStr += str.charAt(++i);
|
||||
} else if (idx === -1) {
|
||||
shuffledStr += char;
|
||||
} else {
|
||||
shuffledStr += this.dictionary.charAt(mod(idx + i, baseDictionary.length));
|
||||
}
|
||||
}
|
||||
return shuffledIndicator + shuffledStr;
|
||||
}
|
||||
unshuffle(str) {
|
||||
if (!str.startsWith(shuffledIndicator)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
str = str.slice(shuffledIndicator.length);
|
||||
|
||||
let unshuffledStr = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charAt(i);
|
||||
const idx = this.dictionary.indexOf(char);
|
||||
if (char === '%' && str.length - i >= 3) {
|
||||
unshuffledStr += char;
|
||||
unshuffledStr += str.charAt(++i);
|
||||
unshuffledStr += str.charAt(++i);
|
||||
} else if (idx === -1) {
|
||||
unshuffledStr += char;
|
||||
} else {
|
||||
unshuffledStr += baseDictionary.charAt(mod(idx - i, baseDictionary.length));
|
||||
}
|
||||
}
|
||||
return unshuffledStr;
|
||||
}
|
||||
}
|
||||
|
||||
function patch(url) {
|
||||
// url = _rhsEPrcb://bqhQko.tHR/
|
||||
// remove slash
|
||||
return url.replace(/(^.*?:\/)\//, '$1');
|
||||
}
|
||||
|
||||
function unpatch(url) {
|
||||
// url = _rhsEPrcb:/bqhQko.tHR/
|
||||
// restore slash
|
||||
return url.replace(/^.*?:\/(?!\/)/, '$&/');
|
||||
}
|
||||
|
||||
const replaceUrl = (url, replacer) => {
|
||||
// regex: https://google.com/ sessionid/ url
|
||||
return (url || '').replace(/^((?:[a-z0-9]+:\/\/[^/]+)?(?:\/[^/]+\/))([^]+)/i, function (_, g1, g2) {
|
||||
return g1 + replacer(g2);
|
||||
})
|
||||
};
|
||||
|
||||
const shuffler = new StrShuffler(shuffleDict);
|
||||
|
||||
// shuffle current url if it isn't already shuffled (unshuffled urls likely come from user input)
|
||||
const oldUrl = location.href;
|
||||
const newUrl = replaceUrl(location.href, (url) => shuffler.shuffle(url));
|
||||
if (oldUrl !== newUrl) {
|
||||
history.replaceState(null, null, newUrl);
|
||||
}
|
||||
|
||||
const getProxyUrl = hammerhead.utils.url.getProxyUrl;
|
||||
const parseProxyUrl = hammerhead.utils.url.parseProxyUrl;
|
||||
hammerhead.utils.url.overrideGetProxyUrl(function (url, opts) {
|
||||
if (noShuffling) {
|
||||
return getProxyUrl(url, opts);
|
||||
}
|
||||
return replaceUrl(getProxyUrl(url, opts), (u) => patch(shuffler.shuffle(u)), true)
|
||||
});
|
||||
hammerhead.utils.url.overrideParseProxyUrl(function (url) {
|
||||
return parseProxyUrl(replaceUrl(url, (u) => shuffler.unshuffle(unpatch(u)), false));
|
||||
});
|
||||
// manual hooks //
|
||||
window.overrideGetProxyUrl(
|
||||
(getProxyUrl$1) =>
|
||||
function (url, opts) {
|
||||
if (noShuffling) {
|
||||
return getProxyUrl$1(url, opts);
|
||||
}
|
||||
return replaceUrl(getProxyUrl$1(url, opts), (u) => patch(shuffler.shuffle(u)), true);
|
||||
}
|
||||
);
|
||||
window.overrideParseProxyUrl(
|
||||
(parseProxyUrl$1) =>
|
||||
function (url) {
|
||||
return parseProxyUrl$1(replaceUrl(url, (u) => shuffler.unshuffle(unpatch(u)), false));
|
||||
}
|
||||
);
|
||||
}
|
||||
function fixUrlRewrite() {
|
||||
const port = location.port || (location.protocol === 'https:' ? '443' : '80');
|
||||
const getProxyUrl = hammerhead.utils.url.getProxyUrl;
|
||||
hammerhead.utils.url.overrideGetProxyUrl(function (url, opts = {}) {
|
||||
if (!opts.proxyPort) {
|
||||
opts.proxyPort = port;
|
||||
}
|
||||
return getProxyUrl(url, opts);
|
||||
});
|
||||
window.overrideParseProxyUrl(
|
||||
(parseProxyUrl$1) =>
|
||||
function (url) {
|
||||
const parsed = parseProxyUrl$1(url);
|
||||
if (!parsed || !parsed.proxy) return parsed;
|
||||
if (!parsed.proxy.port) {
|
||||
parsed.proxy.port = port;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
);
|
||||
}
|
||||
function fixElementGetter() {
|
||||
const fixList = {
|
||||
HTMLAnchorElement: ['href'],
|
||||
HTMLAreaElement: ['href'],
|
||||
HTMLBaseElement: ['href'],
|
||||
HTMLEmbedElement: ['src'],
|
||||
HTMLFormElement: ['action'],
|
||||
HTMLFrameElement: ['src'],
|
||||
HTMLIFrameElement: ['src'],
|
||||
HTMLImageElement: ['src'],
|
||||
HTMLInputElement: ['src'],
|
||||
HTMLLinkElement: ['href'],
|
||||
HTMLMediaElement: ['src'],
|
||||
HTMLModElement: ['cite'],
|
||||
HTMLObjectElement: ['data'],
|
||||
HTMLQuoteElement: ['cite'],
|
||||
HTMLScriptElement: ['src'],
|
||||
HTMLSourceElement: ['src'],
|
||||
HTMLTrackElement: ['src']
|
||||
};
|
||||
const urlRewrite = (url) => (hammerhead.utils.url.parseProxyUrl(url) || {}).destUrl || url;
|
||||
for (const ElementClass in fixList) {
|
||||
for (const attr of fixList[ElementClass]) {
|
||||
if (!window[ElementClass]) {
|
||||
console.warn('unexpected unsupported element class ' + ElementClass);
|
||||
continue;
|
||||
}
|
||||
const desc = Object.getOwnPropertyDescriptor(window[ElementClass].prototype, attr);
|
||||
const originalGet = desc.get;
|
||||
desc.get = function () {
|
||||
return urlRewrite(originalGet.call(this));
|
||||
};
|
||||
if (attr === 'action') {
|
||||
const originalSet = desc.set;
|
||||
// don't shuffle form action urls
|
||||
desc.set = function (value) {
|
||||
noShuffling = true;
|
||||
try {
|
||||
var returnVal = originalSet.call(this, value);
|
||||
} catch (e) {
|
||||
noShuffling = false;
|
||||
throw e;
|
||||
}
|
||||
noShuffling = false;
|
||||
return returnVal;
|
||||
};
|
||||
}
|
||||
Object.defineProperty(window[ElementClass].prototype, attr, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
function fixCrossWindowLocalStorage() {
|
||||
// completely replace hammerhead's implementation as restore() and save() on every
|
||||
// call is just not viable (mainly memory issues as the garbage collector is sometimes not fast enough)
|
||||
|
||||
const prefix = `rammerhead|storage-wrapper|${hammerhead.settings._settings.sessionId}|${
|
||||
window.__get$(window, 'location').host
|
||||
}|`;
|
||||
const toRealStorageKey = (key = '') => prefix + key;
|
||||
const fromRealStorageKey = (key = '') => {
|
||||
if (!key.startsWith(prefix)) return null;
|
||||
return key.slice(prefix.length);
|
||||
};
|
||||
|
||||
const replaceStorageInstance = (storageProp, realStorage) => {
|
||||
const reservedProps = ['internal', 'clear', 'key', 'getItem', 'setItem', 'removeItem', 'length'];
|
||||
Object.defineProperty(window, storageProp, {
|
||||
// define a value-based instead of getter-based property, since with this localStorage implementation,
|
||||
// we don't need to rely on sharing a single memory-based storage across frames, unlike hammerhead
|
||||
configurable: true,
|
||||
writable: true,
|
||||
// still use window[storageProp] as basis to allow scripts to access localStorage.internal
|
||||
value: new Proxy(window[storageProp], {
|
||||
get(target, prop, receiver) {
|
||||
if (reservedProps.includes(prop) && prop !== 'length') {
|
||||
return Reflect.get(target, prop, receiver);
|
||||
} else if (prop === 'length') {
|
||||
let len = 0;
|
||||
for (const [key] of Object.entries(realStorage)) {
|
||||
if (fromRealStorageKey(key)) len++;
|
||||
}
|
||||
return len;
|
||||
} else {
|
||||
return realStorage[toRealStorageKey(prop)];
|
||||
}
|
||||
},
|
||||
set(_, prop, value) {
|
||||
if (!reservedProps.includes(prop)) {
|
||||
realStorage[toRealStorageKey(prop)] = value;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
deleteProperty(_, prop) {
|
||||
delete realStorage[toRealStorageKey(prop)];
|
||||
return true;
|
||||
},
|
||||
has(target, prop) {
|
||||
return toRealStorageKey(prop) in realStorage || prop in target;
|
||||
},
|
||||
ownKeys() {
|
||||
const list = [];
|
||||
for (const [key] of Object.entries(realStorage)) {
|
||||
const proxyKey = fromRealStorageKey(key);
|
||||
if (proxyKey && !reservedProps.includes(proxyKey)) list.push(proxyKey);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
getOwnPropertyDescriptor(_, prop) {
|
||||
return Object.getOwnPropertyDescriptor(realStorage, toRealStorageKey(prop));
|
||||
},
|
||||
defineProperty(_, prop, desc) {
|
||||
if (!reservedProps.includes(prop)) {
|
||||
Object.defineProperty(realStorage, toRealStorageKey(prop), desc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
const rewriteFunction = (prop, newFunc) => {
|
||||
Storage.prototype[prop] = new Proxy(Storage.prototype[prop], {
|
||||
apply(_, thisArg, args) {
|
||||
return newFunc.apply(thisArg, args);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
replaceStorageInstance('localStorage', hammerhead.storages.localStorageProxy.internal.nativeStorage);
|
||||
replaceStorageInstance('sessionStorage', hammerhead.storages.sessionStorageProxy.internal.nativeStorage);
|
||||
rewriteFunction('clear', function () {
|
||||
for (const [key] of Object.entries(this)) {
|
||||
delete this[key];
|
||||
}
|
||||
});
|
||||
rewriteFunction('key', function (keyNum) {
|
||||
return (Object.entries(this)[keyNum] || [])[0] || null;
|
||||
});
|
||||
rewriteFunction('getItem', function (key) {
|
||||
return this.internal.nativeStorage[toRealStorageKey(key)] || null;
|
||||
});
|
||||
rewriteFunction('setItem', function (key, value) {
|
||||
if (key) {
|
||||
this.internal.nativeStorage[toRealStorageKey(key)] = value;
|
||||
}
|
||||
});
|
||||
rewriteFunction('removeItem', function (key) {
|
||||
delete this.internal.nativeStorage[toRealStorageKey(key)];
|
||||
});
|
||||
}
|
||||
|
||||
function hookHammerheadStartOnce(callback) {
|
||||
var originalStart = hammerhead.__proto__.start;
|
||||
hammerhead.__proto__.start = function () {
|
||||
originalStart.apply(this, arguments);
|
||||
hammerhead.__proto__.start = originalStart;
|
||||
callback();
|
||||
};
|
||||
}
|
||||
})();
|
||||
Loading…
Add table
Reference in a new issue