├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── app.rb ├── config.ru ├── public └── script │ └── app.js └── views └── index.haml /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /lib/bundler/man/ 26 | 27 | # for a library or gem, you might want to ignore these files since the code is 28 | # intended to run in multiple environments; otherwise, check them in: 29 | # Gemfile.lock 30 | # .ruby-version 31 | # .ruby-gemset 32 | 33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 34 | .rvmrc 35 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '2.3.1' 3 | 4 | gem 'sinatra', '~> 1.4.7' 5 | gem 'sinatra-websocket', '~> 0.3.1' 6 | gem 'haml', '~> 4.0.7' 7 | gem 'foreman', '~> 0.81.0' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.3.7) 5 | daemons (1.1.9) 6 | em-websocket (0.3.8) 7 | addressable (>= 2.1.1) 8 | eventmachine (>= 0.12.9) 9 | eventmachine (1.0.7) 10 | foreman (0.81.0) 11 | thor (~> 0.19.1) 12 | haml (4.0.7) 13 | tilt 14 | rack (1.6.0) 15 | rack-protection (1.5.3) 16 | rack 17 | sinatra (1.4.7) 18 | rack (~> 1.5) 19 | rack-protection (~> 1.4) 20 | tilt (>= 1.3, < 3) 21 | sinatra-websocket (0.3.1) 22 | em-websocket (~> 0.3.6) 23 | eventmachine 24 | thin (>= 1.3.1, < 2.0.0) 25 | thin (1.6.3) 26 | daemons (~> 1.0, >= 1.0.9) 27 | eventmachine (~> 1.0) 28 | rack (~> 1.0) 29 | thor (0.19.1) 30 | tilt (2.0.2) 31 | 32 | PLATFORMS 33 | ruby 34 | 35 | DEPENDENCIES 36 | foreman (~> 0.81.0) 37 | haml (~> 4.0.7) 38 | sinatra (~> 1.4.7) 39 | sinatra-websocket (~> 0.3.1) 40 | 41 | RUBY VERSION 42 | ruby 2.3.1p112 43 | 44 | BUNDLED WITH 45 | 1.12.3 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Factor.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | service: bundle exec rackup -p $PORT -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebSocketHook.io 2 | [](https://codeclimate.com/github/factor-io/websockethook) 3 | [](https://gemnasium.com/factor-io/websockethook) 4 | 5 | A simple web service to receive web hooks over a web socket. 6 | 7 | [](https://heroku.com/deploy) 8 | 9 | This is a Sinatra based web service. When you run it, you can connect to it using any standard Web Socket client, and register a web hook. When you perform a POST on that newly created web hook, you will receive a message via the web socket. 10 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "websockethook", 3 | "description": "A simple web service to receive web hooks over a web socket.", 4 | "keywords": ["hook", "websocket"], 5 | "addons": [], 6 | "env": {} 7 | } 8 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'sinatra-websocket' 3 | require 'json' 4 | require 'haml' 5 | 6 | set :server, 'thin' 7 | set :sockets, {} 8 | 9 | helpers do 10 | def logger 11 | request.logger 12 | end 13 | end 14 | 15 | def register(ws, id) 16 | socket_info = { 17 | id: id, 18 | path: "/hook/#{id}" 19 | } 20 | settings.sockets[ws] ||= [] 21 | settings.sockets[ws] << socket_info 22 | message_data = { 23 | type: 'registered', 24 | data: socket_info 25 | } 26 | 27 | message = message_data.to_json 28 | logger.info "sending: #{message}" 29 | ws.send message 30 | socket_info 31 | end 32 | 33 | def unregister(ws, id) 34 | end 35 | 36 | get '/' do 37 | if !request.websocket? 38 | haml :index 39 | else 40 | logger.info "websocket initializing" 41 | request.websocket do |ws| 42 | ws.onopen do 43 | id = SecureRandom.hex(8) 44 | register(ws, id) 45 | end 46 | 47 | ws.onmessage do |message| 48 | logger.info "received: #{message}" 49 | data = JSON.parse(message) 50 | if data['type']=='register' && data['id'] 51 | if /^\w+$/ === data['id'] 52 | register(ws,data['id']) 53 | else 54 | ws.send({type:'error', message:'ID must be a letter, number, or underscore'}.to_json) 55 | end 56 | elsif data['type']=='unregister' && data['id'] 57 | unregister(ws,data['id']) 58 | else 59 | ws.send({type:'error', message:'No such command'}.to_json) 60 | end 61 | end 62 | 63 | ws.onclose do 64 | logger.warn "websocket closed" 65 | settings.sockets.delete(ws) 66 | end 67 | end 68 | end 69 | end 70 | 71 | post '/hook/:id' do 72 | id = params[:id] 73 | sockets = settings.sockets.select {|ws,hooks| hooks.any?{|hook| hook[:id] == id}} 74 | halt 404 unless sockets.count > 0 75 | 76 | ['splat','captures','id'].each {|k| params.delete k} 77 | 78 | message_data = { 79 | type: 'hook', 80 | id: id, 81 | data: params 82 | } 83 | message = message_data.to_json 84 | logger.info "sending: #{message}" 85 | sockets.keys.each { |ws| ws.send message } 86 | {}.to_json 87 | end -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require './app' 2 | 3 | run Sinatra::Application -------------------------------------------------------------------------------- /public/script/app.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var ws = new WebSocket(window.location.protocol.replace('http','ws') + '//' + window.location.host + window.location.pathname); 3 | 4 | 5 | var status = function(type,message){ 6 | var close_button = ''; 7 | $('#alerts').append("