tIntegrate http_server (previously in electrum-merchant) Use submodule to fetch HTML and CSS files - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit 747ab7a0a255f183e61feded3f4a0ec804fbde35
 (DIR) parent bd578807997ee6dc13e6a5e996d7c3eb68c01af0
 (HTM) Author: ThomasV <thomasv@electrum.org>
       Date:   Tue,  3 Sep 2019 14:44:33 +0200
       
       Integrate http_server (previously in electrum-merchant)
       Use submodule to fetch HTML and CSS files
       
       Diffstat:
         M .gitmodules                         |       3 +++
         M electrum/commands.py                |       1 -
         M electrum/daemon.py                  |      79 +++++++++++++++++++++++++++++++
         M electrum/gui/qt/invoice_list.py     |       2 +-
         M electrum/gui/qt/main_window.py      |       5 ++---
         M electrum/gui/qt/request_list.py     |      63 +++++++++++++------------------
         M electrum/wallet.py                  |      65 ++++++-------------------------
         D electrum/websockets.py              |     132 -------------------------------
         A electrum/www                        |       1 +
         M run_electrum                        |       9 ---------
       
       10 files changed, 123 insertions(+), 237 deletions(-)
       ---
 (DIR) diff --git a/.gitmodules b/.gitmodules
       t@@ -4,3 +4,6 @@
        [submodule "contrib/CalinsQRReader"]
                path = contrib/osx/CalinsQRReader
                url = https://github.com/spesmilo/CalinsQRReader
       +[submodule "electrum/www"]
       +        path = electrum/www
       +        url = git@github.com:spesmilo/electrum-http.git
 (DIR) diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -1039,7 +1039,6 @@ arg_types = {
        config_variables = {
        
            'addrequest': {
       -        'requests_dir': 'directory where a bip70 file will be written.',
                'ssl_privkey': 'Path to your SSL private key, needed to sign the request.',
                'ssl_chain': 'Chain of SSL certificates, needed for signed requests. Put your certificate at the top and the root CA at the end',
                'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
 (DIR) diff --git a/electrum/daemon.py b/electrum/daemon.py
       t@@ -33,6 +33,7 @@ from typing import Dict, Optional, Tuple
        import aiohttp
        from aiohttp import web
        from base64 import b64decode
       +from collections import defaultdict
        
        import jsonrpcclient
        import jsonrpcserver
       t@@ -41,6 +42,7 @@ from jsonrpcclient.clients.aiohttp_client import AiohttpClient
        
        from .network import Network
        from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare)
       +from .util import PR_PAID, PR_EXPIRED, get_request_status
        from .wallet import Wallet, Abstract_Wallet
        from .storage import WalletStorage
        from .commands import known_commands, Commands
       t@@ -168,6 +170,79 @@ class WatchTowerServer(Logger):
            async def add_sweep_tx(self, *args):
                return await self.lnwatcher.sweepstore.add_sweep_tx(*args)
        
       +class HttpServer(Logger):
       +
       +    def __init__(self, daemon):
       +        Logger.__init__(self)
       +        self.daemon = daemon
       +        self.config = daemon.config
       +        self.pending = defaultdict(asyncio.Event)
       +        self.daemon.network.register_callback(self.on_payment, ['payment_received'])
       +
       +    async def on_payment(self, evt, *args):
       +        print(evt, args)
       +        #await self.pending[key].set()
       +
       +    async def run(self):
       +        from aiohttp import helpers
       +        app = web.Application()
       +        #app.on_response_prepare.append(http_server.on_response_prepare)
       +        app.add_routes([web.post('/api/create_invoice', self.create_request)])
       +        app.add_routes([web.get('/api/get_invoice', self.get_request)])
       +        app.add_routes([web.get('/api/get_status', self.get_status)])
       +        app.add_routes([web.static('/electrum', 'electrum/www')])
       +        runner = web.AppRunner(app)
       +        await runner.setup()
       +        host = self.config.get('http_host', 'localhost')
       +        port = self.config.get('http_port', 8000)
       +        site = web.TCPSite(runner, port=port, host=host)
       +        await site.start()
       +
       +    async def create_request(self, request):
       +        params = await request.post()
       +        wallet = self.daemon.wallet
       +        if 'amount_sat' not in params or not params['amount_sat'].isdigit():
       +            raise web.HTTPUnsupportedMediaType()
       +        amount = int(params['amount_sat'])
       +        message = params['message'] or "donation"
       +        payment_hash = await wallet.lnworker._add_invoice_coro(amount, message, 3600)
       +        key = payment_hash.hex()
       +        raise web.HTTPFound('/electrum/index.html?id=' + key)
       +
       +    async def get_request(self, r):
       +        key = r.query_string
       +        request = self.daemon.wallet.get_request(key)
       +        return web.json_response(request)
       +
       +    async def get_status(self, request):
       +        ws = web.WebSocketResponse()
       +        await ws.prepare(request)
       +        key = request.query_string
       +        info = self.daemon.wallet.get_request(key)
       +        if not info:
       +            await ws.send_str('unknown invoice')
       +            await ws.close()
       +            return ws
       +        if info.get('status') == PR_PAID:
       +            await ws.send_str(f'already paid')
       +            await ws.close()
       +            return ws
       +        if info.get('status') == PR_EXPIRED:
       +            await ws.send_str(f'invoice expired')
       +            await ws.close()
       +            return ws
       +        while True:
       +            try:
       +                await asyncio.wait_for(self.pending[key].wait(), 1)
       +                break
       +            except asyncio.TimeoutError:
       +                # send data on the websocket, to keep it alive
       +                await ws.send_str('waiting')
       +        await ws.send_str('paid')
       +        await ws.close()
       +        return ws
       +
       +
        class AuthenticationError(Exception):
            pass
        
       t@@ -197,6 +272,9 @@ class Daemon(Logger):
                if listen_jsonrpc:
                    jobs.append(self.start_jsonrpc(config, fd))
                # server-side watchtower
       +        self.http_server = HttpServer(self)
       +        if self.http_server:
       +            jobs.append(self.http_server.run())
                self.watchtower = WatchTowerServer(self.network) if self.config.get('watchtower_host') else None
                if self.watchtower:
                    jobs.append(self.watchtower.run)
       t@@ -296,6 +374,7 @@ class Daemon(Logger):
                wallet = Wallet(storage)
                wallet.start_network(self.network)
                self.wallets[path] = wallet
       +        self.wallet = wallet
                return wallet
        
            def add_wallet(self, wallet: Abstract_Wallet):
 (DIR) diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py
       t@@ -151,4 +151,4 @@ class InvoiceList(MyTreeView):
            def create_menu_ln_payreq(self, menu, payreq_key):
                req = self.parent.wallet.lnworker.invoices[payreq_key][0]
                menu.addAction(_("Copy Lightning invoice"), lambda: self.parent.do_copy('Lightning invoice', req))
       -        menu.addAction(_("Delete"), lambda: self.parent.delete_lightning_payreq(payreq_key))
       +        menu.addAction(_("Delete"), lambda: self.parent.delete_invoice(payreq_key))
 (DIR) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -1028,9 +1028,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
        
                return w
        
       -
       -    def delete_payment_request(self, addr):
       -        self.wallet.remove_payment_request(addr, self.config)
       +    def delete_request(self, key):
       +        self.wallet.delete_request(key)
                self.request_list.update()
                self.clear_receive_tab()
        
 (DIR) diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py
       t@@ -40,13 +40,11 @@ from electrum.bitcoin import COIN
        from electrum.lnaddr import lndecode
        import electrum.constants as constants
        
       -from .util import MyTreeView, pr_icons, read_QIcon
       +from .util import MyTreeView, pr_icons, read_QIcon, webopen
        
       -REQUEST_TYPE_BITCOIN = 0
       -REQUEST_TYPE_LN = 1
        
        ROLE_REQUEST_TYPE = Qt.UserRole
       -ROLE_RHASH_OR_ADDR = Qt.UserRole + 1
       +ROLE_KEY = Qt.UserRole + 1
        
        class RequestList(MyTreeView):
        
       t@@ -76,7 +74,7 @@ class RequestList(MyTreeView):
            def select_key(self, key):
                for i in range(self.model().rowCount()):
                    item = self.model().index(i, self.Columns.DATE)
       -            row_key = item.data(ROLE_RHASH_OR_ADDR)
       +            row_key = item.data(ROLE_KEY)
                    if key == row_key:
                        self.selectionModel().setCurrentIndex(item, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
                        break
       t@@ -85,12 +83,12 @@ class RequestList(MyTreeView):
                # TODO use siblingAtColumn when min Qt version is >=5.11
                item = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.DATE))
                request_type = item.data(ROLE_REQUEST_TYPE)
       -        key = item.data(ROLE_RHASH_OR_ADDR)
       -        is_lightning = request_type == REQUEST_TYPE_LN
       -        req = self.wallet.get_request(key, is_lightning)
       +        key = item.data(ROLE_KEY)
       +        req = self.wallet.get_request(key)
                if req is None:
                    self.update()
                    return
       +        is_lightning = request_type == PR_TYPE_LN
                text = req.get('invoice') if is_lightning else req.get('URI')
                self.parent.receive_address_e.setText(text)
        
       t@@ -101,9 +99,9 @@ class RequestList(MyTreeView):
                    date_idx = idx.sibling(idx.row(), self.Columns.DATE)
                    date_item = m.itemFromIndex(date_idx)
                    status_item = m.itemFromIndex(idx)
       -            key = date_item.data(ROLE_RHASH_OR_ADDR)
       -            is_lightning = date_item.data(ROLE_REQUEST_TYPE) == REQUEST_TYPE_LN
       -            req = self.wallet.get_request(key, is_lightning)
       +            key = date_item.data(ROLE_KEY)
       +            is_lightning = date_item.data(ROLE_REQUEST_TYPE) == PR_TYPE_LN
       +            req = self.wallet.get_request(key)
                    if req:
                        status = req['status']
                        status_str = get_request_status(req)
       t@@ -121,7 +119,7 @@ class RequestList(MyTreeView):
                    if status == PR_PAID:
                        continue
                    is_lightning = req['type'] == PR_TYPE_LN
       -            request_type = REQUEST_TYPE_LN if is_lightning else REQUEST_TYPE_BITCOIN
       +            request_type = req['type']
                    timestamp = req.get('time', 0)
                    amount = req.get('amount')
                    message = req['message'] if is_lightning else req['memo']
       t@@ -133,18 +131,17 @@ class RequestList(MyTreeView):
                    self.set_editability(items)
                    items[self.Columns.DATE].setData(request_type, ROLE_REQUEST_TYPE)
                    items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
       -            if request_type == REQUEST_TYPE_LN:
       -                items[self.Columns.DATE].setData(req['rhash'], ROLE_RHASH_OR_ADDR)
       +            if request_type == PR_TYPE_LN:
       +                items[self.Columns.DATE].setData(req['rhash'], ROLE_KEY)
                        items[self.Columns.DATE].setIcon(read_QIcon("lightning.png"))
       -                items[self.Columns.DATE].setData(REQUEST_TYPE_LN, ROLE_REQUEST_TYPE)
       -            else:
       +            elif request_type == PR_TYPE_ADDRESS:
                        address = req['address']
                        if address not in domain:
                            continue
                        expiration = req.get('exp', None)
                        signature = req.get('sig')
                        requestor = req.get('name', '')
       -                items[self.Columns.DATE].setData(address, ROLE_RHASH_OR_ADDR)
       +                items[self.Columns.DATE].setData(address, ROLE_KEY)
                        if signature is not None:
                            items[self.Columns.DATE].setIcon(read_QIcon("seal.png"))
                            items[self.Columns.DATE].setToolTip(f'signed by {requestor}')
       t@@ -167,13 +164,9 @@ class RequestList(MyTreeView):
                item = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.DATE))
                if not item:
                    return
       -        addr = item.data(ROLE_RHASH_OR_ADDR)
       +        key = item.data(ROLE_KEY)
                request_type = item.data(ROLE_REQUEST_TYPE)
       -        assert request_type in [REQUEST_TYPE_BITCOIN, REQUEST_TYPE_LN]
       -        if request_type == REQUEST_TYPE_BITCOIN:
       -            req = self.wallet.receive_requests.get(addr)
       -        elif request_type == REQUEST_TYPE_LN:
       -            req = self.wallet.lnworker.invoices[addr][0]
       +        req = self.wallet.get_request(key)
                if req is None:
                    self.update()
                    return
       t@@ -184,19 +177,15 @@ class RequestList(MyTreeView):
                if column == self.Columns.AMOUNT:
                    column_data = column_data.strip()
                menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.do_copy(column_title, column_data))
       -        if request_type == REQUEST_TYPE_BITCOIN:
       -            self.create_menu_bitcoin_payreq(menu, addr)
       -        elif request_type == REQUEST_TYPE_LN:
       -            self.create_menu_ln_payreq(menu, addr, req)
       -        menu.exec_(self.viewport().mapToGlobal(position))
        
       -    def create_menu_bitcoin_payreq(self, menu, addr):
       -        menu.addAction(_("Copy Address"), lambda: self.parent.do_copy('Address', addr))
       -        menu.addAction(_("Copy URI"), lambda: self.parent.do_copy('URI', self.wallet.get_request_URI(addr)))
       -        menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
       -        menu.addAction(_("Delete"), lambda: self.parent.delete_payment_request(addr))
       -        run_hook('receive_list_menu', menu, addr)
       +        #menu.addAction(_("Copy Address"), lambda: self.parent.do_copy('Address', addr))
       +        menu.addAction(_("Copy Request"), lambda: self.parent.do_copy('URI', self.wallet.get_request_URI(addr)))
       +        if 'http_url' in req:
       +            menu.addAction(_("View in web browser"), lambda: webopen(req['http_url']))
        
       -    def create_menu_ln_payreq(self, menu, payreq_key, req):
       -        menu.addAction(_("Copy Lightning invoice"), lambda: self.parent.do_copy('Lightning invoice', req))
       -        menu.addAction(_("Delete"), lambda: self.parent.delete_lightning_payreq(payreq_key))
       +        # do bip70 only for browser access
       +        # so, each request should have an ID, regardless
       +        #menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
       +        menu.addAction(_("Delete"), lambda: self.parent.delete_request(key))
       +        run_hook('receive_list_menu', menu, key)
       +        menu.exec_(self.viewport().mapToGlobal(position))
 (DIR) diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -1279,32 +1279,6 @@ class Abstract_Wallet(AddressSynchronizer):
                out['status'] = status
                if conf is not None:
                    out['confirmations'] = conf
       -        # check if bip70 file exists
       -        rdir = config.get('requests_dir')
       -        if rdir:
       -            key = out.get('id', addr)
       -            path = os.path.join(rdir, 'req', key[0], key[1], key)
       -            if os.path.exists(path):
       -                baseurl = 'file://' + rdir
       -                rewrite = config.get('url_rewrite')
       -                if rewrite:
       -                    try:
       -                        baseurl = baseurl.replace(*rewrite)
       -                    except BaseException as e:
       -                        self.logger.info(f'Invalid config setting for "url_rewrite". err: {e}')
       -                out['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key)
       -                out['URI'] += '&r=' + out['request_url']
       -                out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key
       -                websocket_server_announce = config.get('websocket_server_announce')
       -                if websocket_server_announce:
       -                    out['websocket_server'] = websocket_server_announce
       -                else:
       -                    out['websocket_server'] = config.get('websocket_server', 'localhost')
       -                websocket_port_announce = config.get('websocket_port_announce')
       -                if websocket_port_announce:
       -                    out['websocket_port'] = websocket_port_announce
       -                else:
       -                    out['websocket_port'] = config.get('websocket_port', 9999)
                return out
        
            def get_request_URI(self, addr):
       t@@ -1346,11 +1320,19 @@ class Abstract_Wallet(AddressSynchronizer):
                    status = PR_INFLIGHT if conf <= 0 else PR_PAID
                return status, conf
        
       -    def get_request(self, key, is_lightning):
       -        if not is_lightning:
       +    def get_request(self, key):
       +        from .simple_config import get_config
       +        config = get_config()
       +        if key in self.receive_requests:
                    req = self.get_payment_request(key, {})
                else:
                    req = self.lnworker.get_request(key)
       +        if not req:
       +            return
       +        if config.get('http_port', 8000):
       +            host = config.get('http_host', 'localhost')
       +            port = config.get('http_port', 8000)
       +            req['http_url'] = 'http://%s:%d/electrum/index.html?id=%s'%(host, port, key)
                return req
        
            def receive_tx_callback(self, tx_hash, tx, tx_height):
       t@@ -1389,24 +1371,6 @@ class Abstract_Wallet(AddressSynchronizer):
                self.receive_requests[addr] = req
                self.storage.put('payment_requests', self.receive_requests)
                self.set_label(addr, message) # should be a default label
       -
       -        rdir = config.get('requests_dir')
       -        if rdir and amount is not None:
       -            key = req.get('id', addr)
       -            pr = paymentrequest.make_request(config, req)
       -            path = os.path.join(rdir, 'req', key[0], key[1], key)
       -            if not os.path.exists(path):
       -                try:
       -                    os.makedirs(path)
       -                except OSError as exc:
       -                    if exc.errno != errno.EEXIST:
       -                        raise
       -            with open(os.path.join(path, key), 'wb') as f:
       -                f.write(pr.SerializeToString())
       -            # reload
       -            req = self.get_payment_request(addr, config)
       -            with open(os.path.join(path, key + '.json'), 'w', encoding='utf-8') as f:
       -                f.write(json.dumps(req))
                return req
        
            def delete_request(self, key):
       t@@ -1427,14 +1391,7 @@ class Abstract_Wallet(AddressSynchronizer):
            def remove_payment_request(self, addr, config):
                if addr not in self.receive_requests:
                    return False
       -        r = self.receive_requests.pop(addr)
       -        rdir = config.get('requests_dir')
       -        if rdir:
       -            key = r.get('id', addr)
       -            for s in ['.json', '']:
       -                n = os.path.join(rdir, 'req', key[0], key[1], key, key + s)
       -                if os.path.exists(n):
       -                    os.unlink(n)
       +        self.receive_requests.pop(addr)
                self.storage.put('payment_requests', self.receive_requests)
                return True
        
 (DIR) diff --git a/electrum/websockets.py b/electrum/websockets.py
       t@@ -1,132 +0,0 @@
       -#!/usr/bin/env python
       -#
       -# Electrum - lightweight Bitcoin client
       -# Copyright (C) 2015 Thomas Voegtlin
       -#
       -# Permission is hereby granted, free of charge, to any person
       -# obtaining a copy of this software and associated documentation files
       -# (the "Software"), to deal in the Software without restriction,
       -# including without limitation the rights to use, copy, modify, merge,
       -# publish, distribute, sublicense, and/or sell copies of the Software,
       -# and to permit persons to whom the Software is furnished to do so,
       -# subject to the following conditions:
       -#
       -# The above copyright notice and this permission notice shall be
       -# included in all copies or substantial portions of the Software.
       -#
       -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       -# SOFTWARE.
       -import threading
       -import os
       -import json
       -from collections import defaultdict
       -import asyncio
       -from typing import Dict, List, Tuple, TYPE_CHECKING
       -import traceback
       -import sys
       -
       -try:
       -    from SimpleWebSocketServer import WebSocket, SimpleSSLWebSocketServer
       -except ImportError:
       -    sys.exit("install SimpleWebSocketServer")
       -
       -from . import bitcoin
       -from .synchronizer import SynchronizerBase
       -from .logging import Logger
       -
       -if TYPE_CHECKING:
       -    from .network import Network
       -    from .simple_config import SimpleConfig
       -
       -
       -request_queue = asyncio.Queue()
       -
       -
       -class ElectrumWebSocket(WebSocket, Logger):
       -
       -    def __init__(self):
       -        WebSocket.__init__(self)
       -        Logger.__init__(self)
       -
       -    def handleMessage(self):
       -        assert self.data[0:3] == 'id:'
       -        self.logger.info(f"message received {self.data}")
       -        request_id = self.data[3:]
       -        asyncio.run_coroutine_threadsafe(
       -            request_queue.put((self, request_id)), asyncio.get_event_loop())
       -
       -    def handleConnected(self):
       -        self.logger.info(f"connected {self.address}")
       -
       -    def handleClose(self):
       -        self.logger.info(f"closed {self.address}")
       -
       -
       -class BalanceMonitor(SynchronizerBase):
       -
       -    def __init__(self, config: 'SimpleConfig', network: 'Network'):
       -        SynchronizerBase.__init__(self, network)
       -        self.config = config
       -        self.expected_payments = defaultdict(list)  # type: Dict[str, List[Tuple[WebSocket, int]]]
       -
       -    def make_request(self, request_id):
       -        # read json file
       -        rdir = self.config.get('requests_dir')
       -        n = os.path.join(rdir, 'req', request_id[0], request_id[1], request_id, request_id + '.json')
       -        with open(n, encoding='utf-8') as f:
       -            s = f.read()
       -        d = json.loads(s)
       -        addr = d.get('address')
       -        amount = d.get('amount')
       -        return addr, amount
       -
       -    async def main(self):
       -        # resend existing subscriptions if we were restarted
       -        for addr in self.expected_payments:
       -            await self._add_address(addr)
       -        # main loop
       -        while True:
       -            ws, request_id = await request_queue.get()
       -            try:
       -                addr, amount = self.make_request(request_id)
       -            except Exception:
       -                self.logger.exception('')
       -                continue
       -            self.expected_payments[addr].append((ws, amount))
       -            await self._add_address(addr)
       -
       -    async def _on_address_status(self, addr, status):
       -        self.logger.info(f'new status for addr {addr}')
       -        sh = bitcoin.address_to_scripthash(addr)
       -        balance = await self.network.get_balance_for_scripthash(sh)
       -        for ws, amount in self.expected_payments[addr]:
       -            if not ws.closed:
       -                if sum(balance.values()) >= amount:
       -                    ws.sendMessage('paid')
       -
       -
       -class WebSocketServer(threading.Thread):
       -
       -    def __init__(self, config: 'SimpleConfig', network: 'Network'):
       -        threading.Thread.__init__(self)
       -        self.config = config
       -        self.network = network
       -        asyncio.set_event_loop(network.asyncio_loop)
       -        self.daemon = True
       -        self.balance_monitor = BalanceMonitor(self.config, self.network)
       -        self.start()
       -
       -    def run(self):
       -        asyncio.set_event_loop(self.network.asyncio_loop)
       -        host = self.config.get('websocket_server')
       -        port = self.config.get('websocket_port', 9999)
       -        certfile = self.config.get('ssl_chain')
       -        keyfile = self.config.get('ssl_privkey')
       -        self.server = SimpleSSLWebSocketServer(host, port, ElectrumWebSocket, certfile, keyfile)
       -        self.server.serveforever()
 (DIR) diff --git a/electrum/www b/electrum/www
       t@@ -0,0 +1 @@
       +Subproject commit 538fa508d41512e670fb84970f821a5db71836d9
 (DIR) diff --git a/run_electrum b/run_electrum
       t@@ -375,15 +375,6 @@ if __name__ == '__main__':
                    # run daemon
                    init_plugins(config, 'cmdline')
                    d = daemon.Daemon(config, fd)
       -            if config.get('websocket_server'):
       -                from electrum import websockets
       -                websockets.WebSocketServer(config, d.network)
       -            if config.get('requests_dir'):
       -                path = os.path.join(config.get('requests_dir'), 'index.html')
       -                if not os.path.exists(path):
       -                    print("Requests directory not configured.")
       -                    print("You can configure it using https://github.com/spesmilo/electrum-merchant")
       -                    sys_exit(1)
                    d.run_daemon()
                    sys_exit(0)
                else: