Commit af967d44 authored by Eugene Shen's avatar Eugene Shen

Implement multi-room messaging

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