Skip to content

Commit 201a34b

Browse files
committed
Update from master
0 parents  commit 201a34b

25 files changed

+1358
-0
lines changed

.babelrc

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"presets": [
3+
["env", {
4+
"targets": {
5+
"browsers": [
6+
"last 2 versions",
7+
"ios_saf >= 8",
8+
"ie >= 10",
9+
"chrome >= 49",
10+
"firefox >= 49",
11+
"> 1%"
12+
]
13+
},
14+
"debug": false,
15+
"loose": true,
16+
"useBuiltIns": true
17+
}],
18+
"react"
19+
],
20+
"plugins": [
21+
"transform-class-properties",
22+
[
23+
"transform-object-rest-spread",
24+
{ "useBuiltIns": true }
25+
]
26+
]
27+
}

.eslintignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.min.js
2+
coverage

.eslintrc

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"parser": "babel-eslint",
3+
"extends": ["standard", "standard-react"],
4+
"rules": {
5+
"jsx-quotes": 0
6+
},
7+
"env": {
8+
"jest": true
9+
}
10+
}

index.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const app = require('./server')()
2+
const port = process.env.PORT || 3000
3+
app.listen(port, () => {
4+
console.log('Listening at http://localhost:' + port)
5+
})

keep-alive.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = class KeepAlive {
2+
constructor (callback, delay) {
3+
this.callback = callback
4+
this.delay = delay
5+
}
6+
7+
start () {
8+
this.id = setInterval(this.callback, this.delay)
9+
}
10+
11+
stop () {
12+
clearInterval(this.id)
13+
}
14+
15+
reset () {
16+
this.stop()
17+
this.start()
18+
}
19+
}

package.json

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"name": "smee-server",
3+
"version": "0.0.3",
4+
"description": "",
5+
"author": "Jason Etcovitch <[email protected]> (https://github.com/JasonEtco)",
6+
"license": "ISC",
7+
"repository": "https://github.com/probot/smee.git",
8+
"scripts": {
9+
"start": "node ./index.js",
10+
"start-dev": "concurrently \"nodemon --ignore src/ ./index.js\" \"webpack -w\"",
11+
"build": "webpack -p",
12+
"test": "jest --coverage && eslint",
13+
"postinstall": "npm run build"
14+
},
15+
"dependencies": {
16+
"autoprefixer": "^7.1.6",
17+
"babel-core": "^6.26.0",
18+
"babel-eslint": "^7.2.3",
19+
"babel-loader": "^7.1.2",
20+
"babel-plugin-transform-class-properties": "^6.24.1",
21+
"babel-plugin-transform-object-rest-spread": "^6.26.0",
22+
"babel-preset-env": "^1.6.1",
23+
"babel-preset-react": "^6.24.1",
24+
"connect-sse": "^1.2.0",
25+
"copy-to-clipboard": "^3.0.8",
26+
"copy-webpack-plugin": "^4.2.0",
27+
"crypto": "^1.0.1",
28+
"css-loader": "^0.28.7",
29+
"eventsource": "^1.0.5",
30+
"express": "^4.16.2",
31+
"express-sslify": "^1.2.0",
32+
"extract-text-webpack-plugin": "^3.0.1",
33+
"get-value": "^2.0.6",
34+
"glob-all": "^3.1.0",
35+
"helmet": "^3.9.0",
36+
"highlight.js": "^9.12.0",
37+
"html-webpack-plugin": "^2.30.1",
38+
"moment": "^2.19.1",
39+
"moment-timezone": "^0.5.14",
40+
"node-sass": "^4.5.3",
41+
"postcss-loader": "^2.0.8",
42+
"primer-css": "^9.6.0",
43+
"prop-types": "^15.6.0",
44+
"purify-css": "^1.2.5",
45+
"purifycss-webpack": "^0.7.0",
46+
"react": "^16.0.0",
47+
"react-dom": "^16.0.0",
48+
"react-json-view": "^1.13.2",
49+
"react-octicons": "^0.2.0",
50+
"sass-loader": "^6.0.6",
51+
"style-loader": "^0.19.0",
52+
"webpack": "^3.8.1"
53+
},
54+
"devDependencies": {
55+
"babel-jest": "^21.2.0",
56+
"concurrently": "^3.5.0",
57+
"enzyme": "^3.2.0",
58+
"enzyme-adapter-react-16": "^1.1.0",
59+
"eslint-config-standard": "^10.2.1",
60+
"eslint-config-standard-react": "^5.0.0",
61+
"eslint-plugin-import": "^2.8.0",
62+
"eslint-plugin-node": "^5.2.1",
63+
"eslint-plugin-promise": "^3.6.0",
64+
"eslint-plugin-react": "^7.5.1",
65+
"eslint-plugin-standard": "^3.0.1",
66+
"jest": "^21.2.1",
67+
"nodemon": "^1.12.1",
68+
"raf": "^3.4.0",
69+
"react-test-renderer": "^16.2.0",
70+
"standard": "^10.0.3",
71+
"supertest": "^3.0.0"
72+
},
73+
"engines": {
74+
"node": "8.9.1"
75+
},
76+
"jest": {
77+
"setupFiles": [
78+
"./tests/setup.js"
79+
],
80+
"testPathIgnorePatterns": [
81+
"/node_modules/"
82+
]
83+
}
84+
}

