Merge branch 'master' of https://github.com/highfidelity/hifi into workload

This commit is contained in:
samcake 2018-04-20 13:20:19 -07:00
commit 3d651de797
28 changed files with 657 additions and 369 deletions

3
.gitignore vendored
View file

@ -91,3 +91,6 @@ interface/compiledResources
# GPUCache
interface/resources/GPUCache/*
# package lock file for JSDoc tool
tools/jsdoc/package-lock.json

View file

@ -380,7 +380,7 @@ void Agent::executeScript() {
using namespace recording;
static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME);
Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this, scriptedAvatar](Frame::ConstPointer frame) {
Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [scriptedAvatar](Frame::ConstPointer frame) {
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel();

View file

@ -451,11 +451,12 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
return SharedNodePointer();
}
QUuid hintNodeID;
QUuid existingNodeID;
// in case this is a node that's failing to connect
// double check we don't have the same node whose sockets match exactly already in the list
limitedNodeList->eachNodeBreakable([&](const SharedNodePointer& node){
if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) {
// we have a node that already has these exact sockets - this can occur if a node
// is failing to connect to the domain
@ -465,15 +466,20 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
auto existingNodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
if (existingNodeData->getUsername() == username) {
hintNodeID = node->getUUID();
qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID();
existingNodeID = node->getUUID();
return false;
}
}
return true;
});
if (!existingNodeID.isNull()) {
limitedNodeList->killNodeWithUUID(existingNodeID);
}
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
// set the edit rights for this user
newNode->setPermissions(userPerms);

View file

@ -2903,7 +2903,7 @@ void DomainServer::updateReplicatedNodes() {
}
auto nodeList = DependencyManager::get<LimitedNodeList>();
nodeList->eachMatchingNode([this](const SharedNodePointer& otherNode) -> bool {
nodeList->eachMatchingNode([](const SharedNodePointer& otherNode) -> bool {
return otherNode->getType() == NodeType::Agent;
}, [this](const SharedNodePointer& otherNode) {
auto shouldReplicate = shouldReplicateNode(*otherNode);

View file

@ -1099,10 +1099,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition();
float distance = glm::distance(myAvatarPosition, position);
bool shouldMute = !audioClient->isMuted() && (distance < radius);
if (shouldMute) {
audioClient->toggleMute();
if (distance < radius) {
audioClient->setMuted(true);
audioScriptingInterface->environmentMuted();
}
});
@ -1527,7 +1526,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
if (state) {
if (action == controller::toInt(controller::Action::TOGGLE_MUTE)) {
DependencyManager::get<AudioClient>()->toggleMute();
auto audioClient = DependencyManager::get<AudioClient>();
audioClient->setMuted(!audioClient->isMuted());
} else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) {
cycleCamera();
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
@ -3468,7 +3468,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_M:
if (isMeta) {
DependencyManager::get<AudioClient>()->toggleMute();
auto audioClient = DependencyManager::get<AudioClient>();
audioClient->setMuted(!audioClient->isMuted());
}
break;
@ -5134,7 +5135,7 @@ void Application::update(float deltaTime) {
if (menu->isOptionChecked(MenuOption::AutoMuteAudio) && !audioClient->isMuted()) {
if (_lastFaceTrackerUpdate > 0
&& ((usecTimestampNow() - _lastFaceTrackerUpdate) > MUTE_MICROPHONE_AFTER_USECS)) {
audioClient->toggleMute();
audioClient->setMuted(true);
_lastFaceTrackerUpdate = 0;
}
} else {

View file

@ -50,110 +50,162 @@ float Audio::loudnessToLevel(float loudness) {
Audio::Audio() : _devices(_contextIsHMD) {
auto client = DependencyManager::get<AudioClient>().data();
connect(client, &AudioClient::muteToggled, this, &Audio::onMutedChanged);
connect(client, &AudioClient::noiseReductionChanged, this, &Audio::onNoiseReductionChanged);
connect(client, &AudioClient::muteToggled, this, &Audio::setMuted);
connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction);
connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::onInputVolumeChanged);
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
enableNoiseReduction(enableNoiseReductionSetting.get());
}
bool Audio::startRecording(const QString& filepath) {
auto client = DependencyManager::get<AudioClient>().data();
return client->startRecording(filepath);
return resultWithWriteLock<bool>([&] {
return client->startRecording(filepath);
});
}
bool Audio::getRecording() {
auto client = DependencyManager::get<AudioClient>().data();
return client->getRecording();
return resultWithReadLock<bool>([&] {
return client->getRecording();
});
}
void Audio::stopRecording() {
auto client = DependencyManager::get<AudioClient>().data();
client->stopRecording();
withWriteLock([&] {
client->stopRecording();
});
}
bool Audio::isMuted() const {
return resultWithReadLock<bool>([&] {
return _isMuted;
});
}
void Audio::setMuted(bool isMuted) {
if (_isMuted != isMuted) {
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "toggleMute");
bool changed = false;
withWriteLock([&] {
if (_isMuted != isMuted) {
_isMuted = isMuted;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
changed = true;
}
});
if (changed) {
emit mutedChanged(isMuted);
}
}
void Audio::onMutedChanged() {
bool isMuted = DependencyManager::get<AudioClient>()->isMuted();
if (_isMuted != isMuted) {
_isMuted = isMuted;
emit mutedChanged(_isMuted);
}
bool Audio::noiseReductionEnabled() const {
return resultWithReadLock<bool>([&] {
return _enableNoiseReduction;
});
}
void Audio::enableNoiseReduction(bool enable) {
if (_enableNoiseReduction != enable) {
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setNoiseReduction", Q_ARG(bool, enable));
enableNoiseReductionSetting.set(enable);
bool changed = false;
withWriteLock([&] {
if (_enableNoiseReduction != enable) {
_enableNoiseReduction = enable;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setNoiseReduction", Q_ARG(bool, enable), Q_ARG(bool, false));
enableNoiseReductionSetting.set(enable);
changed = true;
}
});
if (changed) {
emit noiseReductionChanged(enable);
}
}
void Audio::onNoiseReductionChanged() {
bool noiseReductionEnabled = DependencyManager::get<AudioClient>()->isNoiseReductionEnabled();
if (_enableNoiseReduction != noiseReductionEnabled) {
_enableNoiseReduction = noiseReductionEnabled;
emit noiseReductionChanged(_enableNoiseReduction);
}
float Audio::getInputVolume() const {
return resultWithReadLock<bool>([&] {
return _inputVolume;
});
}
void Audio::setInputVolume(float volume) {
// getInputVolume will not reflect changes synchronously, so clamp beforehand
volume = glm::clamp(volume, 0.0f, 1.0f);
if (_inputVolume != volume) {
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setInputVolume", Q_ARG(float, volume));
bool changed = false;
withWriteLock([&] {
if (_inputVolume != volume) {
_inputVolume = volume;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setInputVolume", Q_ARG(float, volume), Q_ARG(bool, false));
changed = true;
}
});
if (changed) {
emit inputVolumeChanged(volume);
}
}
void Audio::onInputVolumeChanged(float volume) {
if (_inputVolume != volume) {
_inputVolume = volume;
emit inputVolumeChanged(_inputVolume);
}
float Audio::getInputLevel() const {
return resultWithReadLock<bool>([&] {
return _inputLevel;
});
}
void Audio::onInputLoudnessChanged(float loudness) {
float level = loudnessToLevel(loudness);
if (_inputLevel != level) {
_inputLevel = level;
emit inputLevelChanged(_inputLevel);
bool changed = false;
withWriteLock([&] {
if (_inputLevel != level) {
_inputLevel = level;
changed = true;
}
});
if (changed) {
emit inputLevelChanged(level);
}
}
QString Audio::getContext() const {
return _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
return resultWithReadLock<QString>([&] {
return _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
});
}
void Audio::onContextChanged() {
bool changed = false;
bool isHMD = qApp->isHMDMode();
if (_contextIsHMD != isHMD) {
_contextIsHMD = isHMD;
emit contextChanged(getContext());
withWriteLock([&] {
if (_contextIsHMD != isHMD) {
_contextIsHMD = isHMD;
changed = true;
}
});
if (changed) {
emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP);
}
}
void Audio::setReverb(bool enable) {
DependencyManager::get<AudioClient>()->setReverb(enable);
withWriteLock([&] {
DependencyManager::get<AudioClient>()->setReverb(enable);
});
}
void Audio::setReverbOptions(const AudioEffectOptions* options) {
DependencyManager::get<AudioClient>()->setReverbOptions(options);
withWriteLock([&] {
DependencyManager::get<AudioClient>()->setReverbOptions(options);
});
}
void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
_devices.chooseInputDevice(device, isHMD);
withWriteLock([&] {
_devices.chooseInputDevice(device, isHMD);
});
}
void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
_devices.chooseOutputDevice(device, isHMD);
withWriteLock([&] {
_devices.chooseOutputDevice(device, isHMD);
});
}

View file

@ -17,10 +17,11 @@
#include "AudioEffectOptions.h"
#include "SettingHandle.h"
#include "AudioFileWav.h"
#include <shared/ReadWriteLockable.h>
namespace scripting {
class Audio : public AudioScriptingInterface {
class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
Q_OBJECT
SINGLETON_DEPENDENCY
@ -40,16 +41,13 @@ public:
virtual ~Audio() {}
bool isMuted() const { return _isMuted; }
bool noiseReductionEnabled() const { return _enableNoiseReduction; }
float getInputVolume() const { return _inputVolume; }
float getInputLevel() const { return _inputLevel; }
bool isMuted() const;
bool noiseReductionEnabled() const;
float getInputVolume() const;
float getInputLevel() const;
QString getContext() const;
void setMuted(bool muted);
void enableNoiseReduction(bool enable);
void showMicMeter(bool show);
void setInputVolume(float volume);
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
@ -72,9 +70,9 @@ public slots:
void onContextChanged();
private slots:
void onMutedChanged();
void onNoiseReductionChanged();
void onInputVolumeChanged(float volume);
void setMuted(bool muted);
void enableNoiseReduction(bool enable);
void setInputVolume(float volume);
void onInputLoudnessChanged(float loudness);
protected:

View file

@ -531,8 +531,8 @@ private slots:
signals:
/**jsdoc
* Triggered when you change the domain you're visiting. <strong>Warning:</strong> Is not emitted if you go to domain that
* isn't running.
* Triggered when you change the domain you're visiting. <strong>Warning:</strong> Is not emitted if you go to a domain
* that isn't running.
* @function Window.domainChanged
* @param {string} domainURL - The domain's URL.
* @returns {Signal}

View file

@ -757,7 +757,7 @@ void AudioClient::Gate::flush() {
void AudioClient::handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message) {
if (!_muted) {
toggleMute();
setMuted(true);
// have the audio scripting interface emit a signal to say we were muted by the mixer
emit mutedByMixer();
@ -1384,15 +1384,21 @@ void AudioClient::sendMuteEnvironmentPacket() {
}
}
void AudioClient::toggleMute() {
_muted = !_muted;
emit muteToggled();
void AudioClient::setMuted(bool muted, bool emitSignal) {
if (_muted != muted) {
_muted = muted;
if (emitSignal) {
emit muteToggled(_muted);
}
}
}
void AudioClient::setNoiseReduction(bool enable) {
void AudioClient::setNoiseReduction(bool enable, bool emitSignal) {
if (_isNoiseGateEnabled != enable) {
_isNoiseGateEnabled = enable;
emit noiseReductionChanged();
if (emitSignal) {
emit noiseReductionChanged(_isNoiseGateEnabled);
}
}
}
@ -2018,9 +2024,11 @@ void AudioClient::startThread() {
moveToNewNamedThread(this, "Audio Thread", [this] { start(); });
}
void AudioClient::setInputVolume(float volume) {
void AudioClient::setInputVolume(float volume, bool emitSignal) {
if (_audioInput && volume != (float)_audioInput->volume()) {
_audioInput->setVolume(volume);
emit inputVolumeChanged(_audioInput->volume());
if (emitSignal) {
emit inputVolumeChanged(_audioInput->volume());
}
}
}

View file

@ -189,13 +189,13 @@ public slots:
void reset();
void audioMixerKilled();
void toggleMute();
void setMuted(bool muted, bool emitSignal = true);
bool isMuted() { return _muted; }
virtual bool setIsStereoInput(bool stereo) override;
virtual bool isStereoInput() override { return _isStereoInput; }
void setNoiseReduction(bool isNoiseGateEnabled);
void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true);
bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; }
bool getLocalEcho() { return _shouldEchoLocally; }
@ -218,7 +218,7 @@ public slots:
bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName);
float getInputVolume() const { return (_audioInput) ? (float)_audioInput->volume() : 0.0f; }
void setInputVolume(float volume);
void setInputVolume(float volume, bool emitSignal = true);
void setReverb(bool reverb);
void setReverbOptions(const AudioEffectOptions* options);
@ -229,8 +229,8 @@ public slots:
signals:
void inputVolumeChanged(float volume);
void muteToggled();
void noiseReductionChanged();
void muteToggled(bool muted);
void noiseReductionChanged(bool noiseReductionEnabled);
void mutedByMixer();
void inputReceived(const QByteArray& inputSamples);
void inputLoudnessChanged(float loudness);

View file

@ -278,7 +278,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
// and the current rendering load)
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [this](QQmlContext* surfaceContext) {
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [](QQmlContext* surfaceContext) {
// FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml.
surfaceContext->setContextProperty("desktop", QVariant());
// Let us interact with the keyboard

View file

@ -34,15 +34,16 @@ const QString GET_PLACE = "/api/v1/places/%1";
*
* @namespace location
* @property {Uuid} domainID - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid|Uuid.NULL} if you're not
* connected to the domain.
* connected to the domain or are in a serverless domain.
* <em>Read-only.</em>
* @property {Uuid} domainId - Synonym for <code>domainId</code>. <em>Read-only.</em> <strong>Deprecated:</strong> This property
* is deprecated and will soon be removed.
* @property {string} hostname - The name of the domain for your current metaverse address (e.g., <code>"AvatarIsland"</code>,
* <code>localhost</code>, or an IP address).
* <code>localhost</code>, or an IP address). Is blank if you're in a serverless domain.
* <em>Read-only.</em>
* @property {string} href - Your current metaverse address (e.g., <code>"hifi://avatarisland/15,-10,26/0,0,0,1"</code>)
* regardless of whether or not you're connected to the domain.
* regardless of whether or not you're connected to the domain. Starts with <code>"file:///"</code> if you're in a
* serverless domain.
* <em>Read-only.</em>
* @property {boolean} isConnected - <code>true</code> if you're connected to the domain in your current <code>href</code>
* metaverse address, otherwise <code>false</code>.

View file

@ -0,0 +1,94 @@
//
// HMACAuth.cpp
// libraries/networking/src
//
// Created by Simon Walton on 3/19/2018.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <openssl/opensslv.h>
#include <openssl/hmac.h>
#include "HMACAuth.h"
#include <QUuid>
#if OPENSSL_VERSION_NUMBER >= 0x10100000
HMACAuth::HMACAuth(AuthMethod authMethod)
: _hmacContext(HMAC_CTX_new())
, _authMethod(authMethod) { }
HMACAuth::~HMACAuth()
{
HMAC_CTX_free(_hmacContext);
}
#else
HMACAuth::HMACAuth(AuthMethod authMethod)
: _hmacContext(new HMAC_CTX())
, _authMethod(authMethod) {
HMAC_CTX_init(_hmacContext);
}
HMACAuth::~HMACAuth() {
HMAC_CTX_cleanup(_hmacContext);
delete _hmacContext;
}
#endif
bool HMACAuth::setKey(const char* keyValue, int keyLen) {
const EVP_MD* sslStruct = nullptr;
switch (_authMethod) {
case MD5:
sslStruct = EVP_md5();
break;
case SHA1:
sslStruct = EVP_sha1();
break;
case SHA224:
sslStruct = EVP_sha224();
break;
case SHA256:
sslStruct = EVP_sha256();
break;
case RIPEMD160:
sslStruct = EVP_ripemd160();
break;
default:
return false;
}
QMutexLocker lock(&_lock);
return (bool) HMAC_Init_ex(_hmacContext, keyValue, keyLen, sslStruct, nullptr);
}
bool HMACAuth::setKey(const QUuid& uidKey) {
const QByteArray rfcBytes(uidKey.toRfc4122());
return setKey(rfcBytes.constData(), rfcBytes.length());
}
bool HMACAuth::addData(const char* data, int dataLen) {
QMutexLocker lock(&_lock);
return (bool) HMAC_Update(_hmacContext, reinterpret_cast<const unsigned char*>(data), dataLen);
}
HMACAuth::HMACHash HMACAuth::result() {
HMACHash hashValue(EVP_MAX_MD_SIZE);
unsigned int hashLen;
QMutexLocker lock(&_lock);
HMAC_Final(_hmacContext, &hashValue[0], &hashLen);
hashValue.resize((size_t) hashLen);
// Clear state for possible reuse.
HMAC_Init_ex(_hmacContext, nullptr, 0, nullptr, nullptr);
return hashValue;
}

View file

@ -0,0 +1,40 @@
//
// HMACAuth.h
// libraries/networking/src
//
// Created by Simon Walton on 3/19/2018.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_HMACAuth_h
#define hifi_HMACAuth_h
#include <vector>
#include <memory>
#include <QtCore/QMutex>
class QUuid;
class HMACAuth {
public:
enum AuthMethod { MD5, SHA1, SHA224, SHA256, RIPEMD160 };
using HMACHash = std::vector<unsigned char>;
explicit HMACAuth(AuthMethod authMethod = MD5);
~HMACAuth();
bool setKey(const char* keyValue, int keyLen);
bool setKey(const QUuid& uidKey);
bool addData(const char* data, int dataLen);
HMACHash result();
private:
QMutex _lock;
struct hmac_ctx_st* _hmacContext;
AuthMethod _authMethod;
};
#endif // hifi_HMACAuth_h

View file

@ -36,6 +36,7 @@
#include "HifiSockAddr.h"
#include "NetworkLogging.h"
#include "udt/Packet.h"
#include "HMACAuth.h"
static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0);
@ -314,7 +315,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe
if (verifiedPacket && !ignoreVerification) {
QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet);
QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, sourceNode->getConnectionSecret());
QByteArray expectedHash = NLPacket::hashForPacketAndHMAC(packet, sourceNode->getAuthenticateHash());
// check if the md5 hash in the header matches the hash we would expect
if (packetHeaderHash != expectedHash) {
@ -354,15 +355,15 @@ void LimitedNodeList::collectPacketStats(const NLPacket& packet) {
_numCollectedBytes += packet.getDataSize();
}
void LimitedNodeList::fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret) {
void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth) {
if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) {
packet.writeSourceID(getSessionUUID());
}
if (!connectionSecret.isNull()
if (hmacAuth
&& !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())
&& !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) {
packet.writeVerificationHashGivenSecret(connectionSecret);
packet.writeVerificationHash(*hmacAuth);
}
}
@ -378,17 +379,17 @@ qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node&
emit dataSent(destinationNode.getType(), packet.getDataSize());
destinationNode.recordBytesSent(packet.getDataSize());
return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getConnectionSecret());
return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), &destinationNode.getAuthenticateHash());
}
qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret) {
HMACAuth* hmacAuth) {
Q_ASSERT(!packet.isPartOfMessage());
Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket",
"Trying to send a reliable packet unreliably.");
collectPacketStats(packet);
fillPacketHeader(packet, connectionSecret);
fillPacketHeader(packet, hmacAuth);
return _nodeSocket.writePacket(packet, sockAddr);
}
@ -401,7 +402,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
emit dataSent(destinationNode.getType(), packet->getDataSize());
destinationNode.recordBytesSent(packet->getDataSize());
return sendPacket(std::move(packet), *activeSocket, destinationNode.getConnectionSecret());
return sendPacket(std::move(packet), *activeSocket, &destinationNode.getAuthenticateHash());
} else {
qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node" << destinationNode << "- not sending";
return ERROR_SENDING_PACKET_BYTES;
@ -409,18 +410,18 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
}
qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret) {
HMACAuth* hmacAuth) {
Q_ASSERT(!packet->isPartOfMessage());
if (packet->isReliable()) {
collectPacketStats(*packet);
fillPacketHeader(*packet, connectionSecret);
fillPacketHeader(*packet, hmacAuth);
auto size = packet->getDataSize();
_nodeSocket.writePacket(std::move(packet), sockAddr);
return size;
} else {
return sendUnreliablePacket(*packet, sockAddr, connectionSecret);
return sendUnreliablePacket(*packet, sockAddr, hmacAuth);
}
}
@ -429,13 +430,14 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi
if (activeSocket) {
qint64 bytesSent = 0;
auto connectionSecret = destinationNode.getConnectionSecret();
auto& connectionHash = destinationNode.getAuthenticateHash();
// close the last packet in the list
packetList.closeCurrentPacket();
while (!packetList._packets.empty()) {
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), *activeSocket, connectionSecret);
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), *activeSocket,
&connectionHash);
}
emit dataSent(destinationNode.getType(), bytesSent);
@ -448,14 +450,14 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi
}
qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret) {
HMACAuth* hmacAuth) {
qint64 bytesSent = 0;
// close the last packet in the list
packetList.closeCurrentPacket();
while (!packetList._packets.empty()) {
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), sockAddr, connectionSecret);
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), sockAddr, hmacAuth);
}
return bytesSent;
@ -483,7 +485,7 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr<NLPacketList> packetList,
for (std::unique_ptr<udt::Packet>& packet : packetList->_packets) {
NLPacket* nlPacket = static_cast<NLPacket*>(packet.get());
collectPacketStats(*nlPacket);
fillPacketHeader(*nlPacket, destinationNode.getConnectionSecret());
fillPacketHeader(*nlPacket, &destinationNode.getAuthenticateHash());
}
return _nodeSocket.writePacketList(std::move(packetList), *activeSocket);
@ -506,7 +508,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
auto& destinationSockAddr = (overridenSockAddr.isNull()) ? *destinationNode.getActiveSocket()
: overridenSockAddr;
return sendPacket(std::move(packet), destinationSockAddr, destinationNode.getConnectionSecret());
return sendPacket(std::move(packet), destinationSockAddr, &destinationNode.getAuthenticateHash());
}
int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
@ -569,9 +571,10 @@ void LimitedNodeList::reset() {
// we need to make sure any socket connections are gone so wait on that here
_nodeSocket.clearConnections();
_connectionIDs.clear();
}
bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) {
bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID) {
QReadLocker readLocker(&_nodeMutex);
NodeHash::iterator it = _nodeHash.find(nodeUUID);
@ -585,7 +588,7 @@ bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) {
_nodeHash.unsafe_erase(it);
}
handleNodeKill(matchingNode);
handleNodeKill(matchingNode, newConnectionID);
return true;
}
@ -600,7 +603,7 @@ void LimitedNodeList::processKillNode(ReceivedMessage& message) {
killNodeWithUUID(nodeUUID);
}
void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
void LimitedNodeList::handleNodeKill(const SharedNodePointer& node, ConnectionID nextConnectionID) {
qCDebug(networking) << "Killed" << *node;
node->stopPingTimer();
emit nodeKilled(node);
@ -608,6 +611,15 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
if (auto activeSocket = node->getActiveSocket()) {
_nodeSocket.cleanupConnection(*activeSocket);
}
auto it = _connectionIDs.find(node->getUUID());
if (it != _connectionIDs.end()) {
if (nextConnectionID == NULL_CONNECTION_ID) {
it->second++;
} else {
it->second = nextConnectionID;
}
}
}
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
@ -629,6 +641,11 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
return matchingNode;
} else {
auto it = _connectionIDs.find(uuid);
if (it == _connectionIDs.end()) {
_connectionIDs[uuid] = INITIAL_CONNECTION_ID;
}
// we didn't have this node, so add them
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket);
newNode->setIsReplicated(isReplicated);
@ -703,13 +720,13 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
}
}
std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(PingType_t pingType) {
int packetSize = sizeof(PingType_t) + sizeof(quint64);
std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) {
int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(int64_t);
auto pingPacket = NLPacket::create(PacketType::Ping, packetSize);
pingPacket->writePrimitive(pingType);
pingPacket->writePrimitive(usecTimestampNow());
pingPacket->writePrimitive(_connectionIDs[nodeId]);
return pingPacket;
}

View file

@ -66,6 +66,10 @@ const QHostAddress DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME = QHostAddress::Lo
const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username";
using ConnectionID = int64_t;
const ConnectionID NULL_CONNECTION_ID { -1 };
const ConnectionID INITIAL_CONNECTION_ID { 0 };
typedef std::pair<QUuid, SharedNodePointer> UUIDNodePair;
typedef tbb::concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash;
@ -128,22 +132,20 @@ public:
virtual QUuid getDomainUUID() const { assert(false); return QUuid(); }
virtual HifiSockAddr getDomainSockAddr() const { assert(false); return HifiSockAddr(); }
// use sendUnreliablePacket to send an unrelaible packet (that you do not need to move)
// use sendUnreliablePacket to send an unreliable packet (that you do not need to move)
// either to a node (via its active socket) or to a manual sockaddr
qint64 sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode);
qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret = QUuid());
qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr);
// use sendPacket to send a moved unreliable or reliable NL packet to a node's active socket or manual sockaddr
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode);
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret = QUuid());
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr);
// use sendUnreliableUnorderedPacketList to unreliably send separate packets from the packet list
// either to a node's active socket or to a manual sockaddr
qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const Node& destinationNode);
qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret = QUuid());
HMACAuth* hmacAuth = nullptr);
// use sendPacketList to send reliable packet lists (ordered or unordered) to a node's active socket
// or to a manual sock addr
@ -180,7 +182,7 @@ public:
void getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond);
void resetPacketStats();
std::unique_ptr<NLPacket> constructPingPacket(PingType_t pingType = PingType::Agnostic);
std::unique_ptr<NLPacket> constructPingPacket(const QUuid& nodeId, PingType_t pingType = PingType::Agnostic);
std::unique_ptr<NLPacket> constructPingReplyPacket(ReceivedMessage& message);
static std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
@ -319,7 +321,7 @@ public slots:
void startSTUNPublicSocketUpdate();
virtual void sendSTUNRequest();
bool killNodeWithUUID(const QUuid& nodeUUID);
bool killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID = NULL_CONNECTION_ID);
signals:
void dataSent(quint8 channelType, int bytes);
@ -364,14 +366,14 @@ protected:
qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr,
const QUuid& connectionSecret = QUuid());
void collectPacketStats(const NLPacket& packet);
void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret = QUuid());
void fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth = nullptr);
void setLocalSocket(const HifiSockAddr& sockAddr);
bool packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet, Node* sourceNode = nullptr);
void processSTUNResponse(std::unique_ptr<udt::BasePacket> packet);
void handleNodeKill(const SharedNodePointer& node);
void handleNodeKill(const SharedNodePointer& node, ConnectionID newConnectionID = NULL_CONNECTION_ID);
void stopInitialSTUNUpdate(bool success);
@ -418,6 +420,7 @@ protected:
}
}
std::unordered_map<QUuid, ConnectionID> _connectionIDs;
private slots:
void flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp);

View file

@ -11,6 +11,8 @@
#include "NLPacket.h"
#include "HMACAuth.h"
int NLPacket::localHeaderSize(PacketType type) {
bool nonSourced = PacketTypeEnum::getNonSourcedPackets().contains(type);
bool nonVerified = PacketTypeEnum::getNonVerifiedPackets().contains(type);
@ -149,18 +151,12 @@ QByteArray NLPacket::verificationHashInHeader(const udt::Packet& packet) {
return QByteArray(packet.getData() + offset, NUM_BYTES_MD5_HASH);
}
QByteArray NLPacket::hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret) {
QCryptographicHash hash(QCryptographicHash::Md5);
QByteArray NLPacket::hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash) {
int offset = Packet::totalHeaderSize(packet.isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion)
+ NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH;
// add the packet payload and the connection UUID
hash.addData(packet.getData() + offset, packet.getDataSize() - offset);
hash.addData(connectionSecret.toRfc4122());
// return the hash
return hash.result();
auto hashResult { hash.result() };
return QByteArray((const char*) hashResult.data(), (int) hashResult.size());
}
void NLPacket::writeTypeAndVersion() {
@ -212,13 +208,14 @@ void NLPacket::writeSourceID(const QUuid& sourceID) const {
_sourceID = sourceID;
}
void NLPacket::writeVerificationHashGivenSecret(const QUuid& connectionSecret) const {
void NLPacket::writeVerificationHash(HMACAuth& hmacAuth) const {
Q_ASSERT(!PacketTypeEnum::getNonSourcedPackets().contains(_type) &&
!PacketTypeEnum::getNonVerifiedPackets().contains(_type));
auto offset = Packet::totalHeaderSize(isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion)
+ NUM_BYTES_RFC4122_UUID;
QByteArray verificationHash = hashForPacketAndSecret(*this, connectionSecret);
QByteArray verificationHash = hashForPacketAndHMAC(*this, hmacAuth);
memcpy(_packet.get() + offset, verificationHash.data(), verificationHash.size());
}

View file

@ -18,6 +18,8 @@
#include "udt/Packet.h"
class HMACAuth;
class NLPacket : public udt::Packet {
Q_OBJECT
public:
@ -71,7 +73,7 @@ public:
static QUuid sourceIDInHeader(const udt::Packet& packet);
static QByteArray verificationHashInHeader(const udt::Packet& packet);
static QByteArray hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret);
static QByteArray hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash);
PacketType getType() const { return _type; }
void setType(PacketType type);
@ -82,7 +84,7 @@ public:
const QUuid& getSourceID() const { return _sourceID; }
void writeSourceID(const QUuid& sourceID) const;
void writeVerificationHashGivenSecret(const QUuid& connectionSecret) const;
void writeVerificationHash(HMACAuth& hmacAuth) const;
protected:

View file

@ -86,10 +86,10 @@ NodeType_t NodeType::fromString(QString type) {
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
const HifiSockAddr& localSocket, QObject* parent) :
const HifiSockAddr& localSocket, QObject* parent) :
NetworkPeer(uuid, publicSocket, localSocket, parent),
_type(type),
_pingMs(-1), // "Uninitialized"
_authenticateHash(new HMACAuth), _pingMs(-1), // "Uninitialized"
_clockSkewUsec(0),
_mutex(),
_clockSkewMovingPercentile(30, 0.8f) // moving 80th percentile of 30 samples
@ -108,6 +108,7 @@ void Node::setType(char type) {
_symmetricSocket.setObjectName(typeString);
}
void Node::updateClockSkewUsec(qint64 clockSkewSample) {
_clockSkewMovingPercentile.updatePercentile(clockSkewSample);
_clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile();
@ -192,3 +193,12 @@ QDebug operator<<(QDebug debug, const Node& node) {
debug.nospace() << node.getPublicSocket() << "/" << node.getLocalSocket();
return debug.nospace();
}
void Node::setConnectionSecret(const QUuid& connectionSecret) {
if (_connectionSecret == connectionSecret) {
return;
}
_connectionSecret = connectionSecret;
_authenticateHash->setKey(_connectionSecret);
}

View file

@ -33,6 +33,7 @@
#include "SimpleMovingAverage.h"
#include "MovingPercentile.h"
#include "NodePermissions.h"
#include "HMACAuth.h"
class Node : public NetworkPeer {
Q_OBJECT
@ -55,7 +56,8 @@ public:
void setIsUpstream(bool isUpstream) { _isUpstream = isUpstream; }
const QUuid& getConnectionSecret() const { return _connectionSecret; }
void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; }
void setConnectionSecret(const QUuid& connectionSecret);
HMACAuth& getAuthenticateHash() const { return *_authenticateHash; }
NodeData* getLinkedData() const { return _linkedData.get(); }
void setLinkedData(std::unique_ptr<NodeData> linkedData) { _linkedData = std::move(linkedData); }
@ -97,6 +99,7 @@ private:
NodeType_t _type;
QUuid _connectionSecret;
std::unique_ptr<HMACAuth> _authenticateHash;
std::unique_ptr<NodeData> _linkedData;
bool _isReplicated { false };
int _pingMs;

View file

@ -214,6 +214,20 @@ void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, Shared
sendingNode->setSymmetricSocket(senderSockAddr);
}
}
int64_t connectionID;
message->readPrimitive(&connectionID);
auto it = _connectionIDs.find(sendingNode->getUUID());
if (it != _connectionIDs.end()) {
if (connectionID > it->second) {
qDebug() << "Received a ping packet with a larger connection id (" << connectionID << ">" << it->second << ") from "
<< sendingNode->getUUID();
killNodeWithUUID(sendingNode->getUUID(), connectionID);
}
}
}
void NodeList::processPingReplyPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
@ -705,16 +719,18 @@ void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) {
if (node->getConnectionAttempts() > 0 && node->getConnectionAttempts() % NUM_DEBUG_CONNECTION_ATTEMPTS == 0) {
qCDebug(networking) << "No response to UDP hole punch pings for node" << node->getUUID() << "in last second.";
}
auto nodeID = node->getUUID();
// send the ping packet to the local and public sockets for this node
auto localPingPacket = constructPingPacket(PingType::Local);
auto localPingPacket = constructPingPacket(nodeID, PingType::Local);
sendPacket(std::move(localPingPacket), *node, node->getLocalSocket());
auto publicPingPacket = constructPingPacket(PingType::Public);
auto publicPingPacket = constructPingPacket(nodeID, PingType::Public);
sendPacket(std::move(publicPingPacket), *node, node->getPublicSocket());
if (!node->getSymmetricSocket().isNull()) {
auto symmetricPingPacket = constructPingPacket(PingType::Symmetric);
auto symmetricPingPacket = constructPingPacket(nodeID, PingType::Symmetric);
sendPacket(std::move(symmetricPingPacket), *node, node->getSymmetricSocket());
}
@ -784,7 +800,7 @@ void NodeList::sendKeepAlivePings() {
auto type = node->getType();
return !node->isUpstream() && _nodeTypesOfInterest.contains(type) && !NodeType::isDownstream(type);
}, [&](const SharedNodePointer& node) {
sendPacket(constructPingPacket(), *node);
sendPacket(constructPingPacket(node->getUUID()), *node);
});
}

View file

@ -24,6 +24,8 @@ int packetTypeMetaTypeId = qRegisterMetaType<PacketType>();
PacketVersion versionForPacketType(PacketType packetType) {
switch (packetType) {
case PacketType::StunResponse:
return 17;
case PacketType::DomainList:
return static_cast<PacketVersion>(DomainListVersion::GetMachineFingerprintFromUUIDSupport);
case PacketType::EntityAdd:
@ -40,8 +42,21 @@ PacketVersion versionForPacketType(PacketType packetType) {
return static_cast<PacketVersion>(AvatarMixerPacketVersion::FBXReaderNodeReparenting);
case PacketType::MessagesData:
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
// ICE packets
case PacketType::ICEServerPeerInformation:
return 17;
case PacketType::ICEServerHeartbeatACK:
return 17;
case PacketType::ICEServerQuery:
return 17;
case PacketType::ICEServerHeartbeat:
return 18; // ICE Server Heartbeat signing
case PacketType::ICEPing:
return static_cast<PacketVersion>(IcePingVersion::SendICEPeerID);
case PacketType::ICEPingReply:
return 17;
case PacketType::ICEServerHeartbeatDenied:
return 17;
case PacketType::AssetMappingOperation:
case PacketType::AssetMappingOperationReply:
return static_cast<PacketVersion>(AssetServerPacketVersion::RedirectedMappings);
@ -71,12 +86,12 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::MicrophoneAudioWithEcho:
case PacketType::AudioStreamStats:
return static_cast<PacketVersion>(AudioVersion::HighDynamicRangeVolume);
case PacketType::ICEPing:
return static_cast<PacketVersion>(IcePingVersion::SendICEPeerID);
case PacketType::DomainSettings:
return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height
case PacketType::Ping:
return static_cast<PacketVersion>(PingVersion::IncludeConnectionID);
default:
return 17;
return 18;
}
}

View file

@ -323,4 +323,8 @@ enum class IcePingVersion : PacketVersion {
SendICEPeerID = 18
};
enum class PingVersion : PacketVersion {
IncludeConnectionID = 18
};
#endif // hifi_PacketHeaders_h

View file

@ -4,6 +4,12 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "8.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.2.tgz",
"integrity": "sha512-A6Uv1anbsCvrRDtaUXS2xZ5tlzD+Kg7yMRlSLFDy3z0r7KlGXDzL14vELXIAgpk2aJbU3XeZZQRcEkLkowT92g==",
"dev": true
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -40,12 +46,6 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=",
"dev": true
},
"array-find-index": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz",
@ -148,12 +148,6 @@
"lru-cache": "4.0.1"
}
},
"balanced-match": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz",
"integrity": "sha1-qRzdHr7xqGZZ5w/03vAWJfwtZ1Y=",
"dev": true
},
"base64-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz",
@ -217,16 +211,6 @@
"hoek": "2.16.3"
}
},
"brace-expansion": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
"integrity": "sha1-Rr/1ARXUf8mriYVKu4fZgHihCZE=",
"dev": true,
"requires": {
"balanced-match": "0.3.0",
"concat-map": "0.0.1"
}
},
"buffers": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
@ -515,29 +499,70 @@
"jsbn": "0.1.0"
}
},
"electron-download": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-2.1.1.tgz",
"integrity": "sha1-AH07HyrTco0nzP5PhJayY/kTijE=",
"electron": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/electron/-/electron-1.8.4.tgz",
"integrity": "sha512-2f1cx0G3riMFODXFftF5AHXy+oHfhpntZHTDN66Hxtl09gmEr42B3piNEod9MEmw72f75LX2JfeYceqq1PF8cA==",
"dev": true,
"requires": {
"debug": "2.2.0",
"home-path": "1.0.3",
"@types/node": "8.10.2",
"electron-download": "3.3.0",
"extract-zip": "1.5.0"
}
},
"electron-download": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-3.3.0.tgz",
"integrity": "sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg=",
"dev": true,
"requires": {
"debug": "2.6.9",
"fs-extra": "0.30.0",
"home-path": "1.0.5",
"minimist": "1.2.0",
"mkdirp": "0.5.1",
"mv": "2.1.1",
"nugget": "1.6.2",
"path-exists": "1.0.0",
"rc": "1.1.6"
"nugget": "2.0.1",
"path-exists": "2.1.0",
"rc": "1.1.6",
"semver": "5.5.0",
"sumchecker": "1.3.1"
},
"dependencies": {
"debug": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "0.7.1"
"ms": "2.0.0"
}
},
"fs-extra": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
"dev": true,
"requires": {
"graceful-fs": "4.1.3",
"jsonfile": "2.2.3",
"klaw": "1.3.1",
"path-is-absolute": "1.0.0",
"rimraf": "2.6.2"
}
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
"dev": true
},
"sumchecker": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-1.3.1.tgz",
"integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0=",
"dev": true,
"requires": {
"debug": "2.6.9",
"es6-promise": "4.2.4"
}
}
}
@ -579,9 +604,9 @@
}
},
"electron-packager": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-11.0.0.tgz",
"integrity": "sha512-ufyYMe3Gt6IEZm9RuG+KK3Nh+V2jZHWg9gihp8wylUNtleQihECIXtQdpPJxH9740XFERVPraNEaa7cZvDzpyw==",
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-12.0.0.tgz",
"integrity": "sha1-uC0k14ovIUA7v9FmpbFWmJTVzQw=",
"dev": true,
"requires": {
"asar": "0.14.2",
@ -590,20 +615,25 @@
"electron-osx-sign": "0.4.8",
"extract-zip": "1.5.0",
"fs-extra": "5.0.0",
"galactus": "0.2.0",
"get-package-info": "1.0.0",
"mz": "2.7.0",
"nodeify": "1.0.1",
"parse-author": "2.0.0",
"pify": "3.0.0",
"plist": "2.1.0",
"pruner": "0.0.7",
"rcedit": "0.9.0",
"rcedit": "1.0.0",
"resolve": "1.5.0",
"sanitize-filename": "1.6.1",
"semver": "5.5.0",
"yargs-parser": "8.1.0"
"yargs-parser": "9.0.2"
},
"dependencies": {
"camelcase": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
"dev": true
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
@ -726,6 +756,12 @@
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"rcedit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/rcedit/-/rcedit-1.0.0.tgz",
"integrity": "sha512-W7DNa34x/3OgWyDHsI172AG/Lr/lZ+PkavFkHj0QhhkBRcV9QTmRJE1tDKrWkx8XHPSBsmZkNv9OKue6pncLFQ==",
"dev": true
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
@ -746,19 +782,18 @@
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz",
"integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=",
"dev": true
},
"yargs-parser": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz",
"integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=",
"dev": true,
"requires": {
"camelcase": "4.1.0"
}
}
}
},
"electron-prebuilt": {
"version": "0.37.5",
"resolved": "https://registry.npmjs.org/electron-prebuilt/-/electron-prebuilt-0.37.5.tgz",
"integrity": "sha1-OkGJgod4FdOnrB+bLi9KcPQg/3A=",
"dev": true,
"requires": {
"electron-download": "2.1.1",
"extract-zip": "1.5.0"
}
},
"end-of-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
@ -787,6 +822,12 @@
"is-arrayish": "0.2.1"
}
},
"es6-promise": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
"integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@ -873,6 +914,62 @@
}
}
},
"flora-colossus": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-0.0.2.tgz",
"integrity": "sha1-fRvimh8X+k8isb1hSC+Gw04HuQE=",
"dev": true,
"requires": {
"debug": "3.1.0",
"fs-extra": "4.0.3"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"fs-extra": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
"dev": true,
"requires": {
"graceful-fs": "4.1.3",
"jsonfile": "4.0.0",
"universalify": "0.1.1"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11"
},
"dependencies": {
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true,
"optional": true
}
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
}
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@ -904,6 +1001,63 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"galactus": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/galactus/-/galactus-0.2.0.tgz",
"integrity": "sha1-w9Y7pVAkZv5A6mfMaJCFs90kqPw=",
"dev": true,
"requires": {
"debug": "3.1.0",
"flora-colossus": "0.0.2",
"fs-extra": "4.0.3"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"fs-extra": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
"dev": true,
"requires": {
"graceful-fs": "4.1.3",
"jsonfile": "4.0.0",
"universalify": "0.1.1"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11"
},
"dependencies": {
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true,
"optional": true
}
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
}
},
"generate-function": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
@ -1107,9 +1261,9 @@
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
},
"home-path": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.3.tgz",
"integrity": "sha1-ns5Z/sPwMubRC1Q0/uJk30wt4y8=",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.5.tgz",
"integrity": "sha1-eIspgVsS1Tus9XVkhHbm+QQdEz8=",
"dev": true
},
"hosted-git-info": {
@ -1461,15 +1615,6 @@
"mime-db": "1.22.0"
}
},
"minimatch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz",
"integrity": "sha1-UjYVelHk8ATBd/s8Un/33Xjw74M=",
"dev": true,
"requires": {
"brace-expansion": "1.1.3"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
@ -1723,61 +1868,9 @@
}
},
"ms": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
"dev": true
},
"mv": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
"integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
"dev": true,
"requires": {
"mkdirp": "0.5.1",
"ncp": "2.0.0",
"rimraf": "2.4.5"
},
"dependencies": {
"glob": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
"dev": true,
"requires": {
"inflight": "1.0.4",
"inherits": "2.0.1",
"minimatch": "3.0.0",
"once": "1.3.3",
"path-is-absolute": "1.0.0"
}
},
"rimraf": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
"dev": true,
"requires": {
"glob": "6.0.4"
}
}
}
},
"mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"requires": {
"any-promise": "1.3.0",
"object-assign": "4.0.1",
"thenify-all": "1.6.0"
}
},
"ncp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"node-notifier": {
@ -1843,27 +1936,27 @@
}
},
"nugget": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/nugget/-/nugget-1.6.2.tgz",
"integrity": "sha1-iMpuA7pXBqmRc/XaCQJZPWvK4Qc=",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz",
"integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=",
"dev": true,
"requires": {
"debug": "2.2.0",
"debug": "2.6.9",
"minimist": "1.2.0",
"pretty-bytes": "1.0.4",
"progress-stream": "1.2.0",
"request": "2.71.0",
"single-line-log": "0.4.1",
"single-line-log": "1.1.2",
"throttleit": "0.0.2"
},
"dependencies": {
"debug": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "0.7.1"
"ms": "2.0.0"
}
},
"throttleit": {
@ -1966,10 +2059,13 @@
}
},
"path-exists": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz",
"integrity": "sha1-1aiZjrce83p0w06w2eum6HjuoIE=",
"dev": true
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
"dev": true,
"requires": {
"pinkie-promise": "2.0.1"
}
},
"path-is-absolute": {
"version": "1.0.0",
@ -2070,46 +2166,6 @@
"is-promise": "1.0.1"
}
},
"pruner": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/pruner/-/pruner-0.0.7.tgz",
"integrity": "sha1-NF+8s+gHARY6HXrfVrrCKaWh5ME=",
"dev": true,
"requires": {
"fs-extra": "4.0.3"
},
"dependencies": {
"fs-extra": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
"dev": true,
"requires": {
"graceful-fs": "4.1.3",
"jsonfile": "4.0.0",
"universalify": "0.1.1"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11"
},
"dependencies": {
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true,
"optional": true
}
}
}
}
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@ -2153,12 +2209,6 @@
"strip-json-comments": "1.0.4"
}
},
"rcedit": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/rcedit/-/rcedit-0.9.0.tgz",
"integrity": "sha1-ORDfVzRTmeKwMl9KUZAH+J5V7xw=",
"dev": true
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
@ -2288,10 +2338,13 @@
"dev": true
},
"single-line-log": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz",
"integrity": "sha1-h6VWSfdJ14PsDc2AToFA2Yc8fO4=",
"dev": true
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz",
"integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=",
"dev": true,
"requires": {
"string-width": "1.0.1"
}
},
"sntp": {
"version": "1.0.9",
@ -2476,24 +2529,6 @@
}
}
},
"thenify": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
"integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=",
"dev": true,
"requires": {
"any-promise": "1.3.0"
}
},
"thenify-all": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
"dev": true,
"requires": {
"thenify": "3.3.0"
}
},
"throttleit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
@ -2694,23 +2729,6 @@
"y18n": "3.2.1"
}
},
"yargs-parser": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz",
"integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==",
"dev": true,
"requires": {
"camelcase": "4.1.0"
},
"dependencies": {
"camelcase": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
"dev": true
}
}
},
"yauzl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",

View file

@ -8,8 +8,8 @@
""
],
"devDependencies": {
"electron-packager": "^11.0.0",
"electron-prebuilt": "0.37.5"
"electron-packager": "^12.0.0",
"electron": "1.8.4"
},
"repository": {
"type": "git",
@ -25,6 +25,7 @@
"dependencies": {
"always-tail": "0.2.0",
"cheerio": "^0.19.0",
"electron-log": "1.1.1",
"extend": "^3.0.0",
"fs-extra": "^1.0.0",
"node-notifier": "^5.2.1",
@ -32,7 +33,6 @@
"request": "^2.67.0",
"request-progress": "1.0.2",
"tar-fs": "^1.12.0",
"yargs": "^3.30.0",
"electron-log": "1.1.1"
"yargs": "^3.30.0"
}
}

View file

@ -2,7 +2,7 @@ function ready() {
console.log("Ready");
const electron = require('electron');
const remote = require('remote');
const remote = electron.remote;
window.$ = require('./vendor/jquery/jquery-2.1.4.min.js');
$(".state").hide();

View file

@ -8,9 +8,9 @@ const nativeImage = electron.nativeImage;
const notifier = require('node-notifier');
const util = require('util');
const dialog = electron.dialog;
const Menu = require('menu');
const Tray = require('tray');
const shell = require('shell');
const Menu = electron.Menu;
const Tray = electron.Tray;
const shell = electron.shell;
const os = require('os');
const childProcess = require('child_process');
const path = require('path');

View file

@ -226,7 +226,7 @@ Process.prototype = extend(Process.prototype, {
}
});
} else {
var signal = force ? 'SIGKILL' : null;
var signal = force ? 'SIGKILL' : 'SIGTERM';
this.child.kill(signal);
}