Skip to content

Commit

Permalink
Merge pull request #1246 from uProxy/lucyhe-shareerror
Browse files Browse the repository at this point in the history
Show troubleshoot dialog on sharer's side. Keep error flow within popup dialog.
  • Loading branch information
lucyhe committed Apr 13, 2015
2 parents 090843a + 8c5b5e5 commit e1fec9b
Show file tree
Hide file tree
Showing 12 changed files with 351 additions and 84 deletions.
3 changes: 2 additions & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"core-header-panel": "Polymer/core-header-panel#~0.5.5",
"core-drawer-panel": "Polymer/core-drawer-panel#~0.5.5",
"core-overlay": "Polymer/core-overlay#~0.5.5",
"lodash": "~3.6.0"
"lodash": "~3.6.0",
"paper-toast": "Polymer/paper-toast#~0.5.5"
}
}
45 changes: 36 additions & 9 deletions src/generic_core/remote-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,23 @@ module Core {

private isUpdatePending_ = false;

// Resolve this promise when rtcToNet is created and therefore not null.
// Used to help determine when to call handleSignal (which relies
// on rtcToNet or socksToRtc being not null).
// The promise is reset in resetSharerCreated().
public onceSharerCreated :Promise<void> = null;
// Helper function used to fulfill onceSharerCreated.
private fulfillRtcToNetCreated_ :Function;
private sharingReset_ :Promise<void> = null;

// TODO: set up a better type for this
private sendUpdate_ :(x :uProxy.Update, data?:Object) => void;

constructor(
sendUpdate :(x :uProxy.Update, data?:Object) => void
) {
this.sendUpdate_ = sendUpdate;
this.resetSharerCreated();
}

private createSender_ = (type :uProxy.MessageType) => {
Expand Down Expand Up @@ -67,7 +77,11 @@ module Core {
target.handleSignalFromPeer(msg);
};

public startShare = () => {
public startShare = () :Promise<void> => {
if (this.rtcToNet_) {
throw new Error('rtcToNet_ already exists');
}

this.rtcToNet_ = new RtcToNet.RtcToNet(
<freedom_RTCPeerConnection.RTCConfiguration> {
iceServers: core.globalSettings.stunServers
Expand All @@ -81,7 +95,7 @@ module Core {
this.rtcToNet_.bytesReceivedFromPeer.setSyncHandler(this.handleBytesReceived_);
this.rtcToNet_.bytesSentToPeer.setSyncHandler(this.handleBytesSent_);

this.rtcToNet_.onceClosed.then(() => {
this.sharingReset_ = this.rtcToNet_.onceClosed.then(() => {
this.localSharingWithRemote = SharingState.NONE;
this.sendUpdate_(uProxy.Update.STOP_GIVING);
this.rtcToNet_ = null;
Expand All @@ -92,27 +106,40 @@ module Core {

this.localSharingWithRemote = SharingState.TRYING_TO_SHARE_ACCESS;
this.stateRefresh_();
this.fulfillRtcToNetCreated_();

this.rtcToNet_.onceReady.then(() => {
this.localSharingWithRemote = SharingState.SHARING_ACCESS;
this.sendUpdate_(uProxy.Update.START_GIVING);
this.stateRefresh_();
}).catch((e) => {
this.localSharingWithRemote = SharingState.NONE;
this.stateRefresh_();
this.rtcToNet_ = null;
this.stopShare();
});

return this.rtcToNet_.onceReady;
}

// This *must* be called if you receive an OFFER signal while there is an existing
// rtcToNet_ instance. Right before you stop the existing instance, make a call to
// this function so that CANDIDATEs received after the new OFFER will know to wait
// for a new rtcToNet_ instance to be created. Otherwise, CANDIDATE signals can be
// dropped or handled by old rtcToNet_ instances.
public resetSharerCreated = () :void => {
this.onceSharerCreated = new Promise<void>((F, R) => {
this.fulfillRtcToNetCreated_ = F;
});
}

public stopShare = () :Promise<void> => {
if (this.localSharingWithRemote === SharingState.NONE) {
log.warn('Cannot stop when not proxying');
return;
log.warn('Cannot stop sharing when neither sharing nor trying to share.');
return Promise.resolve<void>();
}

this.localSharingWithRemote = SharingState.NONE;
this.stateRefresh_();
return this.rtcToNet_.close();
this.rtcToNet_.close();
return this.sharingReset_;
}

public startGet = () :Promise<Net.Endpoint> => {
Expand Down Expand Up @@ -180,7 +207,7 @@ module Core {

public stopGet = () :Promise<void> => {
if (this.localGettingFromRemote === GettingState.NONE) {
log.warn('Cannot stop proxying when not proxying');
log.warn('Cannot stop proxying when neither proxying nor trying to proxy.');
return;
}

Expand Down
49 changes: 29 additions & 20 deletions src/generic_core/remote-instance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,39 +233,48 @@ describe('Core.RemoteInstance', () => {
spyOn(RtcToNet, 'RtcToNet').and.returnValue(fakeRtcToNet);
});

it('ignores CANDIDATE signal from client peer as server without OFFER', () => {
alice.handleSignal(uProxy.MessageType.SIGNAL_FROM_CLIENT_PEER, fakeCandidate);
expect(fakeSocksToRtc.handleSignalFromPeer).not.toHaveBeenCalled();
expect(fakeRtcToNet.handleSignalFromPeer).not.toHaveBeenCalled();
it('ignores CANDIDATE signal from client peer as server without OFFER', (done) => {
alice.handleSignal(uProxy.MessageType.SIGNAL_FROM_CLIENT_PEER, fakeCandidate).then(() => {
expect(fakeSocksToRtc.handleSignalFromPeer).not.toHaveBeenCalled();
expect(fakeRtcToNet.handleSignalFromPeer).not.toHaveBeenCalled();
done();
});
});

it('handles OFFER signal from client peer as server', () => {
alice.handleSignal(uProxy.MessageType.SIGNAL_FROM_CLIENT_PEER, fakeOffer);
expect(fakeSocksToRtc.handleSignalFromPeer).not.toHaveBeenCalled();
expect(fakeRtcToNet.handleSignalFromPeer).toHaveBeenCalledWith(fakeOffer);
it('handles OFFER signal from client peer as server', (done) => {
alice.handleSignal(uProxy.MessageType.SIGNAL_FROM_CLIENT_PEER, fakeOffer).then(() => {
expect(fakeSocksToRtc.handleSignalFromPeer).not.toHaveBeenCalled();
expect(fakeRtcToNet.handleSignalFromPeer).toHaveBeenCalledWith(fakeOffer);
done();
});
});

it('handles signal from server peer as client', (done) => {
alice.wireConsentFromRemote.isOffering = true;
alice.start().then(() => {
alice.handleSignal(uProxy.MessageType.SIGNAL_FROM_SERVER_PEER, fakeCandidate);
expect(fakeSocksToRtc.handleSignalFromPeer).toHaveBeenCalledWith(fakeCandidate);
expect(fakeRtcToNet.handleSignalFromPeer).not.toHaveBeenCalled();
done();
alice.handleSignal(uProxy.MessageType.SIGNAL_FROM_SERVER_PEER, fakeCandidate).then(() => {
expect(fakeSocksToRtc.handleSignalFromPeer).toHaveBeenCalledWith(fakeCandidate);
expect(fakeRtcToNet.handleSignalFromPeer).not.toHaveBeenCalled();
done();
});
}).catch((e) => console.error('error calling start: ' + e));
});

it('rejects invalid signals', () => {
alice.handleSignal(uProxy.MessageType.INSTANCE, fakeCandidate);
expect(fakeRtcToNet.handleSignalFromPeer).not.toHaveBeenCalled();
expect(fakeSocksToRtc.handleSignalFromPeer).not.toHaveBeenCalled();
it('rejects invalid signals', (done) => {
alice.handleSignal(uProxy.MessageType.INSTANCE, fakeCandidate).then(() => {
expect(fakeRtcToNet.handleSignalFromPeer).not.toHaveBeenCalled();
expect(fakeSocksToRtc.handleSignalFromPeer).not.toHaveBeenCalled();
done();
});
});

it('rejects message from client if consent has not been granted', () => {
it('rejects message from client if consent has not been granted', (done) => {
alice.user.consent.localGrantsAccessToRemote = false;
alice.handleSignal(uProxy.MessageType.SIGNAL_FROM_CLIENT_PEER, fakeCandidate);
expect(fakeSocksToRtc.handleSignalFromPeer).not.toHaveBeenCalled();
expect(fakeRtcToNet.handleSignalFromPeer).not.toHaveBeenCalled();
alice.handleSignal(uProxy.MessageType.SIGNAL_FROM_CLIENT_PEER, fakeCandidate).then(() => {
expect(fakeSocksToRtc.handleSignalFromPeer).not.toHaveBeenCalled();
expect(fakeRtcToNet.handleSignalFromPeer).not.toHaveBeenCalled();
done();
});
});
});
});
131 changes: 111 additions & 20 deletions src/generic_core/remote-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,25 @@ module Core {

// Number of milliseconds before timing out socksToRtc_.start
public SOCKS_TO_RTC_TIMEOUT :number = 30000;
private startupTimeout_ = null;
// Ensure RtcToNet is only closed after SocksToRtc times out (i.e. finishes
// trying to connect) by timing out rtcToNet_.start 15 seconds later than
// socksToRtc_.start
public RTC_TO_NET_TIMEOUT :number = this.SOCKS_TO_RTC_TIMEOUT + 15000;
// Timeouts for when to abort starting up SocksToRtc and RtcToNet.
private startSocksToRtcTimeout_ = null;
private startRtcToNetTimeout_ = null;

private connection_ :Core.RemoteConnection = null;

// By default, or when a successful connection is closed, this promise will
// reject to indicate the remote connection is not ready to accept signals.
// Only if the peer sent an OFFER signal and an rtcToNet instance is created
// will this fulfill.
// TODO: to make it clearer what this promise is waiting for, change to
// something like:
// Promise.all([this.receivedOffer_, this.connection_.onceSharerCreated])
private onceSharerReadyForOffer_ :Promise<void>;

/**
* Construct a Remote Instance as the result of receiving an instance
* handshake, or loadig from storage. Typically, instances are initialized
Expand All @@ -80,6 +96,7 @@ module Core {
public user :Core.User,
public instanceId :string) {
this.connection_ = new Core.RemoteConnection(this.handleConnectionUpdate_);
this.setSharerToNotReady_();

storage.load<RemoteInstanceState>(this.getStorePath())
.then((state) => {
Expand Down Expand Up @@ -110,7 +127,7 @@ module Core {
ui.update(uProxy.Update.START_GIVING_TO_FRIEND, this.instanceId);
break;
case uProxy.Update.STOP_GETTING:
this.clearTimeout_();
clearTimeout(this.startSocksToRtcTimeout_);
ui.update(uProxy.Update.STOP_GETTING_FROM_FRIEND, {
instanceId: this.instanceId,
error: data
Expand Down Expand Up @@ -148,24 +165,109 @@ module Core {
* TODO: return a boolean on success/failure
*/
public handleSignal = (type:uProxy.MessageType,
signalFromRemote:Object) => {
signalFromRemote:Object) :Promise<void> => {
if (uProxy.MessageType.SIGNAL_FROM_CLIENT_PEER === type) {
// If the remote peer sent signal as the client, we act as server.
if (!this.user.consent.localGrantsAccessToRemote) {
log.warn('Remote side attempted access without permission');
return;
return Promise.resolve<void>();
}

// Create a new rtcToNet object everytime there is an OFFER signal
if(signalFromRemote['type'] == WebRtc.SignalType.OFFER) {
this.connection_.startShare();
// Create a new rtcToNet object everytime there is an OFFER signal.
if (signalFromRemote['type'] == WebRtc.SignalType.OFFER) {
// TODO: Move the logic for resetting the onceSharerCreated promise inside
// remote-connection.ts.
this.connection_.resetSharerCreated();
this.onceSharerReadyForOffer_ = this.connection_.onceSharerCreated;
this.startShare_();
}
// Wait for the new rtcToNet instance to be created before you handle
// additional messages from a client peer.
return this.onceSharerReadyForOffer_.then(() => {
this.connection_.handleSignal({
type: type,
data: signalFromRemote
});
}).catch((e) => {
log.info(e + ' Received signal ' + signalFromRemote['type']);
return Promise.resolve<void>();
});

/*
TODO: Uncomment when getter sends a cancel signal if socksToRtc closes while
trying to connect. Something like:
https://github.com/uProxy/uproxy-lib/tree/lucyhe-emitcancelsignal
Issue: https://github.com/uProxy/uproxy/issues/1256
} else if (signalFromRemote['type'] == WebRtc.SignalType.CANCEL_OFFER) {
this.stopShare();
return;
}
*/
}

this.connection_.handleSignal({
type: type,
data: signalFromRemote
});
return Promise.resolve<void>();
}

/**
* When our peer sends us a signal that they'd like to be a client,
* we should try to start sharing.
*/
private startShare_ = () : void => {
var sharingStopped :Promise<void>;
if (this.localSharingWithRemote === SharingState.NONE) {
// Stop any existing sharing attempts with this instance.
sharingStopped = Promise.resolve<void>();
} else {
sharingStopped = this.stopShare();
}

// Start sharing only after an existing connection is stopped.
sharingStopped.then(() => {
// Set timeout to close rtcToNet_ if start() takes too long.
// Calling stopShare() at the end of the timeout makes the
// assumption that our peer failed to start getting access.
this.startRtcToNetTimeout_ = setTimeout(() => {
log.warn('Timing out rtcToNet_ connection');
ui.update(uProxy.Update.FRIEND_FAILED_TO_GET, this.user.name);
this.stopShare();
}, this.RTC_TO_NET_TIMEOUT);

this.connection_.startShare().then(() => {
clearTimeout(this.startRtcToNetTimeout_);
}, () => {
log.warn('Could not start sharing.');
clearTimeout(this.startRtcToNetTimeout_);
});
});
}

public stopShare = () :Promise<void> => {
if (this.localSharingWithRemote === SharingState.NONE) {
log.warn('Cannot stop sharing while currently not sharing.');
return Promise.resolve<void>();
}

if (this.localSharingWithRemote === SharingState.TRYING_TO_SHARE_ACCESS) {
clearTimeout(this.startRtcToNetTimeout_);
} else if (this.localSharingWithRemote === SharingState.SHARING_ACCESS) {
// Stop forwarding messages to the remote connection until we receive
// another OFFER.
this.setSharerToNotReady_();
}
return this.connection_.stopShare();
}

// When a sharer remote-instance is first created, or after a sharer closes
// a connection, we set that they are not ready to handle an incoming offer.
private setSharerToNotReady_ = () => {
this.onceSharerReadyForOffer_ =
Promise.reject(new Error('Ignoring signal from client without ' +
'rtcToNet ready to handle OFFER first.'));
}

/**
Expand All @@ -179,24 +281,17 @@ module Core {
}

// Cancel socksToRtc_ connection if start hasn't completed in 30 seconds.
this.startupTimeout_ = setTimeout(() => {
this.startSocksToRtcTimeout_ = setTimeout(() => {
log.warn('Timing out socksToRtc_ connection');
this.connection_.stopGet();
}, this.SOCKS_TO_RTC_TIMEOUT);

return this.connection_.startGet().then((endpoints :Net.Endpoint) => {
this.clearTimeout_();
clearTimeout(this.startSocksToRtcTimeout_);
return endpoints;
});
}

private clearTimeout_ = () => {
if (this.startupTimeout_) {
clearTimeout(this.startupTimeout_);
this.startupTimeout_ = null;
}
}

/**
* Stop using this remote instance as a proxy server.
*/
Expand Down Expand Up @@ -295,10 +390,6 @@ module Core {
}
}

public stopShare = () => {
this.connection_.stopShare();
}

} // class Core.RemoteInstance

export interface RemoteInstanceState {
Expand Down
4 changes: 3 additions & 1 deletion src/generic_ui/polymer/copypaste.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
paper-button {
background: #009688; /* dark green */
color: white;
margin-bottom: 24px;
}
paper-button.stop {
background: rgb(221, 221, 221);
color: rgb(112, 112, 112);
}
#getting, #sharing {
#getting, #sharing, #nothing {
margin: 24px;
}
a {
Expand All @@ -45,6 +46,7 @@
<uproxy-app-bar disableback='{{ ui.copyPasteGettingState === GettingState.GETTING_ACCESS || ui.copyPasteSharingState === SharingState.SHARING_ACCESS }}' on-go-back='{{ handleBackClick }}'>
<span hidden?='{{ SharingState.NONE === ui.copyPasteSharingState }}'>Share a </span>
<span hidden?='{{ SharingState.NONE !== ui.copyPasteSharingState || GettingState.NONE === ui.copyPasteGettingState }}'>Request a </span>
<span hidden?='{{ SharingState.NONE !== ui.copyPasteSharingState || !(SharingState.NONE !== ui.copyPasteSharingState || GettingState.NONE === ui.copyPasteGettingState) }}'>Start a </span>
<span>one-time connection</span>
</uproxy-app-bar>

Expand Down
Loading

0 comments on commit e1fec9b

Please sign in to comment.