public/favicon.png

4.65 KB
Loading

public/index.html

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<title>smee.io</title>
8+
<link rel="shortcut icon" href="/public/favicon.png">
9+
<link rel="stylesheet" href="/public/main.min.css">
10+
</head>
11+
<body>
12+
<header class="text-white text-center p-responsive bg-blue d-flex flex-column flex-items-center flex-justify-center" style="height: 80vh">
13+
<div class="header__anim">
14+
<div class="header__anim__payload"></div>
15+
<div class="header__anim__line"></div>
16+
<div class="header__anim__circle header__anim__center"></div>
17+
<div class="header__anim__circle header__anim__dashed-circle"></div>
18+
</div>
19+
<h1 class="f00-light">smee.io</h1>
20+
<h2 class="blue-700">Webhook payload delivery service</h2>
21+
<p class="lead text-white" style="opacity: 0.8">Receives payloads then sends them to your locally running application.</p>
22+
<a href="/new" class="btn btn-outline btn-outline-blue">Start a new channel</a>
23+
</header>
24+
25+
<main class="container-lg py-6 mt-6 p-responsive">
26+
<p class="lead text-center col-12 col-md-8 mx-auto">If your application needs to respond to webhooks, you'll need some way to expose <strong>localhost</strong> to the internet. <strong>smee.io</strong> is a small service that uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events" target="_blank" rel="noopener noreferrer">Server-Sent Events</a> to proxy payloads from the webhook source, then transmit them to your locally running application.</p>
27+
28+
<div class="main__anim my-6">
29+
<div class="main__anim__circle main__anim__left">
30+
<span>Webhook Emitter</span>
31+
</div>
32+
<div class="main__anim__circle main__anim__right">
33+
<span>localhost</span>
34+
</div>
35+
<div class="main__anim__line"></div>
36+
<div class="main__anim__payload"></div>
37+
<div class="main__anim__circle main__anim__center">
38+
<span></span>
39+
</div>
40+
</div>
41+
42+
<p class="lead text-center col-12 col-md-8 mx-auto">Tell your webhook source to send payloads to your <strong>smee.io</strong> channel, then either use the <a href="https://npmjs.com/package/smee-cli" target="_blank" rel="noopener noreferrer">smee CLI</a> or, if you're using <a href="https://probot.github.io" target="_blank" rel="noopener noreferrer">Probot</a> to build a GitHub App, just <a href="https://probot.github.io/docs/webhooks/">set the environment variable</a>.</p>
43+
</main>
44+
45+
<footer class="border-top text-center py-4">
46+
Made with 🤖 by the <a href="https://github.com/probot" target="_blank" rel="noopener noreferrer">Probot team</a>.
47+
</footer>
48+
</body>
49+
</html>

