Commit af967d44 authored by Eugene Shen's avatar Eugene Shen

Implement multi-room messaging

Add font, remove unused JIO Mapping Storage,
split chat box into contacts and chats tabs,
only send join and leave notifications to others,
add message colours, change styles with favicons,
declare new WebRTC gadgets for every room connection,
create new room with their own names for each client,
manage more query values in parent gadget with less code,
generate Dropbox link to stuff URL queries as state info,
prepare parseParams to handle new connections from the panel,
strip whitespaces, delete "my_", remove line breaks with CSS,
understand how queues in .ready() block .declareService() forever,
and finally, use flexbox CSS to completely overhaul look and feel.
parent 01b24062
body {
padding: 20px;
}
error {
color: orange;
}
h3 {
color: brown;
}
p {
h4 {
margin: 0;
color: red;
}
img {
max-width: 100%;
max-height: 100%;
label {
display: block;
}
input[type="text"] {
width: 360px;
}
.chat {
border: 1px solid;
width: 680px;
height: 360px;
.radio-item {
display: inline-block;
}
textarea {
display: block;
}
.chat-box {
display: flex;
width: 100%;
height: 80vh;
}
.chat-left-panel {
display: flex;
flex-direction: column;
flex: 1;
padding: 20px;
border: 1.5px solid;
box-sizing: border-box;
}
.chat-right-panel {
display: flex;
flex-direction: column;
flex: 2;
padding: 20px;
border: 1.5px solid;
box-sizing: border-box;
}
.contact-list {
flex: 1;
padding: 0;
overflow: auto;
}
.chat-list {
flex: 1;
padding: 0;
overflow: auto;
}
li {
list-style-type: none;
}
img {
max-width: 100%;
max-height: 100%;
}
.center {
text-align: center;
}
.send-form {
display: flex;
}
.send-form input[type="text"] {
flex: 1;
margin: 0 10px 0 0;
}
.chat-list li {
font-family: "Droid Sans Mono";
font-size: 12px;
}
.contact-list li {
color: blue;
}
.contact-list li:hover {
font-weight: bold;
cursor: pointer;
}
.contact-list li.notify {
font-weight: bold;
color: red;
}
\ No newline at end of file
......@@ -11,74 +11,69 @@
<link rel="stylesheet" href="gadget_erp5_chat.css" />
</head>
<body>
<div data-gadget-url="gadget_jio.html"
data-gadget-scope="storage_gadget"
data-gadget-sandbox="public"></div>
<h1>OfficeJS Chat</h1>
<p class="chat-error">
</p>
<form class="login-form">
<label>Name:</label>
<br />
<input type="text" name="name" required="required" />
<br />
<label>Folder:</label>
<br />
<input type="text" name="folder" required="required" />
<h3>Remote Storage</h3>
<input type="radio" name="remote" value="erp5" required="required" />
<label>ERP5</label>
<br />
<input type="radio" name="remote" value="dav" required="required" />
<label>DAV Storage</label>
<br />
<input type="radio" name="remote" value="dropbox" required="required" />
<label>Dropbox</label>
<br />
<input type="radio" name="remote" value="local" required="required" checked="checked" />
<label>Local is Enough</label>
<br />
<label>
<input type="radio" name="remote" value="erp5" required="required" />
ERP5
</label>
<label>
<input type="radio" name="remote" value="dav" required="required" />
DAV Storage
</label>
<label>
<input type="radio" name="remote" value="dropbox" required="required" />
Dropbox
</label>
<label>
<input type="radio" name="remote" value="local" required="required" checked="checked" />
Local is Enough
</label>
<h3>Storage Configuration</h3>
<a href="https://www.dropbox.com/1/oauth2/authorize?client_id=igeiyv4pkt0y0mm&response_type=token&redirect_uri=https://softinst75770.host.vifib.net/erp5/web_site_module/web_chat/">Dropbox OAuth Link</a>
<br />
<a class="dropbox-link">Dropbox OAuth</a>
<label>Dropbox folder:</label>
<br />
<input type="text" name="dropbox_url" value="/Apps/OfficeJS Chat/.jio_documents" />
<br />
<input type="text" name="remote_dropbox_url" placeholder="/Apps/OfficeJS Chat/.jio_documents" />
<label>ERP5 URL:</label>
<br />
<input type="text" name="erp5_url" value="https://softinst75770.host.vifib.net/erp5/web_site_module/" />
<br />
<input type="text" name="remote_erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/web_site_module/" />
<label>WebDAV URL:</label>
<br />
<input type="text" name="dav_url" value="https://softinst75722.host.vifib.net/share" />
<br />
<input type="text" name="remote_dav_url" placeholder="https://softinst75722.host.vifib.net/share" />
<label>WebDAV Username:</label>
<br />
<input type="text" name="dav_user" value="eyqs" />
<br />
<input type="text" name="remote_dav_user" placeholder="eyqs" />
<label>WebDAV Password:</label>
<br />
<input type="text" name="dav_pass" value="Aoeuidhtns" />
<input type="text" name="remote_dav_pass" placeholder="correct horse battery staple" />
<h3>Default WebRTC Configuration</h3>
<label>Automatically connect to own room:
<input type="checkbox" name="auto" />
</label>
<label>Default storage:</label>
<input type="text" name="auth" placeholder="dropbox" />
<label>Dropbox folder:</label>
<input type="text" name="auth_dropbox_url" placeholder="/Apps/OfficeJS Chat" />
<label>ERP5 URL:</label>
<input type="text" name="auth_erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
<br />
<input type="submit" value="Login!" />
</form>
<form class="room-form">
<h3>Join Rooms</h3>
<input type="text" name="room" required="required" />
<br />
<input type="submit" name="host" value="Create a new room!" />
<input type="submit" name="guest" value="Join an existing room!" />
</form>
<div data-gadget-url="gadget_jio.html"
data-gadget-scope="storage_gadget"
data-gadget-sandbox="public"></div>
<div data-gadget-url="gadget_erp5_chat_webrtc.html"
data-gadget-scope="webrtc_gadget"
data-gadget-sandbox="public" class="webrtc-panel"></div>
<div data-gadget-url="gadget_erp5_chat_panel.html"
data-gadget-scope="chat_gadget"
data-gadget-sandbox="public" class="chat-panel"></div>
</body>
</html>
\ No newline at end of file
(function (window, document, loopEventListener, rJS, RSVP) {
/* URL Options:
* name: your chat username, e.g. eyqs
* folder: the root folder for your organization, e.g. nexedi;
* you can only chat with people with access to the same auth folder
* remote: the type of remote storage to use,
* either dav, dropbox, erp5, or local.
* either dav, dropbox, erp5, or local
* remote_dropbox_url: the remote Dropbox folder to synchronize with,
* e.g. /Apps/OfficeJS Chat/.jio_documents
* remote_erp5_url: the remote ERP5 instance to synchronize with,
......@@ -15,21 +15,19 @@
* remote_dav_user: your WebDAV username, e.g. eyqs
* remote_dav_pass: your WebDAV password, e.g. correct horse battery staple
* login: if present (e.g. login=foo), will automatically click Login
* room: the chat room you want to connect to, e.g. lobby
* role: the type of connection to use, either host or guest;
* if both room and role are valid, will automatically click the button
* auth: the type of authentication to use, either dropbox or erp5
* if both role and auth are valid, will automatically click Authenticate
* auth_dropbox_url: the shared Dropbox folder to act as the auth folder,
* auth: the default type of authentication to use for WebRTC rooms,
* either dropbox or erp5
* auth_dropbox_url: the shared Dropbox folder to act as your auth folder,
* e.g. /Apps/OfficeJS Chat
* auth_erp5_url: the shared ERP5 instance to act as the auth folder,
* auth_erp5_url: the shared ERP5 instance to act as your auth folder,
* e.g. https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/
* auto: if preset (e.g. auto=go), will automatically click Authenticate
*/
function logError(error) {
console.log(error);
}
function logQueue(action) {
return new RSVP.Queue()
.push(function () {
......@@ -37,35 +35,44 @@
})
.push(null, logError);
}
function showElementByClass(my_gadget, query) {
my_gadget.state_parameter_dict.element
function showElementByClass(gadget, query) {
gadget.state_parameter_dict.element
.querySelector(query).style.display = "block";
}
function hideElementByClass(my_gadget, query) {
my_gadget.state_parameter_dict.element
function hideElementByClass(gadget, query) {
gadget.state_parameter_dict.element
.querySelector(query).style.display = "none";
}
function cleanId(input_id) {
const reserved = ["_", "&", "=", ",", ";"];
const reserved = ["_", "&", "=", ",", ";", "\\"];
for (let i = 0, i_len = reserved.length; i < i_len; i++) {
input_id = input_id.replace(reserved[i], "-");
}
return input_id;
}
function getQueryValue(query, value) {
if (document.URL.indexOf(query + "=") != -1) {
const start = document.URL.indexOf(query + "=") + query.length + 1;
let end = document.URL.indexOf("&", start);
if (end === -1) {
end = document.URL.length;
function getQueryValue(query_list, query_string) {
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
if (query_string.indexOf(query + "=") !== -1) {
const start = query_string.indexOf(query + "=") + query.length + 1;
let end = query_string.indexOf("&", start);
if (end === -1) {
end = query_string.length;
}
return query_string.slice(start, end);
}
return decodeURIComponent(document.URL.slice(start, end));
} else {
return value || "";
}
return null;
}
function setQueryValue(query_list, query_string, element) {
value = getQueryValue(query_list, query_string);
if (value) {
element.value = value;
}
}
......@@ -73,7 +80,14 @@
.ready(function (gadget) {
gadget.state_parameter_dict = {
element: null,
archive_amount: 0,
name: null,
folder: null,
auth: null,
dropbox_url: null,
erp5_url: null,
auto: null,
// Dropbox OAuth only allows 500 characters in the state query
max_state_length: 500,
};
return new RSVP.Queue()
.push(function () {
......@@ -81,11 +95,14 @@
})
.push(function (element) {
gadget.state_parameter_dict.element = element;
return gadget.render();
})
.push(null, logError);
})
.declareService(function () {
const gadget = this;
return this.render();
})
.allowPublicAcquisition('wrapJioAccess', function (param_list) {
const gadget = this;
return new RSVP.Queue()
......@@ -98,65 +115,142 @@
})
.push(null, logError);
})
.allowPublicAcquisition('sendMessage', function (param_dict) {
.allowPublicAcquisition('sendMessage', function (param_list) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("webrtc_gadget");
return gadget.getDeclaredGadget(
"webrtc_gadget_" + param_list[0].room);
})
.push(function (webrtc_gadget) {
return webrtc_gadget.sendMessage.apply(webrtc_gadget, param_dict);
});
return webrtc_gadget.sendMessage.apply(webrtc_gadget, param_list);
})
.push(null, logError);
})
.allowPublicAcquisition('parseParams', function (param_list) {
const gadget = this;
return gadget.parseParams.apply(gadget, param_list);
})
.declareMethod('parseParams', function (param_string) {
const gadget = this;
const param_dict = {};
param_dict.auto = getQueryValue(["auto"], param_string);
param_dict.room = getQueryValue(["room"], param_string);
param_dict.role = getQueryValue(["role"], param_string);
param_dict.auth = getQueryValue(["auth"], param_string);
param_dict.dropbox_url =
getQueryValue(["auth_dropbox_url", "ad"], param_string);
param_dict.erp5_url = getQueryValue(["auth_erp5_url", "ae"], param_string);
return gadget.chooseRoom(param_dict);
})
.declareMethod('render', function () {
let url = decodeURIComponent(document.URL);
const gadget = this;
return new RSVP.Queue()
.push(function () {
hideElementByClass(gadget, ".room-form");
hideElementByClass(gadget, ".webrtc-panel");
hideElementByClass(gadget, ".chat-panel");
const state_output = getQueryValue(["state"], url);
if (state_output) {
url += window.atob(state_output);
}
const fields = gadget.state_parameter_dict.element
.querySelector(".login-form").elements;
fields.name.value = getQueryValue("name");
fields.folder.value = getQueryValue("folder");
const remote = getQueryValue("remote");
if (remote === "dav" || remote === "dropbox"
|| remote === "erp5" || remote === "local") {
fields.remote.value = remote;
// A list of login-form fields and short keys in order of priority
const query_list = [["name", "m"], ["folder", "f"], ["remote", "r"],
["remote_dropbox_url", "rd"], ["remote_erp5_url", "re"],
["remote_dav_url", "rv"], ["remote_dav_user", "rvu"],
["remote_dav_pass", "rvp"], ["auth", "a"],
["auth_dropbox_url", "ad"], ["auth_erp5_url", "ae"]]
let state_input = "";
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
setQueryValue(query, url, fields[query[0]]);
const query_string = "&" + query[1] + "=" + fields[query[0]].value;
if (window.btoa(state_input + query_string).length <
gadget.state_parameter_dict.max_state_length) {
state_input += query_string;
}
}
fields.dropbox_url.value = getQueryValue(
"remote_dropbox_url", fields.dropbox_url.value);
fields.erp5_url.value = getQueryValue(
"remote_erp5_url", fields.erp5_url.value);
fields.dav_url.value = getQueryValue(
"remote_dav_url", fields.dav_url.value);
fields.dav_user.value = getQueryValue(
"remote_dav_user", fields.dav_user.value);
fields.dav_pass.value = getQueryValue(
"remote_dav_pass", fields.dav_pass.value);
if (getQueryValue("login")) {
if (getQueryValue(["auto", "o"], url)) {
fields.auto.checked = "Checked";
if (window.btoa(state_input + "&o=o").length <
gadget.state_parameter_dict.max_state_length) {
state_input += "&o=o";
}
}
gadget.state_parameter_dict.element.querySelector(".dropbox-link")
.href = "https://www.dropbox.com/1/oauth2/authorize"
+ "?client_id=igeiyv4pkt0y0mm&response_type=token"
+ "&redirect_uri=https://softinst75770.host.vifib.net/erp5/"
+ "web_site_module/web_chat/&state=" + window.btoa(state_input);
if (getQueryValue(["login"], url)) {
return gadget.login();
} else {
return;
}
})
.push(null, logError);
})
.declareMethod('login', function () {
const gadget = this;
let chat_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.declareGadget(
"gadget_erp5_chat_panel.html", {scope: "chat_gadget"});
})
.push(function (subgadget) {
hideElementByClass(gadget, ".login-form");
showElementByClass(gadget, ".room-form");
chat_gadget = subgadget;
gadget.state_parameter_dict.element.insertBefore(
chat_gadget.state_parameter_dict.element,
gadget.state_parameter_dict.element.querySelector(".login-form"));
const fields = gadget.state_parameter_dict.element
.querySelector(".room-form").elements;
const room = getQueryValue("room");
fields.room.value = room;
const role = getQueryValue("role");
if (room && (role === "host" || role === "guest")) {
return gadget.chooseRoom(role);
.querySelector(".login-form").elements;
gadget.state_parameter_dict.auth = fields.auth.value;
gadget.state_parameter_dict.dropbox_url
= fields.auth_dropbox_url.value;
gadget.state_parameter_dict.erp5_url = fields.auth_erp5_url.value;
gadget.state_parameter_dict.auto = !!fields.auto.checked;
gadget.state_parameter_dict.name = cleanId(fields.name.value);
gadget.state_parameter_dict.folder = cleanId(fields.folder.value);
chat_gadget.state_parameter_dict.name =
gadget.state_parameter_dict.name;
chat_gadget.state_parameter_dict.folder =
gadget.state_parameter_dict.folder;
// XXX apply drivetojiomapping in custom dropbox_url directory
return gadget.createJio({
remote: fields.remote.value,
dropbox_url: fields.remote_dropbox_url.value,
erp5_url: fields.remote_erp5_url.value,
dav_url: fields.remote_dav_url.value,
dav_user: fields.remote_dav_user.value,
dav_pass: fields.remote_dav_pass.value,
});
})
// Log each person in as the host of their own room with their name
.push(function () {
return chat_gadget.startRoom();
})
.push(function () {
if (gadget.state_parameter_dict.auto) {
return gadget.parseParams(
"auto=a&role=host&room=" + gadget.state_parameter_dict.name
+ "&"+ decodeURIComponent(document.URL));
} else {
return;
return gadget.chooseRoom({
room: gadget.state_parameter_dict.name, role: "host"});
}
})
.push(null, logError);
})
.declareMethod('createJio', function (param_dict) {
const gadget = this;
return new RSVP.Queue()
......@@ -244,150 +338,120 @@
})
.push(null, logError);
})
.declareMethod('login', function () {
.declareMethod('chooseRoom', function (param_dict) {
const gadget = this;
let rtc_gadget;
let webrtc_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("webrtc_gadget");
return gadget.declareGadget(
"gadget_erp5_chat_webrtc.html", {
scope: "webrtc_gadget_" + param_dict.room});
})
.push(function (webrtc_gadget) {
rtc_gadget = webrtc_gadget;
.push(function (subgadget) {
webrtc_gadget = subgadget;
return gadget.getDeclaredGadget("chat_gadget");
})
.push(function (chat_gadget) {
const form = gadget.state_parameter_dict.element
.querySelector(".login-form").elements;
rtc_gadget.state_parameter_dict.name = cleanId(form.name.value);
chat_gadget.state_parameter_dict.name = cleanId(form.name.value);
rtc_gadget.state_parameter_dict.folder = cleanId(form.folder.value);
chat_gadget.state_parameter_dict.folder = cleanId(form.folder.value);
hideElementByClass(gadget, ".login-form");
showElementByClass(gadget, ".room-form");
// XXX apply drivetojiomapping in custom dropbox_url directory
return gadget.createJio({
remote: form.remote.value,
dropbox_url: form.dropbox_url.value,
erp5_url: form.erp5_url.value,
dav_url: form.dav_url.value,
dav_user: form.dav_user.value,
dav_pass: form.dav_pass.value,
});
})
.push(null, logError);
})
.declareMethod('chooseRoom', function (role) {
const gadget = this;
let rtc_gadget;
return new RSVP.Queue()
.push(function () {
hideElementByClass(gadget, ".room-form");
showElementByClass(gadget, ".webrtc-panel");
return gadget.getDeclaredGadget("webrtc_gadget");
})
.push(function (webrtc_gadget) {
rtc_gadget = webrtc_gadget;
let room = cleanId(gadget.state_parameter_dict.element
.querySelector(".room-form").elements.room.value);
gadget.state_parameter_dict.element
.querySelector(".room-form").elements.room.value = "";
const fields = rtc_gadget.state_parameter_dict.element
const fields = webrtc_gadget.state_parameter_dict.element
.querySelector(".auth-form").elements;
fields.auth.value = getQueryValue("auth");
fields.dropbox_url.value = getQueryValue(
"auth_dropbox_url", fields.dropbox_url.value);
fields.erp5_url.value = getQueryValue(
"auth_erp5_url", fields.erp5_url.value);
rtc_gadget.state_parameter_dict.login_dict = {
room: room,
role: role,
auth: getQueryValue("auth"),
fields.auth.value = param_dict.auth
|| gadget.state_parameter_dict.auth;
fields.dropbox_url.value = param_dict.dropbox_url
|| gadget.state_parameter_dict.dropbox_url;
fields.erp5_url.value = param_dict.erp5_url
|| gadget.state_parameter_dict.erp5_url;
gadget.state_parameter_dict.element.insertBefore(
webrtc_gadget.state_parameter_dict.element,
gadget.state_parameter_dict.element
.querySelector(".room-form").nextSibling);
webrtc_gadget.state_parameter_dict.login_dict = {
folder: gadget.state_parameter_dict.folder,
room: param_dict.room,
name: gadget.state_parameter_dict.name,
role: param_dict.role,
};
rtc_gadget.state_parameter_dict.dataChannelOnopen = function () {
return new RSVP.Queue()
.push(function () {
hideElementByClass(gadget, ".webrtc-panel");
showElementByClass(gadget, ".room-form");
showElementByClass(gadget, ".chat-panel");
return gadget.getDeclaredGadget("chat_gadget");
})
.push(function (chat_gadget) {
chat_gadget.state_parameter_dict.room = room;
return chat_gadget.render();
})
.push(null, logError);
webrtc_gadget.state_parameter_dict.dataChannelOnopen = function () {
return chat_gadget.changeRoom(param_dict.room);
};
rtc_gadget.state_parameter_dict.dataChannelOnmessage =
function (my_event) {
const message = JSON.parse(my_event.data);
const source = my_event.srcElement;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("chat_gadget");
})
.push(function (chat_gadget) {
if (message.type === "notification"
|| message.type === "message") {
webrtc_gadget.state_parameter_dict.dataChannelOnmessage =
function (event) {
const message = JSON.parse(event.data);
const source = event.srcElement;
if (message.type === "notification"
|| message.type === "message") {
return new RSVP.Queue()
.push(function () {
return chat_gadget.getMessage(message);
})
.push(function () {
if (param_dict.role === "host") {
return webrtc_gadget.sendMessage(message, source);
} else {
return;
}
})
.push(null, logError);
} else if (message.type === "bundle") {
if (param_dict.role === "host") {
webrtc_gadget.state_parameter_dict.archive_amount += 1;
if (webrtc_gadget.state_parameter_dict.archive_amount >=
webrtc_gadget.state_parameter_dict.guest_amount) {
webrtc_gadget.state_parameter_dict.archive_amount = 0;
return new RSVP.Queue()
.push(function () {
return chat_gadget.getMessage(message);
return chat_gadget.getRemoteArchive(message);
})
.push(function () {
if (role === "host") {
return rtc_gadget.sendMessage(message, source);
} else {
return;
}
return chat_gadget.requestRequest();
})
.push(null, logError);
} else if (message.type === "bundle") {
if (role === "host") {
gadget.state_parameter_dict.archive_amount += 1;
if (gadget.state_parameter_dict.archive_amount >=
rtc_gadget.state_parameter_dict.guest_amount) {
gadget.state_parameter_dict.archive_amount = 0;
return new RSVP.Queue()
.push(function () {
return chat_gadget.getRemoteArchive(message);
})
.push(function () {
return chat_gadget.requestRequest();
})
.push(null, logError);
} else {
return chat_gadget.getRemoteArchive(message);
}
} else {
return chat_gadget.getRemoteArchive(message);
}
} else if (message.type === "request") {
return chat_gadget.sendLocalArchive(message, source);
} else if (message.type === "doubler") {
return chat_gadget.sendRequest();
}
})
.push(null, logError);
} else {
return chat_gadget.getRemoteArchive(message);
}
} else if (message.type === "request") {
return chat_gadget.sendLocalArchive(message, source);
} else if (message.type === "doubler") {
return chat_gadget.sendRequest();
}
};
return rtc_gadget.render();
return webrtc_gadget.render();
})
.push(function () {
if (param_dict.auto) {
return webrtc_gadget.authenticate();
} else {
return;
}
})
.push(null, logError);
})
.declareService(function () {
const gadget = this;
function handleSubmit(my_event) {
switch (my_event.target.className) {
function handleSubmit(event) {
switch (event.target.className) {
case "login-form":
return gadget.login();
case "room-form":
return gadget.chooseRoom(
my_event.target.querySelector("input[type=submit]:focus").name);
return new RSVP.Queue()
.push(function () {
return gadget.chooseRoom({
room: event.target.elements.room.value,
role: event.target
.querySelector("input[type=submit]:focus").name
});
})
.push(function () {
event.target.elements.room.value = "";
return;
})
.push(null, logError);
}
}
return loopEventListener(
gadget.state_parameter_dict.element, "submit", false, handleSubmit);
});
}(window, document, loopEventListener, rJS, RSVP));
\ No newline at end of file
......@@ -7,21 +7,29 @@
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="jiodev.js"></script>
<script src="jio_mappingstorage.js"></script>
<script src="gadget_global.js" ></script>
<script src="fast_priority_queue.js"></script>
<script src="gadget_erp5_chat_panel.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" />
</head>
<body>
<h3>Chat List</h3>
<div class="chat">
<div class="chat-box">
<div class="chat-left-panel">
<h4 class="center">Contacts</h4>
<ul class="contact-list"></ul>
<form class="manage-form">
<input type="submit" name="sync" value="Synchronize!" />
<input type="submit" name="contact" value="Add new contact!" />
</form>
</div>
<div class="chat-right-panel">
<h4 class="chat-title center"></h4>
<ul class="chat-list"></ul>
<form class="send-form">
<input type="text" name="content" />
<input type="submit" name="send" value="Send!" />
</form>
</div>
</div>
<form class="send-form">
<input type="text" name="content" />
<input type="submit" value="Send!" />
</form>
<form class="sync-form">
<input type="submit" value="Synchronize!" />
</form>
</body>
</html>
\ No newline at end of file
(function (window, document, FastPriorityQueue, loopEventListener, rJS, RSVP) {
/* Abstract Data Types:
* message is a JSON object representing a message with all its metadata.
* chat is messageToChat(message) for displaying an element in the chat log.
......@@ -9,7 +9,7 @@
* bundle is a list of new messages to be added to the local archive.
* request is a dictionary of the times of the last messages of each peer.
*/
/* Program Workflow:
* JIO storage (the 'archive') can only be accessed through two functions:
* - getLocalArchive takes the archive and stores it in the list
......@@ -28,7 +28,7 @@
* - sendLocalArchive sends the list over WebRTC
* - getRemoteArchive gets a list over WebRTC
*/
/* Summary:
* - getLocalArchive takes archive, calls storeList, appendMessage on each
* - sendLocalArchive sends the list to peer
......@@ -40,11 +40,11 @@
* - appendMessage calls messageToChat, appends it to the chat log
* - refreshChat takes the list, overwrites the chat log
*/
function logError(error) {
console.log(error);
}
function logQueue(action) {
return new RSVP.Queue()
.push(function () {
......@@ -52,9 +52,9 @@
})
.push(null, logError);
}
function pollUntilNotNull(
my_gadget, delay_ms, timeout_ms,
gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) {
if (callbackFunction === undefined) {
callbackFunction = function () {};
......@@ -63,9 +63,9 @@
.push(function () {
return nullableFunction();
})
.push(function (my_result) {
if (my_result !== null) {
return callbackFunction(my_result);
.push(function (result) {
if (result !== null) {
return callbackFunction(result);
} else {
return RSVP.any([
RSVP.timeout(timeout_ms),
......@@ -77,11 +77,11 @@
.push(function () {
return nullableFunction();
})
.push(function (my_result) {
if (my_result === null) {
.push(function (result) {
if (result === null) {
return null;
} else {
return callbackFunction(my_result);
return callbackFunction(result);
}
})
.push(function (nullable) {
......@@ -93,35 +93,25 @@
}
});
}
function stringEndsWith(string, suffix) {
return string.indexOf(suffix, string.length - suffix.length) !== -1;
}
function getTime(message) {
return new Date(message.time).getTime();
}
function isNewMessage(message, last_time) {
return last_time === undefined || last_time < getTime(message)
return last_time === undefined || last_time < getTime(message);
}
function isSameMessage(lhs, rhs) {
return lhs !== undefined && rhs !== undefined && lhs.name === rhs.name
return lhs !== undefined && rhs !== undefined && lhs.name === rhs.name
&& lhs.content === rhs.content && lhs.folder === rhs.folder
&& lhs.room === rhs.room && getTime(lhs) === getTime(rhs);
}
// Add new favicon or change existing favicon to image in favicon_url
function changeFavicon(favicon_url) {
const link = document.querySelector("link[rel*='icon']")
|| document.createElement("link");
link.type = "image/x-icon";
link.rel = "shortcut icon";
link.href = favicon_url;
document.head.appendChild(link);
}
// Keep messages in chronologically ascending or descending order
function messageTimeCompare(ascending_order) {
if (ascending_order) {
......@@ -134,7 +124,7 @@
};
}
}
// Keep messages in chronologically ascending order in refreshChat
function messageTimeComparator(lhs, rhs) {
if (getTime(lhs) < getTime(rhs)) {
......@@ -145,22 +135,68 @@
return 0;
}
}
// Notify when a new message appears and denotify when it is seen
function notifyStatus(gadget, room, notify) {
let favicon_url;
let class_name;
if (notify) {
favicon_url = gadget.state_parameter_dict.alert_icon;
class_name = "notify";
} else {
favicon_url = gadget.state_parameter_dict.default_icon;
class_name = "";
}
const link = document.querySelector("link[rel*='icon']")
|| document.createElement("link");
link.type = "image/x-icon";
link.rel = "shortcut icon";
link.href = favicon_url;
document.head.appendChild(link);
const contact_list = gadget.state_parameter_dict
.element.querySelector(".contact-list").children;
for (let i = 0, i_len = contact_list.length; i < i_len; i++) {
if (contact_list[i].textContent === room) {
contact_list[i].className = class_name;
}
}
}
// Create new message from its type and content
function createMessage(gadget, type, content) {
// Add a contact as some HTML element
function appendContact(gadget, room) {
const container = gadget.state_parameter_dict
.element.querySelector(".contact-list");
const contact = document.createElement("li");
contact.appendChild(document.createTextNode(room));
contact.addEventListener('click', function (event) {
notifyStatus(gadget, contact.textContent, false);
return gadget.changeRoom(room);
}, false);
container.appendChild(contact);
}
// Create a contact and connect to them
function createContact(gadget, room, param_string) {
appendContact(gadget, room);
return gadget.parseParams(param_string);
}
// Create new message from its parameters
function createMessage(gadget, param_dict) {
return {
type: type,
name: gadget.state_parameter_dict.name,
folder: gadget.state_parameter_dict.folder,
room: gadget.state_parameter_dict.room,
time: new Date(),
content: content,
type: param_dict.type || "message",
name: param_dict.name || gadget.state_parameter_dict.name,
folder: param_dict.folder || gadget.state_parameter_dict.folder,
room: param_dict.room || gadget.state_parameter_dict.room,
time: param_dict.time || new Date(),
content: param_dict.content || "",
colour: param_dict.colour || "black",
};
}
// Translate message to chat, in some HTML element
function messageToChat(message) {
const chat = document.createElement("p");
const chat = document.createElement("li");
const image_extensions = [".jpg", ".png", ".gif", ".bmp", ".tif", ".svg"];
const url_regex =
/((?:https?:\/\/)?(?:[-a-zA-Z0-9_~\/]+\.)+[-a-zA-Z0-9_~#?&=\/]+)/g;
......@@ -169,6 +205,7 @@
break;
case "notification":
chat.appendChild(document.createTextNode(message.content));
chat.style.color = message.colour;
return chat;
case "message":
chat.appendChild(document.createTextNode(
......@@ -208,10 +245,11 @@
}
}
chat.appendChild(document.createTextNode(matches[matches.length - 1]));
chat.style.color = message.colour;
return chat;
}
}
// Add message to the list
function storeList(gadget, message) {
if (isNewMessage(message, gadget.state_parameter_dict.last_message_dict
......@@ -221,22 +259,22 @@
}
gadget.state_parameter_dict.message_list_dict[message.room].push(message);
}
// Appends a message to the chat box
function appendMessage(gadget, message) {
if (message.room === gadget.state_parameter_dict.room) {
const container = gadget.state_parameter_dict
.element.querySelector(".chat");
.element.querySelector(".chat-list");
container.appendChild(messageToChat(message));
container.scrollTop = container.scrollHeight;
}
}
// Sort the list, dedupe, and overwrite the chat box,
// efficient because the archive is originally sorted
function refreshChat(gadget) {
const container = gadget.state_parameter_dict
.element.querySelector(".chat");
.element.querySelector(".chat-list");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
......@@ -258,38 +296,68 @@
gadget.state_parameter_dict.message_list_dict
[gadget.state_parameter_dict.room] = new_list;
}
// Parse chat commands
function parseCommand(gadget, chat) {
const split = chat.slice(1).split(" ");
const command = split.shift();
const argument = split.join(" ");
switch (command) {
case "rename":
gadget.state_parameter_dict.name = argument;
break;
case "join":
return gadget.changeRoom(argument);
if (argument in gadget.state_parameter_dict.room_set) {
return gadget.changeRoom(argument);
} else {
return gadget.deployNotification({
content: 'First connect to room "' + argument + '" via WebRTC!',
colour: "red",
});
}
case "leave":
return new RSVP.Queue()
.push(function () {
if (gadget.state_parameter_dict.room ===
gadget.state_parameter_dict.name) {
return gadget.deployNotification({
content: "You cannot leave your own room!",
colour: "red"
});
} else {
delete gadget.state_parameter_dict.room_set
[gadget.state_parameter_dict.room];
return gadget.deployMessage({
type: "notification",
content: gadget.state_parameter_dict.name + " has quit.",
colour: "orange",
});
}
})
.push(function () {
return gadget.changeRoom(gadget.state_parameter_dict.name);
})
.push(null, logError);
case "quit":
window.location.reload();
break;
case "help":
const help_message_list = [
"--------------------------------------------------------------------"
+ "-----------------------------------------------------------------",
"Available commands:",
"/rename [name]: changes your nickname to [name]",
"/join [room]: connects you to [room]",
"/help: displays this help box",
"/leave: disconnects you from the current room",
"/quit: disconnects from the chat and refreshes the page",
"--------------------------------------------------------------------"
+ "-----------------------------------------------------------------",
];
for (let i = 0, i_len = help_message_list.length; i < i_len; i++) {
appendMessage(gadget, createMessage(
gadget, "notification", help_message_list[i]));
}
break;
promise_list = [];
return new RSVP.Queue()
.push(function () {
for (let i = 0, i_len = help_message_list.length; i < i_len; i++) {
promise_list.push(gadget.deployNotification({
content: help_message_list[i],
colour: "green",
}));
}
return RSVP.all(promise_list);
})
.push(null, logError);
}
}
......@@ -302,10 +370,12 @@
room: null,
name: null,
element: null,
initialized: null,
notified_join: false,
guest_amount: 0,
// A set of names of rooms joined within this folder
initialized: false,
// A set of names of rooms joined within this folder;
// as a guest, only notify that you have joined once
// you have received the host's remote archive, so that
// your notification doesn't override all previous messages
// room_set["room"] = false if joined, true if also notified
room_set: {},
// A dictionary of messages based on the current room, e.g.
// message_list_dict["room"] = [message1, message2, ...]
......@@ -320,52 +390,62 @@
return gadget.getElement();
})
.push(function (element) {
element.querySelector(".send-form input").onfocus = function () {
notifyStatus(gadget, gadget.state_parameter_dict.room, false);
};
gadget.state_parameter_dict.element = element;
return;
})
.push(null, logError);
})
// Initialize gadget, after initializing name, folder, and room outside
.declareMethod('render', function () {
.declareMethod('render', function () {})
.declareAcquiredMethod('wrapJioAccess', 'wrapJioAccess')
.declareAcquiredMethod('sendMessage', 'sendMessage')
.declareAcquiredMethod('parseParams', 'parseParams')
// Join a room for the first time
.declareMethod('startRoom', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
gadget.state_parameter_dict.element
.querySelector("input").onfocus = function () {
return changeFavicon(gadget.state_parameter_dict.default_icon);
};
return gadget.changeRoom(gadget.state_parameter_dict.room);
const room = gadget.state_parameter_dict.name;
gadget.state_parameter_dict.element.querySelector(".chat-title")
.textContent = "Room: " + room;
gadget.state_parameter_dict.room = room;
gadget.state_parameter_dict.room_set[room] = false;
gadget.state_parameter_dict.message_list_dict[room] = [];
gadget.state_parameter_dict.last_message_dict[room] = {};
appendContact(gadget, room);
return gadget.getLocalArchive();
})
.push(null, logError);
})
.declareAcquiredMethod('wrapJioAccess', 'wrapJioAccess')
.declareAcquiredMethod('sendMessage', 'sendMessage')
// Join a different room in the same folder
.declareMethod('changeRoom', function (room) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
gadget.state_parameter_dict.element.querySelector(".chat-title")
.textContent = "Room: " + room
gadget.state_parameter_dict.room = room;
if (!(room in gadget.state_parameter_dict.room_set)) {
gadget.state_parameter_dict.room_set[room] = true;
}
if (!(room in gadget.state_parameter_dict.message_list_dict)) {
gadget.state_parameter_dict.room_set[room] = false;
gadget.state_parameter_dict.message_list_dict[room] = [];
}
if (!(room in gadget.state_parameter_dict.last_message_dict)) {
gadget.state_parameter_dict.last_message_dict[room] = {};
appendContact(gadget, room);
}
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(function () {
return refreshChat(gadget);
})
.push(null, logError);
})
// Get all messages stored in archive and add them to the list in order,
// using storeList because the messages are already in the archive
.declareMethod('getLocalArchive', function () {
......@@ -410,7 +490,7 @@
})
.push(null, logError);
})
// Send all requested messages in the list, in sorted order, to peer
.declareMethod('sendLocalArchive', function (request, source) {
const gadget = this;
......@@ -418,14 +498,17 @@
for (let room in request.content.room_set) {
request_dict[room] = [];
const list = gadget.state_parameter_dict.message_list_dict[room];
for (let i = 0, i_len = list.length; i < i_len; i++) {
if (isNewMessage(list[i], request.content.dict[room][list[i].name])) {
request_dict[room].push(list[i]);
if (list) {
for (let i = 0, i_len = list.length; i < i_len; i++) {
if (isNewMessage(list[i], request.content.dict[room][list[i].name])
&& list[i].type !== "notification") {
request_dict[room].push(list[i]);
}
}
}
}
return logQueue(gadget.sendMessage(createMessage(
gadget, "bundle", request_dict), source));
return logQueue(gadget.sendMessage(createMessage(gadget,
{type: "bundle", content: request_dict}), source));
})
// Get all new messages from the sorted list of peer,
......@@ -456,10 +539,15 @@
return RSVP.all(promise_list);
})
.push(function () {
if (!gadget.state_parameter_dict.notified_join) {
gadget.state_parameter_dict.notified_join = true;
return gadget.deployMessage("notification",
gadget.state_parameter_dict.name + " has joined.");
if (!gadget.state_parameter_dict.room_set
[gadget.state_parameter_dict.room]) {
gadget.state_parameter_dict.room_set
[gadget.state_parameter_dict.room] = true;
return gadget.deployMessage({
type: "notification",
content: gadget.state_parameter_dict.name + " has joined.",
colour: "orange",
});
} else {
return;
}
......@@ -469,31 +557,41 @@
})
.push(null, logError);
})
// Create new message and send it to peer
.declareMethod('deployMessage', function (type, content) {
// Create new message and send it to peer
.declareMethod('deployMessage', function (param_dict) {
const gadget = this;
const message = createMessage(gadget, type, content);
const message = createMessage(gadget, param_dict);
return new RSVP.Queue()
.push(function () {
return gadget.getMessage(message);
storeList(gadget, message);
appendMessage(gadget, message);
return gadget.storeArchive(message);
})
.push(function () {
changeFavicon(gadget.state_parameter_dict.default_icon);
return gadget.sendMessage(message);
})
.push(null, logError);
})
// Create new notification and keep it on own machine
.declareMethod('deployNotification', function (param_dict) {
const gadget = this;
param_dict.type = "notification";
const message = createMessage(gadget, param_dict);
storeList(gadget, message);
appendMessage(gadget, message);
})
// Get message from peer, store it in archive and list
.declareMethod('getMessage', function (message) {
const gadget = this;
changeFavicon(gadget.state_parameter_dict.alert_icon);
notifyStatus(gadget, message.room, true);
storeList(gadget, message);
appendMessage(gadget, message);
return logQueue(gadget.storeArchive(message));
})
// Store message in the archive
.declareMethod('storeArchive', function (message) {
const gadget = this;
......@@ -511,58 +609,71 @@
}));
});
})
// Ask a peer to send over a request
.declareMethod('requestRequest', function () {
const gadget = this;
return logQueue(gadget.sendMessage(createMessage(gadget, "doubler", "")));
return logQueue(gadget.sendMessage(createMessage(
gadget, {type: "doubler", content: ""})));
})
// Send a request to update the local archive
.declareMethod('sendRequest', function () {
const gadget = this;
return pollUntilNotNull(gadget, 1000, 30000, function () {
return gadget.state_parameter_dict.initialized;
}, function () {
return gadget.sendMessage(createMessage(
gadget, "request", {
return gadget.sendMessage(createMessage(gadget, {
type: "request",
content: {
room_set: gadget.state_parameter_dict.room_set,
dict: gadget.state_parameter_dict.last_message_dict,
}));
},
}));
});
})
// Listen for new chats
.declareService(function () {
const gadget = this;
function handleSubmit(event) {
switch (event.target.className) {
case "send-form":
const content = event.target.elements.content.value;
event.target.elements.content.value = "";
if (content.indexOf("/") === 0) {
return parseCommand(gadget, content);
} else {
return gadget.deployMessage("message", content);
return new RSVP.Queue()
.push(function () {
switch (event.target.className) {
case "manage-form":
switch (event.target
.querySelector("input[type=submit]:focus").name) {
case "sync":
return new RSVP.Queue()
.push(function () {
return gadget.wrapJioAccess('repair');
})
.push(function () {
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(null, logError);
case "new":
return createContact(gadget, "guest", "room=guest&role=guest&auth=dropbox&dropbox_url=/Apps/OfficeJS Chat/&erp5_url=https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/");
}
case "send-form":
const content = event.target.elements.content.value;
event.target.elements.content.value = "";
if (content.indexOf("/") === 0) {
return parseCommand(gadget, content);
} else {
return gadget.deployMessage({content: content});
}
}
case "sync-form":
return new RSVP.Queue()
.push(function () {
return gadget.wrapJioAccess('repair');
})
.push(function () {
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(null, logError);
}
})
.push(null, logError);
}
return loopEventListener(
gadget.state_parameter_dict.element, "submit", false, handleSubmit);
return loopEventListener(gadget.state_parameter_dict.element
.querySelector(".send-form"), "submit", false, handleSubmit);
})
.declareService(function () {
const gadget = this;
return new RSVP.Queue()
......@@ -571,12 +682,20 @@
})
.push(function () {
if (gadget.state_parameter_dict.initialized) {
return gadget.sendMessage(createMessage(gadget, "notification",
gadget.state_parameter_dict.name + " has quit."));
promise_list = [];
for (room in gadget.state_parameter_dict.room_set) {
promise_list.push(gadget.sendMessage(createMessage(gadget, {
type: "notification",
content: gadget.state_parameter_dict.name + " has quit.",
room: room,
colour: "orange",
})));
}
return RSVP.all(promise_list);
} else {
return;
}
});
});
}(window, document, FastPriorityQueue, loopEventListener, rJS, RSVP));
\ No newline at end of file
......@@ -7,59 +7,44 @@
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="adapter.js"></script>
<script src="gadget_global.js" ></script>
<script src="gadget_global.js"></script>
<script src="gadget_erp5_chat_webrtc.js"></script>
</head>
<body>
<p class="webrtc-error">
</p>
<h3 class="webrtc-heading"></h3>
<p class="error"></p>
<form class="auth-form">
<input type="radio" name="auth" value="erp5" required="required" />
<label>ERP5, URL:</label>
<br />
<input type="text" name="erp5_url" value="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
<br />
<input type="radio" name="auth" value="dropbox" required="required" />
<label>Dropbox, Folder:</label>
<br />
<input type="text" name="dropbox_url" value="/Apps/OfficeJS Chat" />
<br />
<label>
<input type="radio" name="auth" value="erp5" required="required" />
ERP5, URL:
<input type="text" name="erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
</label>
<label>
<input type="radio" name="auth" value="dropbox" required="required" />
Dropbox, Folder:
<input type="text" name="dropbox_url" placeholder="/Apps/OfficeJS Chat" />
</label>
<input type="submit" value="Authenticate!" />
</form>
<form class="host-offer-form">
<label>
Paste your guest's offer in this box:
</label>
<br />
<label>Paste your guest's offer in this box:</label>
<textarea rows="10" cols="80" name="send"></textarea>
<br />
<input type="submit" value="Paste it!" />
</form>
<form class="host-answer-form">
<p>
This is your answer. Send it to your guest!
</p>
<p class="receive">
</p>
<p>This is your answer. Send it to your guest!</p>
<p class="receive"></p>
</form>
<form class="guest-offer-form">
<p>
This is your new offer. Send it to your host!
</p>
<p class="receive">
</p>
<p>This is your new offer. Send it to your host!</p>
<p class="receive"></p>
<input type="submit" value="I sent it to my host." />
</form>
<form class="guest-answer-form">
<label>
Now, paste your host's answer in this box:
</label>
<br />
<label>Now, paste your host's answer in this box:</label>
<textarea rows="10" cols="80" name="send"></textarea>
<br />
<input type="submit" value="Paste it!" />
</form>
</body>
......
(function (window, document, rJS, RSVP) {
// Miscellaneous utility functions
function logError(error) {
console.log(error);
}
function logQueue(action) {
return new RSVP.Queue()
.push(function () {
......@@ -13,19 +13,19 @@
})
.push(null, logError);
}
function showElementByClass(my_gadget, query) {
my_gadget.state_parameter_dict.element
function showElementByClass(gadget, query) {
gadget.state_parameter_dict.element
.querySelector(query).style.display = "block";
}
function hideElementByClass(my_gadget, query) {
my_gadget.state_parameter_dict.element
function hideElementByClass(gadget, query) {
gadget.state_parameter_dict.element
.querySelector(query).style.display = "none";
}
function pollUntilNotNull(
my_gadget, delay_ms, timeout_ms,
gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) {
if (callbackFunction === undefined) {
callbackFunction = function () {};
......@@ -36,8 +36,8 @@
return RSVP.delay(timeout_ms);
})
.push(function () {
my_gadget.state_parameter_dict.element
.querySelector(".webrtc-error").textContent =
gadget.state_parameter_dict.element
.querySelector(".error").textContent =
"Timed out after " + timeout_ms + " ms.";
return;
})
......@@ -50,11 +50,11 @@
.push(function () {
return nullableFunction();
})
.push(function (my_result) {
if (my_result === null) {
.push(function (result) {
if (result === null) {
return null;
} else {
return callbackFunction(my_result);
return callbackFunction(result);
}
})
.push(function (nullable) {
......@@ -64,14 +64,14 @@
})
]);
}
// jIO utility functions
function createDropboxJio(my_gadget, folder) {
function createDropboxJio(gadget, folder) {
let dropbox_gadget;
return new RSVP.Queue()
.push(function () {
return my_gadget.declareGadget(
return gadget.declareGadget(
"gadget_jio.html", {scope: "dropbox_gadget"});
})
.push(function (jio_gadget) {
......@@ -93,7 +93,7 @@
})
.push(null, logError);
}
function resetDropboxContent(dropbox_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
......@@ -108,7 +108,7 @@
return RSVP.all(promise_list);
});
}
function getDropboxOffer(dropbox_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
......@@ -124,7 +124,7 @@
})
.push(null, logError);
}
function getDropboxContent(dropbox_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
......@@ -138,37 +138,37 @@
})
.push(null, logError);
}
function putDropboxContent(dropbox_gadget, param_dict) {
return logQueue(dropbox_gadget.putAttachment(
param_dict.folder, param_dict.file_name,
new Blob([param_dict.content], {type: "text"})));
}
function removeDropboxContent(dropbox_gadget, param_dict) {
return logQueue(dropbox_gadget
.removeAttachment(param_dict.folder, param_dict.file_name));
}
function authenticateDropbox(my_gadget) {
function authenticateDropbox(gadget) {
if (document.URL.indexOf("access_token=") === -1) {
my_gadget.state_parameter_dict.element.querySelector(".webrtc-error")
gadget.state_parameter_dict.element.querySelector(".error")
.textContent = "Please log in to Dropbox!";
return;
}
return new RSVP.Queue()
.push(function () {
return createDropboxJio(my_gadget,
my_gadget.state_parameter_dict.auth_dict.dropbox_folder);
return createDropboxJio(gadget,
gadget.state_parameter_dict.auth_dict.dropbox_folder);
})
.push(function () {
const dropbox_param_dict = {
gadget: my_gadget,
gadget: gadget,
gadget_name: "dropbox_gadget",
function_param_dict: {
folder: my_gadget.state_parameter_dict.auth_dict.dropbox_folder,
room: my_gadget.state_parameter_dict.login_dict.room,
name: my_gadget.state_parameter_dict.name,
folder: gadget.state_parameter_dict.auth_dict.dropbox_folder,
room: gadget.state_parameter_dict.login_dict.room,
name: gadget.state_parameter_dict.login_dict.name,
file_name: null,
content: null,
},
......@@ -180,20 +180,20 @@
removeContent: removeDropboxContent,
},
};
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
if (gadget.state_parameter_dict.login_dict.role === "host") {
return authenticateHost(dropbox_param_dict);
} else if (my_gadget.state_parameter_dict.login_dict.role === "guest") {
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
return authenticateGuest(dropbox_param_dict);
}
})
.push(null, logError);
}
function createErp5Jio(my_gadget, folder, url) {
function createErp5Jio(gadget, folder, url) {
let erp5_gadget;
return new RSVP.Queue()
.push(function () {
return my_gadget.declareGadget(
return gadget.declareGadget(
"gadget_jio.html", {scope: "erp5_gadget"});
})
.push(function (jio_gadget) {
......@@ -232,13 +232,13 @@
})
.push(null, logError);
}
function resetErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.putAttachment(
"/", param_dict.url + "WebrtcRoom_resetContent",
new Blob([JSON.stringify({folder: param_dict.folder})])));
}
function getErp5Content(erp5_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
......@@ -263,18 +263,18 @@
})
.push(null, logError);
}
function putErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.putAttachment(
"/", param_dict.url + "WebrtcRoom_putContent",
new Blob([JSON.stringify({
folder: param_dict.folder,
name: param_dict.file_name,
name: param_dict.file_name,
content: param_dict.content,
})])
));
}
function removeErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.putAttachment(
"/", param_dict.url + "WebrtcRoom_deleteContent",
......@@ -284,37 +284,37 @@
})])
));
}
function authenticateErp5(my_gadget) {
function authenticateErp5(gadget) {
return new RSVP.Queue()
.push(function () {
return createErp5Jio(
my_gadget, my_gadget.state_parameter_dict.folder,
my_gadget.state_parameter_dict.auth_dict.erp5_url);
gadget, gadget.state_parameter_dict.login_dict.folder,
gadget.state_parameter_dict.auth_dict.erp5_url);
})
.push(function (folder_id) {
my_gadget.state_parameter_dict.auth_dict.erp5_folder =
gadget.state_parameter_dict.auth_dict.erp5_folder =
folder_id.slice(folder_id.indexOf("/") + 1);
my_gadget.state_parameter_dict.auth_dict.erp5_folder_url =
my_gadget.state_parameter_dict.auth_dict.erp5_url
+ my_gadget.state_parameter_dict.erp5_folder + "/";
return my_gadget.getDeclaredGadget("erp5_gadget");
gadget.state_parameter_dict.auth_dict.erp5_folder_url =
gadget.state_parameter_dict.auth_dict.erp5_url
+ gadget.state_parameter_dict.erp5_folder + "/";
return gadget.getDeclaredGadget("erp5_gadget");
})
.push(function (erp5_gadget) {
return resetErp5Content(erp5_gadget, {
folder: my_gadget.state_parameter_dict.auth_dict.erp5_folder,
url: my_gadget.state_parameter_dict.auth_dict.erp5_folder_url,
folder: gadget.state_parameter_dict.auth_dict.erp5_folder,
url: gadget.state_parameter_dict.auth_dict.erp5_folder_url,
});
})
.push(function () {
const erp5_param_dict = {
gadget: my_gadget,
gadget: gadget,
gadget_name: "erp5_gadget",
function_param_dict: {
folder: my_gadget.state_parameter_dict.auth_dict.erp5_folder,
room: my_gadget.state_parameter_dict.login_dict.room,
name: my_gadget.state_parameter_dict.name,
url: my_gadget.state_parameter_dict.auth_dict.erp5_folder_url,
folder: gadget.state_parameter_dict.auth_dict.erp5_folder,
room: gadget.state_parameter_dict.login_dict.room,
name: gadget.state_parameter_dict.login_dict.name,
url: gadget.state_parameter_dict.auth_dict.erp5_folder_url,
file_name: null,
content: null,
},
......@@ -332,38 +332,38 @@
removeContent: removeErp5Content,
},
};
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
if (gadget.state_parameter_dict.login_dict.role === "host") {
return authenticateHost(erp5_param_dict);
} else if (my_gadget.state_parameter_dict.login_dict.role === "guest") {
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
return authenticateGuest(erp5_param_dict);
}
})
.push(null, logError);
}
function authenticateHost(param_dict) {
const my_gadget = param_dict.gadget;
const gadget = param_dict.gadget;
let file_name;
let jio_gadget;
return new RSVP.Queue()
.push(function () {
my_gadget.state_parameter_dict.element
.querySelector(".webrtc-error").textContent = "";
return my_gadget.getDeclaredGadget(param_dict.gadget_name);
gadget.state_parameter_dict.element
.querySelector(".error").textContent = "";
return gadget.getDeclaredGadget(param_dict.gadget_name);
})
.push(function (storage_gadget) {
jio_gadget = storage_gadget;
return pollUntilNotNull(my_gadget, 500, 3600000, function () {
return param_dict.function_dict.getOffer(
jio_gadget, param_dict.function_param_dict);
return pollUntilNotNull(gadget, 2000, 86400000, function () {
return param_dict.function_dict.getOffer(
jio_gadget, param_dict.function_param_dict);
}, function (offer_name) {
file_name = offer_name;
});
})
.push(function () {
return pollUntilNotNull(my_gadget, 50, 30000, function () {
if (my_gadget.state_parameter_dict.offer_ready === true) {
my_gadget.state_parameter_dict.offer_ready = false;
return pollUntilNotNull(gadget, 500, 30000, function () {
if (gadget.state_parameter_dict.offer_ready === true) {
gadget.state_parameter_dict.offer_ready = false;
return true;
} else {
return null;
......@@ -375,22 +375,22 @@
})
.push(function () {
param_dict.function_param_dict.file_name = "offer_" + file_name;
return pollUntilNotNull(my_gadget, 50, 5000, function () {
return pollUntilNotNull(gadget, 500, 10000, function () {
return param_dict.function_dict.getContent(
jio_gadget, param_dict.function_param_dict);
}, function (guest_offer) {
return sendOffer(my_gadget, guest_offer);
return sendOffer(gadget, guest_offer);
});
}, function (error) {
if (error.toString().indexOf("Timed out after") === 0) {
throw createInitialOffer(my_gadget);
throw createInitialOffer(gadget);
} else {
throw error;
}
})
.push(function () {
return pollUntilNotNull(my_gadget, 50, 10000, function () {
return my_gadget.state_parameter_dict.candidate;
return pollUntilNotNull(gadget, 500, 10000, function () {
return gadget.state_parameter_dict.candidate;
}, function (host_answer) {
param_dict.function_param_dict.file_name = "answer_" + file_name;
param_dict.function_param_dict.content = host_answer;
......@@ -412,19 +412,19 @@
}
function authenticateGuest(param_dict) {
const my_gadget = param_dict.gadget;
const gadget = param_dict.gadget;
let file_name;
let jio_gadget;
return new RSVP.Queue()
.push(function () {
my_gadget.state_parameter_dict.element
.querySelector(".webrtc-error").textContent = "";
return my_gadget.getDeclaredGadget(param_dict.gadget_name);
gadget.state_parameter_dict.element
.querySelector(".error").textContent = "";
return gadget.getDeclaredGadget(param_dict.gadget_name);
})
.push(function (storage_gadget) {
jio_gadget = storage_gadget;
return pollUntilNotNull(my_gadget, 50, 15000, function () {
return my_gadget.state_parameter_dict.candidate;
return pollUntilNotNull(gadget, 500, 30000, function () {
return gadget.state_parameter_dict.candidate;
}, function (guest_offer) {
file_name = param_dict.function_param_dict.room + "_"
+ param_dict.function_param_dict.name + ".txt";
......@@ -435,12 +435,12 @@
});
})
.push(function () {
return pollUntilNotNull(my_gadget, 50, 30000, function () {
return pollUntilNotNull(gadget, 500, 30000, function () {
param_dict.function_param_dict.file_name = "answer_" + file_name;
return param_dict.function_dict.getContent(
jio_gadget, param_dict.function_param_dict);
}, function (host_answer) {
return sendAnswer(my_gadget, host_answer);
return sendAnswer(gadget, host_answer);
});
}, function () {
return param_dict.function_dict.removeContent(
......@@ -452,156 +452,134 @@
})
.push(null, logError);
}
function authenticate(my_gadget, storage) {
const form = my_gadget.state_parameter_dict.element
.querySelector(".auth-form").elements;
return new RSVP.Queue()
.push(function () {
let folder = my_gadget.state_parameter_dict.folder;
if (folder[0] !== "/") {
folder = "/" + folder;
}
if (folder[folder.length - 1] !== "/") {
folder = folder + "/";
}
my_gadget.state_parameter_dict.auth_dict = {
dropbox_url: form.dropbox_url.value,
dropbox_folder: form.dropbox_url.value + folder,
erp5_url: form.erp5_url.value,
erp5_folder: null,
erp5_folder_url: null,
};
switch (storage) {
case "dropbox":
return authenticateDropbox(my_gadget);
case "erp5":
return authenticateErp5(my_gadget);
default:
return;
}
});
}
// WebRTC signalling functions
function createInitialOffer(my_gadget) {
const peer_list = my_gadget.state_parameter_dict.peer_connection_list;
function createInitialOffer(gadget) {
const peer_list = gadget.state_parameter_dict.peer_connection_list;
return new RSVP.Queue()
.push(function () {
return new RTCPeerConnection(
my_gadget.state_parameter_dict.ice_config,
my_gadget.state_parameter_dict.ice_constraints);
gadget.state_parameter_dict.ice_config,
gadget.state_parameter_dict.ice_constraints);
})
.push(function (peer_connection) {
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
my_gadget.state_parameter_dict.element
if (gadget.state_parameter_dict.login_dict.role === "host") {
gadget.state_parameter_dict.element
.querySelector(".host-offer-form textarea").value = "";
hideElementByClass(my_gadget, ".host-answer-form");
showElementByClass(my_gadget, ".host-offer-form");
hideElementByClass(gadget, ".host-answer-form");
showElementByClass(gadget, ".host-offer-form");
peer_list.push(peer_connection);
return setupPeerConnection(
my_gadget, peer_connection, ".host-answer-form .receive");
} else if (my_gadget.state_parameter_dict.login_dict.role === "guest") {
gadget, peer_connection, ".host-answer-form .receive");
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
peer_list[0] = peer_connection;
return setupPeerConnection(
my_gadget, peer_connection, ".guest-offer-form .receive");
gadget, peer_connection, ".guest-offer-form .receive");
}
})
.push(function () {
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
if (gadget.state_parameter_dict.login_dict.role === "host") {
peer_list[peer_list.length - 1].ondatachannel =
function (my_event) {
setupDataChannel(my_gadget, my_event.channel);
my_gadget.state_parameter_dict
.data_channel_list.push(my_event.channel);
function (event) {
setupDataChannel(gadget, event.channel);
gadget.state_parameter_dict.data_channel_list.push(event.channel);
};
return;
} else if (my_gadget.state_parameter_dict.login_dict.role === "guest") {
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
const peer_connection = peer_list[0];
return new RSVP.Queue()
.push(function () {
return peer_connection.createDataChannel(
my_gadget.state_parameter_dict.login_dict.room + "_"
+ my_gadget.state_parameter_dict.name + "_"
+ my_gadget.state_parameter_dict.data_channel_list.length,
my_gadget.state_parameter_dict.dc_constraints);
gadget.state_parameter_dict.login_dict.room + "_"
+ gadget.state_parameter_dict.login_dict.name + "_"
+ gadget.state_parameter_dict.data_channel_list.length,
gadget.state_parameter_dict.dc_constraints);
})
.push(function (channel) {
my_gadget.state_parameter_dict.data_channel_list.push(channel);
return setupDataChannel(my_gadget, channel);
gadget.state_parameter_dict.data_channel_list.push(channel);
return setupDataChannel(gadget, channel);
})
.push(function () {
return peer_connection.createOffer(
function (offer_description) {
peer_connection.setLocalDescription(offer_description);
}, logError, my_gadget.state_parameter_dict.sdp_constraints);
}, logError, gadget.state_parameter_dict.sdp_constraints);
})
.push(null, logError);
}
})
.push(function () {
my_gadget.state_parameter_dict.offer_ready = true;
gadget.state_parameter_dict.offer_ready = true;
return;
})
.push(null, logError);
}
function setupPeerConnection(my_gadget, peer_connection, form_selector) {
peer_connection.onicecandidate = function (my_event) {
function setupPeerConnection(gadget, peer_connection, form_selector) {
peer_connection.onicecandidate = function (event) {
const candidate = JSON.stringify(peer_connection.localDescription);
my_gadget.state_parameter_dict.candidate = candidate;
my_gadget.state_parameter_dict.element
gadget.state_parameter_dict.candidate = candidate;
gadget.state_parameter_dict.element
.querySelector(form_selector).textContent = candidate;
};
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
if (gadget.state_parameter_dict.login_dict.role === "host") {
peer_connection.oniceconnectionstatechange = function() {
if (peer_connection.iceConnectionState === "connected") {
my_gadget.state_parameter_dict.guest_amount += 1;
gadget.state_parameter_dict.guest_amount += 1;
} else if (peer_connection.iceConnectionState === "disconnected") {
my_gadget.state_parameter_dict.guest_amount -= 1;
gadget.state_parameter_dict.guest_amount -= 1;
}
};
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
peer_connection.oniceconnectionstatechange = function() {
if (peer_connection.iceConnectionState === "failed") {
gadget.state_parameter_dict.element
.querySelector(".error").textContent =
"WebRTC connection failed!";
} else if (peer_connection.iceConnectionState === "failed") {
if (my_gadget.state_parameter_dict.login_dict.role === "guest") {
window.location.reload();
}
gadget.state_parameter_dict.element
.querySelector(".error").textContent =
"WebRTC connection disconnected!";
}
};
}
}
function setupDataChannel(my_gadget, data_channel) {
if (!my_gadget.state_parameter_dict.connected) {
function setupDataChannel(gadget, data_channel) {
if (!gadget.state_parameter_dict.connected) {
data_channel.onopen = function () {
return new RSVP.Queue()
.push(function () {
my_gadget.state_parameter_dict.connected = true;
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
return createInitialOffer(my_gadget);
gadget.state_parameter_dict.connected = true;
if (gadget.state_parameter_dict.login_dict.role === "host") {
return createInitialOffer(gadget);
} else if (
my_gadget.state_parameter_dict.login_dict.role === "guest") {
showElementByClass(my_gadget, ".guest-offer-form");
gadget.state_parameter_dict.login_dict.role === "guest") {
hideElementByClass(gadget, ".guest-offer-form");
hideElementByClass(gadget, ".auth-form");
return;
}
})
.push(my_gadget.state_parameter_dict.dataChannelOnopen)
.push(gadget.state_parameter_dict.dataChannelOnopen)
.push(null, logError);
};
} else if (my_gadget.state_parameter_dict.login_dict.role === "host") {
} else if (gadget.state_parameter_dict.login_dict.role === "host") {
data_channel.onopen = function () {
return new RSVP.Queue()
.push(function () {
return createInitialOffer(my_gadget);
return createInitialOffer(gadget);
})
.push(my_gadget.state_parameter_dict.dataChannelOnopen)
.push(gadget.state_parameter_dict.dataChannelOnopen)
.push(null, logError);
};
}
data_channel.onmessage =
my_gadget.state_parameter_dict.dataChannelOnmessage;
gadget.state_parameter_dict.dataChannelOnmessage;
}
function sendOffer(my_gadget, offer) {
const peer_list = my_gadget.state_parameter_dict.peer_connection_list;
function sendOffer(gadget, offer) {
const peer_list = gadget.state_parameter_dict.peer_connection_list;
const peer_connection = peer_list[peer_list.length - 1];
return new RSVP.Queue()
.push(function () {
......@@ -614,35 +592,34 @@
return peer_connection.createAnswer(
function (answer_description) {
peer_connection.setLocalDescription(answer_description);
}, logError, my_gadget.state_parameter_dict.sdp_constraints);
}, logError, gadget.state_parameter_dict.sdp_constraints);
})
.push(null, logError);
}
function sendAnswer(my_gadget, answer) {
function sendAnswer(gadget, answer) {
return new RSVP.Queue()
.push(function () {
return new RTCSessionDescription(JSON.parse(answer));
})
.push(function (answer_description) {
return my_gadget.state_parameter_dict.peer_connection_list[0]
return gadget.state_parameter_dict.peer_connection_list[0]
.setRemoteDescription(answer_description);
})
.push(null, logError);
}
// Initialization, rendering, public methods, and event listeners
rJS(window)
.ready(function (gadget) {
gadget.state_parameter_dict = {
element: null,
name: null,
folder: null,
login_dict: {
folder: null,
room: null,
name: null,
role: null,
auth: null,
},
auth_dict: {
dropbox_url: null,
......@@ -654,25 +631,29 @@
candidate: null,
connected: false,
offer_ready: false,
room_list: [],
data_channel_list: [],
peer_connection_list: [],
guest_amount: 0,
archive_amount: 0,
ice_config: {
iceServers: [{url: "stun:stun.1.google.com:19302"}]
// iceServers: [{url: "stun:stun.1.google.com:19302"}],
// Must use own STUN server in production, but neither
// TURN nor STUN needed for local networks and IPv6
iceServers: [],
},
ice_constraints: {
optional: [{DtlsSrtpKeyAgreement: true}]
mandatory: {googIPv6: true},
optional: [{DtlsSrtpKeyAgreement: true}],
},
dc_constraints: {
reliable: true
reliable: true,
},
sdp_constraints: {
optional: [],
mandatory: {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
}
OfferToReceiveVideo: false,
},
},
};
return new RSVP.Queue()
......@@ -685,7 +666,7 @@
})
.push(null, logError);
})
.declareMethod('render', function () {
const gadget = this;
return new RSVP.Queue()
......@@ -697,17 +678,14 @@
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
hideElementByClass(gadget, ".host-offer-form");
}
gadget.state_parameter_dict.element.querySelector(".webrtc-heading")
.textContent = "WebRTC Negotiations for: "
+ gadget.state_parameter_dict.login_dict.room;
return createInitialOffer(gadget);
})
.push(function () {
if (gadget.state_parameter_dict.login_dict.auth !== null) {
return authenticate(
gadget, gadget.state_parameter_dict.login_dict.auth);
}
})
.push(null, logError);
})
.declareMethod('sendMessage', function (message, source) {
const gadget = this;
const message_string = JSON.stringify(message);
......@@ -733,23 +711,61 @@
}
return RSVP.all(promise_list);
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
return channel_list[0].send(message_string);
if (channel_list[0].readyState !== "open") {
throw "The data channel is not open!";
} else {
return channel_list[0].send(message_string);
}
}
})
.push(null, logError);
})
.declareMethod('authenticate', function () {
const gadget = this;
const fields = gadget.state_parameter_dict.element
.querySelector(".auth-form").elements;
const auth = fields.auth.value;
const dropbox_url = fields.dropbox_url.value;
const erp5_url = fields.erp5_url.value;
return new RSVP.Queue()
.push(function () {
let folder = gadget.state_parameter_dict.login_dict.folder;
if (folder[0] !== "/") {
folder = "/" + folder;
}
if (folder[folder.length - 1] !== "/") {
folder = folder + "/";
}
gadget.state_parameter_dict.auth_dict = {
dropbox_url: dropbox_url,
dropbox_folder: dropbox_url + folder,
erp5_url: erp5_url,
erp5_folder: null,
erp5_folder_url: null,
};
switch (auth) {
case "dropbox":
return authenticateDropbox(gadget);
case "erp5":
return authenticateErp5(gadget);
default:
return;
}
});
})
.declareService(function () {
const gadget = this;
function handleSubmit(my_event) {
switch (my_event.target.className) {
function handleSubmit(event) {
switch (event.target.className) {
case "auth-form":
return authenticate(gadget, my_event.target.auth.value);
return gadget.authenticate();
case "host-offer-form":
hideElementByClass(gadget, ".host-offer-form");
showElementByClass(gadget, ".host-answer-form");
let offer = my_event.target.elements.send.value;
my_event.target.elements.send.value = "";
let offer = event.target.elements.send.value;
event.target.elements.send.value = "";
return sendOffer(gadget, offer);
case "guest-offer-form":
hideElementByClass(gadget, ".guest-offer-form");
......@@ -757,13 +773,13 @@
break;
case "guest-answer-form":
hideElementByClass(gadget, ".guest-answer-form");
let answer = my_event.target.elements.send.value;
my_event.target.elements.send.value = "";
let answer = event.target.elements.send.value;
event.target.elements.send.value = "";
return sendAnswer(gadget, answer);
}
}
return loopEventListener(
gadget.state_parameter_dict.element, "submit", false, handleSubmit);
});
}(window, document, rJS, RSVP));
\ No newline at end of file
/*jslint indent:2, maxlen: 80, nomen: true */
/*global jIO, RSVP, UriTemplate, SimpleQuery, ComplexQuery, QueryFactory,
Query*/
(function (jIO, RSVP, UriTemplate, SimpleQuery, ComplexQuery, QueryFactory,
Query) {
"use strict";
function getSubIdEqualSubProperty(storage, value, key) {
var query;
if (storage._no_sub_query_id) {
throw new jIO.util.jIOError('no sub query id active', 404);
}
query = new SimpleQuery({
key: key,
value: value,
type: "simple"
});
if (storage._query.query !== undefined) {
query = new ComplexQuery({
operator: "AND",
query_list: [query, storage._query.query],
type: "complex"
});
}
query = Query.objectToSearchText(query);
return storage._sub_storage.allDocs({
"query": query,
"sort_on": storage._query.sort_on,
"select_list": storage._query.select_list,
"limit": storage._query.limit
})
.push(function (data) {
if (data.data.rows.length === 0) {
throw new jIO.util.jIOError(
"Can not find id",
404
);
}
if (data.data.rows.length > 1) {
throw new TypeError("id must be unique field: " + key
+ ", result:" + data.data.rows.toString());
}
return data.data.rows[0].id;
});
}
/*jslint unparam: true*/
var mapping_function = {
"equalSubProperty": {
"mapToSubProperty": function (property, sub_doc, doc, args, id) {
sub_doc[args] = doc[property];
return args;
},
"mapToMainProperty": function (property, sub_doc, doc, args, sub_id) {
if (sub_doc.hasOwnProperty(args)) {
doc[property] = sub_doc[args];
}
return args;
},
"mapToSubId": function (storage, doc, id, args) {
if (doc !== undefined) {
if (storage._property_for_sub_id &&
doc.hasOwnProperty(storage._property_for_sub_id)) {
return doc[storage._property_for_sub_id];
}
if (doc.hasOwnProperty(args)) {
return doc[args];
}
}
return getSubIdEqualSubProperty(storage, id, storage._map_id[1]);
},
"mapToId": function (storage, sub_doc, sub_id, args) {
return sub_doc[args];
}
},
"equalValue": {
"mapToSubProperty": function (property, sub_doc, doc, args) {
sub_doc[property] = args;
return property;
},
"mapToMainProperty": function (property) {
return property;
}
},
"ignore": {
"mapToSubProperty": function () {
return false;
},
"mapToMainProperty": function (property) {
return property;
}
},
"equalSubId": {
"mapToSubProperty": function (property, sub_doc, doc) {
sub_doc[property] = doc[property];
return property;
},
"mapToMainProperty": function (property, sub_doc, doc, args, sub_id) {
if (sub_id === undefined && sub_doc.hasOwnProperty(property)) {
doc[property] = sub_doc[property];
} else {
doc[property] = sub_id;
}
return property;
},
"mapToSubId": function (storage, doc, id, args) {
return id;
},
"mapToId": function (storage, sub_doc, sub_id) {
return sub_id;
}
},
"keep": {
"mapToSubProperty": function (property, sub_doc, doc) {
sub_doc[property] = doc[property];
return property;
},
"mapToMainProperty": function (property, sub_doc, doc) {
doc[property] = sub_doc[property];
return property;
}
},
"switchPropertyValue": {
"mapToSubProperty": function (property, sub_doc, doc, args) {
sub_doc[args[0]] = args[1][doc[property]];
return args[0];
},
"mapToMainProperty": function (property, sub_doc, doc, args) {
var subvalue, value = sub_doc[args[0]];
for (subvalue in args[1]) {
if (args[1].hasOwnProperty(subvalue)) {
if (value === args[1][subvalue]) {
doc[property] = subvalue;
return property;
}
}
}
}
}
};
/*jslint unparam: false*/
function initializeQueryAndDefaultMapping(storage) {
var property, query_list = [];
for (property in storage._mapping_dict) {
if (storage._mapping_dict.hasOwnProperty(property)) {
if (storage._mapping_dict[property][0] === "equalValue") {
if (storage._mapping_dict[property][1] === undefined) {
throw new jIO.util.jIOError("equalValue has not parameter", 400);
}
storage._default_mapping[property] =
storage._mapping_dict[property][1];
query_list.push(new SimpleQuery({
key: property,
value: storage._mapping_dict[property][1],
type: "simple"
}));
}
if (storage._mapping_dict[property][0] === "equalSubId") {
if (storage._property_for_sub_id !== undefined) {
throw new jIO.util.jIOError(
"equalSubId can be defined one time",
400
);
}
storage._property_for_sub_id = property;
}
}
}
if (storage._query.query !== undefined) {
query_list.push(QueryFactory.create(storage._query.query));
}
if (query_list.length > 1) {
storage._query.query = new ComplexQuery({
type: "complex",
query_list: query_list,
operator: "AND"
});
} else if (query_list.length === 1) {
storage._query.query = query_list[0];
}
}
function MappingStorage(spec) {
this._mapping_dict = spec.property || {};
this._sub_storage = jIO.createJIO(spec.sub_storage);
this._map_all_property = spec.map_all_property !== undefined ?
spec.map_all_property : true;
this._no_sub_query_id = spec.no_sub_query_id;
this._attachment_mapping_dict = spec.attachment || {};
this._query = spec.query || {};
this._map_id = spec.id || ["equalSubId"];
this._id_mapped = (spec.id !== undefined) ? spec.id[1] : false;
if (this._query.query !== undefined) {
this._query.query = QueryFactory.create(this._query.query);
}
this._default_mapping = {};
initializeQueryAndDefaultMapping(this);
}
function getAttachmentId(storage, sub_id, attachment_id, method) {
var mapping_dict = storage._attachment_mapping_dict;
if (mapping_dict !== undefined
&& mapping_dict[attachment_id] !== undefined
&& mapping_dict[attachment_id][method] !== undefined
&& mapping_dict[attachment_id][method].uri_template !== undefined) {
return UriTemplate.parse(
mapping_dict[attachment_id][method].uri_template
).expand({id: sub_id});
}
return attachment_id;
}
function getSubStorageId(storage, id, doc) {
return new RSVP.Queue()
.push(function () {
var map_info = storage._map_id || ["equalSubId"];
if (storage._property_for_sub_id && doc !== undefined &&
doc.hasOwnProperty(storage._property_for_sub_id)) {
return doc[storage._property_for_sub_id];
}
return mapping_function[map_info[0]].mapToSubId(
storage,
doc,
id,
map_info[1]
);
});
}
function mapToSubProperty(storage, property, sub_doc, doc, id) {
var mapping_info = storage._mapping_dict[property] || ["keep"];
return mapping_function[mapping_info[0]].mapToSubProperty(
property,
sub_doc,
doc,
mapping_info[1],
id
);
}
function mapToMainProperty(storage, property, sub_doc, doc, sub_id) {
var mapping_info = storage._mapping_dict[property] || ["keep"];
return mapping_function[mapping_info[0]].mapToMainProperty(
property,
sub_doc,
doc,
mapping_info[1],
sub_id
);
}
function mapToMainDocument(storage, sub_doc, sub_id) {
var doc = {},
property,
property_list = [storage._id_mapped];
for (property in storage._mapping_dict) {
if (storage._mapping_dict.hasOwnProperty(property)) {
property_list.push(mapToMainProperty(
storage,
property,
sub_doc,
doc,
sub_id
));
}
}
if (storage._map_all_property) {
for (property in sub_doc) {
if (sub_doc.hasOwnProperty(property)) {
if (property_list.indexOf(property) < 0) {
doc[property] = sub_doc[property];
}
}
}
}
if (storage._map_for_sub_storage_id !== undefined) {
doc[storage._map_for_sub_storage_id] = sub_id;
}
return doc;
}
function mapToSubstorageDocument(storage, doc, id) {
var sub_doc = {}, property;
for (property in doc) {
if (doc.hasOwnProperty(property)) {
mapToSubProperty(storage, property, sub_doc, doc, id);
}
}
for (property in storage._default_mapping) {
if (storage._default_mapping.hasOwnProperty(property)) {
sub_doc[property] = storage._default_mapping[property];
}
}
if (storage._map_id[0] === "equalSubProperty" && id !== undefined) {
sub_doc[storage._map_id[1]] = id;
}
return sub_doc;
}
function handleAttachment(storage, argument_list, method) {
return getSubStorageId(storage, argument_list[0])
.push(function (sub_id) {
argument_list[0] = sub_id;
argument_list[1] = getAttachmentId(
storage,
sub_id,
argument_list[1],
method
);
return storage._sub_storage[method + "Attachment"].apply(
storage._sub_storage,
argument_list
);
});
}
MappingStorage.prototype.get = function (id) {
var storage = this;
return getSubStorageId(this, id)
.push(function (sub_id) {
return storage._sub_storage.get(sub_id)
.push(function (sub_doc) {
return mapToMainDocument(storage, sub_doc, sub_id);
});
});
};
MappingStorage.prototype.post = function (doc) {
var sub_doc = mapToSubstorageDocument(
this,
doc
),
id = doc[this._property_for_sub_id];
if (this._property_for_sub_id && id !== undefined) {
return this._sub_storage.put(id, sub_doc);
}
if (!this._id_mapped || doc[this._id_mapped] !== undefined) {
return this._sub_storage.post(sub_doc);
}
throw new jIO.util.jIOError(
"post is not supported with id mapped",
400
);
};
MappingStorage.prototype.put = function (id, doc) {
var storage = this,
sub_doc = mapToSubstorageDocument(this, doc, id);
return getSubStorageId(this, id, doc)
.push(function (sub_id) {
return storage._sub_storage.put(sub_id, sub_doc);
})
.push(undefined, function (error) {
if (error instanceof jIO.util.jIOError && error.status_code === 404) {
return storage._sub_storage.post(sub_doc);
}
throw error;
})
.push(function () {
return id;
});
};
MappingStorage.prototype.remove = function (id) {
var storage = this;
return getSubStorageId(this, id)
.push(function (sub_id) {
return storage._sub_storage.remove(sub_id);
})
.push(function () {
return id;
});
};
MappingStorage.prototype.putAttachment = function (id, attachment_id) {
return handleAttachment(this, arguments, "put", id)
.push(function () {
return attachment_id;
});
};
MappingStorage.prototype.getAttachment = function () {
return handleAttachment(this, arguments, "get");
};
MappingStorage.prototype.removeAttachment = function (id, attachment_id) {
return handleAttachment(this, arguments, "remove", id)
.push(function () {
return attachment_id;
});
};
MappingStorage.prototype.allAttachments = function (id) {
var storage = this, sub_id;
return getSubStorageId(storage, id)
.push(function (sub_id_result) {
sub_id = sub_id_result;
return storage._sub_storage.allAttachments(sub_id);
})
.push(function (result) {
var attachment_id,
attachments = {},
mapping_dict = {};
for (attachment_id in storage._attachment_mapping_dict) {
if (storage._attachment_mapping_dict.hasOwnProperty(attachment_id)) {
mapping_dict[getAttachmentId(storage, sub_id, attachment_id, "get")]
= attachment_id;
}
}
for (attachment_id in result) {
if (result.hasOwnProperty(attachment_id)) {
if (mapping_dict.hasOwnProperty(attachment_id)) {
attachments[mapping_dict[attachment_id]] = {};
} else {
attachments[attachment_id] = {};
}
}
}
return attachments;
});
};
MappingStorage.prototype.hasCapacity = function (name) {
return this._sub_storage.hasCapacity(name);
};
MappingStorage.prototype.repair = function () {
return this._sub_storage.repair.apply(this._sub_storage, arguments);
};
MappingStorage.prototype.bulk = function (id_list) {
var storage = this;
function mapId(parameter) {
return getSubStorageId(storage, parameter.parameter_list[0])
.push(function (id) {
return {"method": parameter.method, "parameter_list": [id]};
});
}
return new RSVP.Queue()
.push(function () {
var promise_list = id_list.map(mapId);
return RSVP.all(promise_list);
})
.push(function (id_list_mapped) {
return storage._sub_storage.bulk(id_list_mapped);
})
.push(function (result) {
var mapped_result = [], i;
for (i = 0; i < result.length; i += 1) {
mapped_result.push(mapToMainDocument(
storage,
result[i]
));
}
return mapped_result;
});
};
MappingStorage.prototype.buildQuery = function (option) {
var storage = this,
i,
query,
property,
select_list = [],
sort_on = [];
function mapQuery(one_query) {
var j, query_list = [], key, sub_query;
if (one_query.type === "complex") {
for (j = 0; j < one_query.query_list.length; j += 1) {
sub_query = mapQuery(one_query.query_list[j]);
if (sub_query) {
query_list.push(sub_query);
}
}
one_query.query_list = query_list;
return one_query;
}
key = mapToMainProperty(storage, one_query.key, {}, {});
if (key) {
one_query.key = key;
return one_query;
}
return false;
}
if (option.sort_on !== undefined) {
for (i = 0; i < option.sort_on.length; i += 1) {
property = mapToMainProperty(this, option.sort_on[i][0], {}, {});
if (property && sort_on.indexOf(property) < 0) {
sort_on.push([property, option.sort_on[i][1]]);
}
}
}
if (this._query.sort_on !== undefined) {
for (i = 0; i < this._query.sort_on.length; i += 1) {
property = mapToMainProperty(this, this._query.sort_on[i], {}, {});
if (sort_on.indexOf(property) < 0) {
sort_on.push([property, option.sort_on[i][1]]);
}
}
}
if (option.select_list !== undefined) {
for (i = 0; i < option.select_list.length; i += 1) {
property = mapToMainProperty(this, option.select_list[i], {}, {});
if (property && select_list.indexOf(property) < 0) {
select_list.push(property);
}
}
}
if (this._query.select_list !== undefined) {
for (i = 0; i < this._query.select_list; i += 1) {
property = this._query.select_list[i];
if (select_list.indexOf(property) < 0) {
select_list.push(property);
}
}
}
if (this._id_mapped) {
// modify here for future way to map id
select_list.push(this._id_mapped);
}
if (option.query !== undefined) {
query = mapQuery(QueryFactory.create(option.query));
}
if (this._query.query !== undefined) {
if (query === undefined) {
query = this._query.query;
}
query = new ComplexQuery({
operator: "AND",
query_list: [query, this._query.query],
type: "complex"
});
}
if (query !== undefined) {
query = Query.objectToSearchText(query);
}
return this._sub_storage.allDocs(
{
query: query,
select_list: select_list,
sort_on: sort_on,
limit: option.limit
}
)
.push(function (result) {
var sub_doc, map_info = storage._map_id || ["equalSubId"];
for (i = 0; i < result.data.total_rows; i += 1) {
sub_doc = result.data.rows[i].value;
result.data.rows[i].id =
mapping_function[map_info[0]].mapToId(
storage,
sub_doc,
result.data.rows[i].id,
map_info[1]
);
result.data.rows[i].value =
mapToMainDocument(
storage,
sub_doc
);
}
return result.data.rows;
});
};
jIO.addStorage('mapping', MappingStorage);
}(jIO, RSVP, UriTemplate, SimpleQuery, ComplexQuery, QueryFactory, Query));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
<string>contributor/person_module/1</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>jio_mappingstorage.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>jio_mappingstorage_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>JIO Mapping Storage JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
web_page_module/adapter_js
web_page_module/fast_priority_queue_js
web_page_module/gadget_erp5_chat*
web_page_module/jio_mappingstorage_js
web_site_module/web_chat/**
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment