Merge branch 'master' into v2-presence

pull/977/head
Kegan Dougal 10 years ago
commit 2b4641d1f0

@ -111,6 +111,7 @@ Global initial sync API ``[Draft]``
- The invite event - The invite event
- Other state info (e.g. room name, topic, join_rules to know if pubilc) - Other state info (e.g. room name, topic, join_rules to know if pubilc)
- # members? - # members?
so clients know more information about the room other than the user_id of the so clients know more information about the room other than the user_id of the
inviter, timestamp and the room ID. inviter, timestamp and the room ID.
@ -637,7 +638,7 @@ A client may wish to send multiple actions in parallel. The send event APIs
support sending multiple events in a batch. support sending multiple events in a batch.
Inviting a user ``[ONGOING]`` Inviting a user ``[ONGOING]``
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. NOTE:: .. NOTE::
- Clients need to know *why* they are being invited (e.g. a ``reason`` key, - Clients need to know *why* they are being invited (e.g. a ``reason`` key,
just like for kicks/bans). However, this opens up a spam vector where any just like for kicks/bans). However, this opens up a spam vector where any
@ -1040,8 +1041,8 @@ relate to other events are referred to as "Child Events". The event being
related to is referred to as "Parent Events". Child events cannot stand alone as related to is referred to as "Parent Events". Child events cannot stand alone as
a separate entity; they require the parent event in order to make sense. a separate entity; they require the parent event in order to make sense.
Bundling Bundling updates
++++++++ ++++++++++++++++
Events that relate to another event should come down inside that event. That is, Events that relate to another event should come down inside that event. That is,
the top-level event should come down with all the child events at the same time. the top-level event should come down with all the child events at the same time.
This is called a "bundle" and it is represented as an array of events inside the This is called a "bundle" and it is represented as an array of events inside the
@ -1093,8 +1094,8 @@ not update the event itself*, and are *not required* in order to display the
parent event. Crucially, the child events can be paginated, whereas ``updates`` parent event. Crucially, the child events can be paginated, whereas ``updates``
child events cannot be paginated. child events cannot be paginated.
Bundling Bundling relations
++++++++ ++++++++++++++++++
Child events can be optionally bundled with the parent event, depending on your Child events can be optionally bundled with the parent event, depending on your
display mechanism. The number of child events which can be bundled should be display mechanism. The number of child events which can be bundled should be
limited to prevent events becoming too large. This limit should be set by the limited to prevent events becoming too large. This limit should be set by the

@ -198,12 +198,11 @@ Requests that a client can make to its Home Server
* get another user's Display Name / Avatar / metadata fields * get another user's Display Name / Avatar / metadata fields
[[TODO(paul): At some later stage we should consider the API for: TODO(paul): At some later stage we should consider the API for:
* get/set ACL permissions on my metadata fields * get/set ACL permissions on my metadata fields
* manage my ACL tokens * manage my ACL tokens
]]
Server-Server Server-Server
------------- -------------

