diff options
author | Richard van der Hoff <richard@matrix.org> | 2016-05-25 14:53:28 +0100 |
---|---|---|
committer | Richard van der Hoff <richard@matrix.org> | 2016-05-25 17:48:01 +0100 |
commit | 315fbfc9211d0b7369dc9cb43bbffc8100f30e16 (patch) | |
tree | 215122373125eebe736af6b7cbdff8974be63a1f | |
parent | 013f27f3dc8de747e82b74055c4dde55ca04b4c9 (diff) |
Add a demo for group messaging via the JS bindings
-rw-r--r-- | javascript/demo/demo.css | 8 | ||||
-rw-r--r-- | javascript/demo/group_demo.html | 61 | ||||
-rw-r--r-- | javascript/demo/group_demo.js | 421 | ||||
-rw-r--r-- | javascript/demo/one_to_one_demo.html (renamed from javascript/demo.html) | 2 |
4 files changed, 491 insertions, 1 deletions
diff --git a/javascript/demo/demo.css b/javascript/demo/demo.css new file mode 100644 index 0000000..bf07df1 --- /dev/null +++ b/javascript/demo/demo.css @@ -0,0 +1,8 @@ +div.user { + width: 500px; + float: left; + overflow: scroll; + margin: 0px 20px 0px 0px; + border: 1px solid black; + padding: 5px; +}
\ No newline at end of file diff --git a/javascript/demo/group_demo.html b/javascript/demo/group_demo.html new file mode 100644 index 0000000..31aacb0 --- /dev/null +++ b/javascript/demo/group_demo.html @@ -0,0 +1,61 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="demo.css"/> + <script src="../olm.js"></script> + <script src="group_demo.js"></script> + </head> +<body> +<div id="user1" class="user"> + <h1>User1</h1> + + <textarea class="user_plain_input"></textarea> + <button class="user_encrypt">Encrypt</button> + + <h2>Outgoing</h2> + + <h3>One-to-one output</h3> + <div class="user_cipher_output"></div> + + <h3>Group output</h3> + <div class="group_output"></div> + + <h2>Incoming</h2> + + <h3>One-to-one Received</h3> + <div class="user_cipher_input"></div> + + <h3>Group received</h3> + <div class="group_input"></div> + + <h2>Tasks</h2> + <div class="user_progress"></div> +</div> + +<div id="user2" class="user"> + <h1>User 2</h1> + + <textarea class="user_plain_input"></textarea> + <button class="user_encrypt">Encrypt</button> + + <h2>Outgoing</h2> + + <h3>One-to-one output</h3> + <div class="user_cipher_output"></div> + + <h3>Group output</h3> + <div class="group_output"></div> + + <h2>Incoming</h2> + + <h3>One-to-one Received</h3> + <div class="user_cipher_input"></div> + + <h3>Group received</h3> + <div class="group_input"></div> + + <h2>Tasks</h2> + <div class="user_progress"></div> +</div> + +</body> +</html> diff --git a/javascript/demo/group_demo.js b/javascript/demo/group_demo.js new file mode 100644 index 0000000..c8939d3 --- /dev/null +++ b/javascript/demo/group_demo.js @@ -0,0 +1,421 @@ +/* Javascript parts of the group demo. To use, load group_demo.html in your + * browser. + */ + +function buttonAndTextElement(buttonLabel, textContent, clickHandler) { + var el = document.createElement("div"); + + var button = document.createElement("button"); + el.appendChild(button); + button.appendChild(document.createTextNode(buttonLabel)); + + var message_element = document.createElement("tt"); + el.appendChild(message_element); + + var content = document.createTextNode(textContent); + message_element.appendChild(content); + + el.addEventListener("click", clickHandler, false); + return el; +} + +function DemoUser(name) { + this.name = name; + this.olmAccount = new Olm.Account(); + this.olmAccount.create(); + + /* a list of the people in our chat */ + this.peers = []; + + /* for each peer, a one-to-one session - indexed by id key and created on + * demand */ + this.peerSessions = {} + + /* for each peer, info on their sender session - indexed by id key and + * session id */ + this.peerGroupSessions = {}; + + /* our outbound group session */ + this.groupSession = undefined; + + /* a list of pending tasks */ + this.tasks = []; + this.taskWorker = undefined; +} + +DemoUser.prototype._progress = function(message) { + var progress = this.progressElement; + + var message_element = document.createElement("pre"); + var start_content = document.createTextNode(message + "..."); + function start() { + message_element.appendChild(start_content); + progress.appendChild(message_element); + } + function done(res) { + var done_content = document.createTextNode(message + "..." + res); + message_element.replaceChild(done_content, start_content); + } + return {start:start, done:done}; +}; + +DemoUser.prototype._do_tasks = function() { + var self = this; + var task = self.tasks.shift(); + var desc = task[0]; + var func = task[1]; + var callback = task[2]; + + var p = self._progress(desc); + p.start(); + + function done() { + p.done("Done"); + + if (callback) { + try { + callback.apply(undefined, arguments) + } catch (e) { + console.error("Uncaught exception in callback", e.stack || e); + } + } + + start_tasks(); + } + + // sleep 50ms before actually doing the task + self.taskWorker = window.setTimeout(function() { + try { + task[1](done); + } catch (e) { + console.error("Uncaught exception in task", e.stack || e); + p.done("Failed: "+e); + start_tasks(); + } + }, 50); + + + function start_tasks() { + if (self.tasks.length == 0) { + self.taskWorker = undefined; + return; + } + + self.taskWorker = window.setTimeout(self._do_tasks.bind(self), 50); + } +} + +/** + * add a function "task" to this user's queue of things to do. + * + * task is called with a single argument 'done' which is a function to call + * once the task is complete. + * + * 'callback' is called once the task is complete, with any arguments that + * were passed to 'done'. + */ +DemoUser.prototype.addTask = function(description, task, callback) { + this.tasks.push([description, task, callback]); + if(!this.taskWorker) { + this._do_tasks(); + } +}; + +DemoUser.prototype.addPeer = function(peer) { + this.peers.push(peer); +}; + +DemoUser.prototype.getIdKey = function() { + var keys = JSON.parse(this.olmAccount.identity_keys()); + return keys.curve25519; +}; + +DemoUser.prototype.generateKeys = function(callback) { + var self = this; + this.addTask("generate one time key", function(done) { + self.olmAccount.generate_one_time_keys(1); + done(); + }, callback); +}; + +DemoUser.prototype.getOneTimeKey = function() { + var self = this; + var keys = JSON.parse(self.olmAccount.one_time_keys()) + .curve25519; + for (key_id in keys) { + if (keys.hasOwnProperty(key_id)) { + return keys[key_id]; + } + } + throw new Error("No one-time-keys generated"); +}; + +/* ************************************************************************ + * + * one-to-one messaging + */ + +/** + * retrieve, or initiate, a one-to-one session to a given peer + */ +DemoUser.prototype.getPeerSession = function(peer, callback) { + var self = this; + var peerId = peer.getIdKey(); + if (this.peerSessions[peerId]) { + callback(this.peerSessions[peerId]); + return; + } + + this.addTask("get peer keys", function(done) { + key = peer.getOneTimeKey(); + done(key); + }, function(ot_key) { + self.addTask("create peer session", function(done) { + var session = new Olm.Session(); + session.create_outbound(self.olmAccount, peerId, ot_key); + self.peerSessions[peerId] = session; + done(session); + }, callback); + }); +}; + +/** + * encrypt a one-to-one message and prepare it for sending to a peer + */ +DemoUser.prototype.sendToPeer = function(peer, message, callback) { + var self = this; + this.getPeerSession(peer, function(session) { + self.addTask("encrypt one-to-one message", function(done) { + var encrypted = session.encrypt(message); + var packet = { + sender_key: self.getIdKey(), + ciphertext: encrypted, + }; + var json = JSON.stringify(packet); + + var el = buttonAndTextElement("send", json, function(ev) { + peer.receiveOneToOne(json); + }); + self.cipherOutputDiv.appendChild(el); + done(); + }, callback); + }); +}; + +/** + * handler for receiving a one-to-one message + */ +DemoUser.prototype.receiveOneToOne = function(jsonpacket) { + var self = this; + var el = buttonAndTextElement("decrypt", jsonpacket, function(ev) { + var sender = JSON.parse(jsonpacket).sender_key; + self.decryptOneToOne(jsonpacket, function(result) { + + var el2 = document.createElement("tt"); + el.appendChild(el2); + + var content = document.createTextNode(" -> "+result); + el2.appendChild(content); + + var body = JSON.parse(result); + + // create a new inbound session if we don't yet have one + if (!self.peerGroupSessions[sender] || + !self.peerGroupSessions[sender][body.session_id]) { + self.createInboundSession( + sender, body.session_id, body.message_index, body.session_key + ); + } + }); + }); + this.cipherInputDiv.appendChild(el); +}; + +/** + * add a task to decrypt a one-to-one message. Calls the callback with the + * decrypted plaintext + */ +DemoUser.prototype.decryptOneToOne = function(jsonpacket, callback) { + var self = this; + self.addTask("decrypt one-to-one message", function(done) { + var packet = JSON.parse(jsonpacket); + var peerId = packet.sender_key; + + var session = self.peerSessions[peerId]; + var plaintext; + if (session) { + plaintext = session.decrypt(packet.ciphertext.type, packet.ciphertext.body); + done(plaintext); + return; + } + + if (packet.ciphertext.type != 0) { + throw new Error("Unknown one-to-one session"); + } + + session = new Olm.Session(); + session.create_inbound(self.olmAccount, packet.ciphertext.body); + self.peerSessions[peerId] = session; + plaintext = session.decrypt(packet.ciphertext.type, packet.ciphertext.body); + done(plaintext); + }, callback) +}; + +/* ************************************************************************ + * + * group messaging + */ + + +/** + * retrieve, or initiate, an outbound group session + */ +DemoUser.prototype.getGroupSession = function() { + if (this.groupSession) { + return this.groupSession; + } + + this.groupSession = new Olm.OutboundGroupSession(); + this.groupSession.create(); + + var keymsg = { + "session_id": this.groupSession.session_id(), + "session_key": this.groupSession.session_key(), + "message_index": this.groupSession.message_index(), + }; + var jsonmsg = JSON.stringify(keymsg); + + for (var i = 0; i < this.peers.length; i++) { + var peer = this.peers[i]; + this.sendToPeer(peer, jsonmsg); + } + + return this.groupSession; +}; + +/** + * add a task to create an inbound group session + */ +DemoUser.prototype.createInboundSession = function( + peer_id, session_id, message_index, session_key, callback +) { + var self = this; + this.addTask("init inbound session", function(done) { + session = new Olm.InboundGroupSession(); + session.create(message_index, session_key); + if (!self.peerGroupSessions[peer_id]) { + self.peerGroupSessions[peer_id] = {}; + } + self.peerGroupSessions[peer_id][session_id] = session; + done(session); + }, callback); +}; + +/** + * handler for receiving a group message + */ +DemoUser.prototype.receiveGroup = function(jsonpacket) { + var self = this; + var el = buttonAndTextElement("decrypt", jsonpacket, function(ev) { + self.decryptGroup(jsonpacket, function(result) { + var el2 = document.createElement("tt"); + el.appendChild(el2); + + var content = document.createTextNode(" -> "+result); + el2.appendChild(content); + }); + }); + this.groupInputDiv.appendChild(el); +}; + +/** + * add a task to decrypt a received group message. Calls the callback with the + * decrypted plaintext + */ +DemoUser.prototype.decryptGroup = function(jsonpacket, callback) { + var self = this; + this.addTask("decrypt group message", function(done) { + var packet = JSON.parse(jsonpacket); + + var sender = packet.sender_key; + var session_id = packet.session_id; + + var peer_sessions = self.peerGroupSessions[sender]; + if (!peer_sessions) { + throw new Error("No sessions for sender "+sender); + } + + var session = peer_sessions[session_id]; + if (!session) { + throw new Error("Unknown session id " + session_id); + } + + var plaintext = session.decrypt(packet.body); + done(plaintext); + }, callback); +}; + + + +/** + * add a task to encrypt, and prepare for sending, a group message. + * + * Will create a group session if necessary + */ +DemoUser.prototype.encrypt = function(message) { + var self = this; + var session = this.getGroupSession(); + + self.addTask("encrypt group message", function(done) { + var encrypted = session.encrypt(message); + var packet = { + sender_key: self.getIdKey(), + session_id: session.session_id(), + body: encrypted, + }; + var json = JSON.stringify(packet); + + var el = buttonAndTextElement("send", json, function(ev) { + for (var i = 0; i < self.peers.length; i++) { + var peer = self.peers[i]; + peer.receiveGroup(json); + } + }); + self.groupOutputDiv.appendChild(el); + done(); + }); +}; + + + +function initUserDiv(demoUser, div) { + demoUser.progressElement = div.getElementsByClassName("user_progress")[0]; + demoUser.cipherOutputDiv = div.getElementsByClassName("user_cipher_output")[0]; + demoUser.cipherInputDiv = div.getElementsByClassName("user_cipher_input")[0]; + demoUser.groupOutputDiv = div.getElementsByClassName("group_output")[0]; + demoUser.groupInputDiv = div.getElementsByClassName("group_input")[0]; + + var plain_input = div.getElementsByClassName("user_plain_input")[0]; + var encrypt = div.getElementsByClassName("user_encrypt")[0]; + + encrypt.addEventListener("click", function() { + demoUser.encrypt(plain_input.value); + }, false); + +} + +function startDemo() { + var user1 = new DemoUser(); + initUserDiv(user1, document.getElementById("user1")); + user1.generateKeys(); + + var user2 = new DemoUser(); + initUserDiv(user2, document.getElementById("user2")); + user2.generateKeys(); + + user1.addPeer(user2); + user2.addPeer(user1); +} + + +document.addEventListener("DOMContentLoaded", startDemo, false); diff --git a/javascript/demo.html b/javascript/demo/one_to_one_demo.html index bebf918..fc7e654 100644 --- a/javascript/demo.html +++ b/javascript/demo/one_to_one_demo.html @@ -1,6 +1,6 @@ <html> <head> -<script src="olm.js"></script> +<script src="../olm.js"></script> <script> document.addEventListener("DOMContentLoaded", function (event) { function progress(who, message) { |