Holy-Unblocker/lib/js.js
2021-11-29 23:40:38 -08:00

189 lines
8.6 KiB
JavaScript

// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------
const { parse } = require('acorn-hammerhead');
const { generate } = require('./esotope');
class JSRewriter {
constructor(ctx) {
this.parseOptions = {
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
ecmaVersion: 2021,
};
this.generationOptions = {
format: {
quotes: 'double',
escapeless: true,
compact: true,
},
};
this.rewrite = ['location', 'parent', 'top'];
this.map = [
{
type: 'MemberExpression',
handler: (node, parent) => {
let rewrite = false;
if (parent.type == 'UnaryExpression' && parent.operator == 'delete') return;
if (parent.type == 'NewExpression' && parent.callee == node) return;
if (parent.type === 'CallExpression' && parent.callee === node) return;
if (node.preventRewrite) return;
switch(node.property.type) {
case 'Identifier':
//if (node.computed) rewrite = true;
if (!node.computed && this.rewrite.includes(node.property.name)) {
node.property = this.createLiteral(node.property.name);
rewrite = true;
};
break;
case 'Literal':
if (this.rewrite.includes(node.property.name)) rewrite = true;
break;
case 'TemplateLiteral':
rewrite = true;
break;
default:
if (node.computed) rewrite = true;
};
if (rewrite) {
let identifier = '$corrosionGet$m';
let nodeToRewrite = node;
const args = [
node.object,
node.property,
];
if (node.computed) args[1].preventRewrite = true;
if (parent.type == 'AssignmentExpression' && parent.left == node) {
identifier = '$corrosionSet$m';
nodeToRewrite = parent;
args.push(parent.right, this.createLiteral(parent.operator));
};
if (parent.type == 'CallExpression' && parent.callee == node) {
identifier = '$corrosionCall$m';
nodeToRewrite = parent;
args.push(this.createArrayExpression(...parent.arguments))
};
if (parent.type == 'UpdateExpression') {
identifier = '$corrosionSet$m';
nodeToRewrite = parent;
args.push(this.createLiteral(null), this.createLiteral(parent.operator));
};
Object.assign(nodeToRewrite, this.createCallExpression({ type: 'Identifier', name: identifier, }, args));
};
},
},
{
type: 'Identifier',
handler: (node, parent) => {
if (parent.type == 'MemberExpression' && parent.property == node) return; // window.location;
if (parent.type == 'LabeledStatement') return; // { location: null, };
if (parent.type == 'VariableDeclarator' && parent.id == node) return;
if (parent.type == 'Property' && parent.key == node) return;
if (parent.type == 'MethodDefinition') return;
if (parent.type == 'ClassDeclaration') return;
if (parent.type == 'RestElement') return;
if (parent.type == 'ExportSpecifier') return;
if (parent.type == 'ImportSpecifier') return;
if ((parent.type == 'FunctionDeclaration' || parent.type == 'FunctionExpression' || parent.type == 'ArrowFunctionExpression') && parent.params.includes(node)) return;
if ((parent.type == 'FunctionDeclaration' || parent.type == 'FunctionExpression') && parent.id == node) return;
if (parent.type == 'AssignmentPattern' && parent.left == node) return;
if (!this.rewrite.includes(node.name)) return;
if (node.preventRewrite) return;
let identifier = '$corrosionGet$';
let nodeToRewrite = node;
const args = [
this.createIdentifier(node.name, true),
];
if (parent.type == 'AssignmentExpression' && parent.left == node) {
identifier = '$corrosionSet$';
nodeToRewrite = parent;
args.push(parent.right);
args.push(this.createLiteral(parent.operator));
};
Object.assign(nodeToRewrite, this.createCallExpression({ type: 'Identifier', name: identifier }, args));
},
},
{
type: 'ImportDeclaration',
handler: (node, parent, url) => {
if (node.source.type != 'Literal' || !url) return;
node.source = this.createLiteral(ctx.url.wrap(node.source.value, { base: url, }));
},
},
{
type: 'ImportExpression',
handler: (node, parent) => {
node.source = this.createCallExpression(this.createMemberExpression(this.createMemberExpression(this.createIdentifier('$corrosion'), this.createIdentifier('url')), this.createIdentifier('wrap')), [
node.source,
this.createMemberExpression(this.createIdentifier('$corrosion'), this.createIdentifier('meta')),
]);
},
},
];
this.ctx = ctx;
};
process(source, url) {
try {
const ast = parse(source, this.parseOptions);
this.iterate(ast, (node, parent) => {
const fn = this.map.find(entry => entry.type == (node || {}).type);
if (fn) fn.handler(node, parent, url);
});
return (url ? this.createHead(url) : '') + generate(ast, this.generationOptions);
} catch(e) {
return source;
};
};
createHead(url) {
return `
if (!self.$corrosion && self.importScripts) {
importScripts(location.origin + '${this.ctx.prefix}index.js');
self.$corrosion = new Corrosion({ url: '${url}', codec: '${this.ctx.config.codec || 'plain'}', serviceWorker: true, window: self, prefix: '${this.ctx.prefix || '/service/'}', ws: ${this.ctx.config.ws || true}, cookies: ${this.ctx.config.cookies || false}, title: '${this.ctx.config.title}', }); $corrosion.init();
};\n`;
};
iterate(ast, handler) {
if (typeof ast != 'object' || !handler) return;
walk(ast, null, handler);
function walk(node, parent, handler) {
if (typeof node != 'object' || !handler) return;
handler(node, parent, handler);
for (const child in node) {
if (Array.isArray(node[child])) {
node[child].forEach(entry => walk(entry, node, handler));
} else {
walk(node[child], node, handler);
};
};
};
};
createCallExpression(callee, args) {
return { type: 'CallExpression', callee, arguments: args, optional: false, };
};
createArrayExpression(...elements) {
return {
type: 'ArrayExpression',
elements,
};
};
createMemberExpression(object, property) {
return {
type: 'MemberExpression',
object,
property,
};
};
createLiteral(value) {
return {
type: 'Literal',
value,
}
};
createIdentifier(name, preventRewrite) {
return { type: 'Identifier', name, preventRewrite: preventRewrite || false, };
};
};
module.exports = JSRewriter;