UNPKG

phantom

Version:

PhantomJS integration module for NodeJS

294 lines (257 loc) 7.68 kB
/* eslint-disable no-param-reassign, import/no-unresolved, import/no-extraneous-dependencies, import/extensions */ import webpage from 'webpage'; import system from 'system'; /** * Stores all all pages and single instance of phantom */ const objectSpace = { phantom, }; const events = {}; const NOOP = 'NOOP'; /** * Looks for transform key and uses objectSpace to call objects * @param object */ function transform(object) { // eslint-disable-next-line no-restricted-syntax for (const key in object) { // eslint-disable-next-line no-prototype-builtins if (object.hasOwnProperty(key)) { const child = object[key]; if (child === null || child === undefined) { return; } if (child.transform === true) { object[key] = objectSpace[child.parent][child.method](child.target); } else if (typeof child === 'object') { transform(child); } } } } /** * Completes a command by return a response to node and listening again for next command. * @param command */ function completeCommand(command) { system.stdout.writeLine(`>${JSON.stringify(command)}`); } /** * Sync all OutObjects present in the array * * @param objects */ function syncOutObjects(objects) { objects.forEach((param) => { if (param.target !== undefined) { objectSpace[param.target] = param; } }); } /** * Determines a targets type using its id * * @param target * @returns {*} */ function getTargetType(target) { return target.toString().split('$')[0]; } /** * Verifies if an event is supported for a type of target * * @param type * @param eventName * @returns {boolean} */ function isEventSupported(type, eventName) { return type === 'page' && eventName.indexOf('on') === 0; } /** * Returns a function that will notify to node that an event have been triggered * * @param eventName * @param targetId * @returns {Function} */ function getOutsideListener(eventName, targetId) { return (...args) => { system.stdout.writeLine(`<event>${JSON.stringify({ target: targetId, type: eventName, args })}`); }; } /** * Executes all the listeners for an event from a target * * @param target * @param eventName */ function triggerEvent(target, eventName, ...args) { const listeners = events[target][eventName]; listeners.outsideListener.apply(null, args); listeners.otherListeners.forEach((listener) => { listener.apply(objectSpace[target], args); }); } /** * Gets an object containing all the listeners for an event of a target * * @param target the target id * @param eventName the event name */ function getEventListeners(target, eventName) { if (!events[target]) { events[target] = {}; } if (!events[target][eventName]) { events[target][eventName] = { outsideListener: getOutsideListener(eventName, target), otherListeners: [], }; objectSpace[target][eventName] = triggerEvent.bind(null, target, eventName); } return events[target][eventName]; } /** * All commands that have a custom implementation */ const commands = { createPage: (command) => { const page = webpage.create(); objectSpace[`page$${command.id}`] = page; page.onClosing = () => delete objectSpace[`page$${command.id}`]; command.response = { pageId: command.id }; completeCommand(command); }, property: (command) => { if (command.params.length > 1) { if (typeof command.params[1] === 'function') { // If the second parameter is a function then we want to proxy and pass parameters too const callback = command.params[1]; const otherArgs = command.params.slice(2); syncOutObjects(otherArgs); // eslint-disable-next-line objectSpace[command.target][command.params[0]] = (...args) => callback.apply(objectSpace[command.target], args.concat(otherArgs)); } else { // If the second parameter is not a function then just assign const { target, params: [name, value] } = command; objectSpace[target][name] = value; } } else { command.response = objectSpace[command.target][command.params[0]]; } completeCommand(command); }, setting: (command) => { if (command.params.length === 2) { const { target, params: [name, value] } = command; objectSpace[target].settings[name] = value; } else { command.response = objectSpace[command.target].settings[command.params[0]]; } completeCommand(command); }, windowProperty: (command) => { if (command.params.length === 2) { const { params: [name, value] } = command; window[name] = value; } else { command.response = window[command.params[0]]; } completeCommand(command); }, addEvent: (command) => { const type = getTargetType(command.target); if (isEventSupported(type, command.params[0].type)) { const listeners = getEventListeners(command.target, command.params[0].type); if (typeof command.params[0].event === 'function') { listeners.otherListeners.push((...args) => { const params = args.concat(command.params[0].args); return command.params[0].event.apply(objectSpace[command.target], params); }); } } completeCommand(command); }, removeEvent(command) { const type = getTargetType(command.target); if (isEventSupported(type, command.params[0].type)) { events[command.target][command.params[0].type] = null; objectSpace[command.target][command.params[0].type] = null; } completeCommand(command); }, noop: command => completeCommand(command), invokeAsyncMethod(command) { const target = objectSpace[command.target]; target[command.params[0]](...command.params.slice(1).concat((result) => { command.response = result; completeCommand(command); })); }, invokeMethod(command) { const target = objectSpace[command.target]; const method = target[command.params[0]]; command.response = method.apply(target, command.params.slice(1)); completeCommand(command); }, defineMethod(command) { const target = objectSpace[command.target]; const { params: [name, value] } = command; target[name] = value; completeCommand(command); }, }; /** * Executes a command. * @param command the command to execute */ function executeCommand(command) { if (commands[command.name]) { return commands[command.name](command); } throw new Error(`'${command.name}' isn't a command.`); } /** * Calls readLine() and blocks until a message is ready */ function read() { const line = system.stdin.readLine(); if (line) { if (line === NOOP) { system.stdout.writeLine(`>${NOOP}`); setTimeout(read, 100); return; } const command = JSON.parse(line, (key, value) => { if ( value && typeof value === 'string' && value.substr(0, 8) === 'function' && value.indexOf('[native code]') === -1 ) { const startBody = value.indexOf('{') + 1; const endBody = value.lastIndexOf('}'); const startArgs = value.indexOf('(') + 1; const endArgs = value.indexOf(')'); // eslint-disable-next-line no-new-func return new Function( value.substring(startArgs, endArgs), value.substring(startBody, endBody), ); } return value; }); // Call here to look for transform key transform(command.params); try { executeCommand(command); } catch (e) { command.error = e.message; completeCommand(command); } finally { setTimeout(read, 0); } } } read();