Hopefully this isn't against any Milovana rules, but here's an example userscript (Only tested with
Violentmonkey extension) that will inject an EOS module:
I'm a little hesitant to release this in fear of the privacy and security pandora's box I've just opened. Needless to say, people should never install userscripts from untrusted sources!
That said, the Content Security Policy for eosscript.com is locked down pretty well, so it's a pain to get any XMLHttpRequest outside of eosscript.com. I resorted to using the parent window to send XMLHttpRequest via postMessage. It's a bit convoluted, but it kinda works. Also, EOS doesn't handle the asynchronous mode of the jsInterpreter very well, so it's a pain to get results back from an XMLHttpRequest.
Code: Select all
// ==UserScript==
// @name EOS Module Injector Test
// @namespace https://eosscript.com/eos_module_injector_test
// @description EOS Module Injector Test
// @version 1.2
// @include https://eosscript.com/*
// @include https://milovana.com/webteases/*
// @include https://milovana.com/eos/*
// @grant GM_addScript
// @grant GM_xmlhttpRequest
// @run-at document-idle
// ==/UserScript==
/***************************
* READ ME:
* Make sure you change @name, @namespace, @description and var name to something unique to you!
*
* Make sure you keep your module names namespace unique as well. Don't use 'injectorTest'!!!
*
*/
// Module specific code. Just about everything here should be changed for your specific use case!
var name = 'EOS Module Injector Test';
var DEBUG = true;
var ipAddressLastResult = false;
var modules = {
injectorTest: {
queryIpAddress: function (val) {
// Dump what we got
console.warn('Injector Test Got Value:', val, arguments);
ipAddressLastResult = false; // clear our last result
// Return something we shouldn't have access to in EOS as a test
// Since it's a XMLHttpRequest, we'll need to run it in our parent window's scope:
runParentAction('queryIpAddress'); // Send signal to parent action requesting client ip address
// Note: we'll get an "Unknown rpc method" error on the console. No way around this, but it's benign.
},
getLastIpQuery: function (val) {
return ipAddressLastResult;
},
},
// More modules ...
}
// Actions that will run on child (eosscripts.com, iframe)
var childActions = {
returnIpAddress: function (ip) {
console.log('Got client IP address', ip);
ipAddressLastResult = ip;
// Send signal to EOS tease that we got an updated IP address
dispatchEvent('injectorTest', 'ip');
},
// More childActions ...
}
// Actions that will run on parent (milovana.com, parent window)
var parentActions = {
queryIpAddress: function () {
// Use parent window to run XMLHttpRequest, since our iframe doesn't have access
var xhr = new XMLHttpRequest();
xhr.open("GET", 'https://www.cloudflare.com/cdn-cgi/trace');
xhr.onreadystatechange = function () {
// In local files, status is 0 upon success in Mozilla Firefox
if(xhr.readyState === XMLHttpRequest.DONE) {
var status = xhr.status;
var result = false;
if (status === 0 || (status >= 200 && status < 400)) {
// The request has been completed successfully
result = (xhr.responseText || '').match(/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ );
if (result) result = result[0];
console.log('queryIpAddress result', xhr, xhr.responseText);
} else {
console.error('queryIpAddress error', xhr);
// Oh no! There has been an error with the request!
result = 'unable to obtain';
}
// Send result to EOS iframe
runChildAction('returnIpAddress', result);
}
};
xhr.send();
},
// More parentActions ...
}
// Common Module Injector Operations
// Shouldn't need to change anything below here
if (DEBUG) console.warn('Installing ' + name);
var interpreter;
var host;
var hooked = false;
var protos = {};
if (window.location.host.match(/eosscript\.com/)) {
// EOS Script site
// Hook console.info as a trigger for when Interpreter prototype is ready.
// TODO: find a better way to detect that
var origInfo = console.info;
var origInterpreterRun;
console.info = function () {
var a = arguments[0];
if (DEBUG) console.log('Hooking console.info', arguments);
if (!hooked && typeof a === 'string' && a.match(/^loaded module:/)) {
console.log('Interpreter prototype', Interpreter, window.Interpreter, window.Interpreter.prototype);
origInterpreterRun = window.Interpreter.prototype.run;
// Hook JS Interpreter's run method
window.Interpreter.prototype.run = function () {
// Wait until PageManager property exists so we know EOS has added its methods
if (!this['_RUN_START_'+name] && this.globalObject.properties.PageManager) {
// Now inject our methods into the Interpreter
this['_RUN_START_'+name] = true; // And make sure we don't do it again.
if (DEBUG) console.log('Intercepting Interpereter Run', this, arguments);
console.log('Installing ' + name + ' modules...');
interpreter = this;
addObjectToInterpreter(interpreter.globalObject, modules);
}
return origInterpreterRun.apply(this, arguments);
}
hooked = true; // Don't restore original hook, or we will break other EOS Injectors
}
return origInfo.apply(console, arguments);
}
window.addEventListener("message", function (e) {
if (e.data && e.data.source === name && childActions[e.data.action]) {
if (DEBUG) console.log('Running child action', e.data.action, e.data);
childActions[e.data.action].apply(this, e.data.values || [])
}
}, false);
} else {
// Milovana.com?
window.addEventListener("message", function (e) {
if (e.data && e.data.source === name && parentActions[e.data.action]) {
if (DEBUG) console.log('Running parent action', e.data.action, e.data);
parentActions[e.data.action].apply(this, e.data.values || [])
}
}, false);
}
function dispatchEvent(target, type) {
if (!host) {
host = document.getElementById('eosContainer')._reactRootContainer._internalRoot.current.child.stateNode.props.host
}
if (!protos[target]) {
console.error('Unable to dispatchEvent. No known proto:', target);
return;
}
host.virtualMachine.dispatchEvent({ target: protos[target], type: type });
}
// Add defined modules / properties / functions to interpereter
function addObjectToInterpreter (base, obj) {
for (var i in obj) {
if (base[i] !== undefined || (base.properties && base.properties[i] !== undefined)) {
console.error('Property `'+i+'` already exists in object. Unable to add 3rd party module/property', i, base, base.properties);
continue;
}
var el = obj[i];
if (typeof el === 'object') {
var protoName = i.charAt(0).toUpperCase() + i.slice(1);
if (base[protoName] !== undefined || (base.properties && base.properties[protoName] !== undefined)) {
console.error('Prototype `'+protoName+'` already exists in object. Unable to add 3rd party module/property', i, protoName, base, base.properties);
continue;
}
// var container = interpreter.nativeToPseudo({});
// interpreter.setProperty(base, i, container);
var constructor = function () {
throw new Error('Cannot construct ' + protoName + ' object, use `' + i + '` global')
}
var constructorf = interpreter.createNativeFunction(constructor, true)
interpreter.setProperty(
constructorf,
'prototype',
interpreter.createObject(interpreter.globalObject.properties['EventTarget']),
Interpreter.NONENUMERABLE_DESCRIPTOR
)
addObjectToInterpreter(constructorf, el);
var protof = constructorf.properties['prototype'];
interpreter.setProperty(interpreter.globalObject, protoName, constructorf);
var proto = interpreter.createObjectProto(protof);
protos[protoName] = proto;
protos[i] = proto;
interpreter.setProperty(base, i, proto);
if (base === interpreter.globalObject) console.log('Loaded 3rd party module:', protoName);
} else if (typeof el === 'function') {
if (base === interpreter.globalObject) {
interpreter.setProperty(base, i, interpreter.createNativeFunction(el));
} else {
interpreter.setNativeFunctionPrototype(base, i, el);
}
} else {
interpreter.setProperty(base, i, el);
}
}
}
function runChildAction(action) {
if (DEBUG) console.log('Requesting child action', action, arguments);
if (!childActions[action]) {
console.error('No child action defined for:', action);
return;
}
var values = [];
for (var i = 1, l = arguments.length; i < l; i++) {
values.push(arguments[i]);
}
var iframes = document.getElementsByClassName('eosIframe');
var message = {
source: name,
action: action,
values: values
};
for (var i = 0, l = iframes.length; i < l; i++) {
iframes[i].contentWindow.postMessage(message, '*');
}
}
function runParentAction(action) {
if (DEBUG) console.log('Requesting parent action', action, arguments);
if (!parentActions[action]) {
console.error('No parent action defined for:', action);
return;
}
var values = [];
for (i = 1, l = arguments.length; i < l; i++) {
values.push(arguments[i]);
}
window.parent.postMessage({
source: name,
action: action,
values: values
}, '*');
}
Zipped version of script:
And here's an EOS tease that uses it:
https://milovana.com/webteases/showteas ... 76d5c35028
And the JSON for it:
https://milovana.com/webteases/geteossc ... 76d5c35028