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 { body {
padding: 20px; padding: 20px;
} }
error {
color: orange;
}
h3 { h3 {
color: brown; color: brown;
} }
p { h4 {
margin: 0; margin: 0;
color: red;
} }
img {
max-width: 100%; label {
max-height: 100%; display: block;
} }
input[type="text"] { input[type="text"] {
width: 360px; width: 360px;
} }
.chat { .radio-item {
border: 1px solid; display: inline-block;
width: 680px; }
height: 360px; 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; 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 @@ ...@@ -11,74 +11,69 @@
<link rel="stylesheet" href="gadget_erp5_chat.css" /> <link rel="stylesheet" href="gadget_erp5_chat.css" />
</head> </head>
<body> <body>
<div data-gadget-url="gadget_jio.html"
data-gadget-scope="storage_gadget"
data-gadget-sandbox="public"></div>
<h1>OfficeJS Chat</h1> <h1>OfficeJS Chat</h1>
<p class="chat-error">
</p>
<form class="login-form"> <form class="login-form">
<label>Name:</label> <label>Name:</label>
<br />
<input type="text" name="name" required="required" /> <input type="text" name="name" required="required" />
<br />
<label>Folder:</label> <label>Folder:</label>
<br />
<input type="text" name="folder" required="required" /> <input type="text" name="folder" required="required" />
<h3>Remote Storage</h3> <h3>Remote Storage</h3>
<input type="radio" name="remote" value="erp5" required="required" /> <label>
<label>ERP5</label> <input type="radio" name="remote" value="erp5" required="required" />
<br /> ERP5
<input type="radio" name="remote" value="dav" required="required" /> </label>
<label>DAV Storage</label> <label>
<br /> <input type="radio" name="remote" value="dav" required="required" />
<input type="radio" name="remote" value="dropbox" required="required" /> DAV Storage
<label>Dropbox</label> </label>
<br /> <label>
<input type="radio" name="remote" value="local" required="required" checked="checked" /> <input type="radio" name="remote" value="dropbox" required="required" />
<label>Local is Enough</label> Dropbox
<br /> </label>
<label>
<input type="radio" name="remote" value="local" required="required" checked="checked" />
Local is Enough
</label>
<h3>Storage Configuration</h3> <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> <a class="dropbox-link">Dropbox OAuth</a>
<br />
<label>Dropbox folder:</label> <label>Dropbox folder:</label>
<br /> <input type="text" name="remote_dropbox_url" placeholder="/Apps/OfficeJS Chat/.jio_documents" />
<input type="text" name="dropbox_url" value="/Apps/OfficeJS Chat/.jio_documents" />
<br />
<label>ERP5 URL:</label> <label>ERP5 URL:</label>
<br /> <input type="text" name="remote_erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/web_site_module/" />
<input type="text" name="erp5_url" value="https://softinst75770.host.vifib.net/erp5/web_site_module/" />
<br />
<label>WebDAV URL:</label> <label>WebDAV URL:</label>
<br /> <input type="text" name="remote_dav_url" placeholder="https://softinst75722.host.vifib.net/share" />
<input type="text" name="dav_url" value="https://softinst75722.host.vifib.net/share" />
<br />
<label>WebDAV Username:</label> <label>WebDAV Username:</label>
<br /> <input type="text" name="remote_dav_user" placeholder="eyqs" />
<input type="text" name="dav_user" value="eyqs" />
<br />
<label>WebDAV Password:</label> <label>WebDAV Password:</label>
<br /> <input type="text" name="remote_dav_pass" placeholder="correct horse battery staple" />
<input type="text" name="dav_pass" value="Aoeuidhtns" />
<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 /> <br />
<input type="submit" value="Login!" /> <input type="submit" value="Login!" />
</form> </form>
<form class="room-form"> <form class="room-form">
<h3>Join Rooms</h3>
<input type="text" name="room" required="required" /> <input type="text" name="room" required="required" />
<br /> <br />
<input type="submit" name="host" value="Create a new room!" /> <input type="submit" name="host" value="Create a new room!" />
<input type="submit" name="guest" value="Join an existing room!" /> <input type="submit" name="guest" value="Join an existing room!" />
</form> </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> </body>
</html> </html>
\ No newline at end of file
(function (window, document, loopEventListener, rJS, RSVP) { (function (window, document, loopEventListener, rJS, RSVP) {
/* URL Options: /* URL Options:
* name: your chat username, e.g. eyqs * name: your chat username, e.g. eyqs
* folder: the root folder for your organization, e.g. nexedi; * folder: the root folder for your organization, e.g. nexedi;
* you can only chat with people with access to the same auth folder * you can only chat with people with access to the same auth folder
* remote: the type of remote storage to use, * 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, * remote_dropbox_url: the remote Dropbox folder to synchronize with,
* e.g. /Apps/OfficeJS Chat/.jio_documents * e.g. /Apps/OfficeJS Chat/.jio_documents
* remote_erp5_url: the remote ERP5 instance to synchronize with, * remote_erp5_url: the remote ERP5 instance to synchronize with,
...@@ -15,21 +15,19 @@ ...@@ -15,21 +15,19 @@
* remote_dav_user: your WebDAV username, e.g. eyqs * remote_dav_user: your WebDAV username, e.g. eyqs
* remote_dav_pass: your WebDAV password, e.g. correct horse battery staple * remote_dav_pass: your WebDAV password, e.g. correct horse battery staple
* login: if present (e.g. login=foo), will automatically click Login * login: if present (e.g. login=foo), will automatically click Login
* room: the chat room you want to connect to, e.g. lobby * auth: the default type of authentication to use for WebRTC rooms,
* role: the type of connection to use, either host or guest; * either dropbox or erp5
* if both room and role are valid, will automatically click the button * auth_dropbox_url: the shared Dropbox folder to act as your auth folder,
* 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,
* e.g. /Apps/OfficeJS Chat * 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/ * 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) { function logError(error) {
console.log(error); console.log(error);
} }
function logQueue(action) { function logQueue(action) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -37,35 +35,44 @@ ...@@ -37,35 +35,44 @@
}) })
.push(null, logError); .push(null, logError);
} }
function showElementByClass(my_gadget, query) { function showElementByClass(gadget, query) {
my_gadget.state_parameter_dict.element gadget.state_parameter_dict.element
.querySelector(query).style.display = "block"; .querySelector(query).style.display = "block";
} }
function hideElementByClass(my_gadget, query) { function hideElementByClass(gadget, query) {
my_gadget.state_parameter_dict.element gadget.state_parameter_dict.element
.querySelector(query).style.display = "none"; .querySelector(query).style.display = "none";
} }
function cleanId(input_id) { function cleanId(input_id) {
const reserved = ["_", "&", "=", ",", ";"]; const reserved = ["_", "&", "=", ",", ";", "\\"];
for (let i = 0, i_len = reserved.length; i < i_len; i++) { for (let i = 0, i_len = reserved.length; i < i_len; i++) {
input_id = input_id.replace(reserved[i], "-"); input_id = input_id.replace(reserved[i], "-");
} }
return input_id; return input_id;
} }
function getQueryValue(query, value) { function getQueryValue(query_list, query_string) {
if (document.URL.indexOf(query + "=") != -1) { for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const start = document.URL.indexOf(query + "=") + query.length + 1; const query = query_list[i];
let end = document.URL.indexOf("&", start); if (query_string.indexOf(query + "=") !== -1) {
if (end === -1) { const start = query_string.indexOf(query + "=") + query.length + 1;
end = document.URL.length; 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 null;
return value || ""; }
function setQueryValue(query_list, query_string, element) {
value = getQueryValue(query_list, query_string);
if (value) {
element.value = value;
} }
} }
...@@ -73,7 +80,14 @@ ...@@ -73,7 +80,14 @@
.ready(function (gadget) { .ready(function (gadget) {
gadget.state_parameter_dict = { gadget.state_parameter_dict = {
element: null, 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() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -81,11 +95,14 @@ ...@@ -81,11 +95,14 @@
}) })
.push(function (element) { .push(function (element) {
gadget.state_parameter_dict.element = element; gadget.state_parameter_dict.element = element;
return gadget.render();
}) })
.push(null, logError); .push(null, logError);
}) })
.declareService(function () {
const gadget = this;
return this.render();
})
.allowPublicAcquisition('wrapJioAccess', function (param_list) { .allowPublicAcquisition('wrapJioAccess', function (param_list) {
const gadget = this; const gadget = this;
return new RSVP.Queue() return new RSVP.Queue()
...@@ -98,65 +115,142 @@ ...@@ -98,65 +115,142 @@
}) })
.push(null, logError); .push(null, logError);
}) })
.allowPublicAcquisition('sendMessage', function (param_dict) { .allowPublicAcquisition('sendMessage', function (param_list) {
const gadget = this; const gadget = this;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return gadget.getDeclaredGadget("webrtc_gadget"); return gadget.getDeclaredGadget(
"webrtc_gadget_" + param_list[0].room);
}) })
.push(function (webrtc_gadget) { .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 () { .declareMethod('render', function () {
let url = decodeURIComponent(document.URL);
const gadget = this; const gadget = this;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
hideElementByClass(gadget, ".room-form"); hideElementByClass(gadget, ".room-form");
hideElementByClass(gadget, ".webrtc-panel"); const state_output = getQueryValue(["state"], url);
hideElementByClass(gadget, ".chat-panel"); if (state_output) {
url += window.atob(state_output);
}
const fields = gadget.state_parameter_dict.element const fields = gadget.state_parameter_dict.element
.querySelector(".login-form").elements; .querySelector(".login-form").elements;
fields.name.value = getQueryValue("name"); // A list of login-form fields and short keys in order of priority
fields.folder.value = getQueryValue("folder"); const query_list = [["name", "m"], ["folder", "f"], ["remote", "r"],
const remote = getQueryValue("remote"); ["remote_dropbox_url", "rd"], ["remote_erp5_url", "re"],
if (remote === "dav" || remote === "dropbox" ["remote_dav_url", "rv"], ["remote_dav_user", "rvu"],
|| remote === "erp5" || remote === "local") { ["remote_dav_pass", "rvp"], ["auth", "a"],
fields.remote.value = remote; ["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( if (getQueryValue(["auto", "o"], url)) {
"remote_dropbox_url", fields.dropbox_url.value); fields.auto.checked = "Checked";
fields.erp5_url.value = getQueryValue( if (window.btoa(state_input + "&o=o").length <
"remote_erp5_url", fields.erp5_url.value); gadget.state_parameter_dict.max_state_length) {
fields.dav_url.value = getQueryValue( state_input += "&o=o";
"remote_dav_url", fields.dav_url.value); }
fields.dav_user.value = getQueryValue( }
"remote_dav_user", fields.dav_user.value); gadget.state_parameter_dict.element.querySelector(".dropbox-link")
fields.dav_pass.value = getQueryValue( .href = "https://www.dropbox.com/1/oauth2/authorize"
"remote_dav_pass", fields.dav_pass.value); + "?client_id=igeiyv4pkt0y0mm&response_type=token"
if (getQueryValue("login")) { + "&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(); return gadget.login();
} else { } else {
return; return;
} }
}) })
.push(null, logError);
})
.declareMethod('login', function () {
const gadget = this;
let chat_gadget;
return new RSVP.Queue()
.push(function () { .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 const fields = gadget.state_parameter_dict.element
.querySelector(".room-form").elements; .querySelector(".login-form").elements;
const room = getQueryValue("room"); gadget.state_parameter_dict.auth = fields.auth.value;
fields.room.value = room; gadget.state_parameter_dict.dropbox_url
const role = getQueryValue("role"); = fields.auth_dropbox_url.value;
if (room && (role === "host" || role === "guest")) { gadget.state_parameter_dict.erp5_url = fields.auth_erp5_url.value;
return gadget.chooseRoom(role); 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 { } else {
return; return gadget.chooseRoom({
room: gadget.state_parameter_dict.name, role: "host"});
} }
}) })
.push(null, logError); .push(null, logError);
}) })
.declareMethod('createJio', function (param_dict) { .declareMethod('createJio', function (param_dict) {
const gadget = this; const gadget = this;
return new RSVP.Queue() return new RSVP.Queue()
...@@ -244,150 +338,120 @@ ...@@ -244,150 +338,120 @@
}) })
.push(null, logError); .push(null, logError);
}) })
.declareMethod('login', function () { .declareMethod('chooseRoom', function (param_dict) {
const gadget = this; const gadget = this;
let rtc_gadget; let webrtc_gadget;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .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) { .push(function (subgadget) {
rtc_gadget = webrtc_gadget; webrtc_gadget = subgadget;
return gadget.getDeclaredGadget("chat_gadget"); return gadget.getDeclaredGadget("chat_gadget");
}) })
.push(function (chat_gadget) { .push(function (chat_gadget) {
const form = gadget.state_parameter_dict.element const fields = webrtc_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
.querySelector(".auth-form").elements; .querySelector(".auth-form").elements;
fields.auth.value = getQueryValue("auth"); fields.auth.value = param_dict.auth
fields.dropbox_url.value = getQueryValue( || gadget.state_parameter_dict.auth;
"auth_dropbox_url", fields.dropbox_url.value); fields.dropbox_url.value = param_dict.dropbox_url
fields.erp5_url.value = getQueryValue( || gadget.state_parameter_dict.dropbox_url;
"auth_erp5_url", fields.erp5_url.value); fields.erp5_url.value = param_dict.erp5_url
rtc_gadget.state_parameter_dict.login_dict = { || gadget.state_parameter_dict.erp5_url;
room: room, gadget.state_parameter_dict.element.insertBefore(
role: role, webrtc_gadget.state_parameter_dict.element,
auth: getQueryValue("auth"), 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 () { webrtc_gadget.state_parameter_dict.dataChannelOnopen = function () {
return new RSVP.Queue() return chat_gadget.changeRoom(param_dict.room);
.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);
}; };
rtc_gadget.state_parameter_dict.dataChannelOnmessage = webrtc_gadget.state_parameter_dict.dataChannelOnmessage =
function (my_event) { function (event) {
const message = JSON.parse(my_event.data); const message = JSON.parse(event.data);
const source = my_event.srcElement; const source = event.srcElement;
return new RSVP.Queue() if (message.type === "notification"
.push(function () { || message.type === "message") {
return gadget.getDeclaredGadget("chat_gadget"); return new RSVP.Queue()
}) .push(function () {
.push(function (chat_gadget) { return chat_gadget.getMessage(message);
if (message.type === "notification" })
|| message.type === "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() return new RSVP.Queue()
.push(function () { .push(function () {
return chat_gadget.getMessage(message); return chat_gadget.getRemoteArchive(message);
}) })
.push(function () { .push(function () {
if (role === "host") { return chat_gadget.requestRequest();
return rtc_gadget.sendMessage(message, source);
} else {
return;
}
}) })
.push(null, logError); .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();
} }
}) } else {
.push(null, logError); 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); .push(null, logError);
}) })
.declareService(function () { .declareService(function () {
const gadget = this; const gadget = this;
function handleSubmit(my_event) { function handleSubmit(event) {
switch (my_event.target.className) { switch (event.target.className) {
case "login-form": case "login-form":
return gadget.login(); return gadget.login();
case "room-form": case "room-form":
return gadget.chooseRoom( return new RSVP.Queue()
my_event.target.querySelector("input[type=submit]:focus").name); .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( return loopEventListener(
gadget.state_parameter_dict.element, "submit", false, handleSubmit); gadget.state_parameter_dict.element, "submit", false, handleSubmit);
}); });
}(window, document, loopEventListener, rJS, RSVP)); }(window, document, loopEventListener, rJS, RSVP));
\ No newline at end of file
...@@ -7,21 +7,29 @@ ...@@ -7,21 +7,29 @@
<script src="rsvp.js"></script> <script src="rsvp.js"></script>
<script src="renderjs.js"></script> <script src="renderjs.js"></script>
<script src="jiodev.js"></script> <script src="jiodev.js"></script>
<script src="jio_mappingstorage.js"></script>
<script src="gadget_global.js" ></script> <script src="gadget_global.js" ></script>
<script src="fast_priority_queue.js"></script> <script src="fast_priority_queue.js"></script>
<script src="gadget_erp5_chat_panel.js"></script> <script src="gadget_erp5_chat_panel.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" />
</head> </head>
<body> <body>
<h3>Chat List</h3> <div class="chat-box">
<div class="chat"> <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> </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> </body>
</html> </html>
\ No newline at end of file
(function (window, document, FastPriorityQueue, loopEventListener, rJS, RSVP) { (function (window, document, FastPriorityQueue, loopEventListener, rJS, RSVP) {
/* Abstract Data Types: /* Abstract Data Types:
* message is a JSON object representing a message with all its metadata. * message is a JSON object representing a message with all its metadata.
* chat is messageToChat(message) for displaying an element in the chat log. * chat is messageToChat(message) for displaying an element in the chat log.
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* bundle is a list of new messages to be added to the local archive. * 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. * request is a dictionary of the times of the last messages of each peer.
*/ */
/* Program Workflow: /* Program Workflow:
* JIO storage (the 'archive') can only be accessed through two functions: * JIO storage (the 'archive') can only be accessed through two functions:
* - getLocalArchive takes the archive and stores it in the list * - getLocalArchive takes the archive and stores it in the list
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
* - sendLocalArchive sends the list over WebRTC * - sendLocalArchive sends the list over WebRTC
* - getRemoteArchive gets a list over WebRTC * - getRemoteArchive gets a list over WebRTC
*/ */
/* Summary: /* Summary:
* - getLocalArchive takes archive, calls storeList, appendMessage on each * - getLocalArchive takes archive, calls storeList, appendMessage on each
* - sendLocalArchive sends the list to peer * - sendLocalArchive sends the list to peer
...@@ -40,11 +40,11 @@ ...@@ -40,11 +40,11 @@
* - appendMessage calls messageToChat, appends it to the chat log * - appendMessage calls messageToChat, appends it to the chat log
* - refreshChat takes the list, overwrites the chat log * - refreshChat takes the list, overwrites the chat log
*/ */
function logError(error) { function logError(error) {
console.log(error); console.log(error);
} }
function logQueue(action) { function logQueue(action) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -52,9 +52,9 @@ ...@@ -52,9 +52,9 @@
}) })
.push(null, logError); .push(null, logError);
} }
function pollUntilNotNull( function pollUntilNotNull(
my_gadget, delay_ms, timeout_ms, gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) { nullableFunction, callbackFunction) {
if (callbackFunction === undefined) { if (callbackFunction === undefined) {
callbackFunction = function () {}; callbackFunction = function () {};
...@@ -63,9 +63,9 @@ ...@@ -63,9 +63,9 @@
.push(function () { .push(function () {
return nullableFunction(); return nullableFunction();
}) })
.push(function (my_result) { .push(function (result) {
if (my_result !== null) { if (result !== null) {
return callbackFunction(my_result); return callbackFunction(result);
} else { } else {
return RSVP.any([ return RSVP.any([
RSVP.timeout(timeout_ms), RSVP.timeout(timeout_ms),
...@@ -77,11 +77,11 @@ ...@@ -77,11 +77,11 @@
.push(function () { .push(function () {
return nullableFunction(); return nullableFunction();
}) })
.push(function (my_result) { .push(function (result) {
if (my_result === null) { if (result === null) {
return null; return null;
} else { } else {
return callbackFunction(my_result); return callbackFunction(result);
} }
}) })
.push(function (nullable) { .push(function (nullable) {
...@@ -93,35 +93,25 @@ ...@@ -93,35 +93,25 @@
} }
}); });
} }
function stringEndsWith(string, suffix) { function stringEndsWith(string, suffix) {
return string.indexOf(suffix, string.length - suffix.length) !== -1; return string.indexOf(suffix, string.length - suffix.length) !== -1;
} }
function getTime(message) { function getTime(message) {
return new Date(message.time).getTime(); return new Date(message.time).getTime();
} }
function isNewMessage(message, last_time) { 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) { 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.content === rhs.content && lhs.folder === rhs.folder
&& lhs.room === rhs.room && getTime(lhs) === getTime(rhs); && 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 // Keep messages in chronologically ascending or descending order
function messageTimeCompare(ascending_order) { function messageTimeCompare(ascending_order) {
if (ascending_order) { if (ascending_order) {
...@@ -134,7 +124,7 @@ ...@@ -134,7 +124,7 @@
}; };
} }
} }
// Keep messages in chronologically ascending order in refreshChat // Keep messages in chronologically ascending order in refreshChat
function messageTimeComparator(lhs, rhs) { function messageTimeComparator(lhs, rhs) {
if (getTime(lhs) < getTime(rhs)) { if (getTime(lhs) < getTime(rhs)) {
...@@ -145,22 +135,68 @@ ...@@ -145,22 +135,68 @@
return 0; 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 // Add a contact as some HTML element
function createMessage(gadget, type, content) { 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 { return {
type: type, type: param_dict.type || "message",
name: gadget.state_parameter_dict.name, name: param_dict.name || gadget.state_parameter_dict.name,
folder: gadget.state_parameter_dict.folder, folder: param_dict.folder || gadget.state_parameter_dict.folder,
room: gadget.state_parameter_dict.room, room: param_dict.room || gadget.state_parameter_dict.room,
time: new Date(), time: param_dict.time || new Date(),
content: content, content: param_dict.content || "",
colour: param_dict.colour || "black",
}; };
} }
// Translate message to chat, in some HTML element // Translate message to chat, in some HTML element
function messageToChat(message) { function messageToChat(message) {
const chat = document.createElement("p"); const chat = document.createElement("li");
const image_extensions = [".jpg", ".png", ".gif", ".bmp", ".tif", ".svg"]; const image_extensions = [".jpg", ".png", ".gif", ".bmp", ".tif", ".svg"];
const url_regex = const url_regex =
/((?:https?:\/\/)?(?:[-a-zA-Z0-9_~\/]+\.)+[-a-zA-Z0-9_~#?&=\/]+)/g; /((?:https?:\/\/)?(?:[-a-zA-Z0-9_~\/]+\.)+[-a-zA-Z0-9_~#?&=\/]+)/g;
...@@ -169,6 +205,7 @@ ...@@ -169,6 +205,7 @@
break; break;
case "notification": case "notification":
chat.appendChild(document.createTextNode(message.content)); chat.appendChild(document.createTextNode(message.content));
chat.style.color = message.colour;
return chat; return chat;
case "message": case "message":
chat.appendChild(document.createTextNode( chat.appendChild(document.createTextNode(
...@@ -208,10 +245,11 @@ ...@@ -208,10 +245,11 @@
} }
} }
chat.appendChild(document.createTextNode(matches[matches.length - 1])); chat.appendChild(document.createTextNode(matches[matches.length - 1]));
chat.style.color = message.colour;
return chat; return chat;
} }
} }
// Add message to the list // Add message to the list
function storeList(gadget, message) { function storeList(gadget, message) {
if (isNewMessage(message, gadget.state_parameter_dict.last_message_dict if (isNewMessage(message, gadget.state_parameter_dict.last_message_dict
...@@ -221,22 +259,22 @@ ...@@ -221,22 +259,22 @@
} }
gadget.state_parameter_dict.message_list_dict[message.room].push(message); gadget.state_parameter_dict.message_list_dict[message.room].push(message);
} }
// Appends a message to the chat box // Appends a message to the chat box
function appendMessage(gadget, message) { function appendMessage(gadget, message) {
if (message.room === gadget.state_parameter_dict.room) { if (message.room === gadget.state_parameter_dict.room) {
const container = gadget.state_parameter_dict const container = gadget.state_parameter_dict
.element.querySelector(".chat"); .element.querySelector(".chat-list");
container.appendChild(messageToChat(message)); container.appendChild(messageToChat(message));
container.scrollTop = container.scrollHeight; container.scrollTop = container.scrollHeight;
} }
} }
// Sort the list, dedupe, and overwrite the chat box, // Sort the list, dedupe, and overwrite the chat box,
// efficient because the archive is originally sorted // efficient because the archive is originally sorted
function refreshChat(gadget) { function refreshChat(gadget) {
const container = gadget.state_parameter_dict const container = gadget.state_parameter_dict
.element.querySelector(".chat"); .element.querySelector(".chat-list");
while (container.firstChild) { while (container.firstChild) {
container.removeChild(container.firstChild); container.removeChild(container.firstChild);
} }
...@@ -258,38 +296,68 @@ ...@@ -258,38 +296,68 @@
gadget.state_parameter_dict.message_list_dict gadget.state_parameter_dict.message_list_dict
[gadget.state_parameter_dict.room] = new_list; [gadget.state_parameter_dict.room] = new_list;
} }
// Parse chat commands // Parse chat commands
function parseCommand(gadget, chat) { function parseCommand(gadget, chat) {
const split = chat.slice(1).split(" "); const split = chat.slice(1).split(" ");
const command = split.shift(); const command = split.shift();
const argument = split.join(" "); const argument = split.join(" ");
switch (command) { switch (command) {
case "rename":
gadget.state_parameter_dict.name = argument;
break;
case "join": 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": case "quit":
window.location.reload(); window.location.reload();
break; break;
case "help": case "help":
const help_message_list = [ const help_message_list = [
"--------------------------------------------------------------------"
+ "-----------------------------------------------------------------",
"Available commands:", "Available commands:",
"/rename [name]: changes your nickname to [name]",
"/join [room]: connects you to [room]", "/join [room]: connects you to [room]",
"/help: displays this help box", "/help: displays this help box",
"/leave: disconnects you from the current room",
"/quit: disconnects from the chat and refreshes the page", "/quit: disconnects from the chat and refreshes the page",
"--------------------------------------------------------------------"
+ "-----------------------------------------------------------------",
]; ];
for (let i = 0, i_len = help_message_list.length; i < i_len; i++) { promise_list = [];
appendMessage(gadget, createMessage( return new RSVP.Queue()
gadget, "notification", help_message_list[i])); .push(function () {
} for (let i = 0, i_len = help_message_list.length; i < i_len; i++) {
break; promise_list.push(gadget.deployNotification({
content: help_message_list[i],
colour: "green",
}));
}
return RSVP.all(promise_list);
})
.push(null, logError);
} }
} }
...@@ -302,10 +370,12 @@ ...@@ -302,10 +370,12 @@
room: null, room: null,
name: null, name: null,
element: null, element: null,
initialized: null, initialized: false,
notified_join: false, // A set of names of rooms joined within this folder;
guest_amount: 0, // as a guest, only notify that you have joined once
// A set of names of rooms joined within this folder // 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: {}, room_set: {},
// A dictionary of messages based on the current room, e.g. // A dictionary of messages based on the current room, e.g.
// message_list_dict["room"] = [message1, message2, ...] // message_list_dict["room"] = [message1, message2, ...]
...@@ -320,52 +390,62 @@ ...@@ -320,52 +390,62 @@
return gadget.getElement(); return gadget.getElement();
}) })
.push(function (element) { .push(function (element) {
element.querySelector(".send-form input").onfocus = function () {
notifyStatus(gadget, gadget.state_parameter_dict.room, false);
};
gadget.state_parameter_dict.element = element; gadget.state_parameter_dict.element = element;
return;
}) })
.push(null, logError); .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; const gadget = this;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
gadget.state_parameter_dict.element const room = gadget.state_parameter_dict.name;
.querySelector("input").onfocus = function () { gadget.state_parameter_dict.element.querySelector(".chat-title")
return changeFavicon(gadget.state_parameter_dict.default_icon); .textContent = "Room: " + room;
}; gadget.state_parameter_dict.room = room;
return gadget.changeRoom(gadget.state_parameter_dict.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); .push(null, logError);
}) })
.declareAcquiredMethod('wrapJioAccess', 'wrapJioAccess')
.declareAcquiredMethod('sendMessage', 'sendMessage')
// Join a different room in the same folder // Join a different room in the same folder
.declareMethod('changeRoom', function (room) { .declareMethod('changeRoom', function (room) {
const gadget = this; const gadget = this;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
gadget.state_parameter_dict.element.querySelector(".chat-title")
.textContent = "Room: " + room
gadget.state_parameter_dict.room = room; gadget.state_parameter_dict.room = room;
if (!(room in gadget.state_parameter_dict.room_set)) { if (!(room in gadget.state_parameter_dict.room_set)) {
gadget.state_parameter_dict.room_set[room] = true; gadget.state_parameter_dict.room_set[room] = false;
}
if (!(room in gadget.state_parameter_dict.message_list_dict)) {
gadget.state_parameter_dict.message_list_dict[room] = []; 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] = {}; gadget.state_parameter_dict.last_message_dict[room] = {};
appendContact(gadget, room);
} }
return gadget.getLocalArchive(); return gadget.getLocalArchive();
}) })
.push(function () { .push(function () {
return gadget.requestRequest(); return gadget.requestRequest();
}) })
.push(function () {
return refreshChat(gadget);
})
.push(null, logError); .push(null, logError);
}) })
// Get all messages stored in archive and add them to the list in order, // Get all messages stored in archive and add them to the list in order,
// using storeList because the messages are already in the archive // using storeList because the messages are already in the archive
.declareMethod('getLocalArchive', function () { .declareMethod('getLocalArchive', function () {
...@@ -410,7 +490,7 @@ ...@@ -410,7 +490,7 @@
}) })
.push(null, logError); .push(null, logError);
}) })
// Send all requested messages in the list, in sorted order, to peer // Send all requested messages in the list, in sorted order, to peer
.declareMethod('sendLocalArchive', function (request, source) { .declareMethod('sendLocalArchive', function (request, source) {
const gadget = this; const gadget = this;
...@@ -418,14 +498,17 @@ ...@@ -418,14 +498,17 @@
for (let room in request.content.room_set) { for (let room in request.content.room_set) {
request_dict[room] = []; request_dict[room] = [];
const list = gadget.state_parameter_dict.message_list_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 (list) {
if (isNewMessage(list[i], request.content.dict[room][list[i].name])) { for (let i = 0, i_len = list.length; i < i_len; i++) {
request_dict[room].push(list[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( return logQueue(gadget.sendMessage(createMessage(gadget,
gadget, "bundle", request_dict), source)); {type: "bundle", content: request_dict}), source));
}) })
// Get all new messages from the sorted list of peer, // Get all new messages from the sorted list of peer,
...@@ -456,10 +539,15 @@ ...@@ -456,10 +539,15 @@
return RSVP.all(promise_list); return RSVP.all(promise_list);
}) })
.push(function () { .push(function () {
if (!gadget.state_parameter_dict.notified_join) { if (!gadget.state_parameter_dict.room_set
gadget.state_parameter_dict.notified_join = true; [gadget.state_parameter_dict.room]) {
return gadget.deployMessage("notification", gadget.state_parameter_dict.room_set
gadget.state_parameter_dict.name + " has joined."); [gadget.state_parameter_dict.room] = true;
return gadget.deployMessage({
type: "notification",
content: gadget.state_parameter_dict.name + " has joined.",
colour: "orange",
});
} else { } else {
return; return;
} }
...@@ -469,31 +557,41 @@ ...@@ -469,31 +557,41 @@
}) })
.push(null, logError); .push(null, logError);
}) })
// Create new message and send it to peer // Create new message and send it to peer
.declareMethod('deployMessage', function (type, content) { .declareMethod('deployMessage', function (param_dict) {
const gadget = this; const gadget = this;
const message = createMessage(gadget, type, content); const message = createMessage(gadget, param_dict);
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return gadget.getMessage(message); storeList(gadget, message);
appendMessage(gadget, message);
return gadget.storeArchive(message);
}) })
.push(function () { .push(function () {
changeFavicon(gadget.state_parameter_dict.default_icon);
return gadget.sendMessage(message); return gadget.sendMessage(message);
}) })
.push(null, logError); .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 // Get message from peer, store it in archive and list
.declareMethod('getMessage', function (message) { .declareMethod('getMessage', function (message) {
const gadget = this; const gadget = this;
changeFavicon(gadget.state_parameter_dict.alert_icon); notifyStatus(gadget, message.room, true);
storeList(gadget, message); storeList(gadget, message);
appendMessage(gadget, message); appendMessage(gadget, message);
return logQueue(gadget.storeArchive(message)); return logQueue(gadget.storeArchive(message));
}) })
// Store message in the archive // Store message in the archive
.declareMethod('storeArchive', function (message) { .declareMethod('storeArchive', function (message) {
const gadget = this; const gadget = this;
...@@ -511,58 +609,71 @@ ...@@ -511,58 +609,71 @@
})); }));
}); });
}) })
// Ask a peer to send over a request // Ask a peer to send over a request
.declareMethod('requestRequest', function () { .declareMethod('requestRequest', function () {
const gadget = this; 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 // Send a request to update the local archive
.declareMethod('sendRequest', function () { .declareMethod('sendRequest', function () {
const gadget = this; const gadget = this;
return pollUntilNotNull(gadget, 1000, 30000, function () { return pollUntilNotNull(gadget, 1000, 30000, function () {
return gadget.state_parameter_dict.initialized; return gadget.state_parameter_dict.initialized;
}, function () { }, function () {
return gadget.sendMessage(createMessage( return gadget.sendMessage(createMessage(gadget, {
gadget, "request", { type: "request",
content: {
room_set: gadget.state_parameter_dict.room_set, room_set: gadget.state_parameter_dict.room_set,
dict: gadget.state_parameter_dict.last_message_dict, dict: gadget.state_parameter_dict.last_message_dict,
})); },
}));
}); });
}) })
// Listen for new chats // Listen for new chats
.declareService(function () { .declareService(function () {
const gadget = this; const gadget = this;
function handleSubmit(event) { function handleSubmit(event) {
switch (event.target.className) { return new RSVP.Queue()
case "send-form": .push(function () {
const content = event.target.elements.content.value; switch (event.target.className) {
event.target.elements.content.value = ""; case "manage-form":
if (content.indexOf("/") === 0) { switch (event.target
return parseCommand(gadget, content); .querySelector("input[type=submit]:focus").name) {
} else { case "sync":
return gadget.deployMessage("message", content); 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(null, logError);
.push(function () {
return gadget.wrapJioAccess('repair');
})
.push(function () {
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(null, logError);
}
} }
return loopEventListener( return loopEventListener(gadget.state_parameter_dict.element
gadget.state_parameter_dict.element, "submit", false, handleSubmit); .querySelector(".send-form"), "submit", false, handleSubmit);
}) })
.declareService(function () { .declareService(function () {
const gadget = this; const gadget = this;
return new RSVP.Queue() return new RSVP.Queue()
...@@ -571,12 +682,20 @@ ...@@ -571,12 +682,20 @@
}) })
.push(function () { .push(function () {
if (gadget.state_parameter_dict.initialized) { if (gadget.state_parameter_dict.initialized) {
return gadget.sendMessage(createMessage(gadget, "notification", promise_list = [];
gadget.state_parameter_dict.name + " has quit.")); 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 { } else {
return; return;
} }
}); });
}); });
}(window, document, FastPriorityQueue, loopEventListener, rJS, RSVP)); }(window, document, FastPriorityQueue, loopEventListener, rJS, RSVP));
\ No newline at end of file
...@@ -7,59 +7,44 @@ ...@@ -7,59 +7,44 @@
<script src="rsvp.js"></script> <script src="rsvp.js"></script>
<script src="renderjs.js"></script> <script src="renderjs.js"></script>
<script src="adapter.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> <script src="gadget_erp5_chat_webrtc.js"></script>
</head> </head>
<body> <body>
<p class="webrtc-error"> <h3 class="webrtc-heading"></h3>
</p> <p class="error"></p>
<form class="auth-form"> <form class="auth-form">
<input type="radio" name="auth" value="erp5" required="required" /> <label>
<label>ERP5, URL:</label> <input type="radio" name="auth" value="erp5" required="required" />
<br /> ERP5, URL:
<input type="text" name="erp5_url" value="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" /> <input type="text" name="erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
<br /> </label>
<input type="radio" name="auth" value="dropbox" required="required" /> <label>
<label>Dropbox, Folder:</label> <input type="radio" name="auth" value="dropbox" required="required" />
<br /> Dropbox, Folder:
<input type="text" name="dropbox_url" value="/Apps/OfficeJS Chat" /> <input type="text" name="dropbox_url" placeholder="/Apps/OfficeJS Chat" />
<br /> </label>
<input type="submit" value="Authenticate!" /> <input type="submit" value="Authenticate!" />
</form> </form>
<form class="host-offer-form"> <form class="host-offer-form">
<label> <label>Paste your guest's offer in this box:</label>
Paste your guest's offer in this box:
</label>
<br />
<textarea rows="10" cols="80" name="send"></textarea> <textarea rows="10" cols="80" name="send"></textarea>
<br />
<input type="submit" value="Paste it!" /> <input type="submit" value="Paste it!" />
</form> </form>
<form class="host-answer-form"> <form class="host-answer-form">
<p> <p>This is your answer. Send it to your guest!</p>
This is your answer. Send it to your guest! <p class="receive"></p>
</p>
<p class="receive">
</p>
</form> </form>
<form class="guest-offer-form"> <form class="guest-offer-form">
<p> <p>This is your new offer. Send it to your host!</p>
This is your new offer. Send it to your host! <p class="receive"></p>
</p>
<p class="receive">
</p>
<input type="submit" value="I sent it to my host." /> <input type="submit" value="I sent it to my host." />
</form> </form>
<form class="guest-answer-form"> <form class="guest-answer-form">
<label> <label>Now, paste your host's answer in this box:</label>
Now, paste your host's answer in this box:
</label>
<br />
<textarea rows="10" cols="80" name="send"></textarea> <textarea rows="10" cols="80" name="send"></textarea>
<br />
<input type="submit" value="Paste it!" /> <input type="submit" value="Paste it!" />
</form> </form>
</body> </body>
......
(function (window, document, rJS, RSVP) { (function (window, document, rJS, RSVP) {
// Miscellaneous utility functions // Miscellaneous utility functions
function logError(error) { function logError(error) {
console.log(error); console.log(error);
} }
function logQueue(action) { function logQueue(action) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -13,19 +13,19 @@ ...@@ -13,19 +13,19 @@
}) })
.push(null, logError); .push(null, logError);
} }
function showElementByClass(my_gadget, query) { function showElementByClass(gadget, query) {
my_gadget.state_parameter_dict.element gadget.state_parameter_dict.element
.querySelector(query).style.display = "block"; .querySelector(query).style.display = "block";
} }
function hideElementByClass(my_gadget, query) { function hideElementByClass(gadget, query) {
my_gadget.state_parameter_dict.element gadget.state_parameter_dict.element
.querySelector(query).style.display = "none"; .querySelector(query).style.display = "none";
} }
function pollUntilNotNull( function pollUntilNotNull(
my_gadget, delay_ms, timeout_ms, gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) { nullableFunction, callbackFunction) {
if (callbackFunction === undefined) { if (callbackFunction === undefined) {
callbackFunction = function () {}; callbackFunction = function () {};
...@@ -36,8 +36,8 @@ ...@@ -36,8 +36,8 @@
return RSVP.delay(timeout_ms); return RSVP.delay(timeout_ms);
}) })
.push(function () { .push(function () {
my_gadget.state_parameter_dict.element gadget.state_parameter_dict.element
.querySelector(".webrtc-error").textContent = .querySelector(".error").textContent =
"Timed out after " + timeout_ms + " ms."; "Timed out after " + timeout_ms + " ms.";
return; return;
}) })
...@@ -50,11 +50,11 @@ ...@@ -50,11 +50,11 @@
.push(function () { .push(function () {
return nullableFunction(); return nullableFunction();
}) })
.push(function (my_result) { .push(function (result) {
if (my_result === null) { if (result === null) {
return null; return null;
} else { } else {
return callbackFunction(my_result); return callbackFunction(result);
} }
}) })
.push(function (nullable) { .push(function (nullable) {
...@@ -64,14 +64,14 @@ ...@@ -64,14 +64,14 @@
}) })
]); ]);
} }
// jIO utility functions // jIO utility functions
function createDropboxJio(my_gadget, folder) { function createDropboxJio(gadget, folder) {
let dropbox_gadget; let dropbox_gadget;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return my_gadget.declareGadget( return gadget.declareGadget(
"gadget_jio.html", {scope: "dropbox_gadget"}); "gadget_jio.html", {scope: "dropbox_gadget"});
}) })
.push(function (jio_gadget) { .push(function (jio_gadget) {
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
}) })
.push(null, logError); .push(null, logError);
} }
function resetDropboxContent(dropbox_gadget, param_dict) { function resetDropboxContent(dropbox_gadget, param_dict) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
return RSVP.all(promise_list); return RSVP.all(promise_list);
}); });
} }
function getDropboxOffer(dropbox_gadget, param_dict) { function getDropboxOffer(dropbox_gadget, param_dict) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -124,7 +124,7 @@ ...@@ -124,7 +124,7 @@
}) })
.push(null, logError); .push(null, logError);
} }
function getDropboxContent(dropbox_gadget, param_dict) { function getDropboxContent(dropbox_gadget, param_dict) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -138,37 +138,37 @@ ...@@ -138,37 +138,37 @@
}) })
.push(null, logError); .push(null, logError);
} }
function putDropboxContent(dropbox_gadget, param_dict) { function putDropboxContent(dropbox_gadget, param_dict) {
return logQueue(dropbox_gadget.putAttachment( return logQueue(dropbox_gadget.putAttachment(
param_dict.folder, param_dict.file_name, param_dict.folder, param_dict.file_name,
new Blob([param_dict.content], {type: "text"}))); new Blob([param_dict.content], {type: "text"})));
} }
function removeDropboxContent(dropbox_gadget, param_dict) { function removeDropboxContent(dropbox_gadget, param_dict) {
return logQueue(dropbox_gadget return logQueue(dropbox_gadget
.removeAttachment(param_dict.folder, param_dict.file_name)); .removeAttachment(param_dict.folder, param_dict.file_name));
} }
function authenticateDropbox(my_gadget) { function authenticateDropbox(gadget) {
if (document.URL.indexOf("access_token=") === -1) { 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!"; .textContent = "Please log in to Dropbox!";
return; return;
} }
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return createDropboxJio(my_gadget, return createDropboxJio(gadget,
my_gadget.state_parameter_dict.auth_dict.dropbox_folder); gadget.state_parameter_dict.auth_dict.dropbox_folder);
}) })
.push(function () { .push(function () {
const dropbox_param_dict = { const dropbox_param_dict = {
gadget: my_gadget, gadget: gadget,
gadget_name: "dropbox_gadget", gadget_name: "dropbox_gadget",
function_param_dict: { function_param_dict: {
folder: my_gadget.state_parameter_dict.auth_dict.dropbox_folder, folder: gadget.state_parameter_dict.auth_dict.dropbox_folder,
room: my_gadget.state_parameter_dict.login_dict.room, room: gadget.state_parameter_dict.login_dict.room,
name: my_gadget.state_parameter_dict.name, name: gadget.state_parameter_dict.login_dict.name,
file_name: null, file_name: null,
content: null, content: null,
}, },
...@@ -180,20 +180,20 @@ ...@@ -180,20 +180,20 @@
removeContent: removeDropboxContent, 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); 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); return authenticateGuest(dropbox_param_dict);
} }
}) })
.push(null, logError); .push(null, logError);
} }
function createErp5Jio(my_gadget, folder, url) { function createErp5Jio(gadget, folder, url) {
let erp5_gadget; let erp5_gadget;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return my_gadget.declareGadget( return gadget.declareGadget(
"gadget_jio.html", {scope: "erp5_gadget"}); "gadget_jio.html", {scope: "erp5_gadget"});
}) })
.push(function (jio_gadget) { .push(function (jio_gadget) {
...@@ -232,13 +232,13 @@ ...@@ -232,13 +232,13 @@
}) })
.push(null, logError); .push(null, logError);
} }
function resetErp5Content(erp5_gadget, param_dict) { function resetErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.putAttachment( return logQueue(erp5_gadget.putAttachment(
"/", param_dict.url + "WebrtcRoom_resetContent", "/", param_dict.url + "WebrtcRoom_resetContent",
new Blob([JSON.stringify({folder: param_dict.folder})]))); new Blob([JSON.stringify({folder: param_dict.folder})])));
} }
function getErp5Content(erp5_gadget, param_dict) { function getErp5Content(erp5_gadget, param_dict) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -263,18 +263,18 @@ ...@@ -263,18 +263,18 @@
}) })
.push(null, logError); .push(null, logError);
} }
function putErp5Content(erp5_gadget, param_dict) { function putErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.putAttachment( return logQueue(erp5_gadget.putAttachment(
"/", param_dict.url + "WebrtcRoom_putContent", "/", param_dict.url + "WebrtcRoom_putContent",
new Blob([JSON.stringify({ new Blob([JSON.stringify({
folder: param_dict.folder, folder: param_dict.folder,
name: param_dict.file_name, name: param_dict.file_name,
content: param_dict.content, content: param_dict.content,
})]) })])
)); ));
} }
function removeErp5Content(erp5_gadget, param_dict) { function removeErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.putAttachment( return logQueue(erp5_gadget.putAttachment(
"/", param_dict.url + "WebrtcRoom_deleteContent", "/", param_dict.url + "WebrtcRoom_deleteContent",
...@@ -284,37 +284,37 @@ ...@@ -284,37 +284,37 @@
})]) })])
)); ));
} }
function authenticateErp5(my_gadget) { function authenticateErp5(gadget) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return createErp5Jio( return createErp5Jio(
my_gadget, my_gadget.state_parameter_dict.folder, gadget, gadget.state_parameter_dict.login_dict.folder,
my_gadget.state_parameter_dict.auth_dict.erp5_url); gadget.state_parameter_dict.auth_dict.erp5_url);
}) })
.push(function (folder_id) { .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); folder_id.slice(folder_id.indexOf("/") + 1);
my_gadget.state_parameter_dict.auth_dict.erp5_folder_url = gadget.state_parameter_dict.auth_dict.erp5_folder_url =
my_gadget.state_parameter_dict.auth_dict.erp5_url gadget.state_parameter_dict.auth_dict.erp5_url
+ my_gadget.state_parameter_dict.erp5_folder + "/"; + gadget.state_parameter_dict.erp5_folder + "/";
return my_gadget.getDeclaredGadget("erp5_gadget"); return gadget.getDeclaredGadget("erp5_gadget");
}) })
.push(function (erp5_gadget) { .push(function (erp5_gadget) {
return resetErp5Content(erp5_gadget, { return resetErp5Content(erp5_gadget, {
folder: my_gadget.state_parameter_dict.auth_dict.erp5_folder, folder: gadget.state_parameter_dict.auth_dict.erp5_folder,
url: my_gadget.state_parameter_dict.auth_dict.erp5_folder_url, url: gadget.state_parameter_dict.auth_dict.erp5_folder_url,
}); });
}) })
.push(function () { .push(function () {
const erp5_param_dict = { const erp5_param_dict = {
gadget: my_gadget, gadget: gadget,
gadget_name: "erp5_gadget", gadget_name: "erp5_gadget",
function_param_dict: { function_param_dict: {
folder: my_gadget.state_parameter_dict.auth_dict.erp5_folder, folder: gadget.state_parameter_dict.auth_dict.erp5_folder,
room: my_gadget.state_parameter_dict.login_dict.room, room: gadget.state_parameter_dict.login_dict.room,
name: my_gadget.state_parameter_dict.name, name: gadget.state_parameter_dict.login_dict.name,
url: my_gadget.state_parameter_dict.auth_dict.erp5_folder_url, url: gadget.state_parameter_dict.auth_dict.erp5_folder_url,
file_name: null, file_name: null,
content: null, content: null,
}, },
...@@ -332,38 +332,38 @@ ...@@ -332,38 +332,38 @@
removeContent: removeErp5Content, 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); 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); return authenticateGuest(erp5_param_dict);
} }
}) })
.push(null, logError); .push(null, logError);
} }
function authenticateHost(param_dict) { function authenticateHost(param_dict) {
const my_gadget = param_dict.gadget; const gadget = param_dict.gadget;
let file_name; let file_name;
let jio_gadget; let jio_gadget;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
my_gadget.state_parameter_dict.element gadget.state_parameter_dict.element
.querySelector(".webrtc-error").textContent = ""; .querySelector(".error").textContent = "";
return my_gadget.getDeclaredGadget(param_dict.gadget_name); return gadget.getDeclaredGadget(param_dict.gadget_name);
}) })
.push(function (storage_gadget) { .push(function (storage_gadget) {
jio_gadget = storage_gadget; jio_gadget = storage_gadget;
return pollUntilNotNull(my_gadget, 500, 3600000, function () { return pollUntilNotNull(gadget, 2000, 86400000, function () {
return param_dict.function_dict.getOffer( return param_dict.function_dict.getOffer(
jio_gadget, param_dict.function_param_dict); jio_gadget, param_dict.function_param_dict);
}, function (offer_name) { }, function (offer_name) {
file_name = offer_name; file_name = offer_name;
}); });
}) })
.push(function () { .push(function () {
return pollUntilNotNull(my_gadget, 50, 30000, function () { return pollUntilNotNull(gadget, 500, 30000, function () {
if (my_gadget.state_parameter_dict.offer_ready === true) { if (gadget.state_parameter_dict.offer_ready === true) {
my_gadget.state_parameter_dict.offer_ready = false; gadget.state_parameter_dict.offer_ready = false;
return true; return true;
} else { } else {
return null; return null;
...@@ -375,22 +375,22 @@ ...@@ -375,22 +375,22 @@
}) })
.push(function () { .push(function () {
param_dict.function_param_dict.file_name = "offer_" + file_name; 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( return param_dict.function_dict.getContent(
jio_gadget, param_dict.function_param_dict); jio_gadget, param_dict.function_param_dict);
}, function (guest_offer) { }, function (guest_offer) {
return sendOffer(my_gadget, guest_offer); return sendOffer(gadget, guest_offer);
}); });
}, function (error) { }, function (error) {
if (error.toString().indexOf("Timed out after") === 0) { if (error.toString().indexOf("Timed out after") === 0) {
throw createInitialOffer(my_gadget); throw createInitialOffer(gadget);
} else { } else {
throw error; throw error;
} }
}) })
.push(function () { .push(function () {
return pollUntilNotNull(my_gadget, 50, 10000, function () { return pollUntilNotNull(gadget, 500, 10000, function () {
return my_gadget.state_parameter_dict.candidate; return gadget.state_parameter_dict.candidate;
}, function (host_answer) { }, function (host_answer) {
param_dict.function_param_dict.file_name = "answer_" + file_name; param_dict.function_param_dict.file_name = "answer_" + file_name;
param_dict.function_param_dict.content = host_answer; param_dict.function_param_dict.content = host_answer;
...@@ -412,19 +412,19 @@ ...@@ -412,19 +412,19 @@
} }
function authenticateGuest(param_dict) { function authenticateGuest(param_dict) {
const my_gadget = param_dict.gadget; const gadget = param_dict.gadget;
let file_name; let file_name;
let jio_gadget; let jio_gadget;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
my_gadget.state_parameter_dict.element gadget.state_parameter_dict.element
.querySelector(".webrtc-error").textContent = ""; .querySelector(".error").textContent = "";
return my_gadget.getDeclaredGadget(param_dict.gadget_name); return gadget.getDeclaredGadget(param_dict.gadget_name);
}) })
.push(function (storage_gadget) { .push(function (storage_gadget) {
jio_gadget = storage_gadget; jio_gadget = storage_gadget;
return pollUntilNotNull(my_gadget, 50, 15000, function () { return pollUntilNotNull(gadget, 500, 30000, function () {
return my_gadget.state_parameter_dict.candidate; return gadget.state_parameter_dict.candidate;
}, function (guest_offer) { }, function (guest_offer) {
file_name = param_dict.function_param_dict.room + "_" file_name = param_dict.function_param_dict.room + "_"
+ param_dict.function_param_dict.name + ".txt"; + param_dict.function_param_dict.name + ".txt";
...@@ -435,12 +435,12 @@ ...@@ -435,12 +435,12 @@
}); });
}) })
.push(function () { .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; param_dict.function_param_dict.file_name = "answer_" + file_name;
return param_dict.function_dict.getContent( return param_dict.function_dict.getContent(
jio_gadget, param_dict.function_param_dict); jio_gadget, param_dict.function_param_dict);
}, function (host_answer) { }, function (host_answer) {
return sendAnswer(my_gadget, host_answer); return sendAnswer(gadget, host_answer);
}); });
}, function () { }, function () {
return param_dict.function_dict.removeContent( return param_dict.function_dict.removeContent(
...@@ -452,156 +452,134 @@ ...@@ -452,156 +452,134 @@
}) })
.push(null, logError); .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 // WebRTC signalling functions
function createInitialOffer(my_gadget) { function createInitialOffer(gadget) {
const peer_list = my_gadget.state_parameter_dict.peer_connection_list; const peer_list = gadget.state_parameter_dict.peer_connection_list;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return new RTCPeerConnection( return new RTCPeerConnection(
my_gadget.state_parameter_dict.ice_config, gadget.state_parameter_dict.ice_config,
my_gadget.state_parameter_dict.ice_constraints); gadget.state_parameter_dict.ice_constraints);
}) })
.push(function (peer_connection) { .push(function (peer_connection) {
if (my_gadget.state_parameter_dict.login_dict.role === "host") { if (gadget.state_parameter_dict.login_dict.role === "host") {
my_gadget.state_parameter_dict.element gadget.state_parameter_dict.element
.querySelector(".host-offer-form textarea").value = ""; .querySelector(".host-offer-form textarea").value = "";
hideElementByClass(my_gadget, ".host-answer-form"); hideElementByClass(gadget, ".host-answer-form");
showElementByClass(my_gadget, ".host-offer-form"); showElementByClass(gadget, ".host-offer-form");
peer_list.push(peer_connection); peer_list.push(peer_connection);
return setupPeerConnection( return setupPeerConnection(
my_gadget, peer_connection, ".host-answer-form .receive"); gadget, peer_connection, ".host-answer-form .receive");
} else if (my_gadget.state_parameter_dict.login_dict.role === "guest") { } else if (gadget.state_parameter_dict.login_dict.role === "guest") {
peer_list[0] = peer_connection; peer_list[0] = peer_connection;
return setupPeerConnection( return setupPeerConnection(
my_gadget, peer_connection, ".guest-offer-form .receive"); gadget, peer_connection, ".guest-offer-form .receive");
} }
}) })
.push(function () { .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 = peer_list[peer_list.length - 1].ondatachannel =
function (my_event) { function (event) {
setupDataChannel(my_gadget, my_event.channel); setupDataChannel(gadget, event.channel);
my_gadget.state_parameter_dict gadget.state_parameter_dict.data_channel_list.push(event.channel);
.data_channel_list.push(my_event.channel);
}; };
return; 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]; const peer_connection = peer_list[0];
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return peer_connection.createDataChannel( return peer_connection.createDataChannel(
my_gadget.state_parameter_dict.login_dict.room + "_" gadget.state_parameter_dict.login_dict.room + "_"
+ my_gadget.state_parameter_dict.name + "_" + gadget.state_parameter_dict.login_dict.name + "_"
+ my_gadget.state_parameter_dict.data_channel_list.length, + gadget.state_parameter_dict.data_channel_list.length,
my_gadget.state_parameter_dict.dc_constraints); gadget.state_parameter_dict.dc_constraints);
}) })
.push(function (channel) { .push(function (channel) {
my_gadget.state_parameter_dict.data_channel_list.push(channel); gadget.state_parameter_dict.data_channel_list.push(channel);
return setupDataChannel(my_gadget, channel); return setupDataChannel(gadget, channel);
}) })
.push(function () { .push(function () {
return peer_connection.createOffer( return peer_connection.createOffer(
function (offer_description) { function (offer_description) {
peer_connection.setLocalDescription(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(null, logError);
} }
}) })
.push(function () { .push(function () {
my_gadget.state_parameter_dict.offer_ready = true; gadget.state_parameter_dict.offer_ready = true;
return; return;
}) })
.push(null, logError); .push(null, logError);
} }
function setupPeerConnection(my_gadget, peer_connection, form_selector) { function setupPeerConnection(gadget, peer_connection, form_selector) {
peer_connection.onicecandidate = function (my_event) { peer_connection.onicecandidate = function (event) {
const candidate = JSON.stringify(peer_connection.localDescription); const candidate = JSON.stringify(peer_connection.localDescription);
my_gadget.state_parameter_dict.candidate = candidate; gadget.state_parameter_dict.candidate = candidate;
my_gadget.state_parameter_dict.element gadget.state_parameter_dict.element
.querySelector(form_selector).textContent = candidate; .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() { peer_connection.oniceconnectionstatechange = function() {
if (peer_connection.iceConnectionState === "connected") { 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") { } 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") { } else if (peer_connection.iceConnectionState === "failed") {
if (my_gadget.state_parameter_dict.login_dict.role === "guest") { gadget.state_parameter_dict.element
window.location.reload(); .querySelector(".error").textContent =
} "WebRTC connection disconnected!";
} }
}; };
} }
} }
function setupDataChannel(my_gadget, data_channel) { function setupDataChannel(gadget, data_channel) {
if (!my_gadget.state_parameter_dict.connected) { if (!gadget.state_parameter_dict.connected) {
data_channel.onopen = function () { data_channel.onopen = function () {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
my_gadget.state_parameter_dict.connected = true; gadget.state_parameter_dict.connected = true;
if (my_gadget.state_parameter_dict.login_dict.role === "host") { if (gadget.state_parameter_dict.login_dict.role === "host") {
return createInitialOffer(my_gadget); return createInitialOffer(gadget);
} else if ( } else if (
my_gadget.state_parameter_dict.login_dict.role === "guest") { gadget.state_parameter_dict.login_dict.role === "guest") {
showElementByClass(my_gadget, ".guest-offer-form"); hideElementByClass(gadget, ".guest-offer-form");
hideElementByClass(gadget, ".auth-form");
return; return;
} }
}) })
.push(my_gadget.state_parameter_dict.dataChannelOnopen) .push(gadget.state_parameter_dict.dataChannelOnopen)
.push(null, logError); .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 () { data_channel.onopen = function () {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .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); .push(null, logError);
}; };
} }
data_channel.onmessage = data_channel.onmessage =
my_gadget.state_parameter_dict.dataChannelOnmessage; gadget.state_parameter_dict.dataChannelOnmessage;
} }
function sendOffer(my_gadget, offer) { function sendOffer(gadget, offer) {
const peer_list = my_gadget.state_parameter_dict.peer_connection_list; const peer_list = gadget.state_parameter_dict.peer_connection_list;
const peer_connection = peer_list[peer_list.length - 1]; const peer_connection = peer_list[peer_list.length - 1];
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -614,35 +592,34 @@ ...@@ -614,35 +592,34 @@
return peer_connection.createAnswer( return peer_connection.createAnswer(
function (answer_description) { function (answer_description) {
peer_connection.setLocalDescription(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); .push(null, logError);
} }
function sendAnswer(my_gadget, answer) { function sendAnswer(gadget, answer) {
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return new RTCSessionDescription(JSON.parse(answer)); return new RTCSessionDescription(JSON.parse(answer));
}) })
.push(function (answer_description) { .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); .setRemoteDescription(answer_description);
}) })
.push(null, logError); .push(null, logError);
} }
// Initialization, rendering, public methods, and event listeners // Initialization, rendering, public methods, and event listeners
rJS(window) rJS(window)
.ready(function (gadget) { .ready(function (gadget) {
gadget.state_parameter_dict = { gadget.state_parameter_dict = {
element: null, element: null,
name: null,
folder: null,
login_dict: { login_dict: {
folder: null,
room: null, room: null,
name: null,
role: null, role: null,
auth: null,
}, },
auth_dict: { auth_dict: {
dropbox_url: null, dropbox_url: null,
...@@ -654,25 +631,29 @@ ...@@ -654,25 +631,29 @@
candidate: null, candidate: null,
connected: false, connected: false,
offer_ready: false, offer_ready: false,
room_list: [],
data_channel_list: [], data_channel_list: [],
peer_connection_list: [], peer_connection_list: [],
guest_amount: 0, guest_amount: 0,
archive_amount: 0,
ice_config: { 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: { ice_constraints: {
optional: [{DtlsSrtpKeyAgreement: true}] mandatory: {googIPv6: true},
optional: [{DtlsSrtpKeyAgreement: true}],
}, },
dc_constraints: { dc_constraints: {
reliable: true reliable: true,
}, },
sdp_constraints: { sdp_constraints: {
optional: [], optional: [],
mandatory: { mandatory: {
OfferToReceiveAudio: false, OfferToReceiveAudio: false,
OfferToReceiveVideo: false OfferToReceiveVideo: false,
} },
}, },
}; };
return new RSVP.Queue() return new RSVP.Queue()
...@@ -685,7 +666,7 @@ ...@@ -685,7 +666,7 @@
}) })
.push(null, logError); .push(null, logError);
}) })
.declareMethod('render', function () { .declareMethod('render', function () {
const gadget = this; const gadget = this;
return new RSVP.Queue() return new RSVP.Queue()
...@@ -697,17 +678,14 @@ ...@@ -697,17 +678,14 @@
} else if (gadget.state_parameter_dict.login_dict.role === "guest") { } else if (gadget.state_parameter_dict.login_dict.role === "guest") {
hideElementByClass(gadget, ".host-offer-form"); 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); 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); .push(null, logError);
}) })
.declareMethod('sendMessage', function (message, source) { .declareMethod('sendMessage', function (message, source) {
const gadget = this; const gadget = this;
const message_string = JSON.stringify(message); const message_string = JSON.stringify(message);
...@@ -733,23 +711,61 @@ ...@@ -733,23 +711,61 @@
} }
return RSVP.all(promise_list); return RSVP.all(promise_list);
} else if (gadget.state_parameter_dict.login_dict.role === "guest") { } 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); .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 () { .declareService(function () {
const gadget = this; const gadget = this;
function handleSubmit(my_event) { function handleSubmit(event) {
switch (my_event.target.className) { switch (event.target.className) {
case "auth-form": case "auth-form":
return authenticate(gadget, my_event.target.auth.value); return gadget.authenticate();
case "host-offer-form": case "host-offer-form":
hideElementByClass(gadget, ".host-offer-form"); hideElementByClass(gadget, ".host-offer-form");
showElementByClass(gadget, ".host-answer-form"); showElementByClass(gadget, ".host-answer-form");
let offer = my_event.target.elements.send.value; let offer = event.target.elements.send.value;
my_event.target.elements.send.value = ""; event.target.elements.send.value = "";
return sendOffer(gadget, offer); return sendOffer(gadget, offer);
case "guest-offer-form": case "guest-offer-form":
hideElementByClass(gadget, ".guest-offer-form"); hideElementByClass(gadget, ".guest-offer-form");
...@@ -757,13 +773,13 @@ ...@@ -757,13 +773,13 @@
break; break;
case "guest-answer-form": case "guest-answer-form":
hideElementByClass(gadget, ".guest-answer-form"); hideElementByClass(gadget, ".guest-answer-form");
let answer = my_event.target.elements.send.value; let answer = event.target.elements.send.value;
my_event.target.elements.send.value = ""; event.target.elements.send.value = "";
return sendAnswer(gadget, answer); return sendAnswer(gadget, answer);
} }
} }
return loopEventListener( return loopEventListener(
gadget.state_parameter_dict.element, "submit", false, handleSubmit); gadget.state_parameter_dict.element, "submit", false, handleSubmit);
}); });
}(window, document, rJS, RSVP)); }(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/adapter_js
web_page_module/fast_priority_queue_js web_page_module/fast_priority_queue_js
web_page_module/gadget_erp5_chat* web_page_module/gadget_erp5_chat*
web_page_module/jio_mappingstorage_js
web_site_module/web_chat/** 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