Add subscribe address display squeaks (#1112)

* Implement rpc command for subscribe address squeak entires

* Got subscription working for address squeaks

* Implemented download squeaks for address rpc

* Fix handling new squeak items from subscription appending to state array

* Update frontend build
This commit is contained in:
Jonathan Zernik 2021-08-30 02:43:44 -07:00 committed by GitHub
parent d589b0a4a7
commit c801060d35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 254 additions and 27 deletions

View file

@ -57,8 +57,9 @@ export default function SqueakPage() {
};
const subscribeReplySqueaks = (hash) => {
return subscribeReplySqueakDisplaysRequest(hash, (resp) => {
console.log(resp);
setReplySqueaks(replySqueaks.concat(resp));
setReplySqueaks((prevReplySqueaks) => {
return prevReplySqueaks.concat(resp);
});
});
};
const getNetwork = () => {

View file

@ -3,6 +3,7 @@ import { useParams, useHistory } from 'react-router-dom';
import {
Grid,
Button,
Box,
} from '@material-ui/core';
// styles
@ -13,6 +14,8 @@ import TimelineDot from '@material-ui/lab/TimelineDot';
import Paper from '@material-ui/core/Paper';
import FaceIcon from '@material-ui/icons/Face';
import GetAppIcon from '@material-ui/icons/GetApp';
import CreateContactProfileDialog from '../../components/CreateContactProfileDialog';
import SqueakList from '../../components/SqueakList';
import useStyles from './styles';
@ -21,6 +24,8 @@ import {
getSqueakProfileByAddressRequest,
getAddressSqueakDisplaysRequest,
getNetworkRequest,
subscribeAddressSqueakDisplaysRequest,
downloadAddressSqueaksRequest,
} from '../../squeakclient/requests';
import {
goToSqueakAddressPage,
@ -42,6 +47,13 @@ export default function SqueakAddressPage() {
const getSqueaks = (address) => {
getAddressSqueakDisplaysRequest(address, setSqueaks);
};
const subscribeSqueaks = (hash) => {
return subscribeAddressSqueakDisplaysRequest(address, (resp) => {
setSqueaks((prevSqueaks) => {
return [resp].concat(prevSqueaks);
});
});
};
const getNetwork = () => {
getNetworkRequest(setNetwork);
};
@ -54,12 +66,24 @@ export default function SqueakAddressPage() {
setCreateContactProfileDialogOpen(false);
};
const onDownloadSqueaksClick = (event) => {
event.preventDefault();
console.log('Handling download address squeaks click...');
downloadAddressSqueaksRequest(address, (response) => {
// Do nothing.
});
};
useEffect(() => {
getSqueakProfile(address);
}, [address]);
useEffect(() => {
getSqueaks(address);
}, [address]);
useEffect(() => {
const stream = subscribeSqueaks(address);
return () => stream.cancel();
}, [address]);
useEffect(() => {
getNetwork();
}, []);
@ -158,11 +182,28 @@ export default function SqueakAddressPage() {
);
}
function DownloadSqueaksButtonContent() {
return (
<>
<Box p={1}>
<Button
variant="contained"
onClick={onDownloadSqueaksClick}
>
<GetAppIcon />
Download squeaks
</Button>
</Box>
</>
);
}
return (
<>
{squeakProfile
? ProfileContent()
: NoProfileContent()}
{DownloadSqueaksButtonContent()}
{GridContent()}
{CreateContactProfileDialogContent()}
</>

View file

@ -61,11 +61,13 @@ import {
DisconnectPeerRequest as DisconnectSqueakPeerRequest,
DownloadOffersRequest,
DownloadRepliesRequest,
DownloadAddressSqueaksRequest,
SubscribeConnectedPeersRequest,
SubscribeConnectedPeerRequest,
PeerAddress,
SubscribeBuyOffersRequest,
SubscribeReplySqueakDisplaysRequest,
SubscribeAddressSqueakDisplaysRequest,
} from '../proto/squeak_admin_pb';
import { SqueakAdminClient } from '../proto/squeak_admin_grpc_web_pb';
@ -468,6 +470,14 @@ export function downloadRepliesRequest(squeakHash, handleResponse) {
});
}
export function downloadAddressSqueaksRequest(address, handleResponse) {
const request = new DownloadAddressSqueaksRequest();
request.setAddress(address);
client.downloadAddressSqueaks(request, {}, (err, response) => {
handleResponse(response);
});
}
export function getSqueakDetailsRequest(hash, handleResponse) {
const request = new GetSqueakDetailsRequest();
request.setSqueakHash(hash);
@ -663,3 +673,21 @@ export function subscribeReplySqueakDisplaysRequest(hash, handleResponse) {
console.log(stream);
return stream;
}
export function subscribeAddressSqueakDisplaysRequest(address, handleResponse) {
const request = new SubscribeAddressSqueakDisplaysRequest();
request.setAddress(address);
const stream = client.subscribeAddressSqueakDisplays(request);
stream.on('data', (response) => {
console.log("response :" + response);
handleResponse(response.getSqueakDisplayEntry());
});
stream.on('end', (end) => {
// stream end signal
console.log(end);
alert(`Stream ended: ${end}`);
});
console.log("Stream object:");
console.log(stream);
return stream;
}

View file

@ -1,6 +1,6 @@
#!/bin/bash
pytest -s tests
#pytest -s tests
#pytest -s tests -k "test_buy_squeak"
#pytest -s tests -k "test_sell_squeak"
#pytest -s tests -k "test_download_single_squeak"
@ -10,3 +10,4 @@ pytest -s tests
#pytest -s tests -k "test_share_single_squeak"
#pytest -s tests -k "test_delete_squeak"
#pytest -s tests -k "test_subscribe_squeaks"
pytest -s tests -k "test_download_squeaks_for_address"

View file

@ -38,6 +38,7 @@ from tests.util import delete_profile
from tests.util import delete_squeak
from tests.util import download_offers
from tests.util import download_squeak
from tests.util import download_squeaks_for_address
from tests.util import get_connected_peer
from tests.util import get_connected_peers
from tests.util import get_hash
@ -50,6 +51,7 @@ from tests.util import open_channel
from tests.util import open_peer_connection
from tests.util import subscribe_connected_peers
from tests.util import subscribe_squeak_entry
from tests.util import subscribe_squeaks_for_address
def test_get_network(admin_stub):
@ -730,6 +732,47 @@ def test_download_single_squeak(
assert item.squeak_hash == saved_squeak_hash
def test_download_squeaks_for_address(
admin_stub,
other_admin_stub,
connected_tcp_peer_id,
signing_profile_id,
saved_squeak_hash,
):
squeak_profile = get_squeak_profile(admin_stub, signing_profile_id)
squeak_profile_address = squeak_profile.address
with subscribe_squeaks_for_address(other_admin_stub, squeak_profile_address) as subscription_queue:
# Get the squeak display item (should be empty)
squeak_display_entry = get_squeak_display(
other_admin_stub, saved_squeak_hash)
assert squeak_display_entry is None
# Get buy offers for the squeak hash (should be empty)
get_buy_offers_response = other_admin_stub.GetBuyOffers(
squeak_admin_pb2.GetBuyOffersRequest(
squeak_hash=saved_squeak_hash,
)
)
# print(get_buy_offers_response)
assert len(get_buy_offers_response.offers) == 0
# Download squeaks for address
download_squeaks_for_address(other_admin_stub, squeak_profile_address)
time.sleep(5)
# Get the squeak display item
squeak_display_entry = get_squeak_display(
other_admin_stub, saved_squeak_hash)
assert squeak_display_entry is not None
item = subscription_queue.get()
print("item:")
print(item)
assert item.squeak_hash == saved_squeak_hash
def test_get_squeak_details(admin_stub, saved_squeak_hash):
# Get the squeak details
get_squeak_details_response = admin_stub.GetSqueakDetails(

View file

@ -248,6 +248,14 @@ def download_squeaks(node_stub):
)
def download_squeaks_for_address(node_stub, squeak_address):
node_stub.DownloadAddressSqueaks(
squeak_admin_pb2.DownloadAddressSqueaksRequest(
address=squeak_address,
),
)
def download_offers(node_stub, squeak_hash):
node_stub.DownloadOffers(
squeak_admin_pb2.DownloadOffersRequest(
@ -346,3 +354,23 @@ def subscribe_squeak_entry(node_stub, squeak_hash):
).start()
yield q
subscribe_squeak_entry_response.cancel()
@contextmanager
def subscribe_squeaks_for_address(node_stub, squeak_address):
q = queue.Queue()
subscribe_address_squeaks_response = node_stub.SubscribeAddressSqueakDisplays(
squeak_admin_pb2.SubscribeAddressSqueakDisplaysRequest(
address=squeak_address,
)
)
def enqueue_results():
for result in subscribe_address_squeaks_response:
q.put(result.squeak_display_entry)
threading.Thread(
target=enqueue_results,
).start()
yield q
subscribe_address_squeaks_response.cancel()

View file

@ -200,6 +200,10 @@ service SqueakAdmin {
*/
rpc DownloadReplies (DownloadRepliesRequest) returns (DownloadRepliesReply) {}
/** sqkadmin: `downloadaddresssqueaks`
*/
rpc DownloadAddressSqueaks (DownloadAddressSqueaksRequest) returns (DownloadAddressSqueaksReply) {}
/** sqkadmin: `payoffer`
*/
rpc PayOffer (PayOfferRequest) returns (PayOfferReply) {}
@ -288,6 +292,10 @@ service SqueakAdmin {
*/
rpc SubscribeReplySqueakDisplays (SubscribeReplySqueakDisplaysRequest) returns (stream GetSqueakDisplayReply) {}
/** sqkadmin: `subscribeaddresssqueakdisplays`
*/
rpc SubscribeAddressSqueakDisplays (SubscribeAddressSqueakDisplaysRequest) returns (stream GetSqueakDisplayReply) {}
}
message CreateSigningProfileRequest {
@ -803,6 +811,14 @@ message DownloadRepliesRequest {
message DownloadRepliesReply {
}
message DownloadAddressSqueaksRequest {
/// The address
string address = 1;
}
message DownloadAddressSqueaksReply {
}
message GetSqueakDetailsRequest {
/// Hash of the squeak.
string squeak_hash = 1;
@ -1020,3 +1036,8 @@ message SubscribeReplySqueakDisplaysRequest {
/// Hash of the squeak.
string squeak_hash = 1;
}
message SubscribeAddressSqueakDisplaysRequest {
/// The address
string address = 1;
}

View file

@ -559,6 +559,13 @@ class SqueakAdminServerHandler(object):
self.squeak_controller.download_replies(squeak_hash)
return squeak_admin_pb2.DownloadRepliesReply()
def handle_download_address_squeaks(self, request):
squeak_address = request.address
logger.info(
"Handle download address squeaks for address: {}".format(squeak_address))
self.squeak_controller.download_address_squeaks(squeak_address)
return squeak_admin_pb2.DownloadAddressSqueaksReply()
def handle_pay_offer(self, request):
offer_id = request.offer_id
logger.info("Handle pay offer for offer id: {}".format(offer_id))
@ -839,3 +846,23 @@ class SqueakAdminServerHandler(object):
yield squeak_admin_pb2.GetSqueakDisplayReply(
squeak_display_entry=display_message
)
def handle_subscribe_address_squeak_displays(self, request, stopped):
squeak_address = request.address
logger.info(
"Handle subscribe address squeak displays for address: {}".format(squeak_address))
squeak_display_stream = self.squeak_controller.subscribe_squeak_address_entries(
squeak_address,
stopped,
)
for squeak_display in squeak_display_stream:
if squeak_display is None:
yield squeak_admin_pb2.GetSqueakDisplayReply(
squeak_display_entry=None
)
else:
display_message = squeak_entry_to_message(
squeak_display)
yield squeak_admin_pb2.GetSqueakDisplayReply(
squeak_display_entry=display_message
)

View file

@ -205,6 +205,9 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer):
def DownloadReplies(self, request, context):
return self.handler.handle_download_replies(request)
def DownloadAddressSqueaks(self, request, context):
return self.handler.handle_download_address_squeaks(request)
def PayOffer(self, request, context):
return self.handler.handle_pay_offer(request)
@ -324,3 +327,15 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer):
request,
stopped,
)
def SubscribeAddressSqueakDisplays(self, request, context):
stopped = threading.Event()
def on_rpc_done():
logger.info("Stopping SubscribeAddressSqueakDisplaysRequest.")
stopped.set()
context.add_callback(on_rpc_done)
return self.handler.handle_subscribe_address_squeak_displays(
request,
stopped,
)

View file

@ -1,17 +1,17 @@
{
"files": {
"main.js": "/static/js/main.709b4b8c.chunk.js",
"main.js.map": "/static/js/main.709b4b8c.chunk.js.map",
"main.js": "/static/js/main.698dfea5.chunk.js",
"main.js.map": "/static/js/main.698dfea5.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.9f0ba400.js",
"runtime-main.js.map": "/static/js/runtime-main.9f0ba400.js.map",
"static/css/2.ea4ba2f0.chunk.css": "/static/css/2.ea4ba2f0.chunk.css",
"static/js/2.ae9e5f97.chunk.js": "/static/js/2.ae9e5f97.chunk.js",
"static/js/2.ae9e5f97.chunk.js.map": "/static/js/2.ae9e5f97.chunk.js.map",
"static/js/2.9773e69b.chunk.js": "/static/js/2.9773e69b.chunk.js",
"static/js/2.9773e69b.chunk.js.map": "/static/js/2.9773e69b.chunk.js.map",
"index.html": "/index.html",
"precache-manifest.5049c3578b9a689230db580fa3abe606.js": "/precache-manifest.5049c3578b9a689230db580fa3abe606.js",
"precache-manifest.5576855b7ac92f096220ad34d2ca9cd2.js": "/precache-manifest.5576855b7ac92f096220ad34d2ca9cd2.js",
"service-worker.js": "/service-worker.js",
"static/css/2.ea4ba2f0.chunk.css.map": "/static/css/2.ea4ba2f0.chunk.css.map",
"static/js/2.ae9e5f97.chunk.js.LICENSE.txt": "/static/js/2.ae9e5f97.chunk.js.LICENSE.txt",
"static/js/2.9773e69b.chunk.js.LICENSE.txt": "/static/js/2.9773e69b.chunk.js.LICENSE.txt",
"static/media/font-awesome.min.css": "/static/media/fontawesome-webfont.fee66e71.woff",
"static/media/google.svg": "/static/media/google.695a3160.svg",
"static/media/logo.svg": "/static/media/logo.a0185b04.svg"
@ -19,7 +19,7 @@
"entrypoints": [
"static/js/runtime-main.9f0ba400.js",
"static/css/2.ea4ba2f0.chunk.css",
"static/js/2.ae9e5f97.chunk.js",
"static/js/main.709b4b8c.chunk.js"
"static/js/2.9773e69b.chunk.js",
"static/js/main.698dfea5.chunk.js"
]
}

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Squeak Node</title><meta name="description" content="Squeak Node is a frontend for accessing a squeak node"><meta name="keywords" content="squeak, bitcoin, lightning"><meta name="author" content="Flatlogic LLC."><link href="/static/css/2.ea4ba2f0.chunk.css" rel="stylesheet"></head><body style="font-family:Roboto,sans-serif"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,f,l=r[0],a=r[1],i=r[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var l=this["webpackJsonpsqueak-node-frontend"]=this["webpackJsonpsqueak-node-frontend"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var i=0;i<l.length;i++)r(l[i]);var p=a;t()}([])</script><script src="/static/js/2.ae9e5f97.chunk.js"></script><script src="/static/js/main.709b4b8c.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Squeak Node</title><meta name="description" content="Squeak Node is a frontend for accessing a squeak node"><meta name="keywords" content="squeak, bitcoin, lightning"><meta name="author" content="Flatlogic LLC."><link href="/static/css/2.ea4ba2f0.chunk.css" rel="stylesheet"></head><body style="font-family:Roboto,sans-serif"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,f,l=r[0],a=r[1],i=r[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var l=this["webpackJsonpsqueak-node-frontend"]=this["webpackJsonpsqueak-node-frontend"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var i=0;i<l.length;i++)r(l[i]);var p=a;t()}([])</script><script src="/static/js/2.9773e69b.chunk.js"></script><script src="/static/js/main.698dfea5.chunk.js"></script></body></html>

View file

@ -1,23 +1,23 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "ea01fb56c12bad8d7852d0355b7ff2ac",
"revision": "c8e083bdbf1737325048554458dce96d",
"url": "/index.html"
},
{
"revision": "4debec8939f815ec87ef",
"revision": "380aef1bf9eaf28c29e1",
"url": "/static/css/2.ea4ba2f0.chunk.css"
},
{
"revision": "4debec8939f815ec87ef",
"url": "/static/js/2.ae9e5f97.chunk.js"
"revision": "380aef1bf9eaf28c29e1",
"url": "/static/js/2.9773e69b.chunk.js"
},
{
"revision": "279b8724b89bf7d504758b3fa917239f",
"url": "/static/js/2.ae9e5f97.chunk.js.LICENSE.txt"
"url": "/static/js/2.9773e69b.chunk.js.LICENSE.txt"
},
{
"revision": "6b1a16e6b756eacaaf22",
"url": "/static/js/main.709b4b8c.chunk.js"
"revision": "2e9381928cf77facf7fc",
"url": "/static/js/main.698dfea5.chunk.js"
},
{
"revision": "cc9816de5a8639d377ea",

View file

@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
"/precache-manifest.5049c3578b9a689230db580fa3abe606.js"
"/precache-manifest.5576855b7ac92f096220ad34d2ca9cd2.js"
);
self.addEventListener('message', (event) => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -642,6 +642,22 @@ class SqueakController:
)
self.broadcast_msg(getsqueaks_msg)
def download_address_squeaks(self, squeak_address: str):
logger.info("Downloading address squeaks for address: {}".format(
squeak_address,
))
interest = CInterested(
addresses=[CSqueakAddress(squeak_address)],
)
self.temporary_interest_manager.add_range_interest(10, interest)
locator = CSqueakLocator(
vInterested=[interest],
)
getsqueaks_msg = msg_getsqueaks(
locator=locator,
)
self.broadcast_msg(getsqueaks_msg)
def broadcast_msg(self, msg: MsgSerializable) -> None:
self.network_manager.broadcast_msg(msg)
@ -683,3 +699,9 @@ class SqueakController:
if squeak_hash == item.hashReplySqk:
reply_hash = get_hash(item)
yield self.get_squeak_entry(reply_hash)
def subscribe_squeak_address_entries(self, squeak_address: str, stopped: threading.Event):
for item in self.new_squeak_listener.yield_items(stopped):
if squeak_address == str(item.GetAddress()):
squeak_hash = get_hash(item)
yield self.get_squeak_entry(squeak_hash)