aboutsummaryrefslogtreecommitdiff
path: root/javascript/demo
diff options
context:
space:
mode:
authorRichard van der Hoff <richard@matrix.org>2016-05-25 14:53:28 +0100
committerRichard van der Hoff <richard@matrix.org>2016-05-25 17:48:01 +0100
commit315fbfc9211d0b7369dc9cb43bbffc8100f30e16 (patch)
tree215122373125eebe736af6b7cbdff8974be63a1f /javascript/demo
parent013f27f3dc8de747e82b74055c4dde55ca04b4c9 (diff)
Add a demo for group messaging via the JS bindings
Diffstat (limited to 'javascript/demo')
-rw-r--r--javascript/demo/demo.css8
-rw-r--r--javascript/demo/group_demo.html61
-rw-r--r--javascript/demo/group_demo.js421
-rw-r--r--javascript/demo/one_to_one_demo.html141
4 files changed, 631 insertions, 0 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/one_to_one_demo.html b/javascript/demo/one_to_one_demo.html
new file mode 100644
index 0000000..fc7e654
--- /dev/null
+++ b/javascript/demo/one_to_one_demo.html
@@ -0,0 +1,141 @@
+<html>
+<head>
+<script src="../olm.js"></script>
+<script>
+document.addEventListener("DOMContentLoaded", function (event) {
+ function progress(who, message) {
+ var message_element = document.createElement("pre");
+ var progress = document.getElementById(who + "_progress");
+ var start_content = document.createTextNode(message + "...");
+ var done_content = document.createTextNode(message + "... Done");
+ function start() {
+ message_element.appendChild(start_content);
+ progress.appendChild(message_element);
+ }
+ function done() {
+ message_element.replaceChild(done_content, start_content);
+ }
+ return {start:start, done:done};
+ }
+
+ window.alice = new Olm.Account();
+ window.bob = new Olm.Account();
+ var a_session = new Olm.Session();
+ var b_session = new Olm.Session();
+ var message_1;
+ var tasks = [];
+
+ tasks.push(["alice", "Creating account", function() { alice.create() }]);
+ tasks.push(["bob", "Creating account", function() { bob.create() }]);
+ tasks.push(["bob", "Generate one time keys", function() {
+ bob.generate_one_time_keys(1);
+ }]);
+ tasks.push(["alice", "Create outbound session", function() {
+ var bobs_id_keys = JSON.parse(bob.identity_keys());
+ var bobs_id_key = bobs_id_keys.curve25519;
+ var bobs_ot_keys = JSON.parse(bob.one_time_keys());
+ var bobs_ot_key;
+ for (key in bobs_ot_keys.curve25519) {
+ bobs_ot_key = bobs_ot_keys.curve25519[key];
+ }
+ a_session.create_outbound(alice, bobs_id_key, bobs_ot_key);
+ }]);
+ tasks.push(["alice", "Encrypt first message", function() {
+ message_1 = a_session.encrypt("");
+ }]);
+ tasks.push(["bob", "Create inbound session", function() {
+ b_session.create_inbound(bob, message_1.body);
+ }]);
+ tasks.push(["bob", "Decrypt first message", function() {
+ b_session.decrypt(message_1.type, message_1.body);
+ }]);
+
+ function glue_encrypt(from, to, from_session) {
+ var plain_input = document.getElementById(from + "_plain_input");
+ var cipher_output = document.getElementById(from + "_cipher_output");
+ var cipher_input = document.getElementById(to + "_cipher_input");
+ var encrypt = document.getElementById(from + "_encrypt");
+
+ encrypt.addEventListener("click", function() {
+ var message = from_session.encrypt(plain_input.value);
+ var message_element = document.createElement("pre");
+ var content = document.createTextNode(JSON.stringify(message));
+ message_element.appendChild(content);
+ cipher_output.appendChild(message_element);
+ message_element.addEventListener("click", function() {
+ cipher_input.value = JSON.stringify(message);
+ }, false);
+ }, false);
+ }
+
+ function glue_decrypt(to, to_session) {
+ var cipher_input = document.getElementById(to + "_cipher_input");
+ var plain_output = document.getElementById(to + "_plain_output");
+ var decrypt = document.getElementById(to + "_decrypt");
+
+ decrypt.addEventListener("click", function() {
+ var message = JSON.parse(cipher_input.value);
+ try {
+ var plaintext = to_session.decrypt(message.type, message.body);
+ } catch (e) {
+ var plaintext = "ERROR: " + e.message;
+ }
+ var message_element = document.createElement("pre");
+ var message_content = document.createTextNode(plaintext);
+ message_element.appendChild(message_content);
+ plain_output.appendChild(message_element);
+ }, false);
+ }
+
+ function do_tasks(next) {
+ if (tasks.length > 0) {
+ var task = tasks.shift();
+ var p = progress(task[0], task[1])
+ p.start();
+ window.setTimeout(function() {
+ task[2]();
+ p.done();
+ window.setTimeout(do_tasks, 50, next);
+ }, 50)
+ } else {
+ next();
+ }
+ }
+
+ do_tasks(function() {
+ glue_encrypt("alice", "bob", a_session);
+ glue_decrypt("bob", b_session);
+
+ glue_encrypt("bob", "alice", b_session);
+ glue_decrypt("alice", a_session);
+ });
+ }, false);
+
+</script>
+<body>
+<div id="alice">
+ <h1>Alice</h1>
+ <div id="alice_progress"></div>
+ <h2>Encryption</h2>
+ <textarea id="alice_plain_input"></textarea>
+ <button id="alice_encrypt">Encrypt</button>
+ <div id="alice_cipher_output"></div>
+ <h2>Decryption</h2>
+ <textarea id="alice_cipher_input"></textarea>
+ <button id="alice_decrypt">Decrypt</button>
+ <div id="alice_plain_output"></div>
+</div>
+<div id="bob">
+ <h1>Bob</h1>
+ <div id="bob_progress"></div>
+ <h2>Encryption</h2>
+ <textarea id="bob_plain_input"></textarea>
+ <button id="bob_encrypt">Encrypt</button>
+ <div id="bob_cipher_output"></div>
+ <h2>Decryption</h2>
+ <textarea id="bob_cipher_input"></textarea>
+ <button id="bob_decrypt">Decrypt</button>
+ <div id="bob_plain_output"></div>
+</div>
+</body>
+</html>