├── .gitignore ├── .npmignore ├── Makefile ├── README.md ├── chromix-too.coffee ├── client.coffee ├── example └── example.coffee ├── extension ├── .gitignore ├── Makefile ├── background.coffee ├── foreground.coffee ├── manifest.json └── utils.coffee ├── misc ├── Makefile └── _chromix-too ├── package-lock.json ├── package.json ├── server.coffee └── utils.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.log 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smblott-github/chromix-too/332c04ffcadd3c8188f4ad3334159c5935a42732/.npmignore -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | src = utils.coffee extension/utils.coffee chromix-too.coffee server.coffee client.coffee 3 | js = $(src:.coffee=.js) 4 | 5 | build: $(js) 6 | @true 7 | 8 | auto: 9 | watch -n 1 make build 10 | 11 | install: 12 | $(MAKE) build 13 | sudo npm install -g . 14 | 15 | extension: 16 | $(MAKE) -C extension build 17 | 18 | %.js: %.coffee 19 | coffee -c --bare --no-header $< 20 | 21 | publish: 22 | $(MAKE) build 23 | npm publish 24 | 25 | .PHONY: build auto install extension publish 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chromix-Too 2 | 3 | This project provides external (e.g. command-line or scripted) access to Chrome's internal (Javascript) API's. 4 | 5 | For example, the following command closes all tabs on stackoverflow: 6 | 7 | chromix-too rm stackoverflow.com 8 | 9 | Chromix-too is a replacement for 10 | [chromix](https://github.com/smblott-github/chromix). Chromix-too is 11 | considerably simpler, uses a Unix-domain socket for communication between 12 | client and server (which is more secure), and is better packaged (it can be used as a module too). 13 | 14 | ## Getting started 15 | 16 | There are three components: a chrome extension, a server and the `chromix-too` utility. 17 | 18 | Install [the extension](https://chrome.google.com/webstore/detail/chromix-too/ppapdfccnamacakfkpfmpfnefpeajboj) from the Chrome Store. 19 | 20 | To install the server and the `chromix-too` utility: 21 | 22 | ```shell 23 | sudo npm install -g chromix-too 24 | ``` 25 | 26 | Next, you need to run the server: 27 | 28 | ```shell 29 | chromix-too-server 30 | ``` 31 | 32 | And try out the client: 33 | 34 | ```shell 35 | chromix-too ls 36 | ``` 37 | 38 | Of course, you need to keep the server running all the time. There are many ways to do this, but I use [daemontools](https://cr.yp.to/daemontools.html); my daemontools `run` file is just: 39 | 40 | ```shell 41 | PATH="/usr/local/bin:$PATH" 42 | HOME='/home/blott' exec setuidgid blott chromix-too-server 43 | ``` 44 | 45 | (Or perhaps just leave the server running in a tmux session.) 46 | 47 | ## Commands 48 | 49 | List tabs: 50 | 51 | ```shell 52 | chromix-too ls 53 | ``` 54 | 55 | List just tab Ids: 56 | 57 | ```shell 58 | chromix-too tid 59 | ``` 60 | 61 | Focus a tab: 62 | 63 | ```shell 64 | chromix-too focus https://github.com/smblott-github/chromix 65 | ``` 66 | 67 | Select a tab (select the tab but don't `focus` Chrome window): 68 | 69 | ```shell 70 | chromix-too select https://github.com/smblott-github/chromix 71 | ``` 72 | 73 | Remove a tab: 74 | 75 | ```shell 76 | chromix-too rm https://github.com/smblott-github/chromix 77 | ``` 78 | 79 | Reload a tab: 80 | 81 | ```shell 82 | chromix-too reload https://github.com/smblott-github/chromix 83 | ``` 84 | 85 | Open a tab: 86 | 87 | ```shell 88 | chromix-too open https://github.com/smblott-github/chromix 89 | ``` 90 | 91 | View a file: 92 | 93 | ```shell 94 | chromix-too file ./README.html 95 | ``` 96 | 97 | (The `file` command also focuses and reloads an existing tab if one exists.) 98 | 99 | Verify that everything is running correctly: 100 | 101 | ```shell 102 | chromix-too ping 103 | ``` 104 | 105 | ### Raw JSON 106 | 107 | Call any available Chrome function from the command line: 108 | 109 | ```shell 110 | chromix-too raw chrome.storage.local.set '{"pi": 3.141}' 111 | 112 | chromix-too raw chrome.storage.local.get pi 113 | # {"pi":3.141} 114 | 115 | chromix-too raw chrome.storage.local.get pi | jq '.pi' 116 | # 3.141 117 | ``` 118 | 119 | ## Filters 120 | 121 | For all of the commands above (except where it doesn't make sense), you can 122 | filter the list of tabs to which the command applies. 123 | 124 | There are three kinds of filter: 125 | 126 | 1. If the filter is just a bare number, then it is interpreted as a tab Id. 127 | 128 | 2. If the filter is one of the boolean options described 129 | [here](https://developer.chrome.com/extensions/tabs#method-query), then the 130 | corresponding flag is set. For example, you can use `pinned` to operate on all pinned tabs. 131 | 132 | These boolean flags can be inverted: `-pinned` selects all unpinned tabs. 133 | 134 | 3. Any remaining filter arguments are treated as queries. Tabs are removed 135 | from consideration unless the query text is present in either the tab's URL 136 | or the tab's title. 137 | 138 | Examples: 139 | 140 | ```shell 141 | # Remove the tab with this tab Id. 142 | chromix-too rm 1234 143 | 144 | # Remove all audible tabs. 145 | chromix-too rm audible 146 | 147 | # Remove all unpinned tabs. 148 | chromix-too rm -pinned 149 | 150 | # List GMail tabs. 151 | chromix-too ls mail.google.com 152 | 153 | # Focus my Google Inbox tab. 154 | chromix-too focus Inbox smblott@gmail.com 155 | ``` 156 | 157 | All commands which accept filters fail (so, yield a non-zero exit status) if there are no matching tabs. 158 | 159 | ## Usage as a module 160 | 161 | It is also possible to use `chromix-too` as a node module; here's an example: 162 | 163 | ```Coffeescript 164 | chromix = require("chromix-too")().chromix 165 | 166 | chromix "chrome.storage.local.set", {}, {pi: 3.141}, -> 167 | chromix "chrome.storage.local.get", {}, "pi", (response) -> 168 | console.log response.pi 169 | ``` 170 | 171 | The second argument (`{}`, here) is a place holder for future extensions. 172 | 173 | The general form is: 174 | 175 | ```Coffeescript 176 | chromix PATH, REQUEST, ARGS..., CALLBACK 177 | ``` 178 | 179 | The number of `ARGS...` provided must match the number of (non-callback) arguments expected by the relevant 180 | Chrome API call. When the call is actually made, chromix-too simply 181 | appends its own callback, and that callback must be in the correct argument position. 182 | 183 | ## Known issues 184 | 185 | - There is currently no way to set the websocket port used between the Chrome extension and the server. 186 | - Only background-page API calls are possible. It is intended to add the ability to invoke functions in a content script at some point in the future. 187 | 188 | Contributions are welcome. 189 | -------------------------------------------------------------------------------- /chromix-too.coffee: -------------------------------------------------------------------------------- 1 | 2 | # Usage: 3 | # see examples in ./client.coffee. 4 | 5 | utils = require "./utils" 6 | utils.extend global, utils 7 | 8 | module.exports = (sock = config.sock) -> 9 | 10 | # This sends a single request to chrome, unpacks the response, and calls any callbacks with the response as 11 | # argument(s). 12 | chromix: 13 | (path, request, extra_arguments...) -> 14 | extend request, {path} 15 | request.args ?= [] 16 | callbacks = [] 17 | 18 | # Extra arguments which are functions are callbacks (usually just one); all other arguments are added to the 19 | # list of arguments. 20 | for arg in extra_arguments 21 | (if typeof(arg) == "function" then callbacks else request.args).push arg 22 | 23 | client = require("net").connect sock, -> 24 | client.write JSON.stringify request 25 | 26 | client.on "data", (data) -> 27 | response = JSON.parse data.toString "utf8" 28 | if response.error 29 | console.error "error: #{response.error}" 30 | process.exit 1 31 | callback response.response... for callback in callbacks 32 | client.destroy() 33 | -------------------------------------------------------------------------------- /client.coffee: -------------------------------------------------------------------------------- 1 | `#!/usr/bin/env node 2 | ` 3 | utils = require "./utils" 4 | utils.extend global, utils 5 | 6 | optimist = require "optimist" 7 | args = optimist.usage("Usage: $0 [--sock=PATH]") 8 | .alias("h", "help") 9 | .default("sock", config.sock) 10 | .argv 11 | 12 | if args.help 13 | optimist.showHelp() 14 | process.exit(0) 15 | 16 | chromix = require("./chromix-too")(args.sock).chromix 17 | 18 | [ commandName, commandArgs ] = 19 | if 2 < process.argv.length then [ process.argv[2], process.argv[3...] ] else [ "ping", [] ] 20 | 21 | # Extract the query flags (for chrome.tabs.query) from the arguments. Return the new arguments and the 22 | # query-flags object. 23 | getQueryFlags = (commandArgs) -> 24 | validQueryFlags = {} 25 | # These are the valid boolean flags listed here: https://developer.chrome.com/extensions/tabs#method-query. 26 | for flag in "active pinned audible muted highlighted discarded autoDiscardable currentWindow lastFocusedWindow".split " " 27 | validQueryFlags[flag] = true 28 | queryFlags = {} 29 | commandArgs = 30 | for arg in commandArgs 31 | if arg of validQueryFlags 32 | queryFlags[arg] = true 33 | continue 34 | # Use a leading "-" or "!" to negate the test; e.g. "-audible" or "!active". 35 | else if arg[0] in ["-", "!"] and arg[1..] of validQueryFlags 36 | queryFlags[arg[1..]] = false 37 | continue 38 | # For symmetry, we also allow "+"; e.g. "+audible". 39 | else if arg[0] in ["+"] and arg[1..] of validQueryFlags 40 | queryFlags[arg[1..]] = true 41 | continue 42 | else 43 | arg 44 | [ commandArgs, queryFlags ] 45 | 46 | # Filter tabs by the remaining command-line arguements. We require a match in either the URL or the title. 47 | # If the argument is a bare number, then we require it to match the tab Id. 48 | filterTabs = do -> 49 | integerRegex = /^\d+$/ 50 | 51 | (commandArgs, tabs) -> 52 | for tab in tabs 53 | continue unless do -> 54 | for arg in commandArgs 55 | if integerRegex.test(arg) and tab.id == parseInt arg 56 | continue 57 | else if integerRegex.test arg 58 | return false 59 | else if tab.url.indexOf(arg) == -1 and tab.title.indexOf(arg) == -1 60 | return false 61 | true 62 | tab 63 | 64 | # Return an array of tabs matching the flags and other arguments on the command line. 65 | getMatchingTabs = (commandArgs, callback) -> 66 | [ commandArgs, queryFlags ] = getQueryFlags commandArgs 67 | chromix "chrome.tabs.query", {}, queryFlags, (tabs) -> 68 | tabs = filterTabs commandArgs, tabs 69 | process.exit 1 if tabs.length == 0 70 | callback tabs 71 | 72 | focusWindow = (windowId) -> 73 | chromix "chrome.windows.update", {}, windowId, {focused: true}, -> 74 | 75 | switch commandName 76 | when "ls", "list", "tabs" 77 | getMatchingTabs commandArgs, (tabs) -> 78 | console.log "#{tab.id} #{tab.url} #{tab.title}" for tab in tabs 79 | 80 | when "tid" # Like "ls", but outputs only the tab Id of the matching tabs. 81 | getMatchingTabs commandArgs, (tabs) -> 82 | console.log "#{tab.id}" for tab in tabs 83 | 84 | when "focus", "activate" 85 | getMatchingTabs commandArgs, (tabs) -> 86 | for tab in tabs 87 | chromix "chrome.tabs.update", {}, tab.id, {selected: true} 88 | focusWindow tab.windowId 89 | 90 | when "select" 91 | getMatchingTabs commandArgs, (tabs) -> 92 | for tab in tabs 93 | chromix "chrome.tabs.update", {}, tab.id, {selected: true} 94 | 95 | when "reload" 96 | getMatchingTabs commandArgs, (tabs) -> 97 | chromix "chrome.tabs.reload", {}, tab.id, {} for tab in tabs 98 | 99 | when "url" 100 | [url, commandArgs...] = commandArgs 101 | getMatchingTabs commandArgs, (tabs) -> 102 | chromix "chrome.tabs.update", {}, tab.id, {url} for tab in tabs 103 | 104 | when "rm", "remove", "close" 105 | getMatchingTabs commandArgs, (tabs) -> 106 | chromix "chrome.tabs.remove", {}, tab.id for tab in tabs 107 | 108 | when "open", "create" 109 | for arg in commandArgs 110 | do (arg) -> 111 | chromix "chrome.tabs.create", {}, {url: arg}, (tab) -> 112 | focusWindow tab.windowId 113 | console.log "#{tab.id} #{tab.url}" 114 | 115 | when "ping" 116 | chromix "ping", {}, (response) -> 117 | if response == "ok" 118 | process.exit 0 119 | else 120 | process.exit 1 121 | 122 | when "file" 123 | for arg in commandArgs 124 | url = if arg.indexOf("file://") == 0 then arg else "file://#{require("path").resolve arg}" 125 | 126 | do (url) -> 127 | getMatchingTabs [], (tabs) -> 128 | tabs = (t for t in tabs when t.url.indexOf(url) == 0) 129 | if tabs.length == 0 130 | chromix "chrome.tabs.create", {}, {url: url}, (tab) -> 131 | focusWindow tab.windowId 132 | console.log "#{tab.id} #{tab.url}" 133 | else 134 | for tab in tabs 135 | do (tab) -> 136 | chromix "chrome.tabs.update", {}, tab.id, {selected: true}, -> 137 | chromix "chrome.tabs.reload", {}, tab.id, {}, -> 138 | focusWindow tab.windowId 139 | 140 | when "raw", "josn" 141 | args = 142 | for arg in commandArgs[1..] 143 | try 144 | JSON.parse arg 145 | catch 146 | arg 147 | chromix commandArgs[0], {}, args..., (response) -> 148 | console.log JSON.stringify response 149 | 150 | else 151 | console.error "error: unknown command: #{commandName}" 152 | process.exit 2 153 | -------------------------------------------------------------------------------- /example/example.coffee: -------------------------------------------------------------------------------- 1 | 2 | chromix = require("chromix-too")().chromix 3 | 4 | chromix "chrome.storage.local.set", {}, {pi: 3.141}, -> 5 | chromix "chrome.storage.local.get", {}, "pi", (response) -> 6 | console.log response.pi 7 | 8 | # If the unix-domain socket were not in the default localtion, then use something like: 9 | # 10 | # chromix = require("chromix-too")("/var/run/chromix-too.sock").chromix 11 | -------------------------------------------------------------------------------- /extension/.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | -------------------------------------------------------------------------------- /extension/Makefile: -------------------------------------------------------------------------------- 1 | 2 | src = $(wildcard *.coffee) 3 | js = $(src:.coffee=.js) 4 | 5 | build: $(js) 6 | @true 7 | 8 | auto: 9 | coffee -cw . 10 | 11 | %.js: %.coffee 12 | coffee -c $< 13 | 14 | .PHONY: build auto package 15 | 16 | pkg = chromix-too.zip 17 | 18 | package: 19 | ! test -f $(pkg) || rm -vf $(pkg) 20 | $(MAKE) build 21 | zip chromix-too.zip \ 22 | manifest.json \ 23 | utils.js \ 24 | background.js \ 25 | foreground.js 26 | -------------------------------------------------------------------------------- /extension/background.coffee: -------------------------------------------------------------------------------- 1 | # TODO: 2 | # - Add a config page to set port (and possibly host) of the server. 3 | 4 | handleRequest = (sock) -> ({data}) -> 5 | request = JSON.parse data 6 | request.args ?= [] 7 | {path} = request 8 | 9 | if not path? 10 | sock.send JSON.stringify extend request, error: "requests does not contain a path" 11 | 12 | else if path == "ping" 13 | sock.send JSON.stringify extend request, response: ["ok"], error: false 14 | 15 | else 16 | obj = window 17 | for property in path.split "." 18 | try 19 | obj = obj[property] 20 | catch 21 | sock.send JSON.stringify extend request, error: "incorrect path: #{path}" 22 | return 23 | 24 | switch typeof obj 25 | when "function" 26 | obj request.args..., (response...) -> 27 | sock.send JSON.stringify extend request, response: response, error: false 28 | else 29 | sock.send JSON.stringify extend request, response: [obj], error: false 30 | 31 | reTryConnect = -> 32 | console.log "disconnected, retry connection in #{config.timeout}ms..." 33 | setTimeout tryConnect, config.timeout 34 | 35 | tryConnect = -> 36 | reTryFunction = makeIdempotent reTryConnect 37 | try 38 | url = "ws://#{config.host}:#{config.port}/" 39 | sock = new WebSocket url 40 | catch 41 | reTryFunction() 42 | sock.onerror = sock.onclose = reTryFunction 43 | sock.onmessage = handleRequest sock 44 | console.log "connected: #{url}" 45 | 46 | tryConnect() 47 | 48 | -------------------------------------------------------------------------------- /extension/foreground.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smblott-github/chromix-too/332c04ffcadd3c8188f4ad3334159c5935a42732/extension/foreground.coffee -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chromix-Too", 3 | "version": "0.0.2", 4 | "manifest_version": 2, 5 | "description": "Command-line or scripted access to chrome's extension API.", 6 | 7 | "permissions": [ 8 | "tabs", 9 | "bookmarks", 10 | "history", 11 | "clipboardRead", 12 | "storage", 13 | "sessions", 14 | "notifications", 15 | "webNavigation", 16 | "" 17 | ], 18 | 19 | "background": { "scripts": 20 | [ 21 | "utils.js", 22 | "background.js" 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /extension/utils.coffee: -------------------------------------------------------------------------------- 1 | root = exports ? window 2 | 3 | extend = root.extend = (hash1, hash2) -> 4 | hash1[key] = hash2[key] for own key of hash2 5 | hash1 6 | 7 | extend root, 8 | config: 9 | host: "localhost" 10 | port: "7442" 11 | timeout: 5000 12 | 13 | makeIdempotent: (func) -> 14 | (args...) -> ([previousFunc, func] = [func, null])[0]? args... 15 | -------------------------------------------------------------------------------- /misc/Makefile: -------------------------------------------------------------------------------- 1 | 2 | install: $(HOME)/.zsh_completion/_chromix-too 3 | @true 4 | 5 | $(HOME)/.zsh_completion/_chromix-too: _chromix-too 6 | install -v -m 0444 $< $@ 7 | 8 | .PHONY: install 9 | -------------------------------------------------------------------------------- /misc/_chromix-too: -------------------------------------------------------------------------------- 1 | #compdef chromix-too 2 | 3 | if [[ $CURRENT == 2 ]] 4 | then 5 | local -a options 6 | options+=( "ls[(filter and) list tabs]" ) 7 | options+=( "tid[(filter and) list tab identifiers]" ) 8 | options+=( "focus[(filter and) focus tabs]" ) 9 | options+=( "reload[(filter and) reload tabs]" ) 10 | options+=( "rm[(filter and) remove tabs]" ) 11 | options+=( "open[create new tabs]" ) 12 | options+=( "file[open (and possibly reload) files]" ) 13 | options+=( "ping[verify that server and extension are running]" ) 14 | _values 'commands' $options 15 | 16 | elif (( 2 < $CURRENT )) 17 | then 18 | case $words[2] in 19 | file ) _path_files ;; 20 | esac 21 | fi 22 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chromix-too", 3 | "version": "0.0.9", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async-limiter": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 10 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 11 | }, 12 | "minimist": { 13 | "version": "0.0.10", 14 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", 15 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" 16 | }, 17 | "optimist": { 18 | "version": "0.6.1", 19 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 20 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 21 | "requires": { 22 | "minimist": "0.0.10", 23 | "wordwrap": "0.0.3" 24 | } 25 | }, 26 | "wordwrap": { 27 | "version": "0.0.3", 28 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 29 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" 30 | }, 31 | "ws": { 32 | "version": "5.1.1", 33 | "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", 34 | "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", 35 | "requires": { 36 | "async-limiter": "1.0.0" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chromix-too", 3 | "version": "0.0.15", 4 | "author": "Stephen Blott ", 5 | "description": "Command-line (or scripted) access to Chrome's APIs.", 6 | "homepage": "https://github.com/smblott-github/chromix-too", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/smblott-github/chromix-too/issues" 10 | }, 11 | "licenses": [ 12 | { 13 | "type": "MIT" 14 | } 15 | ], 16 | "engines": { 17 | "node": ">=0.4" 18 | }, 19 | "keywords": [ 20 | "chrome", 21 | "chromix", 22 | "extension", 23 | "cli", 24 | "command", 25 | "line", 26 | "linux" 27 | ], 28 | "dependencies": { 29 | "ws": "*", 30 | "optimist": "*" 31 | }, 32 | "main": "chromix-too.js", 33 | "bin": { 34 | "chromix-too-server": "server.js", 35 | "chromix-too": "client.js" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server.coffee: -------------------------------------------------------------------------------- 1 | `#!/usr/bin/env node 2 | ` 3 | utils = require "./utils.js" 4 | utils.extend global, utils 5 | 6 | optimist = require "optimist" 7 | args = optimist.usage("Usage: $0 [--port=PORT] [--host=ADDRESS] [--sock=PATH] [--mode=MODE]") 8 | .alias("h", "help") 9 | .default("port", config.port) 10 | .default("host", config.host) 11 | .default("sock", config.sock) 12 | .default("mode", config.mode) 13 | .argv 14 | 15 | if args.help 16 | optimist.showHelp() 17 | process.exit 0 18 | 19 | responseHandlers = {} 20 | webSock = null 21 | 22 | WSS = require("ws").Server 23 | wss = new WSS port: args.port, host: args.host 24 | wss.on "connection", (ws) -> 25 | console.log "#{new Date().toString()}: websocket client connected" 26 | webSock = ws 27 | ws.on "message", (msg) -> 28 | responseHandlers[JSON.parse(msg).clientId]? msg 29 | 30 | uniqueId = Math.floor(2000000000 * Math.random()).toString() 31 | clientId = 0 32 | 33 | handleWebsocketError = (request) -> 34 | responseHandlers[request.clientId]? JSON.stringify extend request, error: "websocket is not connected" 35 | responseHandlers = {} 36 | webSock = null 37 | 38 | server = require("net").createServer (sock) -> 39 | clientId += 1 40 | myClientId = "#{uniqueId}-#{clientId}" 41 | responseHandlers[myClientId] = sock.write.bind sock 42 | 43 | sock.on "data", (data) -> 44 | try 45 | request = JSON.parse data 46 | catch 47 | console.error "failed to parse JSON: #{data}" 48 | try 49 | extend request, clientId: myClientId 50 | webSock.send JSON.stringify(request), (err) -> 51 | handleWebsocketError request if err 52 | catch 53 | handleWebsocketError request 54 | 55 | sock.on "close", -> 56 | delete responseHandlers[myClientId] 57 | 58 | require("fs").unlink args.sock, -> 59 | server.listen args.sock, -> 60 | require("fs").chmod args.sock, args.mode, -> 61 | console.log "listening on: #{args.sock}" 62 | -------------------------------------------------------------------------------- /utils.coffee: -------------------------------------------------------------------------------- 1 | 2 | utils = require "./extension/utils" 3 | utils.extend exports, utils 4 | 5 | utils.extend exports.config, 6 | sock: require("path").join process.env["HOME"], ".chromix-too.sock" 7 | mode: "0600" 8 | --------------------------------------------------------------------------------