mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-25 15:29:23 -05:00
Add notifications listener
This commit is contained in:
parent
8332964ac5
commit
800b309a4b
@ -36,6 +36,8 @@ import bisq.common.config.Config;
|
|||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.NotificationMessage;
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|
||||||
@ -77,6 +80,7 @@ public class CoreApi {
|
|||||||
private final CoreTradesService coreTradesService;
|
private final CoreTradesService coreTradesService;
|
||||||
private final CoreWalletsService walletsService;
|
private final CoreWalletsService walletsService;
|
||||||
private final TradeStatisticsManager tradeStatisticsManager;
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
|
private final CoreNotificationService notificationService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CoreApi(Config config,
|
public CoreApi(Config config,
|
||||||
@ -87,7 +91,8 @@ public class CoreApi {
|
|||||||
CorePriceService corePriceService,
|
CorePriceService corePriceService,
|
||||||
CoreTradesService coreTradesService,
|
CoreTradesService coreTradesService,
|
||||||
CoreWalletsService walletsService,
|
CoreWalletsService walletsService,
|
||||||
TradeStatisticsManager tradeStatisticsManager) {
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
|
CoreNotificationService notificationService) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.coreDisputeAgentsService = coreDisputeAgentsService;
|
this.coreDisputeAgentsService = coreDisputeAgentsService;
|
||||||
this.coreHelpService = coreHelpService;
|
this.coreHelpService = coreHelpService;
|
||||||
@ -97,6 +102,7 @@ public class CoreApi {
|
|||||||
this.corePriceService = corePriceService;
|
this.corePriceService = corePriceService;
|
||||||
this.walletsService = walletsService;
|
this.walletsService = walletsService;
|
||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
|
this.notificationService = notificationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SameReturnValue")
|
@SuppressWarnings("SameReturnValue")
|
||||||
@ -112,6 +118,21 @@ public class CoreApi {
|
|||||||
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
|
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Notifications
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public interface NotificationListener {
|
||||||
|
void onMessage(@NonNull NotificationMessage message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNotificationListener(NotificationListener listener) {
|
||||||
|
notificationService.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendNotification(NotificationMessage notification) {
|
||||||
|
notificationService.sendNotification(notification);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Help
|
// Help
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package bisq.core.api;
|
||||||
|
|
||||||
|
import bisq.core.api.CoreApi.NotificationListener;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.NotificationMessage;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Slf4j
|
||||||
|
public class CoreNotificationService {
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
private final List<NotificationListener> listeners = new LinkedList<>();
|
||||||
|
|
||||||
|
public void addListener(@NonNull NotificationListener listener) {
|
||||||
|
synchronized (lock) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendNotification(@NonNull NotificationMessage notification) {
|
||||||
|
synchronized (lock) {
|
||||||
|
for (Iterator<NotificationListener> iter = listeners.iterator(); iter.hasNext(); ) {
|
||||||
|
NotificationListener listener = iter.next();
|
||||||
|
try {
|
||||||
|
listener.onMessage(notification);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
log.warn("Failed to send message {} to listener {}", notification, listener, e);
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package bisq.core.trade;
|
package bisq.core.trade;
|
||||||
|
|
||||||
|
import bisq.core.api.CoreNotificationService;
|
||||||
import bisq.core.btc.model.XmrAddressEntry;
|
import bisq.core.btc.model.XmrAddressEntry;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
@ -33,6 +34,7 @@ import bisq.core.provider.price.PriceFeedService;
|
|||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
import bisq.core.support.dispute.mediation.mediator.Mediator;
|
||||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||||
|
import bisq.core.trade.Trade.Phase;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.closed.ClosedTradableManager;
|
||||||
import bisq.core.trade.failed.FailedTradesManager;
|
import bisq.core.trade.failed.FailedTradesManager;
|
||||||
import bisq.core.trade.handlers.TradeResultHandler;
|
import bisq.core.trade.handlers.TradeResultHandler;
|
||||||
@ -64,6 +66,7 @@ import bisq.network.p2p.DecryptedMessageWithPubKey;
|
|||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
import bisq.network.p2p.network.TorNetworkNode;
|
import bisq.network.p2p.network.TorNetworkNode;
|
||||||
|
import bisq.proto.grpc.NotificationMessage;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import bisq.common.ClockWatcher;
|
import bisq.common.ClockWatcher;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
@ -89,7 +92,8 @@ import javafx.collections.ListChangeListener;
|
|||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
import org.fxmisc.easybind.Subscription;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -127,6 +131,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
@Getter
|
@Getter
|
||||||
private final KeyRing keyRing;
|
private final KeyRing keyRing;
|
||||||
private final XmrWalletService xmrWalletService;
|
private final XmrWalletService xmrWalletService;
|
||||||
|
private final CoreNotificationService notificationService;
|
||||||
private final OfferBookService offerBookService;
|
private final OfferBookService offerBookService;
|
||||||
private final OpenOfferManager openOfferManager;
|
private final OpenOfferManager openOfferManager;
|
||||||
private final ClosedTradableManager closedTradableManager;
|
private final ClosedTradableManager closedTradableManager;
|
||||||
@ -166,6 +171,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
public TradeManager(User user,
|
public TradeManager(User user,
|
||||||
KeyRing keyRing,
|
KeyRing keyRing,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
|
CoreNotificationService notificationService,
|
||||||
OfferBookService offerBookService,
|
OfferBookService offerBookService,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
@ -186,6 +192,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
this.user = user;
|
this.user = user;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
|
this.notificationService = notificationService;
|
||||||
this.offerBookService = offerBookService;
|
this.offerBookService = offerBookService;
|
||||||
this.openOfferManager = openOfferManager;
|
this.openOfferManager = openOfferManager;
|
||||||
this.closedTradableManager = closedTradableManager;
|
this.closedTradableManager = closedTradableManager;
|
||||||
@ -511,6 +518,17 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
trade.getSelf().setReserveTxKeyImages(offer.getOfferPayload().getReserveTxKeyImages());
|
trade.getSelf().setReserveTxKeyImages(offer.getOfferPayload().getReserveTxKeyImages());
|
||||||
tradableList.add(trade);
|
tradableList.add(trade);
|
||||||
|
|
||||||
|
// notify on phase changes
|
||||||
|
// TODO (woodser): save subscription, bind on startup
|
||||||
|
EasyBind.subscribe(trade.statePhaseProperty(), phase -> {
|
||||||
|
if (phase == Phase.DEPOSIT_PUBLISHED) {
|
||||||
|
notificationService.sendNotification(NotificationMessage.newBuilder()
|
||||||
|
.setTimestamp(System.currentTimeMillis())
|
||||||
|
.setTitle("Offer Taken")
|
||||||
|
.setMessage("Your offer " + offer.getId() + " has been accepted").build());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
||||||
log.warn("Maker error during trade initialization: " + errorMessage);
|
log.warn("Maker error during trade initialization: " + errorMessage);
|
||||||
openOfferManager.unreserveOpenOffer(openOffer); // offer remains available // TODO: only unreserve if funds not deposited to multisig
|
openOfferManager.unreserveOpenOffer(openOffer); // offer remains available // TODO: only unreserve if funds not deposited to multisig
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
package bisq.daemon.grpc;
|
||||||
|
|
||||||
|
import bisq.core.api.CoreApi;
|
||||||
|
import bisq.core.api.CoreApi.NotificationListener;
|
||||||
|
|
||||||
|
import bisq.proto.grpc.NotificationMessage;
|
||||||
|
import bisq.proto.grpc.NotificationsGrpc.NotificationsImplBase;
|
||||||
|
import bisq.proto.grpc.RegisterNotificationListenerRequest;
|
||||||
|
import bisq.proto.grpc.SendNotificationReply;
|
||||||
|
import bisq.proto.grpc.SendNotificationRequest;
|
||||||
|
|
||||||
|
import io.grpc.ServerInterceptor;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
|
||||||
|
import static bisq.proto.grpc.NotificationsGrpc.getRegisterNotificationListenerMethod;
|
||||||
|
import static bisq.proto.grpc.NotificationsGrpc.getSendNotificationMethod;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
|
||||||
|
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class GrpcNotificationsService extends NotificationsImplBase {
|
||||||
|
|
||||||
|
private final CoreApi coreApi;
|
||||||
|
private final GrpcExceptionHandler exceptionHandler;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public GrpcNotificationsService(CoreApi coreApi, GrpcExceptionHandler exceptionHandler) {
|
||||||
|
this.coreApi = coreApi;
|
||||||
|
this.exceptionHandler = exceptionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerNotificationListener(RegisterNotificationListenerRequest request,
|
||||||
|
StreamObserver<NotificationMessage> responseObserver) {
|
||||||
|
try {
|
||||||
|
coreApi.addNotificationListener(new GrpcNotificationListener(responseObserver));
|
||||||
|
// No onNext / onCompleted, as the response observer should be kept open
|
||||||
|
} catch (Throwable t) {
|
||||||
|
exceptionHandler.handleException(log, t, responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendNotification(SendNotificationRequest request,
|
||||||
|
StreamObserver<SendNotificationReply> responseObserver) {
|
||||||
|
try {
|
||||||
|
coreApi.sendNotification(request.getNotification());
|
||||||
|
responseObserver.onNext(SendNotificationReply.newBuilder().build());
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
exceptionHandler.handleException(log, t, responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
private static class GrpcNotificationListener implements NotificationListener {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
StreamObserver<NotificationMessage> responseObserver;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(@NonNull NotificationMessage message) {
|
||||||
|
responseObserver.onNext(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ServerInterceptor[] interceptors() {
|
||||||
|
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
|
||||||
|
return rateMeteringInterceptor.map(serverInterceptor ->
|
||||||
|
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Optional<ServerInterceptor> rateMeteringInterceptor() {
|
||||||
|
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
|
||||||
|
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
|
||||||
|
new HashMap<>() {{
|
||||||
|
put(getRegisterNotificationListenerMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
|
put(getSendNotificationMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
|
}}
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
@ -58,7 +58,8 @@ public class GrpcServer {
|
|||||||
GrpcVersionService versionService,
|
GrpcVersionService versionService,
|
||||||
GrpcGetTradeStatisticsService tradeStatisticsService,
|
GrpcGetTradeStatisticsService tradeStatisticsService,
|
||||||
GrpcTradesService tradesService,
|
GrpcTradesService tradesService,
|
||||||
GrpcWalletsService walletsService) {
|
GrpcWalletsService walletsService,
|
||||||
|
GrpcNotificationsService notificationsService) {
|
||||||
this.server = ServerBuilder.forPort(config.apiPort)
|
this.server = ServerBuilder.forPort(config.apiPort)
|
||||||
.executor(UserThread.getExecutor())
|
.executor(UserThread.getExecutor())
|
||||||
.addService(interceptForward(disputeAgentsService, disputeAgentsService.interceptors()))
|
.addService(interceptForward(disputeAgentsService, disputeAgentsService.interceptors()))
|
||||||
@ -71,6 +72,7 @@ public class GrpcServer {
|
|||||||
.addService(interceptForward(tradesService, tradesService.interceptors()))
|
.addService(interceptForward(tradesService, tradesService.interceptors()))
|
||||||
.addService(interceptForward(versionService, versionService.interceptors()))
|
.addService(interceptForward(versionService, versionService.interceptors()))
|
||||||
.addService(interceptForward(walletsService, walletsService.interceptors()))
|
.addService(interceptForward(walletsService, walletsService.interceptors()))
|
||||||
|
.addService(interceptForward(notificationsService, notificationsService.interceptors()))
|
||||||
.intercept(passwordAuthInterceptor)
|
.intercept(passwordAuthInterceptor)
|
||||||
.build();
|
.build();
|
||||||
coreContext.setApiUser(true);
|
coreContext.setApiUser(true);
|
||||||
|
@ -40,6 +40,33 @@ message RegisterDisputeAgentRequest {
|
|||||||
message RegisterDisputeAgentReply {
|
message RegisterDisputeAgentReply {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Notifications
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
service Notifications {
|
||||||
|
rpc RegisterNotificationListener (RegisterNotificationListenerRequest) returns (stream NotificationMessage) {
|
||||||
|
}
|
||||||
|
rpc SendNotification (SendNotificationRequest) returns (SendNotificationReply) { // only used for testing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegisterNotificationListenerRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message NotificationMessage {
|
||||||
|
int64 timestamp = 1;
|
||||||
|
string title = 2;
|
||||||
|
string message = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SendNotificationRequest {
|
||||||
|
NotificationMessage notification = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SendNotificationReply {
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Help
|
// Help
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Loading…
Reference in New Issue
Block a user