├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── index.js ├── lib ├── api.base.js ├── api.js ├── api.menu.js ├── api.session.js ├── api.window.js ├── common.js └── thrust.js ├── package.json ├── scripts └── bootstrap.js └── test ├── base.js ├── bugs ├── bug_217.html ├── bug_217.js ├── bug_218.html ├── bug_218.js ├── bug_220.html └── bug_220.js ├── menu_multiple_windows.js ├── menu_popup.js ├── menu_set_application_menu.js ├── session_proxy.js ├── webview_base.html ├── webview_base.js ├── webview_devtools.html ├── webview_devtools.js ├── webview_dialog.html ├── webview_dialog.js ├── window_devtools.js ├── window_fileinput.js ├── window_frameless.html ├── window_frameless.js ├── window_fullscreen.js ├── window_icon.js ├── window_icon.png ├── window_kiosk.js ├── window_remote.html ├── window_remote.js ├── window_show.js ├── window_websecurity.html └── window_websecurity.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | node_modules 3 | vendor 4 | dummy_session 5 | npm-debug.log 6 | bower_components -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Stanislas Polu. All rights reserved. 2 | 3 | The MIT License (MIT) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-thrust 2 | =========== 3 | 4 | Official NodeJS bindings library for [Thrust](http://github.com/breach/thrust) 5 | 6 | ### Getting started 7 | 8 | ``` 9 | npm install node-thrust 10 | ``` 11 | At install, node-thrust `post_install` script automatically downloads a binary 12 | distribution of Thrust for the current platform. 13 | 14 | ``` 15 | require('node-thrust')(function(err, api) { 16 | api.window({ root_url: 'https://breach.cc' }).show(); 17 | }); 18 | ``` 19 | 20 | ### Status 21 | 22 | Supports the complete Thrust API. Works on Linux, MacOSX, Windows. 23 | 24 | ### Documentation 25 | 26 | Pending specific node-thrust documentation, full API reference is available 27 | in the [Thrust Documentation](https://github.com/breach/thrust/tree/master/docs) 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-thrust", 3 | "authors": [ 4 | "Stanislas Polu" 5 | ], 6 | "version": "0.7.5", 7 | "description": "Binding library for Thrust", 8 | "keywords": [ 9 | "thrust", 10 | "chrome", 11 | "framework", 12 | "application", 13 | "cross-platform" 14 | ], 15 | "main": "./index.js", 16 | "license": "MIT", 17 | "homepage": "https://github.com/breach/node-thrust.git", 18 | "dependencies": { 19 | "node-mkdirp": "git://github.com/substack/node-mkdirp.git#~0.5.0", 20 | "async": "~0.9.0", 21 | "node-optimist": "git://github.com/substack/node-optimist.git#~0.6.1", 22 | "node-fs-extra": "git://github.com/jprichardson/node-fs-extra.git#~0.12.0", 23 | "request": "git://github.com/request/request.git#~2.48.1", 24 | "minimist": "git://github.com/substack/minimist.git#~1.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-thrust: index.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-10-09 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | module.exports = require('./lib/thrust.js').thrust({}).spawn; 14 | -------------------------------------------------------------------------------- /lib/api.base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-thrust: api.base.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-10-23 spolu Destroy accessor 10 | * - 2014-10-14 spolu Destroy method & API registration 11 | * - 2014-10-10 spolu Creation 12 | */ 13 | "use strict" 14 | 15 | var common = require('./common.js'); 16 | 17 | var events = require('events'); 18 | 19 | // ## base 20 | // 21 | // Base object representation. Implements creation and base method call as well 22 | // as event emition and remote method call functionalities. 23 | // 24 | // ``` 25 | // @spec { api, type, args } 26 | // @inherits events.EventEmitter 27 | // ``` 28 | var base = function(spec, my) { 29 | var _super = {}; 30 | my = my || {}; 31 | spec = spec || {}; 32 | 33 | my.type = spec.type || 'INVALID_TYPE'; 34 | my.api = spec.api; 35 | 36 | my.target = null; 37 | my.pre_error = null; 38 | my.destroyed = false; 39 | 40 | // 41 | // #### _public_ 42 | // 43 | var destroy; /* destroy(); */ 44 | 45 | // 46 | // #### _protected_ 47 | // 48 | var pre; /* pre(cb_); */ 49 | var call; /* call(method, args, cb_); */ 50 | var invoke; /* invoke(method, args, cb_); */ 51 | var create; /* create(); */ 52 | 53 | 54 | // 55 | // #### _that_ 56 | // 57 | var that = new events.EventEmitter(); 58 | 59 | /****************************************************************************/ 60 | /* PROTECTED METHODS */ 61 | /****************************************************************************/ 62 | // ### pre 63 | // 64 | // Function called before method invokation to ensure the object is fully 65 | // constructed 66 | // ``` 67 | // @cb_ {function(err)} 68 | // ``` 69 | pre = function(cb_) { 70 | if(my.pre_error) { 71 | return cb_(my.pre_error); 72 | } 73 | else if(!my.target) { 74 | that.on('_ready', function() { 75 | return cb_(); 76 | }); 77 | } 78 | else { 79 | return cb_(); 80 | } 81 | }; 82 | 83 | // ### call 84 | // 85 | // Call a remote method 86 | // ``` 87 | // @method {string} the method name 88 | // @args {object} the argument object 89 | // @cb_ {function(err, res)} the callback function 90 | // ``` 91 | call = function(method, args, cb_) { 92 | cb_ = cb_ || (function(err) { 93 | if(err) { 94 | common.log.error(err); 95 | } 96 | }); 97 | pre(function(err) { 98 | if(err) { 99 | return cb_(err); 100 | } 101 | my.api.perform({ 102 | _id: my.api.action_id(), 103 | _action: "call", 104 | _target: my.target, 105 | _method: method, 106 | _args: args || {} 107 | }, cb_); 108 | }); 109 | }; 110 | 111 | // ### invoke 112 | // 113 | // Invokes a local method on the object if it exists. Any object inheriting 114 | // this base object is supposed to imeplement all remote method. 115 | // ``` 116 | // @method {string} the method name 117 | // @args {object} the arguments object 118 | // @cb_ {function(err, res)} the callback function 119 | // ``` 120 | invoke = function(method, args, cb_) { 121 | pre(function(err) { 122 | if(err) { 123 | return cb_(err); 124 | } 125 | else if(!that['invoke_' + method]) { 126 | return cb_(common.err('Method not found [' + my.type + ']: ' + method, 127 | 'thrust:method_not_found')); 128 | } 129 | else { 130 | return that['invoke_' + method](args, cb_); 131 | } 132 | }); 133 | }; 134 | 135 | // ### create 136 | // 137 | // Creation method called at construction 138 | // ``` 139 | // @args {object} args object passed from spec 140 | // ``` 141 | create = function(args) { 142 | my.api.perform({ 143 | _id: my.api.action_id(), 144 | _action: 'create', 145 | _type: my.type, 146 | _args: spec.args 147 | }, function(err, res) { 148 | if(err) { 149 | my.pre_error = err; 150 | } 151 | else { 152 | my.target = res._target; 153 | my.api.register(that); 154 | that.emit('_ready'); 155 | } 156 | }); 157 | }; 158 | 159 | /****************************************************************************/ 160 | /* PUBLIC METHODS */ 161 | /****************************************************************************/ 162 | // ### destroy 163 | // 164 | // Destroy the objects and unregister from the API. Any subsequent call is 165 | // returned with an error 166 | destroy = function(cb_) { 167 | my.api.perform({ 168 | _id: my.api.action_id(), 169 | _action: 'delete', 170 | _target: my.target 171 | }, function(err, res) { 172 | if(err) { 173 | return cb_(err); 174 | } 175 | my.pre_error = common.err('Object destroyed [' + my.type + ']: ' + 176 | my.target, 'thrust:object_destroyed'); 177 | my.destroyed = true; 178 | my.api.unregister(that); 179 | that.emit('_destroy'); 180 | return cb_(); 181 | }); 182 | }; 183 | 184 | /****************************************************************************/ 185 | /* INITIALIZATION */ 186 | /****************************************************************************/ 187 | process.nextTick(function() { 188 | that.create(spec.args); 189 | }); 190 | 191 | common.method(that, 'pre', pre, _super); 192 | common.method(that, 'call', call, _super); 193 | common.method(that, 'invoke', invoke, _super); 194 | common.method(that, 'create', create, _super); 195 | 196 | common.method(that, 'destroy', destroy, _super); 197 | 198 | common.getter(that, 'target', my, 'target'); 199 | common.getter(that, 'destroyed', my, 'destroyed'); 200 | 201 | return that; 202 | } 203 | 204 | exports.base = base; 205 | -------------------------------------------------------------------------------- /lib/api.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-thrust: api.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-10-21 spolu Codacy fixes 10 | * - 2014-10-09 spolu Creation 11 | * - 2014-10-10 spolu Event support 12 | */ 13 | "use strict" 14 | 15 | var common = require('./common.js'); 16 | 17 | var events = require('events'); 18 | 19 | 20 | // ## api 21 | // 22 | // Object in charge of RPC with the wrapper process as well as exposing the 23 | // api components. 24 | // 25 | // ``` 26 | // @spec { process } 27 | // @inherits events.EventEmitter 28 | // ``` 29 | var api = function(spec, my) { 30 | var _super = {}; 31 | my = my || {}; 32 | spec = spec || {}; 33 | 34 | my.process = spec.process; 35 | 36 | my.BOUNDARY = '--(Foo)++__THRUST_SHELL_BOUNDARY__++(Bar)--'; 37 | my.ACTION_TIMEOUT = 500; 38 | my.action_id = 0; 39 | 40 | my.actions = {}; 41 | my.objects = {} 42 | my.acc = ''; 43 | 44 | // 45 | // #### _public_ 46 | // 47 | var init; /* init(cb_); */ 48 | 49 | // 50 | // #### _protected_ 51 | // 52 | var action_id; /* action_id(); */ 53 | var perform; /* peform(action, cb_); */ 54 | var register; /* register(object); */ 55 | var unregister; /* unregister(object); */ 56 | 57 | // 58 | // #### _private_ 59 | // 60 | var stdout_handler; /* stdout_handler(data); */ 61 | 62 | // 63 | // #### _that_ 64 | // 65 | var that = new events.EventEmitter(); 66 | 67 | /****************************************************************************/ 68 | /* PRIVATE HELPERS */ 69 | /****************************************************************************/ 70 | // ### stdout_handler 71 | // 72 | // Handles data coming from the shell to the client library 73 | // ``` 74 | // @chunk {Buffer} the incoming chunk 75 | // ``` 76 | stdout_handler = function(chunk) { 77 | my.acc += chunk; 78 | var splits = my.acc.split(my.BOUNDARY); 79 | while(splits.length > 1) { 80 | var data = splits.shift(); 81 | my.acc = splits.join(my.BOUNDARY); 82 | if(data && data.length > 0) { 83 | try { 84 | var action = JSON.parse(data); 85 | if(action._action === 'reply' && 86 | my.actions[action._id.toString()]) { 87 | /* my.actions is cleaned up by calling the callback. */ 88 | var cb_ = my.actions[action._id.toString()]; 89 | if(action._error) { 90 | cb_(common.err(action._error, 91 | 'thrust:shell_error')); 92 | } 93 | cb_(null, action._result); 94 | } 95 | else if(action._action === 'event' && 96 | my.objects[action._target.toString()]) { 97 | my.objects[action._target.toString()].emit(action._type, 98 | action._event); 99 | } 100 | else if(action._action === 'invoke' && 101 | my.objects[action._target.toString()]) { 102 | (function(id) { 103 | my.objects[action._target.toString()].invoke(action._method, 104 | action._args, 105 | function(err, res) { 106 | perform({ 107 | _id: id, 108 | _action: 'reply', 109 | _error: err ? err.message : '', 110 | _result: res 111 | }); 112 | }); 113 | }(action._id)); 114 | } 115 | else { 116 | /* Log unhandled actions. Exception is catched below. */ 117 | throw common.err('Unhandled action: ' + action._action, 118 | 'thrust:unhandled_action'); 119 | } 120 | } 121 | catch(err) { 122 | common.log.error(err); 123 | common.log.out('========================================='); 124 | common.log.out(data); 125 | common.log.out('========================================='); 126 | } 127 | } 128 | } 129 | }; 130 | 131 | /****************************************************************************/ 132 | /* PROTECTED HELPERS */ 133 | /****************************************************************************/ 134 | // ### action_id 135 | // 136 | // Returns the next action_id 137 | action_id = function() { 138 | return ++my.action_id; 139 | }; 140 | 141 | // ### perform 142 | // 143 | // Performs an action by sending it over the network. If the callback is 144 | // specified, it stores the callback for later execution on action response. 145 | // ``` 146 | // @action {object} a valid action object 147 | // @cb_ {function(err, res)} 148 | // ``` 149 | perform = function(action, cb_) { 150 | if(cb_) { 151 | /* 152 | var itv = setTimeout(function() { 153 | delete my.actions[action._id.toString()]; 154 | return cb_(common.err('Action timed out: ' + action._id, 155 | 'thrust:action_timeout')); 156 | }, my.ACTION_TIMEOUT); 157 | */ 158 | my.actions[action._id.toString()] = function(err, res) { 159 | delete my.actions[action._id.toString()]; 160 | //clearTimeout(itv); 161 | return cb_(err, res); 162 | }; 163 | } 164 | my.process.stdin.write(JSON.stringify(action) + '\n' + my.BOUNDARY + '\n'); 165 | }; 166 | 167 | // ### register 168 | // 169 | // Registers an api object to the API for invocation and event emition 170 | // ``` 171 | // @object {object} api.base object 172 | // ``` 173 | register = function(object) { 174 | my.objects[object.target().toString()] = object; 175 | }; 176 | 177 | // ### unregister 178 | // 179 | // Unregisters an api object from the API 180 | // ``` 181 | // @object {object} api.base object 182 | // ``` 183 | unregister = function(object) { 184 | delete my.objects[object.target().toString()]; 185 | }; 186 | 187 | /****************************************************************************/ 188 | /* PUBLIC METHODS */ 189 | /****************************************************************************/ 190 | // ### init 191 | // 192 | // Initializes the API and opens the JSON RPC channel 193 | // ``` 194 | // @cb_ {function(err, api)} 195 | // ``` 196 | init = function(cb_) { 197 | that.session = function(args) { 198 | return require('./api.session.js').session({ api: that, args: args }); 199 | }; 200 | that.window = function(args) { 201 | return require('./api.window.js').window({ api: that, args: args }); 202 | }; 203 | that.menu = function(args) { 204 | return require('./api.menu.js').menu({ api: that, args: args }); 205 | }; 206 | 207 | my.process.stdout.on('data', stdout_handler); 208 | 209 | return cb_(null, that); 210 | }; 211 | 212 | common.method(that, 'action_id', action_id, _super); 213 | common.method(that, 'perform', perform, _super); 214 | common.method(that, 'register', register, _super); 215 | common.method(that, 'unregister', unregister, _super); 216 | 217 | common.method(that, 'init', init, _super); 218 | 219 | return that; 220 | } 221 | 222 | exports.api = api; 223 | -------------------------------------------------------------------------------- /lib/api.menu.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-thrust: api.menu.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-10-10 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | var common = require('./common.js'); 14 | 15 | // ## menu 16 | // 17 | // API `menu` object reprensetation. 18 | // 19 | // ``` 20 | // @spec { api, args } 21 | // @inherits base 22 | // ``` 23 | var menu = function(spec, my) { 24 | var _super = {}; 25 | my = my || {}; 26 | spec = spec || {}; 27 | 28 | // 29 | // #### _public_ 30 | // 31 | var popup; /* popup(window, cb_); */ 32 | var set_application_menu; /* set_application_menu(cb_); */ 33 | 34 | var add_item; /* add_item(command_id, label, cb_); */ 35 | var add_check_item; /* add_check_item(command_id, label, cb_); */ 36 | var add_radio_item; /* add_radio_item(command_id, label, group_id, cb_); */ 37 | var add_separator; /* add_separator(cb_); */ 38 | 39 | var set_checked; /* set_checked(command_id, value); */ 40 | var set_enabled; /* set_enabled(command_id, value); */ 41 | var set_visible; /* set_visible(command_id, value); */ 42 | 43 | var add_submenu; /* add_submenu(command_id, label, menu, cb_); */ 44 | 45 | // 46 | // #### _private_ 47 | // 48 | 49 | // 50 | // #### _that_ 51 | // 52 | var that = require('./api.base.js').base({ 53 | api: spec.api, 54 | type: 'menu', 55 | args: spec.args 56 | }); 57 | 58 | /****************************************************************************/ 59 | /* PRIVATE HELPERS */ 60 | /****************************************************************************/ 61 | 62 | /****************************************************************************/ 63 | /* PUBLIC METHODS */ 64 | /****************************************************************************/ 65 | // ### popup 66 | // 67 | // Popups the menu to the window passed as argument 68 | // ``` 69 | // @window {object} the window on which to popup the menu 70 | // @cb_ {function(err)} 71 | // ``` 72 | popup = function(window, cb_) { 73 | window.pre(function(err) { 74 | if(err) { 75 | return cb_(err); 76 | } 77 | that.call('popup', { window_id: window.target() }, cb_); 78 | }); 79 | }; 80 | 81 | // ### set_application_menu 82 | // 83 | // Sets the global application menu on OSX 84 | // ``` 85 | // @cb_ {function(err)} 86 | // ``` 87 | set_application_menu = function(cb_) { 88 | that.call('set_application_menu', {}, cb_); 89 | }; 90 | 91 | 92 | // ### add_item 93 | // 94 | // Adds an item with the provided `command_id` to the menu 95 | // ``` 96 | // @command_id {number} the command_id 97 | // @label {string} the label to use 98 | // @cb_ {function(err)} 99 | // ``` 100 | add_item = function(command_id, label, cb_) { 101 | that.call('add_item', { 102 | command_id: command_id, 103 | label: label 104 | }, cb_); 105 | }; 106 | 107 | // ### add_check_item 108 | // 109 | // Adds a check item with the provided `command_id` to the menu 110 | // ``` 111 | // @command_id {number} the command_id 112 | // @label {string} the label to use 113 | // @cb_ {function(err)} 114 | // ``` 115 | add_check_item = function(command_id, label, cb_) { 116 | that.call('add_check_item', { 117 | command_id: command_id, 118 | label: label 119 | }, cb_); 120 | }; 121 | 122 | // ### add_radio_item 123 | // 124 | // Adds a check item with the provided `command_id` to the menu 125 | // ``` 126 | // @command_id {number} the command_id 127 | // @label {string} the label to use 128 | // @group_id {number} the group id 129 | // @cb_ {function(err)} 130 | // ``` 131 | add_radio_item = function(command_id, label, group_id, cb_) { 132 | that.call('add_radio_item', { 133 | command_id: command_id, 134 | label: label, 135 | group_id: group_id 136 | }, cb_); 137 | }; 138 | 139 | // ### add_separator 140 | // 141 | // Adds a separator item to the menu 142 | // ``` 143 | // @cb_ {function(err)} 144 | // ``` 145 | add_separator = function(cb_) { 146 | that.call('add_separator', {}, cb_); 147 | }; 148 | 149 | // ### add_submenu 150 | // 151 | // Adds a sub menu to the menu 152 | // ``` 153 | // @command_id {number} the command_id 154 | // @label {string} the label to use 155 | // @menu {object} the group id 156 | // @cb_ {function(err)} 157 | // ``` 158 | add_submenu = function(command_id, label, menu, cb_) { 159 | menu.pre(function(err) { 160 | if(err) { 161 | return cb_(err); 162 | } 163 | that.call('add_submenu', { 164 | command_id: command_id, 165 | label: label, 166 | menu_id: menu.target() 167 | }, cb_); 168 | }); 169 | }; 170 | 171 | // ### set_checked 172 | // 173 | // Sets whether a command_id is checked or not 174 | // ``` 175 | // @command_id {number} the command_id 176 | // @value {boolean} is checked or not 177 | // @cb_ {function(err)} 178 | // ``` 179 | set_checked = function(command_id, value, cb_) { 180 | that.call('set_checked', { 181 | command_id: command_id, 182 | value: value 183 | }, cb_); 184 | }; 185 | 186 | // ### set_enabled 187 | // 188 | // Sets whether a command_id is enabled or not 189 | // ``` 190 | // @command_id {number} the command_id 191 | // @value {boolean} is enabled or not 192 | // ``` 193 | set_enabled = function(command_id, value, cb_) { 194 | that.call('set_enabled', { 195 | command_id: command_id, 196 | value: value 197 | }, cb_); 198 | }; 199 | 200 | // ### set_visible 201 | // 202 | // Sets whether a command_id is visible or not 203 | // ``` 204 | // @command_id {number} the command_id 205 | // @value {boolean} is visible or not 206 | // ``` 207 | set_visible = function(command_id, value, cb_) { 208 | that.call('set_visible', { 209 | command_id: command_id, 210 | value: value 211 | }, cb_); 212 | }; 213 | 214 | 215 | common.method(that, 'popup', popup, _super); 216 | common.method(that, 'set_application_menu', set_application_menu, _super); 217 | 218 | common.method(that, 'add_item', add_item, _super); 219 | common.method(that, 'add_check_item', add_check_item, _super); 220 | common.method(that, 'add_radio_item', add_radio_item, _super); 221 | common.method(that, 'add_separator', add_separator, _super); 222 | common.method(that, 'add_submenu', add_submenu, _super); 223 | 224 | common.method(that, 'set_checked', set_checked, _super); 225 | common.method(that, 'set_enabled', set_enabled, _super); 226 | common.method(that, 'set_visible', set_visible, _super); 227 | 228 | return that; 229 | } 230 | 231 | exports.menu = menu; 232 | -------------------------------------------------------------------------------- /lib/api.session.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-thrust: api.session.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-10-10 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | var common = require('./common.js'); 14 | 15 | // ## session 16 | // 17 | // API `menu` object reprensetation. 18 | // 19 | // ``` 20 | // @spec { api, args } 21 | // @inherits base 22 | // ``` 23 | var session = function(spec, my) { 24 | var _super = {}; 25 | my = my || {}; 26 | spec = spec || {}; 27 | 28 | // 29 | // #### _public_ 30 | // 31 | var visitedlink_add; /* visitedlink_add(url, cb_); */ 32 | var visitedlink_clear; /* visitedlink_clear(cb_); */ 33 | var proxy_set; /* proxy_set(rules, cb_); */ 34 | var proxy_clear; /* proxy_clear(cb_); */ 35 | 36 | var is_off_the_record; /* is_off_the_record(cb_); */ 37 | 38 | // 39 | // #### _protected_ 40 | // 41 | var invoke_cookies_load; /* invoke_cookies_load(args, cb_); */ 42 | var invoke_cookies_load_for_key; /* invoke_cookies_load_for_key(args, cb_); */ 43 | var invoke_cookies_flush; /* invoke_cookies_flush(args, cb_); */ 44 | 45 | // 46 | // #### _private_ 47 | // 48 | 49 | // 50 | // #### _that_ 51 | // 52 | var that = require('./api.base.js').base({ 53 | api: spec.api, 54 | type: 'session', 55 | args: spec.args 56 | }); 57 | 58 | /****************************************************************************/ 59 | /* PRIVATE HELPERS */ 60 | /****************************************************************************/ 61 | 62 | /****************************************************************************/ 63 | /* REMOTE INVOCATION METHODS */ 64 | /****************************************************************************/ 65 | // ### invoke_cookies_load 66 | // 67 | // Transfer the initial cookies loading to the installed handler or reply with 68 | // an empty array 69 | // ``` 70 | // @args {object} remote method argument 71 | // @cb_ {function(err, res)} 72 | // ``` 73 | invoke_cookies_load = function(args, cb_) { 74 | return cb_(null, { cookies: [] }); 75 | }; 76 | 77 | // ### invoke_cookies_load_for_key 78 | // 79 | // Transfer the cookie loading call for a given domain key or reply with an 80 | // empty array 81 | // ``` 82 | // @args {object} remote method argument 83 | // @cb_ {function(err, res)} 84 | // ``` 85 | invoke_cookies_load_for_key = function(args, cb_) { 86 | return cb_(null, { cookies: [] }); 87 | }; 88 | 89 | // ### invoke_cookies_flush 90 | // 91 | // Transfer the cookie flush invocation the installed handler or return the 92 | // callback directly. 93 | // ``` 94 | // @args {object} remote method argument 95 | // @cb_ {function(err, res)} 96 | // ``` 97 | invoke_cookies_flush = function(args, cb_) { 98 | return cb_(); 99 | } 100 | 101 | /* 102 | that.on('cookies_add', function(evt) { 103 | common.log.out('COOKIES_ADD'); 104 | common.log.out(JSON.stringify(evt, null, 2)); 105 | }); 106 | that.on('cookies_update_access_time', function(evt) { 107 | common.log.out('COOKIES_UPDATE_ACCESS_TIME'); 108 | common.log.out(JSON.stringify(evt, null, 2)); 109 | }); 110 | that.on('cookies_delete', function(evt) { 111 | common.log.out('COOKIES_DELETE'); 112 | common.log.out(JSON.stringify(evt, null, 2)); 113 | }); 114 | */ 115 | 116 | /****************************************************************************/ 117 | /* PUBLIC METHODS */ 118 | /****************************************************************************/ 119 | // ### visitedlink_add 120 | // 121 | // Adds a link to the list of visitedlinks 122 | // ``` 123 | // @url {string} 124 | // @cb_ {function(err)} 125 | // ``` 126 | visitedlink_add = function(url, cb_) { 127 | that.call('visitedlink_add', { url: url }, cb_); 128 | }; 129 | 130 | // ### visitedlink_clear 131 | // 132 | // Clears the list of visitedlink 133 | // ``` 134 | // @cb_ {function(err)} 135 | // ``` 136 | visitedlink_clear = function(cb_) { 137 | that.call('visitedlink_clear', {}, cb_); 138 | }; 139 | 140 | // ### proxy_set 141 | // 142 | // Sets a proxy-rules string for this session to replace the default system 143 | // settings. 144 | // 145 | // ``` 146 | // @rules {string} 147 | // @cb_ {function(err)} 148 | // ``` 149 | proxy_set = function(rules, cb_) { 150 | that.call('proxy_set', { rules: rules }, cb_); 151 | }; 152 | 153 | // ### proxy_clear 154 | // 155 | // Sets a proxy-rules string for this session to replace the default system 156 | // settings. 157 | // 158 | // ``` 159 | // @cb_ {function(err)} 160 | // ``` 161 | proxy_clear = function(cb_) { 162 | that.call('proxy_clear', {}, cb_); 163 | }; 164 | 165 | // ### is_off_the_record 166 | // 167 | // Returns wether this session is off the record or not 168 | // 169 | // ``` 170 | // @cb_ {function(err, is_off_the_record)} 171 | // ``` 172 | is_off_the_record = function(cb_) { 173 | that.call('is_off_the_record', {}, function(err, res) { 174 | return cb_(err, res ? res.off_the_record : null); 175 | }); 176 | }; 177 | 178 | 179 | common.method(that, 'invoke_cookies_load', invoke_cookies_load, _super); 180 | common.method(that, 'invoke_cookies_load_for_key', invoke_cookies_load_for_key, _super); 181 | common.method(that, 'invoke_cookies_flush', invoke_cookies_flush, _super); 182 | 183 | common.method(that, 'visitedlink_add', visitedlink_add, _super); 184 | common.method(that, 'visitedlink_clear', visitedlink_clear, _super); 185 | common.method(that, 'proxy_set', proxy_set, _super); 186 | common.method(that, 'proxy_clear', proxy_clear, _super); 187 | 188 | common.method(that, 'is_off_the_record', is_off_the_record, _super); 189 | 190 | return that; 191 | } 192 | 193 | exports.session = session; 194 | -------------------------------------------------------------------------------- /lib/api.window.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-thrust: api.window.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-11-06 spolu DevTools API 10 | * - 2014-10-23 spolu Methods and accessors 11 | * - 2014-10-14 spolu Renaming `shell` -> `window` 12 | * - 2014-10-10 spolu Creation 13 | */ 14 | "use strict" 15 | 16 | var common = require('./common.js'); 17 | 18 | // ## window 19 | // 20 | // API `window` object reprensetation. 21 | // 22 | // Arguments: 23 | // - `root_url` : the url to open 24 | // - `title` : the title to use for the window 25 | // - `session` : the session to use 26 | // - `size` : { width, height } size object 27 | // 28 | // ``` 29 | // @spec { api, args } 30 | // @inherits base 31 | // ``` 32 | var window = function(spec, my) { 33 | var _super = {}; 34 | my = my || {}; 35 | spec = spec || {}; 36 | 37 | // 38 | // #### _public_ 39 | // 40 | var show; /* show(cb_); */ 41 | var focus; /* focus(cb_); */ 42 | var maximize; /* maximize(cb_); */ 43 | var unmaximize; /* unmaximize(cb_); */ 44 | var minimize; /* minimize(cb_); */ 45 | var restore; /* restore(cb_); */ 46 | var close; /* close(cb_); */ 47 | var remote; /* remote(message, cb_); */ 48 | 49 | var set_title; /* set_title(title, cb_); */ 50 | var set_fullscreen; /* set_fullscreen(fullscreen, cb_); */ 51 | var set_kiosk; /* set_kiosk(kiosk, cb_); */ 52 | var open_devtools; /* open_devtools(cb_); */ 53 | var close_devtools; /* close_devtools(cb_); */ 54 | var move; /* move(x, y, cb_); */ 55 | var resize; /* resize(w, h, cb_); */ 56 | 57 | var is_closed; /* is_closed(cb_); */ 58 | var is_maximized; /* is_maximized(cb_); */ 59 | var is_minimized; /* is_minimized(cb_); */ 60 | var is_fullscreen; /* is_fullscreen(cb_); */ 61 | var is_kiosk; /* is_kiosk(cb_); */ 62 | var is_devtools_opened; /* is_devtools_opened(cb_); */ 63 | 64 | var size; /* size(cb_); */ 65 | var position; /* position(cb_); */ 66 | 67 | // 68 | // #### _protected_ 69 | // 70 | var create; /* create(); */ 71 | 72 | // 73 | // #### _that_ 74 | // 75 | var that = require('./api.base.js').base({ 76 | api: spec.api, 77 | type: 'window', 78 | args: spec.args 79 | }); 80 | 81 | /****************************************************************************/ 82 | /* PRIVATE HELPERS */ 83 | /****************************************************************************/ 84 | 85 | /****************************************************************************/ 86 | /* PROTECTED METHODS */ 87 | /****************************************************************************/ 88 | // ### create 89 | // 90 | // Override of creation method called at construction to ensure initialization 91 | // of the Creation method called at construction 92 | // ``` 93 | // @args {object} arguments for creation 94 | // ``` 95 | create = function(args) { 96 | if(args.session) { 97 | args.session.pre(function(err) { 98 | if(!err) { 99 | args.session_id = args.session.target() 100 | } 101 | delete args.session; 102 | _super.create(args); 103 | }); 104 | } 105 | else { 106 | _super.create(args); 107 | } 108 | }; 109 | 110 | /****************************************************************************/ 111 | /* PUBLIC METHODS */ 112 | /****************************************************************************/ 113 | // ### show 114 | // 115 | // Shows the created window 116 | // ``` 117 | // @cb_ {function(err)} 118 | // ``` 119 | show = function(cb_) { 120 | that.call('show', {}, cb_); 121 | }; 122 | 123 | // ### focus 124 | // 125 | // Focuses the window 126 | // ``` 127 | // @cb_ {function(err)} 128 | // ``` 129 | focus = function(cb_) { 130 | that.call('focus', {}, cb_); 131 | }; 132 | 133 | // ### maximize 134 | // 135 | // Maximizes the window 136 | // ``` 137 | // @cb_ {function(err)} 138 | // ``` 139 | maximize = function(cb_) { 140 | that.call('maximize', {}, cb_); 141 | }; 142 | 143 | // ### unmaximize 144 | // 145 | // Unmaximizes the window 146 | // ``` 147 | // @cb_ {function(err)} 148 | // ``` 149 | unmaximize = function(cb_) { 150 | that.call('unmaximize', {}, cb_); 151 | }; 152 | 153 | // ### close 154 | // 155 | // Closes the window 156 | // ``` 157 | // @cb_ {function(err)} 158 | // ``` 159 | close = function(cb_) { 160 | that.call('close', {}, cb_); 161 | }; 162 | 163 | // ### remote 164 | // 165 | // Sends a remote message to the window JS context 166 | // ``` 167 | // @message {object} 168 | // @cb_ {function(err)} 169 | // ``` 170 | remote = function(message, cb_) { 171 | if(typeof message !== 'object') { 172 | message = { payload: message }; 173 | } 174 | that.call('remote', { message: message }, cb_); 175 | }; 176 | 177 | 178 | // ### set_title 179 | // 180 | // Sets the window title 181 | // ``` 182 | // @title {string} the window title 183 | // @cb_ {function(err)} 184 | // ``` 185 | set_title = function(title, cb_) { 186 | that.call('set_title', { title: title }, cb_); 187 | }; 188 | 189 | // ### set_fullscreen 190 | // 191 | // Sets the window fullscreen state 192 | // ``` 193 | // @fullscreen {boolean} the window fullscreen state 194 | // @cb_ {function(err)} 195 | // ``` 196 | set_fullscreen = function(fullscreen, cb_) { 197 | that.call('set_fullscreen', { fullscreen: fullscreen }, cb_); 198 | }; 199 | 200 | // ### set_kiosk 201 | // 202 | // Sets the window kiosk state 203 | // ``` 204 | // @kiosk {string} the window kiosk state 205 | // @cb_ {function(err)} 206 | // ``` 207 | set_kiosk = function(kiosk, cb_) { 208 | that.call('set_kiosk', { kiosk: kiosk }, cb_); 209 | }; 210 | 211 | // ### open_devtools 212 | // 213 | // Opens the devtools view for this window 214 | // ``` 215 | // @cb_ {function(err)} 216 | // ``` 217 | open_devtools = function(cb_) { 218 | that.call('open_devtools', {}, cb_); 219 | }; 220 | 221 | // ### close_devtools 222 | // 223 | // Closes the devtools view for this window 224 | // ``` 225 | // @cb_ {function(err)} 226 | // ``` 227 | close_devtools = function(cb_) { 228 | that.call('close_devtools', {}, cb_); 229 | }; 230 | 231 | // ### move 232 | // 233 | // Sets the window position 234 | // ``` 235 | // @x {number} the window target x position 236 | // @x {number} the window target y position 237 | // @cb_ {function(err)} 238 | // ``` 239 | move = function(x, y, cb_) { 240 | that.call('move', { x: x, y: y }, cb_); 241 | }; 242 | 243 | // ### resize 244 | // 245 | // Sets the window position 246 | // ``` 247 | // @width {number} the window target width 248 | // @height {number} the window target height 249 | // @cb_ {function(err)} 250 | // ``` 251 | resize = function(width, height, cb_) { 252 | that.call('resize', { width: width, height: height }, cb_); 253 | }; 254 | 255 | 256 | // ### is_closed 257 | // 258 | // Gets the window closed status 259 | // ``` 260 | // @cb_ {function(err, is_closed)} 261 | // ``` 262 | is_closed = function(cb_) { 263 | that.call('is_closed', {}, function(err, res) { 264 | return cb_(err, res ? res.closed : null); 265 | }); 266 | }; 267 | 268 | // ### is_maximized 269 | // 270 | // Gets the window maximization status 271 | // ``` 272 | // @cb_ {function(err, is_maximized)} 273 | // ``` 274 | is_maximized = function(cb_) { 275 | that.call('is_maximized', {}, function(err, res) { 276 | return cb_(err, res ? res.maximized : null); 277 | }); 278 | }; 279 | 280 | // ### is_minimized 281 | // 282 | // Gets the window minimization status 283 | // ``` 284 | // @cb_ {function(err, is_minimized)} 285 | // ``` 286 | is_minimized = function(cb_) { 287 | that.call('is_minimized', {}, function(err, res) { 288 | return cb_(err, res ? res.minimized : null); 289 | }); 290 | }; 291 | 292 | // ### is_fullscreen 293 | // 294 | // Gets the window fullscreen status 295 | // ``` 296 | // @cb_ {function(err, is_fullscreen)} 297 | // ``` 298 | is_fullscreen = function(cb_) { 299 | that.call('is_fullscreen', {}, function(err, res) { 300 | return cb_(err, res ? res.fullscreen : null); 301 | }); 302 | }; 303 | 304 | // ### is_kiosk 305 | // 306 | // Gets the window kiosk status 307 | // ``` 308 | // @cb_ {function(err, is_kiosk)} 309 | // ``` 310 | is_kiosk = function(cb_) { 311 | that.call('is_kiosk', {}, function(err, res) { 312 | return cb_(err, res ? res.kiosk : null); 313 | }); 314 | }; 315 | 316 | // ### is_devtools_opened 317 | // 318 | // Returns wether the devtools view is opened for this window 319 | // ``` 320 | // @cb_ {function(err, is_kiosk)} 321 | // ``` 322 | is_devtools_opened = function(cb_) { 323 | that.call('is_devtools_opened', {}, function(err, res) { 324 | return cb_(err, res ? res.opened : null); 325 | }); 326 | }; 327 | 328 | // ### size 329 | // 330 | // Gets the window size 331 | // ``` 332 | // @cb_ {function(err, size)} 333 | // ``` 334 | size = function(cb_) { 335 | that.call('size', {}, function(err, res) { 336 | return cb_(err, res ? res.size : null); 337 | }); 338 | }; 339 | 340 | // ### position 341 | // 342 | // Gets the window position 343 | // ``` 344 | // @cb_ {function(err, position)} 345 | // ``` 346 | position = function(cb_) { 347 | that.call('position', {}, function(err, res) { 348 | return cb_(err, res ? res.position : null); 349 | }); 350 | }; 351 | 352 | common.method(that, 'create', create, _super); 353 | 354 | common.method(that, 'show', show, _super); 355 | common.method(that, 'focus', focus, _super); 356 | common.method(that, 'maximize', maximize, _super); 357 | common.method(that, 'unmaximize', unmaximize, _super); 358 | common.method(that, 'minimize', minimize, _super); 359 | common.method(that, 'restore', restore, _super); 360 | common.method(that, 'close', close, _super); 361 | common.method(that, 'remote', remote, _super); 362 | 363 | common.method(that, 'set_title', set_title, _super); 364 | common.method(that, 'set_fullscreen', set_fullscreen, _super); 365 | common.method(that, 'set_kiosk', set_kiosk, _super); 366 | common.method(that, 'open_devtools', open_devtools, _super); 367 | common.method(that, 'close_devtools', close_devtools, _super); 368 | common.method(that, 'move', move, _super); 369 | common.method(that, 'resize', resize, _super); 370 | 371 | common.method(that, 'is_closed', is_closed, _super); 372 | common.method(that, 'is_maximized', is_maximized, _super); 373 | common.method(that, 'is_minimized', is_minimized, _super); 374 | common.method(that, 'is_fullscreen', is_fullscreen, _super); 375 | common.method(that, 'is_kiosk', is_kiosk, _super); 376 | common.method(that, 'is_devtools_opened', is_devtools_opened, _super); 377 | 378 | common.method(that, 'size', size, _super); 379 | common.method(that, 'position', position, _super); 380 | 381 | return that; 382 | } 383 | 384 | exports.window = window; 385 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * node-thrust: common.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-05-16 spolu Added `exit` method 10 | * - 2014-01-14 spolu Added `hash` method 11 | * - 2013-12-15 spolu Creation 12 | */ 13 | "use strict"; 14 | 15 | var util = require('util'); 16 | var crypto = require('crypto'); 17 | 18 | /******************************************************************************/ 19 | /* GLOBAL CONFIG */ 20 | /******************************************************************************/ 21 | 22 | exports.DEBUG = false; 23 | 24 | /******************************************************************************/ 25 | /* CROCKFORD */ 26 | /******************************************************************************/ 27 | 28 | // ### method 29 | // 30 | // Adds a method to the current object denoted by that and preserves _super 31 | // implementation (see Crockford) 32 | // ``` 33 | // @that {object} object to extend 34 | // @name {string} the method name 35 | // @method {function} the method 36 | // @_super {object} parent object for functional inheritance 37 | // ``` 38 | exports.method = function(that, name, method, _super) { 39 | if(_super) { 40 | var m = that[name]; 41 | _super[name] = function() { 42 | return m.apply(that, arguments); 43 | }; 44 | } 45 | that[name] = method; 46 | }; 47 | 48 | // ### getter 49 | // 50 | // Generates a getter on obj for key 51 | // ``` 52 | // @that {object} object to extend 53 | // @name {string} the getter name 54 | // @obj {object} the object targeted by the getter 55 | // @key {string} the key to get on obj 56 | // ``` 57 | exports.getter = function(that, name, obj, prop) { 58 | var getter = function() { 59 | return obj[prop]; 60 | }; 61 | that[name] = getter; 62 | }; 63 | 64 | // ### setter 65 | // 66 | // Generates a getter on obj for key 67 | // ``` 68 | // @that {object} object to extend 69 | // @name {string} the getter name 70 | // @obj {object} the object targeted by the getter 71 | // @key {string} the key to get on obj 72 | // ``` 73 | exports.setter = function(that, name, obj, prop) { 74 | var setter = function (arg) { 75 | obj[prop] = arg; 76 | return that; 77 | }; 78 | that['set' + name.substring(0, 1).toUpperCase() + name.substring(1)] = setter; 79 | that['set' + '_' + name] = setter; 80 | }; 81 | 82 | // ### responds 83 | // 84 | // Tests whether the object responds to the given method name 85 | // ``` 86 | // @that {object} object to test 87 | // @name {string} the method/getter/setter name 88 | // ``` 89 | exports.responds = function(that, name) { 90 | return (that[name] && typeof that[name] === 'function'); 91 | }; 92 | 93 | 94 | /******************************************************************************/ 95 | /* HELPERS */ 96 | /******************************************************************************/ 97 | 98 | // #### once 99 | // 100 | // Returns a function that will call the underlying function only once 101 | // whether it is called once or multiple times 102 | // ``` 103 | // @fn {function} function to call once 104 | // ``` 105 | exports.once = function(fn) { 106 | if(fn === void 0 || fn === null || typeof fn !== 'function') 107 | throw new TypeError(); 108 | 109 | var done = false; 110 | return function() { 111 | if(!done) { 112 | var args = Array.prototype.slice.call(arguments); 113 | done = true; 114 | fn.apply(null, args); 115 | } 116 | }; 117 | }; 118 | 119 | // ### remove 120 | // 121 | // Removes the element e from the Array, using the JS '===' equality 122 | // ``` 123 | // @that {array} the array to operate on 124 | // @e {object} element to remove from the array 125 | // @only_one {boolean} remove only one 126 | // ``` 127 | exports.remove = function(that, e, only_one) { 128 | "use strict"; 129 | 130 | if(that === void 0 || that === null || !Array.isArray(that)) 131 | throw new TypeError(); 132 | 133 | for(var i = that.length - 1; i >= 0; i--) { 134 | if(e === that[i]) { 135 | that.splice(i, 1); 136 | if(only_one) return; 137 | } 138 | } 139 | }; 140 | 141 | // ### clamp 142 | // 143 | // Clamp a given integer to a specified range and pad 144 | // ``` 145 | // @v {number} value 146 | // @min {number} minimum 147 | // @max {number} maximum 148 | // ``` 149 | exports.clamp = function(v, min, max) { 150 | if (v < min) 151 | return min; 152 | if (v > max) 153 | return max; 154 | return v; 155 | }; 156 | 157 | // ### lpad 158 | // 159 | // Pads a string to the provided length with ' ' or opt_ch 160 | // ``` 161 | // @str {string} string to pad 162 | // @length {number} pad to length character 163 | // @opt_ch {string} [optional] character 164 | // ``` 165 | exports.lpad = function(str, length, opt_ch) { 166 | str = String(str); 167 | opt_ch = opt_ch || ' '; 168 | 169 | while (str.length < length) 170 | str = opt_ch + str; 171 | 172 | return str; 173 | }; 174 | 175 | // ### rpad 176 | // 177 | // Pads a string to the provided length with ' ' or opt_ch 178 | // ``` 179 | // @str {string} string to pad 180 | // @length {number} pad to length character 181 | // @opt_ch {string} [optional] character 182 | // ``` 183 | exports.rpad = function(str, length, opt_ch) { 184 | str = String(str); 185 | opt_ch = opt_ch || ' '; 186 | 187 | while (str.length < length) 188 | str += opt_ch; 189 | 190 | return str.substr(0, length); 191 | }; 192 | 193 | // ### zpad 194 | // 195 | // Pads a string to the provided length with zeroes 196 | // ``` 197 | // @str {string} string to pad 198 | // @length {number} pad to length character 199 | // ``` 200 | exports.zpad = function(str, length) { 201 | return exports.lpad(str, length, '0'); 202 | }; 203 | 204 | // ### hash 205 | // 206 | // Computes a hash value from the given strings 207 | // ``` 208 | // @strings {array} an array of string used to update HMAC 209 | // @encoding {string} the encoding used [optional] (default: 'hex') 210 | // @return {string} the hash value 211 | // ``` 212 | // 213 | exports.hash = function(strings, encoding) { 214 | encoding = encoding || 'hex'; 215 | var hash = crypto.createHash('sha256'); 216 | strings.forEach(function(update) { 217 | hash.update(new Buffer(update.toString())); 218 | }); 219 | return hash.digest(encoding); 220 | }; 221 | 222 | 223 | /******************************************************************************/ 224 | /* LOGGING AND ERROR REPORTING */ 225 | /******************************************************************************/ 226 | 227 | var log = function(str, debug, error) { 228 | var pre = '[' + new Date().toISOString() + '] '; 229 | //pre += (my.name ? '{' + my.name.toUpperCase() + '} ' : ''); 230 | pre += (debug ? 'DEBUG: ' : ''); 231 | str.toString().split('\n').forEach(function(line) { 232 | if(error) 233 | console.error(pre + line) 234 | else if(debug) 235 | console.log(pre + line); 236 | else 237 | console.log(pre + line); 238 | }); 239 | }; 240 | 241 | 242 | // ### log 243 | // 244 | // Logging helpers. Object based on the `log` function including 4 logging 245 | // functions: `out`, `error`, `debug`, `info` 246 | // ``` 247 | // @str {string|error} the string or error to log 248 | // ``` 249 | exports.log = { 250 | out: function(str) { 251 | log(str); 252 | }, 253 | error: function(err) { 254 | if(typeof err === 'object') { 255 | log('*********************************************', false, true); 256 | log('ERROR: ' + err.message); 257 | log('*********************************************', false, true); 258 | log(err.stack); 259 | log('---------------------------------------------', false, true); 260 | } 261 | else { 262 | log('*********************************************', false, true); 263 | log('ERROR: ' + JSON.stringify(err)); 264 | log('*********************************************', false, true); 265 | log('---------------------------------------------', false, true); 266 | } 267 | }, 268 | debug: function(str) { 269 | if(exports.DEBUG) 270 | log(str, true); 271 | }, 272 | info: function(str) { 273 | util.print(str + '\n'); 274 | } 275 | }; 276 | 277 | // ### exit 278 | // 279 | // Makes sure to kill all subprocesses 280 | // `` 281 | // @code {number} exit code 282 | // ``` 283 | exports.exit = function(code) { 284 | try { 285 | process.kill('-' + process.pid); 286 | } 287 | finally { 288 | process.exit(code); 289 | } 290 | }; 291 | 292 | // ### fatal 293 | // 294 | // Prints out the error and exits the process while killing all sub processes 295 | // ``` 296 | // @err {error} 297 | // ``` 298 | exports.fatal = function(err) { 299 | exports.log.error(err); 300 | exports.exit(1); 301 | }; 302 | 303 | // ### err 304 | // 305 | // Generates a proper error with name set 306 | // ``` 307 | // @msg {string} the error message 308 | // @name {string} the error name 309 | // ``` 310 | exports.err = function(msg, name) { 311 | var err = new Error(msg); 312 | err.name = name || 'CommonError'; 313 | return err; 314 | }; 315 | 316 | -------------------------------------------------------------------------------- /lib/thrust.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-thrust: thrust.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-10-09 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | var common = require('./common.js'); 14 | 15 | var path = require('path'); 16 | var os = require('os'); 17 | 18 | // ## thrust 19 | // 20 | // Main node-thrust entry point. See `spawn` method. 21 | // 22 | // ``` 23 | // @spec {} 24 | // @inherits {} 25 | // ``` 26 | var thrust = function(spec, my) { 27 | var _super = {}; 28 | my = my || {}; 29 | spec = spec || {}; 30 | 31 | my.THRUST_PATH = path.join(__dirname, '..', 'vendor', 'thrust'); 32 | my.THRUST_EXEC = { 33 | 'linux': path.join(my.THRUST_PATH, 'thrust_shell'), 34 | 'win32': path.join(my.THRUST_PATH, 'thrust_shell'), 35 | 'darwin': path.join(my.THRUST_PATH, 36 | 'ThrustShell.app', 'Contents', 'MacOS', 'ThrustShell'), 37 | } 38 | my.salt = Date.now().toString(); 39 | my.next_id = Math.floor(Math.random() * 1000); 40 | 41 | // 42 | // #### _public_ 43 | // 44 | var spawn; /* spawn(cb_); */ 45 | 46 | // 47 | // #### _private_ 48 | // 49 | var uid; /* uid(); */ 50 | 51 | // 52 | // #### _that_ 53 | // 54 | var that = {}; 55 | 56 | /****************************************************************************/ 57 | /* PRIVATE HELPERS */ 58 | /****************************************************************************/ 59 | // ### uid 60 | // 61 | // Returns a new unique id 62 | uid = function() { 63 | return my.salt + '-' + (++my.next_id); 64 | }; 65 | 66 | /****************************************************************************/ 67 | /* PUBLIC METHODS */ 68 | /****************************************************************************/ 69 | // ### spawn 70 | // 71 | // Spawns a new `thrust` instace and returns an associated `api` object 72 | // to access its API. The `api` object communicates with the shell process 73 | // using unix domain socket. 74 | // 75 | // This method can be called multiple times to spawn different instances. 76 | // ``` 77 | // @cb_ {function(err, api)} 78 | // @options {object} `exec_path`: The path to the thrust exec to use 79 | // `args` : dictionary of command line arguments 80 | // @exec_path {string} the executable to use 81 | // ``` 82 | spawn = function(cb_, options) { 83 | options = options || {}; 84 | if(!my.THRUST_EXEC[os.platform()]) { 85 | return cb_(common.err('Platform not supported: ' + os.platform(), 86 | 'thrust:platform_not_supported')); 87 | } 88 | 89 | var exec_path = options.exec_path || my.THRUST_EXEC[os.platform()]; 90 | common.log.out('SPAWING ' + exec_path); 91 | 92 | options.args = options.args || {}; 93 | var args = Object.keys(options.args).filter(function(arg) { 94 | return !!options.args[arg]; 95 | }).map(function(arg) { 96 | if(typeof options.args[arg] !== 'string') { 97 | return '--' + arg; 98 | } 99 | else { 100 | return '--' + arg + '=' + options.args[arg]; 101 | } 102 | }); 103 | 104 | var p = require('child_process').spawn(exec_path, args); 105 | p.stderr.on('data', function(c) { 106 | require('util').print(c.toString()); 107 | }); 108 | 109 | require('./api.js').api({ 110 | process: p 111 | }).init(cb_); 112 | }; 113 | 114 | common.method(that, 'spawn', spawn, _super); 115 | 116 | return that; 117 | }; 118 | 119 | exports.thrust = thrust; 120 | 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-thrust", 3 | "description": "Binding library for Thrust", 4 | "author": { 5 | "name": "Stanislas Polu", 6 | "email": "polu.stanislas@gmail.com", 7 | "url": "http://github.com/spolu" 8 | }, 9 | "version": "0.7.6-rc.1", 10 | "dependencies": { 11 | "async": "0.9.x", 12 | "fs-extra": "0.12.x", 13 | "minimist": "1.1.x", 14 | "mkdirp": "0.5.x", 15 | "optimist": "0.6.x", 16 | "request": "2.45.x", 17 | "unzip": "^0.1.11" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "http://github.com/breach/node-thrust.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/breach/node-thrust/issues" 25 | }, 26 | "licenses": [ 27 | { 28 | "type": "MIT", 29 | "url": "https://github.com/breach/breach_core/raw/master/LICENSE" 30 | } 31 | ], 32 | "main": "./index.js", 33 | "THRUST_VERSION": "v0.7.6", 34 | "THRUST_BASE_URL": "https://github.com/breach/thrust/releases/download/", 35 | "THRUST_RELEASE_PLATFORMS": { 36 | "darwin": { 37 | "x64": "darwin-x64" 38 | }, 39 | "linux": { 40 | "ia32": "linux-ia32", 41 | "x64": "linux-x64" 42 | }, 43 | "win32": { 44 | "ia32": "win32-ia32", 45 | "x64": "win32-ia32" 46 | } 47 | }, 48 | "scripts": { 49 | "postinstall": "node scripts/bootstrap.js" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-thrust: scripts/bootstrap.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-10-16 spolu Creation 10 | */ 11 | 12 | var async = require('async'); 13 | var mkdirp = require('mkdirp'); 14 | var path = require('path'); 15 | var fs = require('fs-extra'); 16 | var os = require('os'); 17 | var request = require('request'); 18 | 19 | var common = require('../lib/common.js'); 20 | var unzip = require('unzip'); 21 | 22 | /******************************************************************************/ 23 | /* CONFIGURATION */ 24 | /******************************************************************************/ 25 | var MODULE_PATH = path.join(__dirname, '..'); 26 | var VENDOR_PATH = path.join(MODULE_PATH, 'vendor'); 27 | 28 | var package_json = require(path.join(MODULE_PATH, 'package.json')); 29 | 30 | var THRUST_BASE_URL = package_json.THRUST_BASE_URL; 31 | var THRUST_VERSION = package_json.THRUST_VERSION; 32 | 33 | var THRUST_PATH = path.join(VENDOR_PATH, 'thrust'); 34 | var THRUST_VERSION_PATH = path.join(THRUST_PATH, '.version'); 35 | 36 | var THRUST_RELEASE_PLATFORM = package_json.THRUST_RELEASE_PLATFORMS 37 | [os.platform()] 38 | [os.arch()]; 39 | 40 | var THRUST_RELEASE_FILENAME = 'thrust-' + THRUST_VERSION + '-' + 41 | THRUST_RELEASE_PLATFORM + '.zip'; 42 | 43 | var THRUST_RELEASE_URL = THRUST_BASE_URL + 44 | THRUST_VERSION + '/' + 45 | THRUST_RELEASE_FILENAME; 46 | 47 | /******************************************************************************/ 48 | /* BOOTSTRAPPING STEPS */ 49 | /******************************************************************************/ 50 | // ### install_thrust 51 | // 52 | // Installs thrust binary distribution for the adequate platform if not already 53 | // present unless the force parameter is passed. 54 | // ``` 55 | // @force {boolean} force install of thrust binary distribution 56 | // ``` 57 | var install_thrust = function(force, cb_) { 58 | async.series([ 59 | /* Clean if force flag */ 60 | function(cb_) { 61 | if(force) { 62 | return fs.remove(THRUST_PATH, cb_); 63 | } 64 | return cb_(); 65 | }, 66 | /* Check for the THRUST_VERSION_PATH or remove it */ 67 | function(cb_) { 68 | console.log(THRUST_VERSION_PATH) 69 | fs.readFile(THRUST_VERSION_PATH, function(err, data) { 70 | if(err && err.code !== 'ENOENT') { 71 | return cb_(err); 72 | } 73 | else if(err && err.code === 'ENOENT') { 74 | fs.remove(THRUST_PATH, cb_); 75 | } 76 | else { 77 | if(data.toString() === THRUST_VERSION) { 78 | /* We're done here! */ 79 | return cb_(common.err('thrust-' + THRUST_VERSION + 80 | ' already installed in ' + 81 | path.join(THRUST_PATH), 82 | 'bootstrap:already_installed')); 83 | } 84 | else { 85 | fs.remove(THRUST_PATH, cb_); 86 | } 87 | } 88 | }); 89 | }, 90 | /* Create THRUST_PATH */ 91 | function(cb_) { 92 | console.log('Creating: ' + THRUST_PATH); 93 | mkdirp(THRUST_PATH, cb_); 94 | }, 95 | /* Download THRUST */ 96 | function(cb_) { 97 | console.log('Downloading: ' + THRUST_RELEASE_URL); 98 | 99 | var itv = setInterval(function() { 100 | process.stdout.write('.'); 101 | }, 1000); 102 | var finish = function(err) { 103 | clearInterval(itv); 104 | process.stdout.write('\n'); 105 | return cb_(err); 106 | } 107 | 108 | request(THRUST_RELEASE_URL) 109 | .on('error', finish) 110 | .on('end', finish) 111 | .pipe(fs.createWriteStream(path.join(THRUST_PATH, 112 | THRUST_RELEASE_FILENAME))); 113 | }, 114 | /* Extract thrust in THRUST_PATH */ 115 | function(cb_) { 116 | console.log('Extracting ' + path.join(THRUST_PATH, 117 | THRUST_RELEASE_FILENAME)); 118 | if(os.platform() === 'darwin') { 119 | var unzip_p = require('child_process').spawn('unzip', 120 | [ '-oqq', path.join(THRUST_PATH, THRUST_RELEASE_FILENAME) ], { 121 | cwd: THRUST_PATH 122 | }); 123 | unzip_p.on('close', function (code) { 124 | if(code !== 0) { 125 | return cb_(common.err('Extraction failed with code: ' + code, 126 | 'boostrap:failed_extraction')); 127 | } 128 | return cb_(); 129 | }); 130 | } 131 | else { 132 | async.series([ 133 | function(cb_) { 134 | fs.createReadStream(path.join(THRUST_PATH, THRUST_RELEASE_FILENAME)) 135 | .on('error', cb_) 136 | .pipe(unzip.Extract({ 137 | path: THRUST_PATH 138 | }).on('close', cb_)); 139 | }, 140 | function(cb_) { 141 | if(os.platform() === 'linux') { 142 | var exec_path = path.join(THRUST_PATH, 'thrust_shell'); 143 | fs.chmod(exec_path, 0755, cb_); 144 | } 145 | else { 146 | return cb_(); 147 | } 148 | } 149 | ], cb_); 150 | } 151 | }, 152 | /* Cleaning up */ 153 | function(cb_) { 154 | console.log('Cleaning up ' + path.join(THRUST_PATH, 155 | THRUST_RELEASE_FILENAME)); 156 | fs.remove(path.join(THRUST_PATH, THRUST_RELEASE_FILENAME), cb_); 157 | }, 158 | /* Create THRUST_VERSION */ 159 | function(cb_) { 160 | fs.writeFile(THRUST_VERSION_PATH, THRUST_VERSION, { 161 | flag: 'w' 162 | }, cb_); 163 | } 164 | ], cb_); 165 | }; 166 | 167 | /******************************************************************************/ 168 | /* MAIN */ 169 | /******************************************************************************/ 170 | var argv = require('optimist') 171 | .usage('Usage: $0 [-f]') 172 | .argv; 173 | 174 | async.series([ 175 | function(cb_) { 176 | install_thrust(argv.f || false, function(err) { 177 | if(err && err.name === 'bootstrap:already_installed') { 178 | console.log(err.message); 179 | return cb_(); 180 | } 181 | return cb_(err); 182 | }); 183 | } 184 | ], function(err) { 185 | if(err) { 186 | common.fatal(err); 187 | } 188 | console.log('Done!'); 189 | process.exit(0); 190 | }); 191 | 192 | -------------------------------------------------------------------------------- /test/base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node-thrust [test]: base.js 3 | * 4 | * Copyright (c) 2014, Stanislas Polu. All rights reserved. 5 | * 6 | * @author: spolu 7 | * 8 | * @log: 9 | * - 2014-10-23 spolu Creation 10 | */ 11 | "use strict" 12 | 13 | // ### base 14 | // 15 | // Base test class, sets up the API on a specific executable if passed as an 16 | // argument or the one installed locally 17 | 18 | module.exports = function(cb_) { 19 | var argv = require('minimist')(process.argv.slice(2)); 20 | require('../index.js')(cb_, { 21 | exec_path: argv.exec_path || null, 22 | args: {} 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /test/bugs/bug_217.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |