Commit 01b24062 authored by Eugene Shen's avatar Eugene Shen

Flatten gadget hierarchy and purify WebRTC gadget

Instead of having chat_panel as a subgadget of chat_webrtc,
both chat_panel and chat_webrtc are direct subgadgets of chat.
Strip out all chat-related functions and variables from chat_webrtc,
so that it can be used for non-chat-related purposes in the future.
Bundle state variables into dictionaries for cross-gadget passing.
Completely rewrite HTML structure to reflect a linear hierarchy.
Fully document and augment existing URL query parameters.
Replace all var declarations with let or const from ES6.
Modify chat panel message_list and last_message_dict
to prepare for multi-room messaging, coming soon.
parent 6ee093ae
......@@ -12,6 +12,8 @@
</head>
<body>
<h1>OfficeJS Chat</h1>
<p class="chat-error">
</p>
<form class="login-form">
<label>Name:</label>
......@@ -21,21 +23,8 @@
<label>Folder:</label>
<br />
<input type="text" name="folder" required="required" />
<br />
<label>Room:</label>
<br />
<input type="text" name="room" required="required" />
<br />
<label>Role:</label>
<br />
<input type="radio" name="role" value="host" required="required" />
<label>Host</label>
<br />
<input type="radio" name="role" value="guest" checked="checked" />
<label>Guest</label>
<br />
<label>Remote Storage:</label>
<br />
<h3>Remote Storage</h3>
<input type="radio" name="remote" value="erp5" required="required" />
<label>ERP5</label>
<br />
......@@ -45,30 +34,51 @@
<input type="radio" name="remote" value="dropbox" required="required" />
<label>Dropbox</label>
<br />
<input type="radio" name="remote" value="local" checked="checked" />
<input type="radio" name="remote" value="local" required="required" checked="checked" />
<label>Local is Enough</label>
<br />
<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/">
Log in to Dropbox first, if you wish to use Dropbox.
</a>
<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 />
<label>Fill out ERP5 URL first, if you wish to use ERP5.</label>
<label>Dropbox folder:</label>
<br />
<input type="text" name="dropbox_url" value="/Apps/OfficeJS Chat/.jio_documents" />
<br />
<label>ERP5 URL:</label>
<br />
<input type="text" name="erp5_url" value="https://softinst75770.host.vifib.net/erp5/web_site_module/" />
<br />
<label>Enter WebDAV parameters first, if you wish to use DAV.</label>
<label>WebDAV URL:</label>
<br />
<label>URL: </label>
<input type="text" name="dav_url" value="https://softinst75722.host.vifib.net/share" />
<br />
<label>Username: </label>
<label>WebDAV Username:</label>
<br />
<input type="text" name="dav_user" value="eyqs" />
<br />
<label>Password: </label>
<label>WebDAV Password:</label>
<br />
<input type="text" name="dav_pass" value="Aoeuidhtns" />
<br />
<input type="submit" value="Login!" />
</form>
<form class="room-form">
<input type="text" name="room" required="required" />
<br />
<input type="submit" name="host" value="Create a new room!" />
<input type="submit" name="guest" value="Join an existing room!" />
</form>
<div data-gadget-url="gadget_jio.html"
data-gadget-scope="storage_gadget"
data-gadget-sandbox="public"></div>
<div data-gadget-url="gadget_erp5_chat_webrtc.html"
data-gadget-scope="webrtc_gadget"
data-gadget-sandbox="public" class="webrtc-panel"></div>
<div data-gadget-url="gadget_erp5_chat_panel.html"
data-gadget-scope="chat_gadget"
data-gadget-sandbox="public" class="chat-panel"></div>
</body>
</html>
\ No newline at end of file
(function (window, document, loopEventListener, rJS, RSVP) {
/* URL Options:
* name: your chat username, e.g. eyqs
* folder: the root folder for your organization, e.g. nexedi;
* you can only chat with people with access to the same auth folder
* remote: the type of remote storage to use,
* either dav, dropbox, erp5, or local.
* 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,
* e.g. https://softinst75770.host.vifib.net/erp5/web_site_module/
* remote_dav_url: the remote WebDAV server to synchronize with,
* e.g. https://softinst75722.host.vifib.net/share
* 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,
* e.g. /Apps/OfficeJS Chat
* auth_erp5_url: the shared ERP5 instance to act as the auth folder,
* e.g. https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/
*/
function logError(error) {
console.log(error);
}
function logQueue(action) {
return new RSVP.Queue()
.push(function () {
return action;
})
.push(null, logError);
}
function showElementByClass(my_gadget, query) {
my_gadget.state_parameter_dict.element
.querySelector(query).style.display = "block";
}
function hideElementByClass(my_gadget, query) {
my_gadget.state_parameter_dict.element
.querySelector(query).style.display = "none";
}
function cleanId(input_id) {
var reserved = ["_", "&", "=", ",", ";"];
for (var i = 0, i_len = reserved.length; i < i_len; i++) {
const reserved = ["_", "&", "=", ",", ";"];
for (let i = 0, i_len = reserved.length; i < i_len; i++) {
input_id = input_id.replace(reserved[i], "-");
}
return input_id;
......@@ -10,8 +58,8 @@
function getQueryValue(query, value) {
if (document.URL.indexOf(query + "=") != -1) {
var start = document.URL.indexOf(query + "=") + query.length + 1;
var end = document.URL.indexOf("&", start);
const start = document.URL.indexOf(query + "=") + query.length + 1;
let end = document.URL.indexOf("&", start);
if (end === -1) {
end = document.URL.length;
}
......@@ -20,79 +68,326 @@
return value || "";
}
}
function login(my_gadget) {
return new RSVP.Queue()
.push(function () {
return my_gadget.declareGadget(
"gadget_erp5_chat_webrtc.html", {scope: "webrtc_gadget"}
);
})
.push(function (webrtc_gadget) {
var form = my_gadget.state_parameter_dict.element
.querySelector(".login-form").elements;
webrtc_gadget.state_parameter_dict.name = cleanId(form.name.value);
webrtc_gadget.state_parameter_dict.folder = cleanId(form.folder.value);
webrtc_gadget.state_parameter_dict.room = cleanId(form.room.value);
webrtc_gadget.state_parameter_dict.role = form.role.value;
webrtc_gadget.state_parameter_dict.remote = form.remote.value;
webrtc_gadget.state_parameter_dict.auth = getQueryValue("auth");
webrtc_gadget.state_parameter_dict.erp5_url = form.erp5_url.value;
webrtc_gadget.state_parameter_dict.dav_url = form.dav_url.value;
webrtc_gadget.state_parameter_dict.dav_user = form.dav_user.value;
webrtc_gadget.state_parameter_dict.dav_pass = form.dav_pass.value;
var element = my_gadget.state_parameter_dict.element;
while (element.firstChild) {
element.removeChild(element.firstChild);
}
element.appendChild(webrtc_gadget.state_parameter_dict.element);
return webrtc_gadget.render();
});
}
rJS(window)
.ready(function (gadget) {
gadget.state_parameter_dict = {};
return gadget.getElement()
gadget.state_parameter_dict = {
element: null,
archive_amount: 0,
};
return new RSVP.Queue()
.push(function () {
return gadget.getElement();
})
.push(function (element) {
gadget.state_parameter_dict.element = element;
fields = gadget.state_parameter_dict.element
return gadget.render();
})
.push(null, logError);
})
.allowPublicAcquisition('wrapJioAccess', function (param_list) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("storage_gadget");
})
.push(function (storage_gadget) {
return storage_gadget[param_list[0]]
.apply(storage_gadget, param_list.slice(1));
})
.push(null, logError);
})
.allowPublicAcquisition('sendMessage', function (param_dict) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("webrtc_gadget");
})
.push(function (webrtc_gadget) {
return webrtc_gadget.sendMessage.apply(webrtc_gadget, param_dict);
});
})
.declareMethod('render', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
hideElementByClass(gadget, ".room-form");
hideElementByClass(gadget, ".webrtc-panel");
hideElementByClass(gadget, ".chat-panel");
const fields = gadget.state_parameter_dict.element
.querySelector(".login-form").elements;
fields.folder.value = getQueryValue("folder");
fields.room.value = getQueryValue("room");
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;
}
fields.dropbox_url.value = getQueryValue(
"remote_dropbox_url", fields.dropbox_url.value);
fields.erp5_url.value = getQueryValue(
"erp5_url", fields.erp5_url.value);
"remote_erp5_url", fields.erp5_url.value);
fields.dav_url.value = getQueryValue(
"dav_url", fields.dav_url.value);
"remote_dav_url", fields.dav_url.value);
fields.dav_user.value = getQueryValue(
"dav_user", fields.dav_user.value);
"remote_dav_user", fields.dav_user.value);
fields.dav_pass.value = getQueryValue(
"dav_pass", fields.dav_pass.value);
var role = getQueryValue("role");
if (role === "host" || role === "guest") {
fields.role.value = role;
"remote_dav_pass", fields.dav_pass.value);
if (getQueryValue("login")) {
return gadget.login();
} else {
return;
}
var remote = getQueryValue("remote");
if (remote === "dav" || remote === "dropbox"
|| remote === "erp5" || remote === "local") {
fields.remote.value = remote;
})
.push(function () {
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);
} else {
return;
}
if (getQueryValue("submit")) {
return login(gadget);
})
.push(null, logError);
})
.declareMethod('createJio', function (param_dict) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("storage_gadget");
})
.push(function (storage_gadget) {
let remote_config = {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: "officejs-chat",
},
},
};
let remote_post = true;
// We want the remote ERP5 storage to be canonical, so true for ERP5.
// Post is not implemented in drivetojiomapping, so false for others.
switch (param_dict.remote) {
case "dropbox":
const start = document.URL.indexOf("access_token=") + 13;
const end = document.URL.indexOf("&", start);
const token = document.URL.slice(start, end);
remote_config = {
type: "query",
sub_storage: {
type: "drivetojiomapping",
sub_storage: {
type: "dropbox",
access_token: token,
root: "dropbox",
},
},
};
remote_post = false;
break;
case "dav":
remote_config = {
type: "query",
sub_storage: {
type: "drivetojiomapping",
sub_storage: {
type: "dav",
url: param_dict.dav_url,
basic_login: btoa(param_dict.dav_user
+ ":" + param_dict.dav_pass),
with_credentials: true,
},
},
};
remote_post = false;
break;
case "erp5":
remote_config = {
type: "erp5",
url: (new URI("hateoas"))
.absoluteTo(param_dict.erp5_url).toString(),
default_view_reference: "view",
};
remote_post = true;
break;
}
});
return storage_gadget.createJio({
type: "replicate",
use_remote_post: remote_post,
conflict_handling: 2,
query: {
query: 'portal_type: "Text Post"',
limit: [0, 1000000000],
},
local_sub_storage: {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: "officejs-chat",
},
},
},
remote_sub_storage: remote_config,
});
})
.push(null, logError);
})
.declareMethod('login', function () {
const gadget = this;
let rtc_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("webrtc_gadget");
})
.push(function (webrtc_gadget) {
rtc_gadget = webrtc_gadget;
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
.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"),
};
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);
};
rtc_gadget.state_parameter_dict.dataChannelOnmessage =
function (my_event) {
const message = JSON.parse(my_event.data);
const source = my_event.srcElement;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("chat_gadget");
})
.push(function (chat_gadget) {
if (message.type === "notification"
|| message.type === "message") {
return new RSVP.Queue()
.push(function () {
return chat_gadget.getMessage(message);
})
.push(function () {
if (role === "host") {
return rtc_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;
return new RSVP.Queue()
.push(function () {
return chat_gadget.getRemoteArchive(message);
})
.push(function () {
return chat_gadget.requestRequest();
})
.push(null, logError);
} else {
return chat_gadget.getRemoteArchive(message);
}
} else {
return chat_gadget.getRemoteArchive(message);
}
} else if (message.type === "request") {
return chat_gadget.sendLocalArchive(message, source);
} else if (message.type === "doubler") {
return chat_gadget.sendRequest();
}
})
.push(null, logError);
};
return rtc_gadget.render();
})
.push(null, logError);
})
.declareMethod('render', function () {})
.declareService(function () {
var gadget = this;
return loopEventListener(
gadget.state_parameter_dict.element, "submit", false, function () {
return login(gadget);
const gadget = this;
function handleSubmit(my_event) {
switch (my_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 loopEventListener(
gadget.state_parameter_dict.element, "submit", false, handleSubmit);
});
}(window, document, loopEventListener, rJS, RSVP));
\ No newline at end of file
......@@ -13,13 +13,7 @@
<script src="gadget_erp5_chat_panel.js"></script>
</head>
<body>
<div data-gadget-url="gadget_jio.html"
data-gadget-scope="storage_gadget"
data-gadget-sandbox="public"></div>
<h3>Chat List</h3>
<p class="error">
</p>
<div class="chat">
</div>
<form class="send-form">
......
......@@ -20,7 +20,7 @@
* - storeList stores a message in the list
* - refreshChat sorts the entire list
* The displayed chat log can only be accessed through two functions:
* - appendChat appends a chat to the chat log
* - appendMessage turns a message into a chat and appends it to the log
* - refreshChat takes the list to overwrite chat log
* Peer interaction via WebRTC can only be accessed through four functions:
* - deployMessage sends a message over WebRTC
......@@ -30,14 +30,14 @@
*/
/* Summary:
* - getLocalArchive takes archive, calls storeList and appendChat on each
* - getLocalArchive takes archive, calls storeList, appendMessage on each
* - sendLocalArchive sends the list to peer
* - getRemoteArchive gets a list from peer, calls storeArchive on each
* - deployMessage sends a message to peer, calls getMessage
* - getMessage gets from peer, calls storeArchive, storeList, appendChat
* - getMessage gets peer, calls storeArchive, storeList, appendMessage
* - storeArchive stores a message in the archive
* - storeList stores a message in the list
* - appendChat appends a chat to a chat log
* - appendMessage calls messageToChat, appends it to the chat log
* - refreshChat takes the list, overwrites the chat log
*/
......@@ -94,23 +94,27 @@
});
}
function stringEndsWith(string, suffix) {
return string.indexOf(suffix, string.length - suffix.length) !== -1;
}
function getTime(message) {
return new Date(message.time).getTime();
}
function sameMessage(lhs, rhs) {
return lhs !== undefined && rhs !== undefined && lhs.name === rhs.name &&
lhs.content === rhs.content && lhs.folder === rhs.folder &&
lhs.room === rhs.room && getTime(lhs) === getTime(rhs);
function isNewMessage(message, last_time) {
return last_time === undefined || last_time < getTime(message)
}
function stringEndsWith(string, suffix) {
return string.indexOf(suffix, string.length - suffix.length) !== -1;
function isSameMessage(lhs, rhs) {
return lhs !== undefined && rhs !== undefined && lhs.name === rhs.name
&& lhs.content === rhs.content && lhs.folder === rhs.folder
&& lhs.room === rhs.room && getTime(lhs) === getTime(rhs);
}
// Add new favicon or change existing favicon to image in favicon_url
function changeFavicon(favicon_url) {
var link = document.querySelector("link[rel*='icon']")
const link = document.querySelector("link[rel*='icon']")
|| document.createElement("link");
link.type = "image/x-icon";
link.rel = "shortcut icon";
......@@ -142,93 +146,6 @@
}
}
function createJio(my_gadget) {
return new RSVP.Queue()
.push(function () {
return my_gadget.getDeclaredGadget("storage_gadget");
})
.push(function (storage_gadget) {
var remote_config = {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: "officejs-chat"
}
}
}
var remote_post = true;
// We want the remote ERP5 storage to be canonical, so true for ERP5.
// Post is not implemented in drivetojiomapping, so false for others.
switch (my_gadget.state_parameter_dict.remote) {
case "dropbox":
var start = document.URL.indexOf("access_token=") + 13;
var end = document.URL.indexOf("&", start);
var token = document.URL.slice(start, end);
remote_config = {
type: "query",
sub_storage: {
type: "drivetojiomapping",
sub_storage: {
type: "dropbox",
access_token: token,
root: "dropbox"
}
}
};
remote_post = false;
break;
case "dav":
remote_config = {
type: "query",
sub_storage: {
type: "drivetojiomapping",
sub_storage: {
type: "dav",
url: my_gadget.state_parameter_dict.dav_url,
basic_login: btoa(my_gadget.state_parameter_dict.dav_user
+ ":" + my_gadget.state_parameter_dict.dav_pass),
with_credentials: true
}
}
};
remote_post = false;
break;
case "erp5":
remote_config = {
type: "erp5",
url: (new URI("hateoas"))
.absoluteTo(my_gadget.state_parameter_dict.erp5_url).toString(),
default_view_reference: "view"
};
remote_post = true;
break;
}
return storage_gadget.createJio({
type: "replicate",
use_remote_post: remote_post,
conflict_handling: 2,
query: {
query: 'portal_type: "Text Post"',
limit: [0, 1000000000]
},
local_sub_storage: {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: "officejs-chat"
}
}
},
remote_sub_storage: remote_config
});
})
.push(null, logError);
}
// Create new message from its type and content
function createMessage(gadget, type, content) {
return {
......@@ -238,14 +155,15 @@
room: gadget.state_parameter_dict.room,
time: new Date(),
content: content,
}
};
}
// Translate message to chat, in some HTML element
function messageToChat(message) {
var chat = document.createElement("p");
var image_extensions = [".jpg", ".png", ".gif", ".bmp", ".tif", ".svg"];
var re = /((?:https?:\/\/)?(?:[-a-zA-Z0-9_~\/]+\.)+[-a-zA-Z0-9_~#?&=\/]+)/g;
const chat = document.createElement("p");
const image_extensions = [".jpg", ".png", ".gif", ".bmp", ".tif", ".svg"];
const url_regex =
/((?:https?:\/\/)?(?:[-a-zA-Z0-9_~\/]+\.)+[-a-zA-Z0-9_~#?&=\/]+)/g;
switch (message.type) {
case "bundle":
break;
......@@ -256,25 +174,25 @@
chat.appendChild(document.createTextNode(
"[" + new Date(message.time).toLocaleString() + "] "
+ message.name + ": "));
var matches = message.content.split(re);
for (var i = 0, i_len = matches.length - 1; i < i_len; i += 2) {
const matches = message.content.split(url_regex);
for (let i = 0, i_len = matches.length - 1; i < i_len; i += 2) {
chat.appendChild(document.createTextNode(matches[i]));
var link_string = matches[i + 1];
var dot_count = link_string.match(/\./g).length;
const link_string = matches[i + 1];
const dot_count = link_string.match(/\./g).length;
// If 2d + 1 >= L, then the string has only single letters
if (2 * dot_count + 1 >= link_string.length) {
chat.appendChild(document.createTextNode(link_string));
} else {
var is_image = false;
var absolute_url;
let is_image = false;
let absolute_url;
if (link_string.indexOf(":") != -1) {
absolute_url = link_string;
} else {
absolute_url = "http://" + link_string;
}
for (var j = 0, j_len = image_extensions.length; j < j_len; j++) {
for (let j = 0, j_len = image_extensions.length; j < j_len; j++) {
if (stringEndsWith(link_string, image_extensions[j])) {
var image = document.createElement("img");
const image = document.createElement("img");
image.src = absolute_url;
chat.appendChild(image);
is_image = true;
......@@ -282,7 +200,7 @@
}
}
if (!is_image) {
var link = document.createElement("a");
const link = document.createElement("a");
link.href = absolute_url;
link.innerHTML = link_string;
chat.appendChild(link);
......@@ -294,38 +212,42 @@
}
}
// Add message to the list and append chat to chat box
// Add message to the list
function storeList(gadget, message) {
var last_time = gadget.state_parameter_dict.last_message_dict[message.name];
if (last_time === undefined || last_time < getTime(message)) {
gadget.state_parameter_dict.last_message_dict[message.name] =
getTime(message);
if (isNewMessage(message, gadget.state_parameter_dict.last_message_dict
[message.room][message.name])) {
gadget.state_parameter_dict.last_message_dict[message.room][message.name]
= getTime(message);
}
gadget.state_parameter_dict.message_list.push(message);
gadget.state_parameter_dict.message_list_dict[message.room].push(message);
}
// Appends a message to the chat
function appendChat(gadget, chat) {
var container = gadget.state_parameter_dict
.element.querySelector(".chat");
container.appendChild(chat);
container.scrollTop = container.scrollHeight;
// Appends a message to the chat box
function appendMessage(gadget, message) {
if (message.room === gadget.state_parameter_dict.room) {
const container = gadget.state_parameter_dict
.element.querySelector(".chat");
container.appendChild(messageToChat(message));
container.scrollTop = container.scrollHeight;
}
}
// Sort the list, dedupe, and overwrite the chat box,
// efficient because the archive is originally sorted
function refreshChat(gadget) {
var old_list = gadget.state_parameter_dict.message_list;
var container = gadget.state_parameter_dict.element.querySelector(".chat");
const container = gadget.state_parameter_dict
.element.querySelector(".chat");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
const old_list = gadget.state_parameter_dict.message_list_dict
[gadget.state_parameter_dict.room];
old_list.sort(messageTimeComparator);
var new_list = [];
var last_message;
for (var i = 0, i_len = old_list.length; i < i_len; i++) {
var message = old_list[i];
if (sameMessage(last_message, message)) {
const new_list = [];
let last_message;
for (let i = 0, i_len = old_list.length; i < i_len; i++) {
const message = old_list[i];
if (isSameMessage(last_message, message)) {
continue;
}
last_message = message;
......@@ -333,36 +255,41 @@
container.appendChild(messageToChat(message));
}
container.scrollTop = container.scrollHeight;
gadget.state_parameter_dict.message_list = new_list;
gadget.state_parameter_dict.message_list_dict
[gadget.state_parameter_dict.room] = new_list;
}
// Parse chat commands
function parseCommand(gadget, chat) {
var split = chat.slice(1).split(" ");
var command = split.shift();
var argument = split.join(" ");
const split = chat.slice(1).split(" ");
const command = split.shift();
const argument = split.join(" ");
switch (command) {
case "rename":
gadget.state_parameter_dict.name = argument;
break;
case "join":
return gadget.changeRoom(argument);
case "quit":
window.location.reload();
break;
case "help":
var help_message_list = [
const help_message_list = [
"--------------------------------------------------------------------"
+ "-----------------------------------------------------------------",
"Available commands:",
"/rename [name]: changes your nickname to [name]",
"/join [room]: connects you to [room]",
"/help: displays this help box",
"/quit: disconnects from the chat and refreshes the page",
"--------------------------------------------------------------------"
+ "-----------------------------------------------------------------",
];
for (var i = 0, i_len = help_message_list.length; i < i_len; i++) {
appendChat(gadget, messageToChat(createMessage(
gadget, "notification", help_message_list[i])));
for (let i = 0, i_len = help_message_list.length; i < i_len; i++) {
appendMessage(gadget, createMessage(
gadget, "notification", help_message_list[i]));
}
break;
}
}
......@@ -371,43 +298,66 @@
return new RSVP.Queue()
.push(function () {
gadget.state_parameter_dict = {
initialized: null,
notified_join: false,
name: null,
folder: null,
room: null,
remote: null,
erp5_url: null,
dav_url: null,
dav_user: null,
dav_pass: null,
message_list: [],
name: null,
element: null,
initialized: null,
notified_join: false,
guest_amount: 0,
// A set of names of rooms joined within this folder
room_set: {},
// A dictionary of messages based on the current room, e.g.
// message_list_dict["room"] = [message1, message2, ...]
message_list_dict: {},
// A dictionary of epoch times of the last messages received, e.g.
// last_message_dict["room"]["name"] = 1234567890
last_message_dict: {},
alert_icon: "https://pricespy.co.nz/favicon.ico",
default_icon: "https://softinst75770.host.vifib.net/"
+ "erp5/web_site_module/web_chat/favicon.ico"
+ "erp5/web_site_module/web_chat/favicon.ico",
};
return gadget.getElement();
})
.push(function (element) {
gadget.state_parameter_dict.element = element;
return;
})
.push(null, logError);
})
// Initialize gadget, after initializing name, folder, and room outside
.declareMethod('render', function () {
var gadget = this;
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 createJio(gadget);
return gadget.changeRoom(gadget.state_parameter_dict.room);
})
.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.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.message_list_dict[room] = [];
}
if (!(room in gadget.state_parameter_dict.last_message_dict)) {
gadget.state_parameter_dict.last_message_dict[room] = {};
}
return gadget.getLocalArchive();
})
.push(function () {
......@@ -416,99 +366,91 @@
.push(null, logError);
})
.declareAcquiredMethod('sendMessage', 'sendMessage')
// Get all messages stored in archive and add them to the list in order,
// using storeList because the messages are already in the archive
.declareMethod('getLocalArchive', function () {
var gadget = this;
var storage_gadget;
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("storage_gadget");
})
.push(function (jio_gadget) {
storage_gadget = jio_gadget;
return storage_gadget.allDocs();
return gadget.wrapJioAccess('allDocs');
})
.push(function (document_list) {
var list = document_list.data.rows;
var promise_list = [];
for (var i = 0, i_len = list.length; i < i_len; i++) {
promise_list.push(storage_gadget.get(list[i].id));
const list = document_list.data.rows;
const promise_list = [];
for (let i = 0, i_len = list.length; i < i_len; i++) {
promise_list.push(gadget.wrapJioAccess('get', list[i].id));
}
return RSVP.all(promise_list);
})
.push(function (message_list) {
var message_queue = new FastPriorityQueue(messageTimeCompare(true));
for (var i = 0, i_len = message_list.length; i < i_len; i++) {
const message_queue = new FastPriorityQueue(messageTimeCompare(true));
for (let i = 0, i_len = message_list.length; i < i_len; i++) {
try {
var message = JSON.parse(message_list[i].content);
const message = JSON.parse(message_list[i].content);
if (message && typeof message === "object") {
message_queue.add(message);
}
} catch (error) {}
}
var last_message;
let last_message;
const message_dict = gadget.state_parameter_dict.last_message_dict;
while (!message_queue.isEmpty()) {
var message = message_queue.poll();
var message_dict = gadget.state_parameter_dict.last_message_dict;
if (message.folder === gadget.state_parameter_dict.folder &&
message.room === gadget.state_parameter_dict.room &&
!sameMessage(last_message, message) &&
(message_dict[message.name] === undefined ||
message_dict[message.name] < getTime(message))) {
const message = message_queue.poll();
if (message.folder === gadget.state_parameter_dict.folder
&& (message.room in gadget.state_parameter_dict.room_set)
&& !isSameMessage(last_message, message) && isNewMessage(
message, message_dict[message.room][message.name])) {
last_message = message;
storeList(gadget, message);
appendChat(gadget, messageToChat(message));
appendMessage(gadget, message);
}
}
gadget.state_parameter_dict.initialized = true;
console.log("Received local archive. Number of messages: " +
gadget.state_parameter_dict.message_list.length);
return;
})
.push(null, logError);
})
// Send all requested messages in the list, in sorted order, to peer
.declareMethod('sendLocalArchive', function (request, source) {
var gadget = this;
var request_list = [];
var list = gadget.state_parameter_dict.message_list;
for (var i = 0, i_len = list.length; i < i_len; i++) {
if (!(list[i].name in request.content) ||
request.content[list[i].name] < getTime(list[i])) {
request_list.push(list[i]);
const gadget = this;
const request_dict = {};
for (let room in request.content.room_set) {
request_dict[room] = [];
const list = gadget.state_parameter_dict.message_list_dict[room];
for (let i = 0, i_len = list.length; i < i_len; i++) {
if (isNewMessage(list[i], request.content.dict[room][list[i].name])) {
request_dict[room].push(list[i]);
}
}
}
console.log("Sent local archive. Number of messages sent: "
+ request_list.length + " of "
+ gadget.state_parameter_dict.message_list.length);
return logQueue(gadget.sendMessage(createMessage(
gadget, "bundle", request_list), source));
gadget, "bundle", request_dict), source));
})
// Get all new messages from the sorted list of peer,
// add them to both the archive and list using storeArchive,
// and refresh, dedupe, and resort the list using refreshChat
.declareMethod('getRemoteArchive', function (bundle) {
var gadget = this;
console.log("Received remote archive. Number of messages received: " +
bundle.content.length);
const gadget = this;
return new RSVP.Queue()
.push(function () {
var list = gadget.state_parameter_dict.message_list;
var promise_list = [];
var index = 0;
for (var i = 0, i_len = bundle.content.length; i < i_len; i++) {
var message = bundle.content[i];
while (index < list.length &&
getTime(list[index]) < getTime(message)) {
index++;
}
if (index >= list.length || !sameMessage(list[index], message)) {
storeList(gadget, message);
promise_list.push(gadget.storeArchive(message));
const promise_list = [];
for (let room in bundle.content) {
const list = gadget.state_parameter_dict.message_list_dict[room];
const remote_list = bundle.content[room];
let index = 0;
for (let i = 0, i_len = remote_list.length; i < i_len; i++) {
const message = remote_list[i];
while (index < list.length
&& getTime(list[index]) < getTime(message)) {
index++;
}
if (index >= list.length
|| !isSameMessage(list[index], message)) {
storeList(gadget, message);
promise_list.push(gadget.storeArchive(message));
}
}
}
return RSVP.all(promise_list);
......@@ -517,7 +459,9 @@
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.");
gadget.state_parameter_dict.name + " has joined.");
} else {
return;
}
})
.push(function () {
......@@ -528,8 +472,8 @@
// Create new message and send it to peer
.declareMethod('deployMessage', function (type, content) {
var gadget = this;
var message = createMessage(gadget, type, content);
const gadget = this;
const message = createMessage(gadget, type, content);
return new RSVP.Queue()
.push(function () {
return gadget.getMessage(message);
......@@ -543,60 +487,58 @@
// Get message from peer, store it in archive and list
.declareMethod('getMessage', function (message) {
var gadget = this;
const gadget = this;
changeFavicon(gadget.state_parameter_dict.alert_icon);
storeList(gadget, message);
appendChat(gadget, messageToChat(message));
appendMessage(gadget, message);
return logQueue(gadget.storeArchive(message));
})
// Store message in the archive
.declareMethod('storeArchive', function (message) {
var gadget = this;
var id = message.folder + "_" + message.room + "_"
const gadget = this;
const id = message.folder + "_" + message.room + "_"
+ message.name + "_" + getTime(message).toString();
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("storage_gadget");
})
.push(function (storage_gadget) {
return logQueue(storage_gadget.put(id, {
return logQueue(gadget.wrapJioAccess('put', id, {
portal_type: "Text Post",
parent_relative_url: "post_text_module",
reference: id, // XXX use uuid here
reference: id,
author: message.name,
date_ms: getTime(message),
content: JSON.stringify(message)
content: JSON.stringify(message),
}));
});
})
// Ask a peer to send over a request
.declareMethod('requestRequest', function () {
var gadget = this;
console.log("Sent request for a request.");
const gadget = this;
return logQueue(gadget.sendMessage(createMessage(gadget, "doubler", "")));
})
// Send a request to update the local archive
.declareMethod('sendRequest', function () {
var gadget = this;
const gadget = this;
return pollUntilNotNull(gadget, 1000, 30000, function () {
return gadget.state_parameter_dict.initialized;
}, function () {
console.log("Sent request for messages.");
return gadget.sendMessage(createMessage(
gadget, "request", gadget.state_parameter_dict.last_message_dict));
gadget, "request", {
room_set: gadget.state_parameter_dict.room_set,
dict: gadget.state_parameter_dict.last_message_dict,
}));
});
})
// Listen for new chats
.declareService(function () {
var gadget = this;
const gadget = this;
function handleSubmit(event) {
switch (event.target.className) {
case "send-form":
var content = event.target.elements.content.value;
const content = event.target.elements.content.value;
event.target.elements.content.value = "";
if (content.indexOf("/") === 0) {
return parseCommand(gadget, content);
......@@ -606,10 +548,7 @@
case "sync-form":
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("storage_gadget");
})
.push(function (storage_gadget) {
return storage_gadget.repair();
return gadget.wrapJioAccess('repair');
})
.push(function () {
return gadget.getLocalArchive();
......@@ -625,14 +564,18 @@
})
.declareService(function () {
var gadget = this;
const gadget = this;
return new RSVP.Queue()
.push(function () {
return promiseEventListener(window, "beforeunload", true);
})
.push(function () {
return gadget.sendMessage(createMessage(gadget, "notification",
gadget.state_parameter_dict.name + " has quit."));
if (gadget.state_parameter_dict.initialized) {
return gadget.sendMessage(createMessage(gadget, "notification",
gadget.state_parameter_dict.name + " has quit."));
} else {
return;
}
});
});
......
......@@ -11,19 +11,19 @@
<script src="gadget_erp5_chat_webrtc.js"></script>
</head>
<body>
<h1>OfficeJS Chat</h1>
<p class="timeout-alert">
<p class="webrtc-error">
</p>
<form class="dropbox-form">
<label>Automatically connect via Dropbox</label>
<form class="auth-form">
<input type="radio" name="auth" value="erp5" required="required" />
<label>ERP5, URL:</label>
<br />
<input type="submit" value="Authenticate!" />
</form>
<form class="erp5-form">
<label>Automatically connect via ERP5</label>
<input type="text" name="erp5_url" value="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
<br />
<input type="radio" name="auth" value="dropbox" required="required" />
<label>Dropbox, Folder:</label>
<br />
<input type="text" name="dropbox_url" value="/Apps/OfficeJS Chat" />
<br />
<input type="submit" value="Authenticate!" />
</form>
......@@ -62,9 +62,5 @@
<br />
<input type="submit" value="Paste it!" />
</form>
<div data-gadget-url="gadget_erp5_chat_panel.html"
data-gadget-scope="chat_gadget"
data-gadget-sandbox="public" class="chat-panel"></div>
</body>
</html>
\ No newline at end of file
(function (window, document, rJS, RSVP) {
// Miscellaneous utility functions
function logError(error) {
console.log(error);
}
......@@ -37,9 +37,11 @@
})
.push(function () {
my_gadget.state_parameter_dict.element
.querySelector(".timeout-alert").textContent =
.querySelector(".webrtc-error").textContent =
"Timed out after " + timeout_ms + " ms.";
}),
return;
})
.push(null, logError),
promiseDoWhile(function () {
return new RSVP.Queue()
.push(function () {
......@@ -66,7 +68,7 @@
// jIO utility functions
function createDropboxJio(my_gadget, folder) {
var dropbox_gadget;
let dropbox_gadget;
return new RSVP.Queue()
.push(function () {
return my_gadget.declareGadget(
......@@ -74,13 +76,13 @@
})
.push(function (jio_gadget) {
dropbox_gadget = jio_gadget;
var start = document.URL.indexOf("access_token=") + 13;
var end = document.URL.indexOf("&", start);
var token = document.URL.slice(start, end);
const start = document.URL.indexOf("access_token=") + 13;
const end = document.URL.indexOf("&", start);
const token = document.URL.slice(start, end);
return dropbox_gadget.createJio({
type: "dropbox",
access_token: token,
root: "dropbox"
root: "dropbox",
});
})
.push(function () {
......@@ -93,31 +95,29 @@
}
function resetDropboxContent(dropbox_gadget, param_dict) {
var folder = param_dict.folder;
return new RSVP.Queue()
.push(function () {
return dropbox_gadget.allAttachments(folder);
return dropbox_gadget.allAttachments(param_dict.folder);
})
.push(function (attachment_list) {
var promise_list = [];
for (var name in attachment_list) {
promise_list.push(dropbox_gadget.removeAttachment(folder, name));
const promise_list = [];
for (let file_name in attachment_list) {
promise_list.push(dropbox_gadget
.removeAttachment(param_dict.folder, file_name));
}
return RSVP.all(promise_list);
});
}
function getDropboxOffer(dropbox_gadget, param_dict) {
var folder = param_dict.folder;
var room = param_dict.room;
return new RSVP.Queue()
.push(function () {
return dropbox_gadget.allAttachments(folder);
return dropbox_gadget.allAttachments(param_dict.folder);
})
.push(function (attachment_list) {
for (var name in attachment_list) {
if (name.indexOf("offer_" + room + "_") === 0) {
return name.slice(6);
for (let file_name in attachment_list) {
if (file_name.indexOf("offer_" + param_dict.room + "_") === 0) {
return file_name.slice(6);
}
}
return null;
......@@ -126,11 +126,10 @@
}
function getDropboxContent(dropbox_gadget, param_dict) {
var folder = param_dict.folder;
var name = param_dict.name;
return new RSVP.Queue()
.push(function () {
return dropbox_gadget.getAttachment(folder, name);
return dropbox_gadget.getAttachment(
param_dict.folder, param_dict.file_name);
})
.push(function (attachment) {
return promiseReadAsText(attachment);
......@@ -141,51 +140,57 @@
}
function putDropboxContent(dropbox_gadget, param_dict) {
var folder = param_dict.folder;
var name = param_dict.name;
var content = param_dict.content;
return logQueue(dropbox_gadget.putAttachment(
folder, name, new Blob([content], {type: "text"})));
param_dict.folder, param_dict.file_name,
new Blob([param_dict.content], {type: "text"})));
}
function removeDropboxContent(dropbox_gadget, param_dict) {
var folder = param_dict.folder;
var name = param_dict.name;
return logQueue(dropbox_gadget.removeAttachment(folder, name));
return logQueue(dropbox_gadget
.removeAttachment(param_dict.folder, param_dict.file_name));
}
function authenticateDropbox(my_gadget) {
if (document.URL.indexOf("access_token=") === -1) {
alert("Please log in to Dropbox first!");
my_gadget.state_parameter_dict.element.querySelector(".webrtc-error")
.textContent = "Please log in to Dropbox!";
return;
}
return new RSVP.Queue()
.push(function () {
return createDropboxJio(
my_gadget, my_gadget.state_parameter_dict.dropbox_folder);
return createDropboxJio(my_gadget,
my_gadget.state_parameter_dict.auth_dict.dropbox_folder);
})
.push(function () {
var dropbox_params = [
my_gadget, "dropbox_gadget",
my_gadget.state_parameter_dict.dropbox_folder, {
const dropbox_param_dict = {
gadget: my_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,
file_name: null,
content: null,
},
function_dict: {
resetContent: resetDropboxContent,
getOffer: getDropboxOffer,
getContent: getDropboxContent,
putContent: putDropboxContent,
removeContent: removeDropboxContent
}
];
if (my_gadget.state_parameter_dict.role === "host") {
return authenticateHost.apply(undefined, dropbox_params);
} else if (my_gadget.state_parameter_dict.role === "guest") {
return authenticateGuest.apply(undefined, dropbox_params);
removeContent: removeDropboxContent,
},
};
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
return authenticateHost(dropbox_param_dict);
} else if (my_gadget.state_parameter_dict.login_dict.role === "guest") {
return authenticateGuest(dropbox_param_dict);
}
})
.push(null, logError);
}
function createErp5Jio(my_gadget, folder, url) {
var erp5_gadget;
let erp5_gadget;
return new RSVP.Queue()
.push(function () {
return my_gadget.declareGadget(
......@@ -196,7 +201,7 @@
return erp5_gadget.createJio({
type: "erp5",
url: (new URI("hateoas")).absoluteTo(url).toString(),
default_view_reference: "view"
default_view_reference: "view",
});
})
.push(function () {
......@@ -205,12 +210,12 @@
limit: [0, 1000000],
query: 'portal_type: "Webrtc Room"',
sort_on: [["last_modified", "descending"]],
select_list: ["title"]
select_list: ["title"],
});
})
.push(function (room_list) {
if (room_list.data.rows.length) {
for (var i = 0, i_len = room_list.data.rows.length; i < i_len; i++) {
for (let i = 0, i_len = room_list.data.rows.length; i < i_len; i++) {
if (room_list.data.rows[i].value.title === folder) {
return room_list.data.rows[i].id;
}
......@@ -219,7 +224,7 @@
return erp5_gadget.post({
title: folder,
portal_type: "Webrtc Room",
parent_relative_url: "webrtc_rooms_module"
parent_relative_url: "webrtc_rooms_module",
});
})
.push(function (result) {
......@@ -229,23 +234,22 @@
}
function resetErp5Content(erp5_gadget, param_dict) {
var folder = param_dict.folder;
var url = param_dict.url;
return logQueue(erp5_gadget.putAttachment(
"/", url + "WebrtcRoom_resetContent",
new Blob([JSON.stringify({folder: folder})])));
"/", param_dict.url + "WebrtcRoom_resetContent",
new Blob([JSON.stringify({folder: param_dict.folder})])));
}
function getErp5Content(erp5_gadget, param_dict) {
var folder = param_dict.folder;
var room = param_dict.room;
var name = param_dict.name;
var url = param_dict.url;
var action = param_dict.action;
return new RSVP.Queue()
.push(function () {
return erp5_gadget.putAttachment("/", url + action,
new Blob([JSON.stringify({folder: folder, room: room, name: name})]));
return erp5_gadget.putAttachment(
"/", param_dict.url + param_dict.action,
new Blob([JSON.stringify({
folder: param_dict.folder,
room: param_dict.room,
name: param_dict.file_name,
})])
);
})
.push(function (response) {
return response.currentTarget.responseText;
......@@ -261,51 +265,60 @@
}
function putErp5Content(erp5_gadget, param_dict) {
var content = param_dict.content;
var folder = param_dict.folder;
var name = param_dict.name;
var url = param_dict.url;
return logQueue(erp5_gadget.putAttachment(
"/", url + "WebrtcRoom_putContent",
new Blob([JSON.stringify(
{folder: folder, name: name, content: content})])));
"/", param_dict.url + "WebrtcRoom_putContent",
new Blob([JSON.stringify({
folder: param_dict.folder,
name: param_dict.file_name,
content: param_dict.content,
})])
));
}
function removeErp5Content(erp5_gadget, param_dict) {
var folder = param_dict.folder;
var name = param_dict.name;
var url = param_dict.url;
return logQueue(erp5_gadget.putAttachment(
"/", url + "WebrtcRoom_deleteContent",
new Blob([JSON.stringify({folder: folder, name: name})])));
"/", param_dict.url + "WebrtcRoom_deleteContent",
new Blob([JSON.stringify({
folder: param_dict.folder,
name: param_dict.file_name,
})])
));
}
function authenticateErp5(my_gadget) {
var erp5_folder_url =
"https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/";
return new RSVP.Queue()
.push(function () {
return createErp5Jio(
my_gadget, my_gadget.state_parameter_dict.folder,
my_gadget.state_parameter_dict.erp5_url);
my_gadget.state_parameter_dict.auth_dict.erp5_url);
})
.push(function (folder_id) {
var lastIndex = folder_id.indexOf("/") + 1;
my_gadget.state_parameter_dict.erp5_folder = folder_id.slice(lastIndex);
erp5_folder_url += my_gadget.state_parameter_dict.erp5_folder + "/";
my_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");
})
.push(function (erp5_gadget) {
return resetErp5Content(erp5_gadget, {
folder: my_gadget.state_parameter_dict.erp5_folder,
url: erp5_folder_url
folder: my_gadget.state_parameter_dict.auth_dict.erp5_folder,
url: my_gadget.state_parameter_dict.auth_dict.erp5_folder_url,
});
})
.push(function () {
var erp5_params = [
my_gadget, "erp5_gadget",
my_gadget.state_parameter_dict.erp5_folder, {
erp5_folder_url: erp5_folder_url,
const erp5_param_dict = {
gadget: my_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,
file_name: null,
content: null,
},
function_dict: {
resetContent: resetErp5Content,
getOffer: function (erp5_gadget, param_dict) {
param_dict.action = "WebrtcRoom_getOffer";
......@@ -316,37 +329,35 @@
return getErp5Content(erp5_gadget, param_dict);
},
putContent: putErp5Content,
removeContent: removeErp5Content
}
];
if (my_gadget.state_parameter_dict.role === "host") {
return authenticateHost.apply(undefined, erp5_params);
} else if (my_gadget.state_parameter_dict.role === "guest") {
return authenticateGuest.apply(undefined, erp5_params);
removeContent: removeErp5Content,
},
};
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
return authenticateHost(erp5_param_dict);
} else if (my_gadget.state_parameter_dict.login_dict.role === "guest") {
return authenticateGuest(erp5_param_dict);
}
})
.push(null, logError);
}
function authenticateHost(
my_gadget, jio_gadget_name, folder, jio_function_dict) {
var room = my_gadget.state_parameter_dict.room;
var name;
var url = jio_function_dict.erp5_folder_url;
var jio_gadget;
function authenticateHost(param_dict) {
const my_gadget = param_dict.gadget;
let file_name;
let jio_gadget;
return new RSVP.Queue()
.push(function () {
my_gadget.state_parameter_dict.element
.querySelector(".timeout-alert").textContent = "";
return my_gadget.getDeclaredGadget(jio_gadget_name);
.querySelector(".webrtc-error").textContent = "";
return my_gadget.getDeclaredGadget(param_dict.gadget_name);
})
.push(function (storage_gadget) {
jio_gadget = storage_gadget;
return pollUntilNotNull(my_gadget, 500, 3600000, function () {
return jio_function_dict.getOffer(
jio_gadget, {folder: folder, room: room, url: url});
return param_dict.function_dict.getOffer(
jio_gadget, param_dict.function_param_dict);
}, function (offer_name) {
name = offer_name;
file_name = offer_name;
});
})
.push(function () {
......@@ -359,13 +370,14 @@
}
});
}, function () {
throw jio_function_dict.resetContent(
jio_gadget, {folder: folder, url: url});
throw param_dict.function_dict.resetContent(
jio_gadget, param_dict.function_param_dict);
})
.push(function () {
param_dict.function_param_dict.file_name = "offer_" + file_name;
return pollUntilNotNull(my_gadget, 50, 5000, function () {
return jio_function_dict.getContent(
jio_gadget, {folder: folder, name: "offer_" + name, url: url});
return param_dict.function_dict.getContent(
jio_gadget, param_dict.function_param_dict);
}, function (guest_offer) {
return sendOffer(my_gadget, guest_offer);
});
......@@ -380,69 +392,101 @@
return pollUntilNotNull(my_gadget, 50, 10000, function () {
return my_gadget.state_parameter_dict.candidate;
}, function (host_answer) {
return jio_function_dict.putContent(jio_gadget, {
folder: folder, name: "answer_" + name,
content: host_answer, url: url });
param_dict.function_param_dict.file_name = "answer_" + file_name;
param_dict.function_param_dict.content = host_answer;
return param_dict.function_dict.putContent(
jio_gadget, param_dict.function_param_dict);
});
})
.push(null, logError)
.push(function () {
return jio_function_dict.removeContent(
jio_gadget, {folder: folder, name: "offer_" + name, url: url});
param_dict.function_param_dict.file_name = "offer_" + file_name;
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
})
.push(null, logError)
.push(function () {
return authenticateHost(
my_gadget, jio_gadget_name, folder, jio_function_dict);
return authenticateHost(param_dict);
})
.push(null, logError);
}
function authenticateGuest(
my_gadget, jio_gadget_name, folder, jio_function_dict) {
var room = my_gadget.state_parameter_dict.room;
var name;
var url = jio_function_dict.erp5_folder_url;
var jio_gadget;
function authenticateGuest(param_dict) {
const my_gadget = param_dict.gadget;
let file_name;
let jio_gadget;
return new RSVP.Queue()
.push(function () {
my_gadget.state_parameter_dict.element
.querySelector(".timeout-alert").textContent = "";
return my_gadget.getDeclaredGadget(jio_gadget_name);
.querySelector(".webrtc-error").textContent = "";
return my_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;
}, function (guest_offer) {
name = room + "_" + my_gadget.state_parameter_dict.name + ".txt";
return jio_function_dict.putContent(jio_gadget, {
folder: folder, name: "offer_" + name,
content: guest_offer, url: url});
file_name = param_dict.function_param_dict.room + "_"
+ param_dict.function_param_dict.name + ".txt";
param_dict.function_param_dict.file_name = "offer_" + file_name;
param_dict.function_param_dict.content = guest_offer;
return param_dict.function_dict.putContent(
jio_gadget, param_dict.function_param_dict);
});
})
.push(function () {
return pollUntilNotNull(my_gadget, 50, 30000, function () {
return jio_function_dict.getContent(
jio_gadget, {folder: folder, name: "answer_" + name, url: url});
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);
});
}, function () {
return jio_function_dict.removeContent(
jio_gadget, {folder: folder, name: "answer_" + name, url: url});
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
})
.push(function () {
return jio_function_dict.removeContent(
jio_gadget, {folder: folder, name: "answer_" + name, url: url});
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
})
.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) {
var peer_list = my_gadget.state_parameter_dict.peer_connection_list;
const peer_list = my_gadget.state_parameter_dict.peer_connection_list;
return new RSVP.Queue()
.push(function () {
return new RTCPeerConnection(
......@@ -450,7 +494,7 @@
my_gadget.state_parameter_dict.ice_constraints);
})
.push(function (peer_connection) {
if (my_gadget.state_parameter_dict.role === "host") {
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
my_gadget.state_parameter_dict.element
.querySelector(".host-offer-form textarea").value = "";
hideElementByClass(my_gadget, ".host-answer-form");
......@@ -458,26 +502,27 @@
peer_list.push(peer_connection);
return setupPeerConnection(
my_gadget, peer_connection, ".host-answer-form .receive");
} else if (my_gadget.state_parameter_dict.role === "guest") {
} else if (my_gadget.state_parameter_dict.login_dict.role === "guest") {
peer_list[0] = peer_connection;
return setupPeerConnection(
my_gadget, peer_connection, ".guest-offer-form .receive");
}
})
.push(function () {
if (my_gadget.state_parameter_dict.role === "host") {
return peer_list[peer_list.length - 1].ondatachannel =
if (my_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);
};
} else if (my_gadget.state_parameter_dict.role === "guest") {
var peer_connection = peer_list[0];
return;
} else if (my_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.room + "_"
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);
......@@ -497,30 +542,29 @@
})
.push(function () {
my_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) {
var candidate = JSON.stringify(peer_connection.localDescription);
const candidate = JSON.stringify(peer_connection.localDescription);
my_gadget.state_parameter_dict.candidate = candidate;
my_gadget.state_parameter_dict.element
.querySelector(form_selector).textContent = candidate;
};
if (my_gadget.state_parameter_dict.role === "host") {
if (my_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;
} 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.role === "guest") {
if (my_gadget.state_parameter_dict.login_dict.role === "guest") {
window.location.reload();
}
}
console.log("A guest is now " + peer_connection.iceConnectionState +
". Number of guests: " + my_gadget.state_parameter_dict.guest_amount);
};
}
}
......@@ -530,113 +574,35 @@
data_channel.onopen = function () {
return new RSVP.Queue()
.push(function () {
showElementByClass(my_gadget, ".chat-panel");
if (my_gadget.state_parameter_dict.role === "guest") {
hideElementByClass(my_gadget, ".dropbox-form");
hideElementByClass(my_gadget, ".erp5-form");
hideElementByClass(my_gadget, ".guest-offer-form");
hideElementByClass(my_gadget, ".timeout-alert");
}
return my_gadget.getDeclaredGadget("chat_gadget");
})
.push(function (chat_gadget) {
my_gadget.state_parameter_dict.connected = true;
chat_gadget.state_parameter_dict.name =
my_gadget.state_parameter_dict.name;
chat_gadget.state_parameter_dict.folder =
my_gadget.state_parameter_dict.folder;
chat_gadget.state_parameter_dict.room =
my_gadget.state_parameter_dict.room;
chat_gadget.state_parameter_dict.remote =
my_gadget.state_parameter_dict.remote;
chat_gadget.state_parameter_dict.erp5_url =
my_gadget.state_parameter_dict.erp5_url;
chat_gadget.state_parameter_dict.dav_url =
my_gadget.state_parameter_dict.dav_url;
chat_gadget.state_parameter_dict.dav_user =
my_gadget.state_parameter_dict.dav_user;
chat_gadget.state_parameter_dict.dav_pass =
my_gadget.state_parameter_dict.dav_pass;
my_gadget.state_parameter_dict.element.appendChild(
chat_gadget.state_parameter_dict.element);
return chat_gadget.render();
})
.push(function () {
if (my_gadget.state_parameter_dict.role === "host") {
if (my_gadget.state_parameter_dict.login_dict.role === "host") {
return createInitialOffer(my_gadget);
} else {
} else if (
my_gadget.state_parameter_dict.login_dict.role === "guest") {
showElementByClass(my_gadget, ".guest-offer-form");
return;
}
})
.push(my_gadget.state_parameter_dict.dataChannelOnopen)
.push(null, logError);
};
} else if (my_gadget.state_parameter_dict.role === "host") {
} else if (my_gadget.state_parameter_dict.login_dict.role === "host") {
data_channel.onopen = function () {
return new RSVP.Queue()
.push(function () {
return createInitialOffer(my_gadget);
})
.push(my_gadget.state_parameter_dict.dataChannelOnopen)
.push(null, logError);
};
}
data_channel.onmessage = function (my_event) {
var message = JSON.parse(my_event.data);
var source = my_event.srcElement;
return new RSVP.Queue()
.push(function () {
return my_gadget.getDeclaredGadget("chat_gadget");
})
.push(function (chat_gadget) {
if (message.type === "message" || message.type === "notification") {
return new RSVP.Queue()
.push(function () {
return chat_gadget.getMessage(message);
})
.push(function () {
if (my_gadget.state_parameter_dict.role === "host") {
return my_gadget.sendMessage(message, source);
} else if (my_gadget.state_parameter_dict.role === "guest") {
return;
}
})
.push(null, logError);
} else if (message.type === "bundle") {
if (my_gadget.state_parameter_dict.role === "host") {
my_gadget.state_parameter_dict.archive_amount += 1;
console.log("Received archive number: " +
my_gadget.state_parameter_dict.archive_amount +
" of " + my_gadget.state_parameter_dict.guest_amount);
if (my_gadget.state_parameter_dict.archive_amount >=
my_gadget.state_parameter_dict.guest_amount) {
my_gadget.state_parameter_dict.archive_amount = 0;
return new RSVP.Queue()
.push(function () {
return chat_gadget.getRemoteArchive(message);
})
.push(function () {
return chat_gadget.requestRequest();
});
} else {
return chat_gadget.getRemoteArchive(message);
}
} else {
return chat_gadget.getRemoteArchive(message);
}
} else if (message.type === "request") {
console.log("Received request for messages.");
return chat_gadget.sendLocalArchive(message, source);
} else if (message.type === "doubler") {
console.log("Received request for a request.");
return chat_gadget.sendRequest();
}
})
.push(null, logError);
};
data_channel.onmessage =
my_gadget.state_parameter_dict.dataChannelOnmessage;
}
function sendOffer(my_gadget, offer) {
var peer_list = my_gadget.state_parameter_dict.peer_connection_list;
var peer_connection = peer_list[peer_list.length - 1];
const peer_list = my_gadget.state_parameter_dict.peer_connection_list;
const peer_connection = peer_list[peer_list.length - 1];
return new RSVP.Queue()
.push(function () {
return new RTCSessionDescription(JSON.parse(offer));
......@@ -670,25 +636,28 @@
rJS(window)
.ready(function (gadget) {
gadget.state_parameter_dict = {
element: null,
name: null,
folder: null,
room: null,
role: null,
remote: null,
login_dict: {
room: null,
role: null,
auth: null,
},
auth_dict: {
dropbox_url: null,
dropbox_folder: null,
erp5_url: null,
erp5_folder: null,
erp5_folder_url: null,
},
candidate: null,
connected: false,
offer_ready: false,
room_list: [],
data_channel_list: [],
peer_connection_list: [],
guest_amount: 0,
archive_amount: 0,
// full path including user folder, e.g. "/Apps/OfficeJS Chat/nexedi/"
dropbox_folder: "/Apps/OfficeJS Chat",
erp5_url: null,
dav_url: null,
dav_user: null,
dav_pass: null,
erp5_folder: null,
ice_config: {
iceServers: [{url: "stun:stun.1.google.com:19302"}]
},
......@@ -704,7 +673,7 @@
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
}
}
},
};
return new RSVP.Queue()
.push(function () {
......@@ -712,97 +681,85 @@
})
.push(function (element) {
gadget.state_parameter_dict.element = element;
return;
})
.push(null, logError);
})
.allowPublicAcquisition('sendMessage', function (param_dict) {
var gadget = this;
return gadget.sendMessage.apply(gadget, param_dict);
.declareMethod('render', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
hideElementByClass(gadget, ".host-answer-form");
hideElementByClass(gadget, ".guest-answer-form");
if (gadget.state_parameter_dict.login_dict.role === "host") {
hideElementByClass(gadget, ".guest-offer-form");
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
hideElementByClass(gadget, ".host-offer-form");
}
return createInitialOffer(gadget);
})
.push(function () {
if (gadget.state_parameter_dict.login_dict.auth !== null) {
return authenticate(
gadget, gadget.state_parameter_dict.login_dict.auth);
}
})
.push(null, logError);
})
.declareMethod('sendMessage', function (message, source) {
var gadget = this;
var message_string = JSON.stringify(message);
var channel_list = gadget.state_parameter_dict.data_channel_list;
const gadget = this;
const message_string = JSON.stringify(message);
const channel_list = gadget.state_parameter_dict.data_channel_list;
return new RSVP.Queue()
.push(function () {
if (gadget.state_parameter_dict.role === "host") {
if (gadget.state_parameter_dict.login_dict.role === "host") {
if (message.type === "bundle") {
for (var i = 0, i_len = channel_list.length; i < i_len; i++) {
var channel = channel_list[i];
for (let i = 0, i_len = channel_list.length; i < i_len; i++) {
const channel = channel_list[i];
if (channel.readyState === "open" && channel === source) {
return channel.send(message_string);
}
}
} else {
promise_list = [];
for (var i = 0, i_len = channel_list.length; i < i_len; i++) {
var channel = channel_list[i];
for (let i = 0, i_len = channel_list.length; i < i_len; i++) {
const channel = channel_list[i];
if (channel.readyState === "open" && channel !== source) {
promise_list.push(channel.send(message_string));
}
}
}
return RSVP.all(promise_list);
} else if (gadget.state_parameter_dict.role === "guest") {
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
return channel_list[0].send(message_string);
}
})
.push(null, logError);
})
.declareMethod('render', function () {
var gadget = this;
return new RSVP.Queue()
.push(function () {
var folder = gadget.state_parameter_dict.folder;
if (folder[0] !== "/") {
folder = "/" + folder;
}
if (folder[folder.length - 1] !== "/") {
folder = folder + "/";
}
gadget.state_parameter_dict.dropbox_folder += folder;
hideElementByClass(gadget, ".chat-panel");
hideElementByClass(gadget, ".host-answer-form");
hideElementByClass(gadget, ".guest-answer-form");
if (gadget.state_parameter_dict.role === "host") {
hideElementByClass(gadget, ".guest-offer-form");
} else if (gadget.state_parameter_dict.role === "guest") {
hideElementByClass(gadget, ".host-offer-form");
}
return createInitialOffer(gadget);
})
.push(function () {
if (gadget.state_parameter_dict.auth === "dropbox") {
return authenticateDropbox(gadget);
} else if (gadget.state_parameter_dict.auth === "erp5") {
return authenticateErp5(gadget);
}
})
.push(null, logError);
})
.declareService(function () {
var gadget = this;
const gadget = this;
function handleSubmit(my_event) {
switch (my_event.target.className) {
case "dropbox-form":
return authenticateDropbox(gadget);
case "erp5-form":
return authenticateErp5(gadget);
case "auth-form":
return authenticate(gadget, my_event.target.auth.value);
case "host-offer-form":
hideElementByClass(gadget, ".host-offer-form");
showElementByClass(gadget, ".host-answer-form");
return sendOffer(gadget, my_event.target.elements.send.value);
let offer = my_event.target.elements.send.value;
my_event.target.elements.send.value = "";
return sendOffer(gadget, offer);
case "guest-offer-form":
hideElementByClass(gadget, ".guest-offer-form");
showElementByClass(gadget, ".guest-answer-form");
break;
case "guest-answer-form":
hideElementByClass(gadget, ".guest-answer-form");
return sendAnswer(gadget, my_event.target.elements.send.value);
let answer = my_event.target.elements.send.value;
my_event.target.elements.send.value = "";
return sendAnswer(gadget, answer);
}
}
return loopEventListener(
......
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