Add priv. notifications

This commit is contained in:
Manfred Karrer 2016-07-03 04:05:33 +02:00
parent dc5cc522e2
commit 3e2f09b4a1
14 changed files with 576 additions and 12 deletions

View File

@ -57,7 +57,7 @@ public final class Alert implements StoragePayload {
} }
} }
public void setSigAndStoragePubKey(String signatureAsBase64, PublicKey storagePublicKey) { public void setSigAndPubKey(String signatureAsBase64, PublicKey storagePublicKey) {
this.signatureAsBase64 = signatureAsBase64; this.signatureAsBase64 = signatureAsBase64;
this.storagePublicKey = storagePublicKey; this.storagePublicKey = storagePublicKey;
this.storagePublicKeyBytes = new X509EncodedKeySpec(this.storagePublicKey.getEncoded()).getEncoded(); this.storagePublicKeyBytes = new X509EncodedKeySpec(this.storagePublicKey.getEncoded()).getEncoded();

View File

@ -125,7 +125,7 @@ public class AlertManager {
private void signAndAddSignatureToAlertMessage(Alert alert) { private void signAndAddSignatureToAlertMessage(Alert alert) {
String alertMessageAsHex = Utils.HEX.encode(alert.message.getBytes()); String alertMessageAsHex = Utils.HEX.encode(alert.message.getBytes());
String signatureAsBase64 = alertSigningKey.signMessage(alertMessageAsHex); String signatureAsBase64 = alertSigningKey.signMessage(alertMessageAsHex);
alert.setSigAndStoragePubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic()); alert.setSigAndPubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic());
} }
private boolean verifySignature(Alert alert) { private boolean verifySignature(Alert alert) {

View File

@ -34,5 +34,7 @@ public class AlertModule extends AppModule {
protected final void configure() { protected final void configure() {
bind(AlertManager.class).in(Singleton.class); bind(AlertManager.class).in(Singleton.class);
bind(AlertService.class).in(Singleton.class); bind(AlertService.class).in(Singleton.class);
bind(PrivateNotificationManager.class).in(Singleton.class);
bind(PrivateNotificationService.class).in(Singleton.class);
} }
} }

View File

@ -0,0 +1,63 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.alert;
import io.bitsquare.app.Version;
import io.bitsquare.common.crypto.Sig;
import io.bitsquare.common.wire.Payload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
public final class PrivateNotification implements Payload {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
private static final Logger log = LoggerFactory.getLogger(PrivateNotification.class);
public final String message;
private String signatureAsBase64;
private transient PublicKey publicKey;
private byte[] publicKeyBytes;
public PrivateNotification(String message) {
this.message = message;
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
try {
in.defaultReadObject();
publicKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
} catch (Throwable t) {
log.warn("Exception at readObject: " + t.getMessage());
}
}
public void setSigAndPubKey(String signatureAsBase64, PublicKey storagePublicKey) {
this.signatureAsBase64 = signatureAsBase64;
this.publicKey = storagePublicKey;
this.publicKeyBytes = new X509EncodedKeySpec(this.publicKey.getEncoded()).getEncoded();
}
public String getSignatureAsBase64() {
return signatureAsBase64;
}
}

View File

@ -0,0 +1,140 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.alert;
import com.google.inject.Inject;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.crypto.DecryptedMsgWithPubKey;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.trade.offer.Offer;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.security.SignatureException;
import static org.bitcoinj.core.Utils.HEX;
public class PrivateNotificationManager {
private static final Logger log = LoggerFactory.getLogger(PrivateNotificationManager.class);
private final PrivateNotificationService privateNotificationService;
private final KeyRing keyRing;
private final ObjectProperty<PrivateNotification> privateNotificationMessageProperty = new SimpleObjectProperty<>();
// Pub key for developer global privateNotification message
private static final String pubKeyAsHex = "02ba7c5de295adfe57b60029f3637a2c6b1d0e969a8aaefb9e0ddc3a7963f26925";
private ECKey privateNotificationSigningKey;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, Initialization
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public PrivateNotificationManager(PrivateNotificationService privateNotificationService, KeyRing keyRing) {
this.privateNotificationService = privateNotificationService;
this.keyRing = keyRing;
privateNotificationService.addDecryptedDirectMessageListener(this::handleMessage);
privateNotificationService.addDecryptedMailboxListener(this::handleMessage);
}
private void handleMessage(DecryptedMsgWithPubKey decryptedMsgWithPubKey, NodeAddress senderNodeAddress) {
Message message = decryptedMsgWithPubKey.message;
if (message instanceof PrivateNotificationMessage) {
PrivateNotificationMessage privateNotificationMessage = (PrivateNotificationMessage) message;
log.trace("Received privateNotificationMessage: " + privateNotificationMessage);
if (privateNotificationMessage.getSenderNodeAddress().equals(senderNodeAddress)) {
final PrivateNotification privateNotification = privateNotificationMessage.privateNotification;
if (verifySignature(privateNotification))
privateNotificationMessageProperty.set(privateNotification);
} else {
log.warn("Peer address not matching for privateNotificationMessage");
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public ReadOnlyObjectProperty<PrivateNotification> privateNotificationProperty() {
return privateNotificationMessageProperty;
}
public boolean sendPrivateNotificationMessageIfKeyIsValid(PrivateNotification privateNotification, Offer offer, String privKeyString) {
// if there is a previous message we remove that first
// if (user.getDevelopersPrivateNotification() != null)
// removePrivateNotificationMessageIfKeyIsValid(privKeyString);
boolean isKeyValid = isKeyValid(privKeyString);
if (isKeyValid) {
signAndAddSignatureToPrivateNotificationMessage(privateNotification);
// user.setDevelopersPrivateNotification(privateNotification);
privateNotificationService.sendPrivateNotificationMessage(privateNotification, offer, null, null);
}
return isKeyValid;
}
public boolean removePrivateNotificationMessageIfKeyIsValid(String privKeyString) {
/* PrivateNotification developersPrivateNotification = user.getDevelopersPrivateNotification();
if (isKeyValid(privKeyString) && developersPrivateNotification != null) {
privateNotificationService.removePrivateNotificationMessage(developersPrivateNotification, null, null);
user.setDevelopersPrivateNotification(null);
return true;
} else {
return false;
}*/
return false;
}
private boolean isKeyValid(String privKeyString) {
try {
privateNotificationSigningKey = ECKey.fromPrivate(new BigInteger(1, HEX.decode(privKeyString)));
return pubKeyAsHex.equals(Utils.HEX.encode(privateNotificationSigningKey.getPubKey()));
} catch (Throwable t) {
return false;
}
}
private void signAndAddSignatureToPrivateNotificationMessage(PrivateNotification privateNotification) {
String privateNotificationMessageAsHex = Utils.HEX.encode(privateNotification.message.getBytes());
String signatureAsBase64 = privateNotificationSigningKey.signMessage(privateNotificationMessageAsHex);
privateNotification.setSigAndPubKey(signatureAsBase64, keyRing.getSignatureKeyPair().getPublic());
}
private boolean verifySignature(PrivateNotification privateNotification) {
String privateNotificationMessageAsHex = Utils.HEX.encode(privateNotification.message.getBytes());
try {
ECKey.fromPublicOnly(HEX.decode(pubKeyAsHex)).verifyMessage(privateNotificationMessageAsHex, privateNotification.getSignatureAsBase64());
return true;
} catch (SignatureException e) {
log.warn("verifySignature failed");
return false;
}
}
}

View File

@ -0,0 +1,74 @@
package io.bitsquare.alert;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.messaging.MailboxMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
public class PrivateNotificationMessage implements MailboxMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
private static final Logger log = LoggerFactory.getLogger(PrivateNotificationMessage.class);
private NodeAddress myNodeAddress;
public PrivateNotification privateNotification;
private final String uid = UUID.randomUUID().toString();
private final int messageVersion = Version.getP2PMessageVersion();
public PrivateNotificationMessage(PrivateNotification privateNotification, NodeAddress myNodeAddress) {
this.myNodeAddress = myNodeAddress;
this.privateNotification = privateNotification;
}
@Override
public NodeAddress getSenderNodeAddress() {
return myNodeAddress;
}
@Override
public String getUID() {
return uid;
}
@Override
public int getMessageVersion() {
return messageVersion;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PrivateNotificationMessage)) return false;
PrivateNotificationMessage that = (PrivateNotificationMessage) o;
if (messageVersion != that.messageVersion) return false;
if (myNodeAddress != null ? !myNodeAddress.equals(that.myNodeAddress) : that.myNodeAddress != null)
return false;
if (privateNotification != null ? !privateNotification.equals(that.privateNotification) : that.privateNotification != null)
return false;
return !(uid != null ? !uid.equals(that.uid) : that.uid != null);
}
@Override
public int hashCode() {
int result = myNodeAddress != null ? myNodeAddress.hashCode() : 0;
result = 31 * result + (privateNotification != null ? privateNotification.hashCode() : 0);
result = 31 * result + (uid != null ? uid.hashCode() : 0);
result = 31 * result + messageVersion;
return result;
}
@Override
public String toString() {
return "PrivateNotificationMessage{" +
"myNodeAddress=" + myNodeAddress +
", privateNotification=" + privateNotification +
", uid='" + uid + '\'' +
", messageVersion=" + messageVersion +
'}';
}
}

View File

@ -0,0 +1,88 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.alert;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.messaging.DecryptedDirectMessageListener;
import io.bitsquare.p2p.messaging.DecryptedMailboxListener;
import io.bitsquare.p2p.messaging.SendMailboxMessageListener;
import io.bitsquare.trade.offer.Offer;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
/**
* Used to load global privateNotification messages.
* The message is signed by the project developers private key and use data protection.
*/
public class PrivateNotificationService {
private static final Logger log = LoggerFactory.getLogger(PrivateNotificationService.class);
private final P2PService p2PService;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, Initialization
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public PrivateNotificationService(P2PService p2PService) {
this.p2PService = p2PService;
}
public void addDecryptedMailboxListener(DecryptedMailboxListener listener) {
p2PService.addDecryptedMailboxListener(listener);
}
public void addDecryptedDirectMessageListener(DecryptedDirectMessageListener listener) {
p2PService.addDecryptedDirectMessageListener(listener);
}
public void sendPrivateNotificationMessage(PrivateNotification privateNotification, Offer offer, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
p2PService.sendEncryptedMailboxMessage(offer.getOffererNodeAddress(),
offer.getPubKeyRing(),
new PrivateNotificationMessage(privateNotification, p2PService.getNetworkNode().getNodeAddress()),
new SendMailboxMessageListener() {
@Override
public void onArrived() {
log.trace("PrivateNotificationMessage arrived at peer. PrivateNotificationMessage = " + privateNotification);
if (resultHandler != null) resultHandler.handleResult();
}
@Override
public void onStoredInMailbox() {
log.trace("PrivateNotificationMessage was stored in mailbox. PrivateNotificationMessage = " + privateNotification);
if (resultHandler != null) resultHandler.handleResult();
}
@Override
public void onFault(String errorMessage) {
if (errorMessageHandler != null)
errorMessageHandler.handleErrorMessage("Add privateNotificationMessage failed");
}
});
}
public void removePrivateNotificationMessage(PrivateNotification privateNotification, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
}
}

View File

@ -1,6 +1,8 @@
package io.bitsquare.gui.components; package io.bitsquare.gui.components;
import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.gui.main.overlays.editor.PeerInfoWithTagEditor; import io.bitsquare.gui.main.overlays.editor.PeerInfoWithTagEditor;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences; import io.bitsquare.user.Preferences;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.Group; import javafx.scene.Group;
@ -24,6 +26,8 @@ public class PeerInfoIcon extends Group {
private final String hostName; private final String hostName;
private final String tooltipText; private final String tooltipText;
private final int numTrades; private final int numTrades;
private PrivateNotificationManager privateNotificationManager;
private Offer offer;
private final Map<String, String> peerTagMap; private final Map<String, String> peerTagMap;
private final Label numTradesLabel; private final Label numTradesLabel;
private final double SIZE = 26; private final double SIZE = 26;
@ -33,10 +37,12 @@ public class PeerInfoIcon extends Group {
private final Pane tagPane; private final Pane tagPane;
private final Pane numTradesPane; private final Pane numTradesPane;
public PeerInfoIcon(String hostName, String tooltipText, int numTrades) { public PeerInfoIcon(String hostName, String tooltipText, int numTrades, PrivateNotificationManager privateNotificationManager, Offer offer) {
this.hostName = hostName; this.hostName = hostName;
this.tooltipText = tooltipText; this.tooltipText = tooltipText;
this.numTrades = numTrades; this.numTrades = numTrades;
this.privateNotificationManager = privateNotificationManager;
this.offer = offer;
peerTagMap = Preferences.INSTANCE.getPeerTagMap(); peerTagMap = Preferences.INSTANCE.getPeerTagMap();
@ -97,7 +103,7 @@ public class PeerInfoIcon extends Group {
getChildren().addAll(background, avatarImageView, tagPane, numTradesPane); getChildren().addAll(background, avatarImageView, tagPane, numTradesPane);
setOnMouseClicked(e -> new PeerInfoWithTagEditor() setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, offer)
.hostName(hostName) .hostName(hostName)
.numTrades(numTrades) .numTrades(numTrades)
.position(localToScene(new Point2D(0, 0))) .position(localToScene(new Point2D(0, 0)))

View File

@ -20,6 +20,8 @@ package io.bitsquare.gui.main;
import com.google.inject.Inject; import com.google.inject.Inject;
import io.bitsquare.alert.Alert; import io.bitsquare.alert.Alert;
import io.bitsquare.alert.AlertManager; import io.bitsquare.alert.AlertManager;
import io.bitsquare.alert.PrivateNotification;
import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.app.BitsquareApp; import io.bitsquare.app.BitsquareApp;
import io.bitsquare.app.DevFlags; import io.bitsquare.app.DevFlags;
import io.bitsquare.app.Log; import io.bitsquare.app.Log;
@ -101,6 +103,7 @@ public class MainViewModel implements ViewModel {
private final DisputeManager disputeManager; private final DisputeManager disputeManager;
final Preferences preferences; final Preferences preferences;
private final AlertManager alertManager; private final AlertManager alertManager;
private PrivateNotificationManager privateNotificationManager;
private final WalletPasswordWindow walletPasswordWindow; private final WalletPasswordWindow walletPasswordWindow;
private final NotificationCenter notificationCenter; private final NotificationCenter notificationCenter;
private final TacWindow tacWindow; private final TacWindow tacWindow;
@ -171,7 +174,8 @@ public class MainViewModel implements ViewModel {
PriceFeed priceFeed, PriceFeed priceFeed,
ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager, ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager,
OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences, OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences,
User user, AlertManager alertManager, WalletPasswordWindow walletPasswordWindow, User user, AlertManager alertManager, PrivateNotificationManager privateNotificationManager,
WalletPasswordWindow walletPasswordWindow,
NotificationCenter notificationCenter, TacWindow tacWindow, Clock clock, NotificationCenter notificationCenter, TacWindow tacWindow, Clock clock,
KeyRing keyRing, Navigation navigation, BSFormatter formatter) { KeyRing keyRing, Navigation navigation, BSFormatter formatter) {
this.priceFeed = priceFeed; this.priceFeed = priceFeed;
@ -185,6 +189,7 @@ public class MainViewModel implements ViewModel {
this.disputeManager = disputeManager; this.disputeManager = disputeManager;
this.preferences = preferences; this.preferences = preferences;
this.alertManager = alertManager; this.alertManager = alertManager;
this.privateNotificationManager = privateNotificationManager;
this.walletPasswordWindow = walletPasswordWindow; this.walletPasswordWindow = walletPasswordWindow;
this.notificationCenter = notificationCenter; this.notificationCenter = notificationCenter;
this.tacWindow = tacWindow; this.tacWindow = tacWindow;
@ -517,6 +522,7 @@ public class MainViewModel implements ViewModel {
openOfferManager.onAllServicesInitialized(); openOfferManager.onAllServicesInitialized();
arbitratorManager.onAllServicesInitialized(); arbitratorManager.onAllServicesInitialized();
alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) -> displayAlertIfPresent(newValue)); alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) -> displayAlertIfPresent(newValue));
privateNotificationManager.privateNotificationProperty().addListener((observable, oldValue, newValue) -> displayPrivateNotification(newValue));
displayAlertIfPresent(alertManager.alertMessageProperty().get()); displayAlertIfPresent(alertManager.alertMessageProperty().get());
setupBtcNumPeersWatcher(); setupBtcNumPeersWatcher();
@ -865,6 +871,12 @@ public class MainViewModel implements ViewModel {
new DisplayAlertMessageWindow().alertMessage(alert).show(); new DisplayAlertMessageWindow().alertMessage(alert).show();
} }
private void displayPrivateNotification(PrivateNotification privateNotification) {
new Popup<>().headLine("Important notification from Bitsquare developers!")
.information(privateNotification.message)
.show();
}
private void swapPendingOfferFundingEntries() { private void swapPendingOfferFundingEntries() {
tradeManager.getAddressEntriesForAvailableBalanceStream() tradeManager.getAddressEntriesForAvailableBalanceStream()
.filter(addressEntry -> addressEntry.getOfferId() != null) .filter(addressEntry -> addressEntry.getOfferId() != null)

View File

@ -17,6 +17,7 @@
package io.bitsquare.gui.main.offer.offerbook; package io.bitsquare.gui.main.offer.offerbook;
import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.ActivatableViewAndModel; import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.FxmlView;
@ -68,6 +69,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
private final Navigation navigation; private final Navigation navigation;
private final OfferDetailsWindow offerDetailsWindow; private final OfferDetailsWindow offerDetailsWindow;
private BSFormatter formatter; private BSFormatter formatter;
private PrivateNotificationManager privateNotificationManager;
private ComboBox<TradeCurrency> currencyComboBox; private ComboBox<TradeCurrency> currencyComboBox;
private ComboBox<PaymentMethod> paymentMethodComboBox; private ComboBox<PaymentMethod> paymentMethodComboBox;
@ -87,12 +89,13 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
OfferBookView(OfferBookViewModel model, Navigation navigation, OfferDetailsWindow offerDetailsWindow, BSFormatter formatter) { OfferBookView(OfferBookViewModel model, Navigation navigation, OfferDetailsWindow offerDetailsWindow, BSFormatter formatter, PrivateNotificationManager privateNotificationManager) {
super(model); super(model);
this.navigation = navigation; this.navigation = navigation;
this.offerDetailsWindow = offerDetailsWindow; this.offerDetailsWindow = offerDetailsWindow;
this.formatter = formatter; this.formatter = formatter;
this.privateNotificationManager = privateNotificationManager;
} }
@Override @Override
@ -706,7 +709,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
boolean hasTraded = numPastTrades > 0; boolean hasTraded = numPastTrades > 0;
String tooltipText = hasTraded ? "Offerers onion address: " + hostName + "\n" + String tooltipText = hasTraded ? "Offerers onion address: " + hostName + "\n" +
"You have already traded " + numPastTrades + " times with that offerer." : "Offerers onion address: " + hostName; "You have already traded " + numPastTrades + " times with that offerer." : "Offerers onion address: " + hostName;
Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades); Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades, privateNotificationManager, newItem.getOffer());
setPadding(new Insets(-2, 0, -2, 0)); setPadding(new Insets(-2, 0, -2, 0));
if (identIcon != null) if (identIcon != null)
setGraphic(identIcon); setGraphic(identIcon);

View File

@ -1,8 +1,11 @@
package io.bitsquare.gui.main.overlays.editor; package io.bitsquare.gui.main.overlays.editor;
import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.gui.components.InputTextField; import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.overlays.Overlay; import io.bitsquare.gui.main.overlays.Overlay;
import io.bitsquare.gui.main.overlays.windows.SendPrivateNotificationWindow;
import io.bitsquare.gui.util.FormBuilder; import io.bitsquare.gui.util.FormBuilder;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences; import io.bitsquare.user.Preferences;
import javafx.animation.Interpolator; import javafx.animation.Interpolator;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
@ -16,6 +19,7 @@ import javafx.geometry.Point2D;
import javafx.scene.Camera; import javafx.scene.Camera;
import javafx.scene.PerspectiveCamera; import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.transform.Rotate; import javafx.scene.transform.Rotate;
@ -38,9 +42,13 @@ public class PeerInfoWithTagEditor extends Overlay<PeerInfoWithTagEditor> {
private String hostName; private String hostName;
private int numTrades; private int numTrades;
private ChangeListener<Boolean> focusListener; private ChangeListener<Boolean> focusListener;
private PrivateNotificationManager privateNotificationManager;
private Offer offer;
public PeerInfoWithTagEditor() { public PeerInfoWithTagEditor(PrivateNotificationManager privateNotificationManager, Offer offer) {
this.privateNotificationManager = privateNotificationManager;
this.offer = offer;
width = 400; width = 400;
type = Type.Undefined; type = Type.Undefined;
if (INSTANCE != null) if (INSTANCE != null)
@ -123,6 +131,14 @@ public class PeerInfoWithTagEditor extends Overlay<PeerInfoWithTagEditor> {
Map<String, String> peerTagMap = Preferences.INSTANCE.getPeerTagMap(); Map<String, String> peerTagMap = Preferences.INSTANCE.getPeerTagMap();
String tag = peerTagMap.containsKey(hostName) ? peerTagMap.get(hostName) : ""; String tag = peerTagMap.containsKey(hostName) ? peerTagMap.get(hostName) : "";
inputTextField.setText(tag); inputTextField.setText(tag);
Button button = FormBuilder.addButton(gridPane, ++rowIndex, "Send private message");
button.setOnAction(e -> {
new SendPrivateNotificationWindow(offer)
.onAddAlertMessage(privateNotificationManager::sendPrivateNotificationMessageIfKeyIsValid)
.onRemoveAlertMessage(privateNotificationManager::removePrivateNotificationMessageIfKeyIsValid)
.show();
});
} }
@Override @Override

View File

@ -0,0 +1,154 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.overlays.windows;
import io.bitsquare.alert.PrivateNotification;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.overlays.Overlay;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.trade.offer.Offer;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.bitsquare.gui.util.FormBuilder.addLabelInputTextField;
import static io.bitsquare.gui.util.FormBuilder.addLabelTextArea;
public class SendPrivateNotificationWindow extends Overlay<SendPrivateNotificationWindow> {
private static final Logger log = LoggerFactory.getLogger(SendPrivateNotificationWindow.class);
private Button sendButton;
private SendPrivateNotificationHandler sendPrivateNotificationHandler;
private RemoveAlertMessageHandler removeAlertMessageHandler;
private Offer offer;
///////////////////////////////////////////////////////////////////////////////////////////
// Interface
///////////////////////////////////////////////////////////////////////////////////////////
public interface SendPrivateNotificationHandler {
boolean handle(PrivateNotification privateNotification, Offer offer, String privKey);
}
public interface RemoveAlertMessageHandler {
boolean handle(String privKey);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////////////////////////////////////////////
public SendPrivateNotificationWindow(Offer offer) {
this.offer = offer;
type = Type.Attention;
}
public void show() {
if (headLine == null)
headLine = "Edit ban list";
width = 600;
createGridPane();
addHeadLine();
addSeparator();
addContent();
applyStyles();
display();
}
public SendPrivateNotificationWindow onAddAlertMessage(SendPrivateNotificationHandler sendPrivateNotificationHandler) {
this.sendPrivateNotificationHandler = sendPrivateNotificationHandler;
return this;
}
public SendPrivateNotificationWindow onRemoveAlertMessage(RemoveAlertMessageHandler removeAlertMessageHandler) {
this.removeAlertMessageHandler = removeAlertMessageHandler;
return this;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void setupKeyHandler(Scene scene) {
if (!hideCloseButton) {
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ESCAPE) {
e.consume();
doClose();
}
});
}
}
private void addContent() {
InputTextField keyInputTextField = addLabelInputTextField(gridPane, ++rowIndex, "Ban list private key:", 10).second;
Tuple2<Label, TextArea> labelTextAreaTuple2 = addLabelTextArea(gridPane, ++rowIndex, "Private alert message:", "Enter message");
TextArea alertMessageTextArea = labelTextAreaTuple2.second;
Label first = labelTextAreaTuple2.first;
first.setMinWidth(150);
sendButton = new Button("Send private alert message");
sendButton.setOnAction(e -> {
if (alertMessageTextArea.getText().length() > 0 && keyInputTextField.getText().length() > 0) {
if (sendPrivateNotificationHandler.handle(
new PrivateNotification(alertMessageTextArea.getText()),
offer,
keyInputTextField.getText()))
hide();
else
new Popup().warning("The key you entered was not correct.").width(300).onClose(() -> blurAgain()).show();
}
});
Button removeAlertMessageButton = new Button("Remove notification");
removeAlertMessageButton.setOnAction(e -> {
if (keyInputTextField.getText().length() > 0) {
if (removeAlertMessageHandler.handle(keyInputTextField.getText()))
hide();
else
new Popup().warning("The key you entered was not correct.").width(300).onClose(() -> blurAgain()).show();
}
});
closeButton = new Button("Close");
closeButton.setOnAction(e -> {
hide();
closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run());
});
HBox hBox = new HBox();
hBox.setSpacing(10);
GridPane.setRowIndex(hBox, ++rowIndex);
GridPane.setColumnIndex(hBox, 1);
hBox.getChildren().addAll(sendButton, removeAlertMessageButton, closeButton);
gridPane.getChildren().add(hBox);
GridPane.setMargin(hBox, new Insets(10, 0, 0, 0));
}
}

View File

@ -17,6 +17,7 @@
package io.bitsquare.gui.main.portfolio.closedtrades; package io.bitsquare.gui.main.portfolio.closedtrades;
import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.gui.common.view.ActivatableViewAndModel; import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.HyperlinkWithIcon; import io.bitsquare.gui.components.HyperlinkWithIcon;
@ -52,14 +53,16 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
private final BSFormatter formatter; private final BSFormatter formatter;
private final OfferDetailsWindow offerDetailsWindow; private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private PrivateNotificationManager privateNotificationManager;
private SortedList<ClosedTradableListItem> sortedList; private SortedList<ClosedTradableListItem> sortedList;
@Inject @Inject
public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) { public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow, PrivateNotificationManager privateNotificationManager) {
super(model); super(model);
this.formatter = formatter; this.formatter = formatter;
this.offerDetailsWindow = offerDetailsWindow; this.offerDetailsWindow = offerDetailsWindow;
this.tradeDetailsWindow = tradeDetailsWindow; this.tradeDetailsWindow = tradeDetailsWindow;
this.privateNotificationManager = privateNotificationManager;
} }
@Override @Override
@ -235,7 +238,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
int numPastTrades = model.getNumPastTrades(newItem.getTradable()); int numPastTrades = model.getNumPastTrades(newItem.getTradable());
String hostName = ((Trade) newItem.getTradable()).getTradingPeerNodeAddress().hostName; String hostName = ((Trade) newItem.getTradable()).getTradingPeerNodeAddress().hostName;
Node identIcon = new PeerInfoIcon(hostName, "Trading peers onion address: " + hostName, numPastTrades); Node identIcon = new PeerInfoIcon(hostName, "Trading peers onion address: " + hostName, numPastTrades, privateNotificationManager, newItem.getTradable().getOffer());
setPadding(new Insets(-2, 0, -2, 0)); setPadding(new Insets(-2, 0, -2, 0));
if (identIcon != null) if (identIcon != null)
setGraphic(identIcon); setGraphic(identIcon);

View File

@ -17,6 +17,7 @@
package io.bitsquare.gui.main.portfolio.pendingtrades; package io.bitsquare.gui.main.portfolio.pendingtrades;
import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.gui.common.view.ActivatableViewAndModel; import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.FxmlView;
@ -50,6 +51,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private final BSFormatter formatter; private final BSFormatter formatter;
private PrivateNotificationManager privateNotificationManager;
@FXML @FXML
TableView<PendingTradesListItem> tableView; TableView<PendingTradesListItem> tableView;
@FXML @FXML
@ -70,10 +72,11 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public PendingTradesView(PendingTradesViewModel model, TradeDetailsWindow tradeDetailsWindow, BSFormatter formatter) { public PendingTradesView(PendingTradesViewModel model, TradeDetailsWindow tradeDetailsWindow, BSFormatter formatter, PrivateNotificationManager privateNotificationManager) {
super(model); super(model);
this.tradeDetailsWindow = tradeDetailsWindow; this.tradeDetailsWindow = tradeDetailsWindow;
this.formatter = formatter; this.formatter = formatter;
this.privateNotificationManager = privateNotificationManager;
} }
@Override @Override
@ -454,7 +457,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
boolean hasTraded = numPastTrades > 0; boolean hasTraded = numPastTrades > 0;
String tooltipText = hasTraded ? "Trading peers onion address: " + hostName + "\n" + String tooltipText = hasTraded ? "Trading peers onion address: " + hostName + "\n" +
"You have already traded " + numPastTrades + " times with that peer." : "Trading peers onion address: " + hostName; "You have already traded " + numPastTrades + " times with that peer." : "Trading peers onion address: " + hostName;
Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades); Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades, privateNotificationManager, newItem.getTrade().getOffer());
setPadding(new Insets(-2, 0, -2, 0)); setPadding(new Insets(-2, 0, -2, 0));
if (identIcon != null) if (identIcon != null)
setGraphic(identIcon); setGraphic(identIcon);