///
///
///
///
///
import arraybuffers = require('../../../../third_party/uproxy-lib/arraybuffers/arraybuffers');
import signals = require('../../../../third_party/uproxy-lib/webrtc/signals');
import freedom_types = require('freedom.types');
import net = require('../../net/net.types');
import copypaste_api = require('./copypaste-api');
module copypaste_module {
export var onceReady :Promise> =
freedom('freedom-module.json', {
'logger': 'uproxy-lib/loggingprovider/freedom-module.json',
'debug': 'debug'
}).then((copypasteSocksFactory:() => freedom_types.OnAndEmit) => {
var copypaste :freedom_types.OnAndEmit = copypasteSocksFactory();
copypaste.on('signalForPeer', (message:signals.Message) => {
model.readyForStep2 = true;
// Append the new signalling message to the previous message(s), if any.
// Base64-encode the concatenated messages because some communication
// channels are likely to transform portions of the raw concatenated JSON
// into emoticons, whereas the base64 alphabet is much less prone to such
// unintended transformation.
var oldConcatenatedJson = base64Decode(model.outboundMessageValue.trim());
var newConcatenatedJson = oldConcatenatedJson + '\n' + JSON.stringify(message);
if (model.usingCrypto) {
copypaste.emit('friendKey', model.friendPublicKey);
copypaste.emit('signEncrypt', base64Encode(newConcatenatedJson));
}
model.outboundMessageValue = base64Encode(newConcatenatedJson);
});
copypaste.on('publicKeyExport', (publicKey:string) => {
model.userPublicKey = publicKey;
});
copypaste.on('ciphertext', (ciphertext:string) => {
model.outboundMessageValue = ciphertext;
});
copypaste.on('verifyDecryptResult', (result:VerifyDecryptResult) => {
model.inputDecrypted = true;
model.inputSigned = result.signedBy[0] == model.friendUserId;
model.inboundText = arraybuffers.arrayBufferToString(result.data);
parseInboundMessages(model.inboundText);
});
copypaste.on('bytesReceived', (numNewBytesReceived:number) => {
model.totalBytesReceived += numNewBytesReceived;
});
copypaste.on('bytesSent', (numNewBytesSent:number) => {
model.totalBytesSent += numNewBytesSent;
});
copypaste.on('proxyingStarted', (listeningEndpoint:net.Endpoint) => {
if (listeningEndpoint !== null) {
model.endpoint = listeningEndpoint.address + ':' + listeningEndpoint.port;
}
model.proxyingState = 'started';
});
copypaste.on('proxyingStopped', () => {
model.proxyingState = 'stopped';
});
return copypaste;
}, (e:Error) => {
console.error('could not load freedom: ' + e.message);
});
// TODO: use actual interaction to define user-ids.
export var model :copypaste_api.Model = {
givingOrGetting : null,
usingCrypto : false,
inputDecrypted : false,
inputSigned : false,
userPublicKey : '',
friendPublicKey : '',
friendUserId : 'Joe ',
readyForStep2 : false,
outboundMessageValue : '',
inboundText: '',
inputIsWellFormed : false,
proxyingState : 'notYetAttempted',
endpoint : null, // E.g., '127.0.0.1:9999'
totalBytesReceived : 0,
totalBytesSent : 0
};
// Stores the parsed messages for use later, if & when the user clicks the
// button for consuming the messages.
var parsedInboundMessages :signals.Message[];
// Define basee64 helper functions that are type-annotated and meaningfully
// named.
function base64Encode(unencoded:string): string {
return window.btoa(unencoded);
}
// Throws an exception if the input is malformed.
function base64Decode(encoded:string): string {
return window.atob(encoded);
}
// Parses the contents of the form field 'inboundMessageField' as a sequence of
// signalling messages. Enables/disables the corresponding form button, as
// appropriate. Returns null if the field contents are malformed.
export function parseInboundMessages(inboundMessageFieldValue:string) : void {
// Base64-decode the pasted text.
var signalsString :string = null;
try {
signalsString = base64Decode(inboundMessageFieldValue.trim());
} catch (e) {
// TODO: Notify the user that the pasted text is malformed.
return null;
}
var signals :string[] = signalsString.trim().split('\n');
// Each line should be a JSON representation of a signals.Message.
// Parse the lines here.
var parsedSignals :signals.Message[] = [];
for (var i = 0; i < signals.length; i++) {
var s :string = signals[i].trim();
// TODO: Consider detecting the error if the text is well-formed JSON but
// does not represent a signals.Message.
var message :signals.Message;
try {
message = JSON.parse(s);
} catch (e) {
parsedSignals = null;
break;
}
parsedSignals.push(message);
}
// Enable/disable, as appropriate, the button for consuming the messages.
if (null !== parsedSignals && parsedSignals.length > 0) {
model.inputIsWellFormed = true;
} else {
// TODO: Notify the user that the pasted text is malformed.
}
parsedInboundMessages = parsedSignals;
}
// Forwards each line from the paste box to the Freedom app, which
// interprets each as a signalling channel message. The Freedom app
// knows whether this message should be sent to the socks-to-rtc
// or rtc-to-net module. Disables the form field.
export function consumeInboundMessage() : void {
// Forward the signalling messages to the Freedom app.
onceReady.then(function(copypaste) {
for (var i = 0; i < parsedInboundMessages.length; i++) {
copypaste.emit('handleSignalMessage', parsedInboundMessages[i]);
}
});
model.proxyingState = 'connecting';
// TODO: Report success/failure to the user.
};
export function verifyDecryptInboundMessage(ciphertext:string) : void {
onceReady.then(function(copypaste) {
copypaste.emit('friendKey', model.friendPublicKey);
copypaste.emit('verifyDecrypt', ciphertext);
});
};
} // module copypaste_api
export var copypaste :copypaste_api.CopypasteApi = copypaste_module;