@ -3,6 +3,8 @@ Host: ...
Content-Length: ... Content-Length: ...
Content-Type: application/json Content-Type: application/json
.. code :: javascript
{ {
"origin": "localhost:5000", "origin": "localhost:5000",
"pdus": [ "pdus": [
@ -42,6 +44,8 @@ HTTP/1.1 200 OK
Content-Length: ... Content-Length: ...
Content-Type: application/json Content-Type: application/json
.. code :: javascript
{ {
origin: ..., origin: ...,
prev_ids: ..., prev_ids: ...,

@ -125,12 +125,13 @@ m.invite_level
m.join_rules m.join_rules
Encodes the rules on how non-members can join the room. Has the following Encodes the rules on how non-members can join the room. Has the following
possibilities: possibilities:
"public" - a non-member can join the room directly
"knock" - a non-member cannot join the room, but can post a single "knock" - "public" - a non-member can join the room directly
- "knock" - a non-member cannot join the room, but can post a single "knock"
message requesting access, which existing members may approve or deny message requesting access, which existing members may approve or deny
"invite" - non-members cannot join the room without an invite from an - "invite" - non-members cannot join the room without an invite from an
existing member existing member
"private" - nobody who is not in the 'may_join' list or already a member - "private" - nobody who is not in the 'may_join' list or already a member
may join by any mechanism may join by any mechanism
In any of the first three modes, existing members with sufficient permission In any of the first three modes, existing members with sufficient permission
@ -263,9 +264,10 @@ resolve this:
that duplicate requests can be suppressed. On receipt of a room creation that duplicate requests can be suppressed. On receipt of a room creation
request that the HS thinks there already exists a room for, the invitation to request that the HS thinks there already exists a room for, the invitation to
join can be rejected if: join can be rejected if:
a) the HS believes the sending user is already a member of the room (and
- a) the HS believes the sending user is already a member of the room (and
maybe their HS has forgotten this fact), or maybe their HS has forgotten this fact), or
b) the proposed room has a lexicographically-higher ID than the existing - b) the proposed room has a lexicographically-higher ID than the existing
room (to resolve true race condition conflicts) room (to resolve true race condition conflicts)
* The room ID for a private 1:1 chat has a special form, determined by * The room ID for a private 1:1 chat has a special form, determined by

File diff suppressed because it is too large Load Diff

@ -27,6 +27,7 @@ all state events eventually.
Algorithm requirements Algorithm requirements
---------------------- ----------------------
We want the algorithm to have the following properties: We want the algorithm to have the following properties:
- Since we aren't guaranteed what order we receive state events in, except that - Since we aren't guaranteed what order we receive state events in, except that
we see parents before children, the state resolution algorithm must not depend we see parents before children, the state resolution algorithm must not depend
on the order and must always come to the same result. on the order and must always come to the same result.

@ -268,6 +268,7 @@ Chat Screen
What's visible: What's visible:
- Enough scrollback to fill a "screen full" of content. - Enough scrollback to fill a "screen full" of content.
- Threads: - Threads:
- Initially will only display the timestamp and user ID of the *first* - Initially will only display the timestamp and user ID of the *first*
message. But can expand to show the entire tree. message. But can expand to show the entire tree.
- Tree of messages indicating which message is a reply to which. - Tree of messages indicating which message is a reply to which.
@ -280,6 +281,7 @@ Chat Screen
- Desktop/Push Notifications for messages - Desktop/Push Notifications for messages
What you can do: What you can do:
- Send a message in reply to another message: - Send a message in reply to another message:
- Immediate local echo, may cause messages to re-order - Immediate local echo, may cause messages to re-order
- Messages that haven't reached the server are queued. - Messages that haven't reached the server are queued.
- Thread is displayed where it should be in the thread order once the - Thread is displayed where it should be in the thread order once the
@ -303,6 +305,7 @@ Chat Screen
+------------+ | * A2 -> A1 | | * B2 -> B1 | +------------+ | * A2 -> A1 | | * B2 -> B1 |
| * M -> A2, B2 | +--------------+ | * M -> A2, B2 | +--------------+
+----------------+ +----------------+
- Typing notifications. Displayed within the correct thread/column. - Typing notifications. Displayed within the correct thread/column.
What you can do: What you can do:

@ -0,0 +1,17 @@
.loggedin {
visibility: hidden;
}
p {
font-family: monospace;
}
table
{
border-spacing:5px;
}
th,td
{
padding:5px;
}

@ -0,0 +1,30 @@
<div>
<p>This room creation / message sending demo requires a home server to be running on http://localhost:8008</p>
</div>
<form class="loginForm">
<input type="text" id="userLogin" placeholder="Username"></input>
<input type="password" id="passwordLogin" placeholder="Password"></input>
<input type="button" class="login" value="Login"></input>
</form>
<div class="loggedin">
<form class="createRoomForm">
<input type="text" id="roomAlias" placeholder="Room alias (optional)"></input>
<input type="button" class="createRoom" value="Create Room"></input>
</form>
<form class="sendMessageForm">
<input type="text" id="roomId" placeholder="Room ID"></input>
<input type="text" id="messageBody" placeholder="Message body"></input>
<input type="button" class="sendMessage" value="Send Message"></input>
</form>
<table id="rooms">
<tbody>
<tr>
<th>Room ID</th>
<th>My state</th>
<th>Room Alias</th>
<th>Latest message</th>
</tr>
</tbody>
</table>
</div>

@ -0,0 +1,113 @@
var accountInfo = {};
var showLoggedIn = function(data) {
accountInfo = data;
getCurrentRoomList();
$(".loggedin").css({visibility: "visible"});
};
$('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
dataType: "json",
success: function(data) {
showLoggedIn(data);
},
error: function(err) {
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});
var getCurrentRoomList = function() {
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
var rooms = data.rooms;
for (var i=0; i<rooms.length; ++i) {
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
addRoom(rooms[i]);
}
}).fail(function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
});
};
$('.createRoom').live('click', function() {
var roomAlias = $("#roomAlias").val();
var data = {};
if (roomAlias.length > 0) {
data.room_alias_name = roomAlias;
}
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
success: function(data) {
data.membership = "join"; // you are automatically joined into every room you make.
data.latest_message = "";
addRoom(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
});
var addRoom = function(data) {
row = "<tr>" +
"<td>"+data.room_id+"</td>" +
"<td>"+data.membership+"</td>" +
"<td>"+data.room_alias+"</td>" +
"<td>"+data.latest_message+"</td>" +
"</tr>";
$("#rooms").append(row);
};
$('.sendMessage').live('click', function() {
var roomId = $("#roomId").val();
var body = $("#messageBody").val();
var msgId = $.now();
if (roomId.length === 0 || body.length === 0) {
return;
}
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
var data = {
msgtype: "m.text",
body: body
};
$.ajax({
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
success: function(data) {
$("#messageBody").val("");
// wipe the table and reload it. Using the event stream would be the best
// solution but that is out of scope of this fiddle.
$("#rooms").find("tr:gt(0)").remove();
getCurrentRoomList();
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
});

@ -0,0 +1,17 @@
.loggedin {
visibility: hidden;
}
p {
font-family: monospace;
}
table
{
border-spacing:5px;
}
th,td
{
padding:5px;
}

@ -0,0 +1,23 @@
<div>
<p>This event stream demo requires a home server to be running on http://localhost:8008</p>
</div>
<form class="loginForm">
<input type="text" id="userLogin" placeholder="Username"></input>
<input type="password" id="passwordLogin" placeholder="Password"></input>
<input type="button" class="login" value="Login"></input>
</form>
<div class="loggedin">
<form class="sendMessageForm">
<input type="button" class="sendMessage" value="Send random message"></input>
</form>
<p id="streamErrorText"></p>
<table id="rooms">
<tbody>
<tr>
<th>Room ID</th>
<th>Latest message</th>
</tr>
</tbody>
</table>
</div>

@ -0,0 +1,145 @@
var accountInfo = {};
var eventStreamInfo = {
from: "END"
};
var roomInfo = [];
var longpollEventStream = function() {
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$from", eventStreamInfo.from);
$.getJSON(url, function(data) {
eventStreamInfo.from = data.end;
var hasNewLatestMessage = false;
for (var i=0; i<data.chunk.length; ++i) {
if (data.chunk[i].type === "m.room.message") {
for (var j=0; j<roomInfo.length; ++j) {
if (roomInfo[j].room_id === data.chunk[i].room_id) {
roomInfo[j].latest_message = data.chunk[i].content.body;
hasNewLatestMessage = true;
}
}
}
}
if (hasNewLatestMessage) {
setRooms(roomInfo);
}
$("#streamErrorText").text("");
longpollEventStream();
}).fail(function(err) {
$("#streamErrorText").text("Event stream error: "+JSON.stringify($.parseJSON(err.responseText)));
setTimeout(longpollEventStream, 5000);
});
};
var showLoggedIn = function(data) {
accountInfo = data;
longpollEventStream();
getCurrentRoomList();
$(".loggedin").css({visibility: "visible"});
};
$('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
dataType: "json",
success: function(data) {
$("#rooms").find("tr:gt(0)").remove();
showLoggedIn(data);
},
error: function(err) {
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});
var getCurrentRoomList = function() {
$("#roomId").val("");
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
var rooms = data.rooms;
for (var i=0; i<rooms.length; ++i) {
if ("messages" in rooms[i]) {
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
}
}
roomInfo = rooms;
setRooms(roomInfo);
}).fail(function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
});
};
$('.sendMessage').live('click', function() {
if (roomInfo.length === 0) {
alert("There is no room to send a message to!");
return;
}
var index = Math.floor(Math.random() * roomInfo.length);
sendMessage(roomInfo[index].room_id);
});
var sendMessage = function(roomId) {
var body = "jsfiddle message @" + $.now();
if (roomId.length === 0) {
return;
}
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
var data = {
msgtype: "m.text",
body: body
};
$.ajax({
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
success: function(data) {
$("#messageBody").val("");
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
};
var setRooms = function(roomList) {
// wipe existing entries
$("#rooms").find("tr:gt(0)").remove();
var rows = "";
for (var i=0; i<roomList.length; ++i) {
row = "<tr>" +
"<td>"+roomList[i].room_id+"</td>" +
"<td>"+roomList[i].latest_message+"</td>" +
"</tr>";
rows += row;
}
$("#rooms").append(rows);
};

@ -0,0 +1,43 @@
.roomListDashboard, .roomContents, .sendMessageForm {
visibility: hidden;
}
.roomList {
background-color: #909090;
}
.messageWrapper {
background-color: #EEEEEE;
height: 400px;
overflow: scroll;
}
.membersWrapper {
background-color: #EEEEEE;
height: 200px;
width: 50%;
overflow: scroll;
}
.textEntry {
width: 100%
}
p {
font-family: monospace;
}
table
{
border-spacing:5px;
}
th,td
{
padding:5px;
}
.roomList tr:not(:first-child):hover {
background-color: orange;
cursor: pointer;
}

@ -0,0 +1,7 @@
name: Example Matrix Client
description: Includes login, live event streaming, creating rooms, sending messages and viewing member lists.
authors:
- matrix.org
resources:
- http://matrix.org
normalize_css: no

@ -0,0 +1,56 @@
<div class="signUp">
<p>Matrix example application: Requires a local home server running at http://localhost:8008</p>
<form class="registrationForm">
<p>No account? Register:</p>
<input type="text" id="userReg" placeholder="Username"></input>
<input type="password" id="passwordReg" placeholder="Password"></input>
<input type="button" class="register" value="Register"></input>
</form>
<form class="loginForm">
<p>Got an account? Login:</p>
<input type="text" id="userLogin" placeholder="Username"></input>
<input type="password" id="passwordLogin" placeholder="Password"></input>
<input type="button" class="login" value="Login"></input>
</form>
</div>
<div class="roomListDashboard">
<form class="createRoomForm">
<input type="text" id="roomAlias" placeholder="Room alias"></input>
<input type="button" class="createRoom" value="Create Room"></input>
</form>
<table id="rooms" class="roomList">
<tbody>
<tr>
<th>Room</th>
<th>My state</th>
<th>Latest message</th>
</tr>
</tbody>
</table>
</div>
<div class="roomContents">
<p id="roomName">Select a room</p>
<div class="messageWrapper">
<table id="messages">
<tbody>
</tbody>
</table>
</div>
<form class="sendMessageForm">
<input type="text" class="textEntry" id="body" placeholder="Enter text here..." onkeydown="javascript:if (event.keyCode == 13) document.getElementById('sendMsg').focus()"></input>
<input type="button" class="sendMessage" id="sendMsg" value="Send"></input>
</form>
</div>
<div>
<p>Member list:</p>
<div class="membersWrapper">
<table id="members">
<tbody>
</tbody>
</table>
</div>
</div>

@ -0,0 +1,327 @@
var accountInfo = {};
var eventStreamInfo = {
from: "END"
};
var roomInfo = [];
var memberInfo = [];
var viewingRoomId;
// ************** Event Streaming **************
var longpollEventStream = function() {
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$from", eventStreamInfo.from);
$.getJSON(url, function(data) {
eventStreamInfo.from = data.end;
var hasNewLatestMessage = false;
var updatedMemberList = false;
var i=0;
var j=0;
for (i=0; i<data.chunk.length; ++i) {
if (data.chunk[i].type === "m.room.message") {
console.log("Got new message: " + JSON.stringify(data.chunk[i]));
if (viewingRoomId === data.chunk[i].room_id) {
addMessage(data.chunk[i]);
}
for (j=0; j<roomInfo.length; ++j) {
if (roomInfo[j].room_id === data.chunk[i].room_id) {
roomInfo[j].latest_message = data.chunk[i].content.body;
hasNewLatestMessage = true;
}
}
}
else if (data.chunk[i].type === "m.room.member") {
if (viewingRoomId === data.chunk[i].room_id) {
console.log("Got new member: " + JSON.stringify(data.chunk[i]));
addMessage(data.chunk[i]);
for (j=0; j<memberInfo.length; ++j) {
if (memberInfo[j].state_key === data.chunk[i].state_key) {
memberInfo[j] = data.chunk[i];
updatedMemberList = true;
break;
}
}
if (!updatedMemberList) {
memberInfo.push(data.chunk[i]);
updatedMemberList = true;
}
}
if (data.chunk[i].state_key === accountInfo.user_id) {
getCurrentRoomList(); // update our join/invite list
}
}
else {
console.log("Discarding: " + JSON.stringify(data.chunk[i]));
}
}
if (hasNewLatestMessage) {
setRooms(roomInfo);
}
if (updatedMemberList) {
$("#members").empty();
for (i=0; i<memberInfo.length; ++i) {
addMember(memberInfo[i]);
}
}
longpollEventStream();
}).fail(function(err) {
setTimeout(longpollEventStream, 5000);
});
};
// ************** Registration and Login **************
var onLoggedIn = function(data) {
accountInfo = data;
longpollEventStream();
getCurrentRoomList();
$(".roomListDashboard").css({visibility: "visible"});
$(".roomContents").css({visibility: "visible"});
$(".signUp").css({display: "none"});
};
$('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
dataType: "json",
success: function(data) {
onLoggedIn(data);
},
error: function(err) {
alert("Unable to login: is the home server running?");
}
});
});
$('.register').live('click', function() {
var user = $("#userReg").val();
var password = $("#passwordReg").val();
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/register",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
dataType: "json",
success: function(data) {
onLoggedIn(data);
},
error: function(err) {
var msg = "Is the home server running?";
var errJson = $.parseJSON(err.responseText);
if (errJson !== null) {
msg = errJson.error;
}
alert("Unable to register: "+msg);
}
});
});
// ************** Creating a room ******************
$('.createRoom').live('click', function() {
var roomAlias = $("#roomAlias").val();
var data = {};
if (roomAlias.length > 0) {
data.room_alias_name = roomAlias;
}
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
success: function(response) {
$("#roomAlias").val("");
response.membership = "join"; // you are automatically joined into every room you make.
response.latest_message = "";
roomInfo.push(response);
setRooms(roomInfo);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
});
// ************** Getting current state **************
var getCurrentRoomList = function() {
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
var rooms = data.rooms;
for (var i=0; i<rooms.length; ++i) {
if ("messages" in rooms[i]) {
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
}
}
roomInfo = rooms;
setRooms(roomInfo);
}).fail(function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
});
};
var loadRoomContent = function(roomId) {
console.log("loadRoomContent " + roomId);
viewingRoomId = roomId;
$("#roomName").text("Room: "+roomId);
$(".sendMessageForm").css({visibility: "visible"});
getMessages(roomId);
getMemberList(roomId);
};
var getMessages = function(roomId) {
$("#messages").empty();
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
encodeURIComponent(roomId) + "/messages?access_token=" + accountInfo.access_token + "&from=END&dir=b&limit=10";
$.getJSON(url, function(data) {
for (var i=data.chunk.length-1; i>=0; --i) {
addMessage(data.chunk[i]);
}
});
};
var getMemberList = function(roomId) {
$("#members").empty();
memberInfo = [];
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
encodeURIComponent(roomId) + "/members?access_token=" + accountInfo.access_token;
$.getJSON(url, function(data) {
for (var i=0; i<data.chunk.length; ++i) {
memberInfo.push(data.chunk[i]);
addMember(data.chunk[i]);
}
});
};
// ************** Sending messages **************
$('.sendMessage').live('click', function() {
if (viewingRoomId === undefined) {
alert("There is no room to send a message to!");
return;
}
var body = $("#body").val();
sendMessage(viewingRoomId, body);
});
var sendMessage = function(roomId, body) {
var msgId = $.now();
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
var data = {
msgtype: "m.text",
body: body
};
$.ajax({
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
success: function(data) {
$("#body").val("");
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
};
// ************** Navigation and DOM manipulation **************
var setRooms = function(roomList) {
// wipe existing entries
$("#rooms").find("tr:gt(0)").remove();
var rows = "";
for (var i=0; i<roomList.length; ++i) {
row = "<tr>" +
"<td>"+roomList[i].room_id+"</td>" +
"<td>"+roomList[i].membership+"</td>" +
"<td>"+roomList[i].latest_message+"</td>" +
"</tr>";
rows += row;
}
$("#rooms").append(rows);
$('#rooms').find("tr").click(function(){
var roomId = $(this).find('td:eq(0)').text();
var membership = $(this).find('td:eq(1)').text();
if (membership !== "join") {
console.log("Joining room " + roomId);
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/join?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
$.ajax({
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({membership: "join"}),
dataType: "json",
success: function(data) {
loadRoomContent(roomId);
getCurrentRoomList();
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
}
else {
loadRoomContent(roomId);
}
});
};
var addMessage = function(data) {
var msg = data.content.body;
if (data.type === "m.room.member") {
if (data.content.membership === undefined) {
return;
}
if (data.content.membership === "invite") {
msg = "<em>invited " + data.state_key + " to the room</em>";
}
else if (data.content.membership === "join") {
msg = "<em>joined the room</em>";
}
else if (data.content.membership === "leave") {
msg = "<em>left the room</em>";
}
else if (data.content.membership === "ban") {
msg = "<em>was banned from the room</em>";
}
}
if (msg === undefined) {
return;
}
var row = "<tr>" +
"<td>"+data.user_id+"</td>" +
"<td>"+msg+"</td>" +
"</tr>";
$("#messages").append(row);
};
var addMember = function(data) {
var row = "<tr>" +
"<td>"+data.state_key+"</td>" +
"<td>"+data.content.membership+"</td>" +
"</tr>";
$("#members").append(row);
};

@ -0,0 +1,7 @@
.loggedin {
visibility: hidden;
}
p {
font-family: monospace;
}

@ -0,0 +1,20 @@
<div>
<p>This registration/login demo requires a home server to be running on http://localhost:8008</p>
</div>
<form class="registrationForm">
<input type="text" id="user" placeholder="Username"></input>
<input type="password" id="password" placeholder="Password"></input>
<input type="button" class="register" value="Register"></input>
</form>
<form class="loginForm">
<input type="text" id="userLogin" placeholder="Username"></input>
<input type="password" id="passwordLogin" placeholder="Password"></input>
<input type="button" class="login" value="Login"></input>
</form>
<div class="loggedin">
<p id="welcomeText"></p>
<input type="button" class="testToken" value="Test token"></input>
<input type="button" class="logout" value="Logout"></input>
<p id="imSyncText"></p>
</div>

@ -0,0 +1,79 @@
var accountInfo = {};
var showLoggedIn = function(data) {
accountInfo = data;
$(".loggedin").css({visibility: "visible"});
$("#welcomeText").text("Welcome " + accountInfo.user_id+". Your access token is: " +
accountInfo.access_token);
};
$('.register').live('click', function() {
var user = $("#user").val();
var password = $("#password").val();
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/register",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
dataType: "json",
success: function(data) {
showLoggedIn(data);
},
error: function(err) {
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});
var login = function(user, password) {
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
dataType: "json",
success: function(data) {
showLoggedIn(data);
},
error: function(err) {
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
};
$('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.getJSON("http://localhost:8008/_matrix/client/api/v1/login", function(data) {
if (data.flows[0].type !== "m.login.password") {
alert("I don't know how to login with this type: " + data.type);
return;
}
login(user, password);
});
});
$('.logout').live('click', function() {
accountInfo = {};
$("#imSyncText").text("");
$(".loggedin").css({visibility: "hidden"});
});
$('.testToken').live('click', function() {
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
$("#imSyncText").text(JSON.stringify(data, undefined, 2));
}).fail(function(err) {
$("#imSyncText").text(JSON.stringify($.parseJSON(err.responseText)));
});
});

@ -0,0 +1,17 @@
.loggedin {
visibility: hidden;
}
p {
font-family: monospace;
}
table
{
border-spacing:5px;
}
th,td
{
padding:5px;
}

@ -0,0 +1,37 @@
<div>
<p>This room membership demo requires a home server to be running on http://localhost:8008</p>
</div>
<form class="loginForm">
<input type="text" id="userLogin" placeholder="Username"></input>
<input type="password" id="passwordLogin" placeholder="Password"></input>
<input type="button" class="login" value="Login"></input>
</form>
<div class="loggedin">
<form class="createRoomForm">
<input type="button" class="createRoom" value="Create Room"></input>
</form>
<form class="changeMembershipForm">
<input type="text" id="roomId" placeholder="Room ID"></input>
<input type="text" id="targetUser" placeholder="Target User ID"></input>
<select id="membership">
<option value="invite">invite</option>
<option value="join">join</option>
<option value="leave">leave</option>
</select>
<input type="button" class="changeMembership" value="Change Membership"></input>
</form>
<form class="joinAliasForm">
<input type="text" id="roomAlias" placeholder="Room Alias (#name:domain)"></input>
<input type="button" class="joinAlias" value="Join via Alias"></input>
</form>
<table id="rooms">
<tbody>
<tr>
<th>Room ID</th>
<th>My state</th>
<th>Room Alias</th>
</tr>
</tbody>
</table>
</div>

@ -0,0 +1,141 @@
var accountInfo = {};
var showLoggedIn = function(data) {
accountInfo = data;
getCurrentRoomList();
$(".loggedin").css({visibility: "visible"});
$("#membership").change(function() {
if ($("#membership").val() === "invite") {
$("#targetUser").css({visibility: "visible"});
}
else {
$("#targetUser").css({visibility: "hidden"});
}
});
};
$('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
dataType: "json",
success: function(data) {
$("#rooms").find("tr:gt(0)").remove();
showLoggedIn(data);
},
error: function(err) {
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});
var getCurrentRoomList = function() {
$("#roomId").val("");
// wipe the table and reload it. Using the event stream would be the best
// solution but that is out of scope of this fiddle.
$("#rooms").find("tr:gt(0)").remove();
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
var rooms = data.rooms;
for (var i=0; i<rooms.length; ++i) {
addRoom(rooms[i]);
}
}).fail(function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
});
};
$('.createRoom').live('click', function() {
var data = {};
$.ajax({
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
success: function(data) {
data.membership = "join"; // you are automatically joined into every room you make.
data.latest_message = "";
addRoom(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
});
var addRoom = function(data) {
row = "<tr>" +
"<td>"+data.room_id+"</td>" +
"<td>"+data.membership+"</td>" +
"<td>"+data.room_alias+"</td>" +
"</tr>";
$("#rooms").append(row);
};
$('.changeMembership').live('click', function() {
var roomId = $("#roomId").val();
var member = $("#targetUser").val();
var membership = $("#membership").val();
if (roomId.length === 0) {
return;
}
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/$membership?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
url = url.replace("$membership", membership);
var data = {};
if (membership === "invite") {
data = {
user_id: member
};
}
$.ajax({
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
success: function(data) {
getCurrentRoomList();
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
});
$('.joinAlias').live('click', function() {
var roomAlias = $("#roomAlias").val();
var url = "http://localhost:8008/_matrix/client/api/v1/join/$roomalias?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomalias", encodeURIComponent(roomAlias));
$.ajax({
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({}),
dataType: "json",
success: function(data) {
getCurrentRoomList();
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
});
Loading…
Cancel
Save