public/webhooks.html

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<title>Webhooks</title>
8+
<link rel="shortcut icon" href="/public/favicon.png">
9+
<link rel="stylesheet" href="/public/main.min.css">
10+
</head>
11+
<body>
12+
<noscript>
13+
<div class="no-script">
14+
<h1>You must enable JavaScript to use try out todo.</h1>
15+
<a href="https://github.com/probot/webhooks/issues" target="_blank" rel="noopener noreferrer">Need help?</a>
16+
</div>
17+
</noscript>
18+
<div class="mount"></div>
19+
<script src="/public/main.min.js"></script>
20+
</body>
21+
</html>

server.js

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
const sse = require('connect-sse')()
2+
const express = require('express')
3+
const crypto = require('crypto')
4+
const bodyParser = require('body-parser')
5+
const EventEmitter = require('events')
6+
const path = require('path')
7+
8+
const KeepAlive = require('./keep-alive')
9+
10+
// Tiny logger to prevent logs in tests
11+
const log = process.env.NODE_ENV === 'test' ? _ => _ : console.log
12+
13+
module.exports = () => {
14+
const events = new EventEmitter()
15+
const app = express()
16+
const pubFolder = path.join(__dirname, 'public')
17+
18+
if (process.env.FORCE_HTTPS) {
19+
app.use(require('helmet')())
20+
app.use(require('express-sslify').HTTPS({ trustProtoHeader: true }))
21+
}
22+
23+
app.use(bodyParser.json())
24+
app.use('/public', express.static(pubFolder))
25+
26+
app.get('/', (req, res) => {
27+
res.sendFile(path.join(pubFolder, 'index.html'))
28+
})
29+
30+
app.get('/new', (req, res) => {
31+
const protocol = req.headers['x-forwarded-proto'] || req.protocol
32+
const host = req.headers['x-forwarded-host'] || req.get('host')
33+
const channel = crypto
34+
.randomBytes(12)
35+
.toString('base64')
36+
.replace(/\+/g, '-')
37+
.replace(/\//g, '_')
38+
.replace(/=/g, '~')
39+
40+
res.redirect(307, `${protocol}://${host}/${channel}`)
41+
})
42+
43+
app.get('/:channel', (req, res, next) => {
44+
if (req.accepts('html')) {
45+
res.sendFile(path.join(pubFolder, 'webhooks.html'))
46+
} else {
47+
next()
48+
}
49+
}, sse, (req, res) => {
50+
function send (data) {
51+
res.json(data)
52+
keepAlive.reset()
53+
}
54+
55+
function close () {
56+
events.removeListener(channel, send)
57+
keepAlive.stop()
58+
log('Client disconnected', channel, events.listenerCount(channel))
59+
}
60+
61+
const channel = req.params.channel
62+
63+
// Setup interval to ping every 30 seconds to keep the connection alive
64+
const keepAlive = new KeepAlive(() => res.json({}, 'ping'), 30 * 1000)
65+
keepAlive.start()
66+
67+
// Allow CORS
68+
res.setHeader('Access-Control-Allow-Origin', '*')
69+
70+
// Listen for events on this channel
71+
events.on(channel, send)
72+
73+
// Clean up when the client disconnects
74+
res.on('close', close)
75+
76+
res.json({}, 'ready')
77+
78+
log('Client connected', channel, events.listenerCount(channel))
79+
})
80+
81+
app.post('/:channel', (req, res) => {
82+
events.emit(req.params.channel, {
83+
...req.headers,
84+
body: req.body,
85+
timestamp: Date.now()
86+
})
87+
res.status(200).end()
88+
})
89+
90+
// Resend payload via the event emitter
91+
app.post('/:channel/redeliver', (req, res) => {
92+
events.emit(req.params.channel, req.body)
93+
res.status(200).end()
94+
})
95+
96+
return app
97+
}

0 commit comments

Comments
 (0)