Permalink
Browse files

Add a chat demo using Flask (Python) (#32)

* Add a chat example using Flask (Python)

* Deploy the chat demo on Heroku

* Use innerHTML
  • Loading branch information...
dunglas committed Nov 27, 2018
1 parent 13fa88e commit b3dd0b39eca3f1a6cac58f18a68c502affa90413
4 .env
@@ -4,12 +4,12 @@ ADDR=:3001
ALLOW_ANONYMOUS=1
CERT_FILE=
CERT_KEY=
CORS_ALLOWED_ORIGINS=
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8000
DB_PATH=
DEBUG=1
DEMO=
JWT_KEY=!UnsecureChangeMe!
LOG_FORMAT=JSON
PUBLISH_ALLOWED_ORIGINS=http://localhost:3000
PUBLISH_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8000
PUBLISHER_JWT_KEY=
SUBSCRIBER_JWT_KEY=
@@ -7,3 +7,5 @@
/spec/mercure.html
/spec/mercure.txt
/spec/mercure.xml
__pycache__
*.pyc
@@ -43,7 +43,7 @@ The reference hub implementation:
* Cloud Native, follows [the Twelve-Factor App](https://12factor.net) methodology
* Open source (AGPL)
# Examples
## Examples
Example implementation of a client (the subscriber), in JavaScript:
@@ -110,7 +110,11 @@ req.end();
The JWT must contain a `publish` property containing an array of targets. This array can be empty to allow publishing anonymous updates only. [Example publisher JWT](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.LRLvirgONK13JgacQ_VbcjySbVhkSmHy3IznH3tA9PM) (demo key: `!UnsecureChangeMe!`).
Examples in other languages are available in [the `examples/` directory](examples/).
### Other Examples
* [Python/Flask: chat application](examples/chat-python-flask) ([live demo](https://python-chat.mercure.rocks/))
* [Node.js: publishing](examples/publisher-node.js)
* [PHP: publishing](examples/publisher-php.php)
## Use Cases
@@ -0,0 +1 @@
web: gunicorn chat:app
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
"""A minimalist chat system, using the Flask microframework to handle cookie authentication
Install:
pip install -r requirements.txt
Run (prod):
gunicorn chat:app
Run (dev):
FLASK_APP=chat.py FLASK_DEBUG=1 flask run
Deploy on Heroku:
heroku login
heroku config:set HUB_URL=https://demo.mercure.rocks/hub
heroku config:set COOKIE_DOMAIN=.mercure.rocks
git subtree push --prefix examples/chat-python-flask heroku master
Environment variables:
JWT_KEY: the JWT key to use (must be shared with the Mercure hub)
HUB_URL: the URL of the Mercure hub (default: http://localhost:3000/hub)
TOPIC: the topic to use (default: http://example.com/chat)
TARGET: the target to use (default: chan)
COOKIE_DOMAIN: the cookie domain (default: None)
"""
from flask import Flask, make_response, request, render_template
import jwt
import os
app = Flask(__name__)
@app.route("/")
def chat():
targets = [os.environ.get('TARGET', 'chan')]
token = jwt.encode(
{'mercure': {'subscribe': targets, 'publish': targets}},
os.environ.get('JWT_KEY', '!UnsecureChangeMe!'),
algorithm='HS256'
)
hub_url = os.environ.get('HUB_URL', 'http://localhost:3000/hub')
topic = os.environ.get('HUB_URL', 'http://example.com/chat')
resp = make_response(render_template('chat.html', config={
'hubURL': hub_url, 'topic': topic}))
resp.set_cookie('mercureAuthorization', token, httponly=True, path='/hub',
samesite="strict", domain=os.environ.get('COOKIE_DOMAIN', None), secure=request.is_secure) # Force secure to True for real apps
return resp
@@ -0,0 +1,3 @@
Flask==1.0.2
PyJWT==1.6.4
gunicorn==19.9.0
@@ -0,0 +1,35 @@
const { hubURL, topic } = JSON.parse(document.getElementById('config').textContent)
const subscribeURL = new URL(hubURL)
subscribeURL.searchParams.append('topic', topic)
const es = new EventSource(subscribeURL, { withCredentials: true })
let ul = null
es.onmessage = ({ data }) => {
const { username, message } = JSON.parse(data)
if (!username || !message) throw new Error('Invalid payload')
if (!ul) {
ul = document.createElement('ul')
const messages = document.getElementById('messages')
messages.innerHTML = ''
messages.append(ul)
}
const li = document.createElement('li')
li.append(document.createTextNode(`<${username}> ${message}`))
ul.append(li)
}
document.querySelector('form').onsubmit = function (e) {
e.preventDefault()
const body = new URLSearchParams({
data: JSON.stringify({ username: this.elements.username.value, message: this.elements.message.value }),
topic,
})
fetch(hubURL, { method: 'POST', body, credentials: 'include' })
this.elements.message.value = ''
this.elements.message.focus()
}
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>A chat using Mercure</title>
</head>
<body>
<h1>This is a chat using <a href="https://mercure.rocks">Mercure</a></h1>
<form>
<input name="username" placeholder="username" size="10">
<input name="message" placeholder="Your message..." size="50" autocomplete="off">
<input type="submit" value="Post">
</form>
<hr>
<div id="messages">
No messages
</div>
<script type="application/json" id="config">
{{ config|tojson|safe }}
</script>
<script src="/static/chat.js"></script>
</body>
</html>

0 comments on commit b3dd0b3

Please sign in to comment.