Commit b9917283 authored by Eugene Shen's avatar Eugene Shen

Squeeze WebRTC authentication into chat panel

Make WebRTC gadget spawn a new hidden div inside the chat panel
instead of indefinitely expanding downwards, highlight current room,
remove automatic login and authentication, add field for hateoas URL,
move all utility functions such as logError into gadget_global.js,
add hasOwnProperty, send join/quit notifications in bundles once more,
fix cannot-make-second-connection bug by resetting host candidate,
change declareService to submit onEvent, split submit buttons among
separate forms, and declare chat panel functions as gadget methods.
parent af967d44
body {
padding: 20px;
}
error {
color: orange;
.error {
color: red;
}
h3 {
color: brown;
......@@ -16,7 +16,7 @@ label {
display: block;
}
input[type="text"] {
width: 360px;
width: 50%;
}
.radio-item {
display: inline-block;
......@@ -28,7 +28,7 @@ textarea {
.chat-box {
display: flex;
width: 100%;
height: 80vh;
min-height: 80vh;
}
.chat-left-panel {
display: flex;
......@@ -46,6 +46,11 @@ textarea {
border: 1.5px solid;
box-sizing: border-box;
}
.chat-right-panel-chat {
display: flex;
flex-direction: inherit;
flex: 1;
}
.contact-list {
flex: 1;
padding: 0;
......@@ -88,4 +93,7 @@ img {
.contact-list li.notify {
font-weight: bold;
color: red;
}
.contact-list li.current {
color: orange;
}
\ No newline at end of file
......@@ -55,25 +55,16 @@
<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/" />
<label>ERP5 Hateoas:</label>
<input type="text" name="auth_hateoas_url", placeholder="https://softinst75770.host.vifib.net/erp5/web_site_module/hateoas" />
<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!" />
<input type="submit" name="login" value="Login!" />
</form>
</body>
</html>
\ No newline at end of file
......@@ -14,38 +14,14 @@
* 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
* 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 your auth folder,
* e.g. https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/
* auto: if preset (e.g. auto=go), will automatically click Authenticate
*/
function logError(error) {
console.log(error);
}
function logQueue(action) {
return new RSVP.Queue()
.push(function () {
return action;
})
.push(null, logError);
}
function showElementByClass(gadget, query) {
gadget.state_parameter_dict.element
.querySelector(query).style.display = "block";
}
function hideElementByClass(gadget, query) {
gadget.state_parameter_dict.element
.querySelector(query).style.display = "none";
}
function cleanId(input_id) {
const reserved = ["_", "&", "=", ",", ";", "\\"];
for (let i = 0, i_len = reserved.length; i < i_len; i++) {
......@@ -85,7 +61,7 @@
auth: null,
dropbox_url: null,
erp5_url: null,
auto: null,
hateoas_url: null,
// Dropbox OAuth only allows 500 characters in the state query
max_state_length: 500,
};
......@@ -100,7 +76,7 @@
})
.declareService(function () {
const gadget = this;
return this.render();
return gadget.render();
})
.allowPublicAcquisition('wrapJioAccess', function (param_list) {
......@@ -129,22 +105,90 @@
.push(null, logError);
})
.allowPublicAcquisition('parseParams', function (param_list) {
const gadget = this;
return gadget.parseParams.apply(gadget, param_list);
})
.declareMethod('parseParams', function (param_string) {
.allowPublicAcquisition('chooseRoom', function (param_list) {
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);
const param_dict = param_list[0];
let webrtc_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.declareGadget(
"gadget_erp5_chat_webrtc.html", {
scope: "webrtc_gadget_" + param_dict.room});
})
.push(function (subgadget) {
webrtc_gadget = subgadget;
return gadget.getDeclaredGadget("chat_gadget");
})
.push(function (chat_gadget) {
const fields = webrtc_gadget.state_parameter_dict.element
.querySelector(".contact-form").elements;
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;
fields.hateoas_url.value = param_dict.hateoas_url
|| gadget.state_parameter_dict.hateoas_url;
webrtc_gadget.state_parameter_dict.element.querySelector(".auth-form")
.auth.value = param_dict.auth || gadget.state_parameter_dict.auth;
chat_gadget.state_parameter_dict.element
.querySelector(".chat-right-panel").insertBefore(
webrtc_gadget.state_parameter_dict.element,
chat_gadget.state_parameter_dict.element
.querySelector(".chat-right-panel-chat"));
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,
};
webrtc_gadget.state_parameter_dict.dataChannelOnopen = function () {
return chat_gadget.changeRoom(param_dict.room);
};
webrtc_gadget.state_parameter_dict.dataChannelOnmessage =
function (event) {
const message = JSON.parse(event.data);
const source = event.srcElement;
if (message.type === "notification"
|| message.type === "message") {
return new RSVP.Queue()
.push(function () {
return chat_gadget.getMessage(message);
})
.push(function () {
if (param_dict.role === "host") {
return webrtc_gadget.sendMessage(message, source);
} else {
return;
}
})
.push(null, logError);
} else if (message.type === "bundle") {
if (param_dict.role === "host") {
webrtc_gadget.state_parameter_dict.archive_amount += 1;
if (webrtc_gadget.state_parameter_dict.archive_amount >=
webrtc_gadget.state_parameter_dict.guest_amount) {
webrtc_gadget.state_parameter_dict.archive_amount = 0;
return new RSVP.Queue()
.push(function () {
return chat_gadget.getRemoteArchive(message);
})
.push(function () {
return chat_gadget.requestRequest();
})
.push(null, logError);
}
} else {
return chat_gadget.getRemoteArchive(message);
}
} else if (message.type === "request") {
return chat_gadget.sendLocalArchive(message, source);
} else if (message.type === "doubler") {
return chat_gadget.sendRequest();
}
};
return webrtc_gadget.render();
})
.push(null, logError);
})
.declareMethod('render', function () {
......@@ -152,20 +196,20 @@
const gadget = this;
return new RSVP.Queue()
.push(function () {
hideElementByClass(gadget, ".room-form");
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;
let state_input = "";
// 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 = "";
["auth_dropbox_url", "ad"], ["auth_erp5_url", "ae"],
["auth_hateoas_url", "ah"]]
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
setQueryValue(query, url, fields[query[0]]);
......@@ -175,23 +219,12 @@
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";
}
}
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;
}
return;
})
.push(null, logError);
})
......@@ -205,8 +238,7 @@
"gadget_erp5_chat_panel.html", {scope: "chat_gadget"});
})
.push(function (subgadget) {
hideElementByClass(gadget, ".login-form");
showElementByClass(gadget, ".room-form");
styleElementByQuery(gadget, ".login-form", "none");
chat_gadget = subgadget;
gadget.state_parameter_dict.element.insertBefore(
chat_gadget.state_parameter_dict.element,
......@@ -214,10 +246,11 @@
const fields = gadget.state_parameter_dict.element
.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.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.hateoas_url =
fields.auth_hateoas_url.value;
gadget.state_parameter_dict.name = cleanId(fields.name.value);
gadget.state_parameter_dict.folder = cleanId(fields.folder.value);
chat_gadget.state_parameter_dict.name =
......@@ -234,19 +267,12 @@
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();
return chat_gadget.render();
})
.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 gadget.chooseRoom({
room: gadget.state_parameter_dict.name, role: "host"});
}
return chat_gadget.createContact({
room: gadget.state_parameter_dict.name, role: "host"});
})
.push(null, logError);
})
......@@ -339,119 +365,12 @@
.push(null, logError);
})
.declareMethod('chooseRoom', function (param_dict) {
.onEvent('submit', function (event) {
const gadget = this;
let webrtc_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.declareGadget(
"gadget_erp5_chat_webrtc.html", {
scope: "webrtc_gadget_" + param_dict.room});
})
.push(function (subgadget) {
webrtc_gadget = subgadget;
return gadget.getDeclaredGadget("chat_gadget");
})
.push(function (chat_gadget) {
const fields = webrtc_gadget.state_parameter_dict.element
.querySelector(".auth-form").elements;
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,
};
webrtc_gadget.state_parameter_dict.dataChannelOnopen = function () {
return chat_gadget.changeRoom(param_dict.room);
};
webrtc_gadget.state_parameter_dict.dataChannelOnmessage =
function (event) {
const message = JSON.parse(event.data);
const source = event.srcElement;
if (message.type === "notification"
|| message.type === "message") {
return new RSVP.Queue()
.push(function () {
return chat_gadget.getMessage(message);
})
.push(function () {
if (param_dict.role === "host") {
return webrtc_gadget.sendMessage(message, source);
} else {
return;
}
})
.push(null, logError);
} else if (message.type === "bundle") {
if (param_dict.role === "host") {
webrtc_gadget.state_parameter_dict.archive_amount += 1;
if (webrtc_gadget.state_parameter_dict.archive_amount >=
webrtc_gadget.state_parameter_dict.guest_amount) {
webrtc_gadget.state_parameter_dict.archive_amount = 0;
return new RSVP.Queue()
.push(function () {
return chat_gadget.getRemoteArchive(message);
})
.push(function () {
return chat_gadget.requestRequest();
})
.push(null, logError);
}
} else {
return chat_gadget.getRemoteArchive(message);
}
} else if (message.type === "request") {
return chat_gadget.sendLocalArchive(message, source);
} else if (message.type === "doubler") {
return chat_gadget.sendRequest();
}
};
return 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(event) {
switch (event.target.className) {
case "login-form":
return gadget.login();
case "room-form":
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);
}
switch (event.target.className) {
case "login-form":
return gadget.login();
}
return loopEventListener(
gadget.state_parameter_dict.element, "submit", false, handleSubmit);
});
}(window, document, loopEventListener, rJS, RSVP));
\ No newline at end of file
......@@ -17,18 +17,30 @@
<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 class="sync-form">
<input type="submit" value="Synchronize!" />
</form>
<form class="edit-form">
<input type="submit" value="Edit contact!" />
</form>
<form class="join-form">
<input type="text" name="content" />
<input type="submit" value="Add new contact (join existing room as guest)!" />
</form>
<form class="make-form">
<input type="text" name="content" />
<input type="submit" value="Add new room (make new room as host)!" />
</form>
</div>
<div class="chat-right-panel">
<h4 class="chat-title center"></h4>
<ul class="chat-list"></ul>
<form class="send-form">
<input type="text" name="content" />
<input type="submit" name="send" value="Send!" />
</form>
<div class="chat-right-panel-chat">
<ul class="chat-list"></ul>
<form class="send-form">
<input type="text" name="content" />
<input type="submit" name="send" value="Send!" />
</form>
</div>
</div>
</div>
</body>
......
......@@ -41,59 +41,6 @@
* - refreshChat takes the list, overwrites the chat log
*/
function logError(error) {
console.log(error);
}
function logQueue(action) {
return new RSVP.Queue()
.push(function () {
return action;
})
.push(null, logError);
}
function pollUntilNotNull(
gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) {
if (callbackFunction === undefined) {
callbackFunction = function () {};
}
return new RSVP.Queue()
.push(function () {
return nullableFunction();
})
.push(function (result) {
if (result !== null) {
return callbackFunction(result);
} else {
return RSVP.any([
RSVP.timeout(timeout_ms),
promiseDoWhile(function () {
return new RSVP.Queue()
.push(function () {
return RSVP.delay(delay_ms);
})
.push(function () {
return nullableFunction();
})
.push(function (result) {
if (result === null) {
return null;
} else {
return callbackFunction(result);
}
})
.push(function (nullable) {
return nullable === null;
})
.push(null, logError);
})
]);
}
});
}
function stringEndsWith(string, suffix) {
return string.indexOf(suffix, string.length - suffix.length) !== -1;
}
......@@ -136,64 +83,6 @@
}
}
// 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: 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("li");
......@@ -250,117 +139,6 @@
}
}
// Add message to the list
function storeList(gadget, 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_dict[message.room].push(message);
}
// Appends a message to the chat box
function appendMessage(gadget, message) {
if (message.room === gadget.state_parameter_dict.room) {
const container = gadget.state_parameter_dict
.element.querySelector(".chat-list");
container.appendChild(messageToChat(message));
container.scrollTop = container.scrollHeight;
}
}
// Sort the list, dedupe, and overwrite the chat box,
// efficient because the archive is originally sorted
function refreshChat(gadget) {
const container = gadget.state_parameter_dict
.element.querySelector(".chat-list");
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);
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;
new_list.push(message);
container.appendChild(messageToChat(message));
}
container.scrollTop = container.scrollHeight;
gadget.state_parameter_dict.message_list_dict
[gadget.state_parameter_dict.room] = new_list;
}
// Parse chat commands
function parseCommand(gadget, chat) {
const split = chat.slice(1).split(" ");
const command = split.shift();
const argument = split.join(" ");
switch (command) {
case "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:",
"/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++) {
promise_list.push(gadget.deployNotification({
content: help_message_list[i],
colour: "green",
}));
}
return RSVP.all(promise_list);
})
.push(null, logError);
}
}
rJS(window)
.ready(function (gadget) {
return new RSVP.Queue()
......@@ -390,58 +168,27 @@
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);
})
.declareMethod('render', function () {})
.declareAcquiredMethod('wrapJioAccess', 'wrapJioAccess')
.declareAcquiredMethod('sendMessage', 'sendMessage')
.declareAcquiredMethod('parseParams', 'parseParams')
.declareAcquiredMethod('chooseRoom', 'chooseRoom')
// Join a room for the first time
.declareMethod('startRoom', function () {
.declareMethod('render', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
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);
})
// 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] = false;
gadget.state_parameter_dict.message_list_dict[room] = [];
gadget.state_parameter_dict.last_message_dict[room] = {};
appendContact(gadget, room);
}
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(function () {
return refreshChat(gadget);
gadget.state_parameter_dict.element
.querySelector(".send-form input").onfocus = function () {
return logQueue(
gadget.notifyStatus(gadget.state_parameter_dict.room, false));
};
gadget.state_parameter_dict.room = gadget.state_parameter_dict.name;
})
.push(null, logError);
})
......@@ -472,21 +219,18 @@
}
} catch (error) {}
}
let last_message;
const promise_list = [];
const message_dict = gadget.state_parameter_dict.last_message_dict;
while (!message_queue.isEmpty()) {
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);
appendMessage(gadget, message);
&& (message.room in gadget.state_parameter_dict.room_set)) {
promise_list.push(gadget.storeList(message));
promise_list.push(gadget.appendMessage(message));
}
}
gadget.state_parameter_dict.initialized = true;
return;
return RSVP.all(promise_list);
})
.push(null, logError);
})
......@@ -494,21 +238,28 @@
// Send all requested messages in the list, in sorted order, to peer
.declareMethod('sendLocalArchive', function (request, source) {
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];
if (list) {
for (let i = 0, i_len = list.length; i < i_len; i++) {
if (isNewMessage(list[i], request.content.dict[room][list[i].name])
&& list[i].type !== "notification") {
request_dict[room].push(list[i]);
return new RSVP.Queue()
.push(function () {
const request_dict = {};
for (let room in request.content.room_set) {
if (request.content.room_set.hasOwnProperty(room)
&& gadget.state_parameter_dict.message_list_dict[room]) {
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]);
}
}
}
}
}
}
return logQueue(gadget.sendMessage(createMessage(gadget,
{type: "bundle", content: request_dict}), source));
return gadget.createMessage({type: "bundle", content: request_dict});
})
.push(function (message) {
return gadget.sendMessage(message, source);
})
.push(null, logError);
})
// Get all new messages from the sorted list of peer,
......@@ -520,19 +271,12 @@
.push(function () {
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));
if (bundle.content.hasOwnProperty(room)) {
const list = gadget.state_parameter_dict.message_list_dict[room];
const remote_list = bundle.content[room];
for (let i = 0, i_len = remote_list.length; i < i_len; i++) {
promise_list.push(gadget.storeList(remote_list[i]));
promise_list.push(gadget.storeArchive(remote_list[i]));
}
}
}
......@@ -553,7 +297,61 @@
}
})
.push(function () {
return refreshChat(gadget);
return gadget.refreshChat();
})
.push(null, logError);
})
// Ask a peer to send over a request
.declareMethod('requestRequest', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.createMessage({type: "doubler"});
})
.push(function (message) {
return gadget.sendMessage(message);
})
.push(null, logError);
})
// Send a request to update the local archive
.declareMethod('sendRequest', function () {
const gadget = this;
return pollUntilNotNull(gadget, 1000, 30000, function () {
return gadget.state_parameter_dict.initialized;
}, function () {
return new RSVP.Queue()
.push(function () {
return gadget.createMessage({
type: "request",
content: {
room_set: gadget.state_parameter_dict.room_set,
dict: gadget.state_parameter_dict.last_message_dict,
},
});
})
.push(function (message) {
return gadget.sendMessage(message);
})
.push(null, logError);
});
})
// Create new message from its parameters
.declareMethod('createMessage', function (param_dict) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return {
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",
};
})
.push(null, logError);
})
......@@ -561,11 +359,19 @@
// Create new message and send it to peer
.declareMethod('deployMessage', function (param_dict) {
const gadget = this;
const message = createMessage(gadget, param_dict);
let message;
return new RSVP.Queue()
.push(function () {
storeList(gadget, message);
appendMessage(gadget, message);
return gadget.createMessage(param_dict);
})
.push(function (result) {
message = result;
return gadget.storeList(message);
})
.push(function () {
return gadget.appendMessage(message);
})
.push(function () {
return gadget.storeArchive(message);
})
.push(function () {
......@@ -577,101 +383,370 @@
// 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);
let notification;
return new RSVP.Queue()
.push(function () {
param_dict.type = "notification";
return gadget.createMessage(param_dict);
})
.push(function (result) {
notification = result;
return gadget.storeList(notification);
})
.push(function () {
return gadget.appendMessage(notification);
})
.push(null, logError);
})
// Get message from peer, store it in archive and list
.declareMethod('getMessage', function (message) {
const gadget = this;
notifyStatus(gadget, message.room, true);
storeList(gadget, message);
appendMessage(gadget, message);
return logQueue(gadget.storeArchive(message));
return new RSVP.Queue()
.push(function () {
return gadget.notifyStatus(message.room, true);
})
.push(function () {
return gadget.storeList(message);
})
.push(function () {
return gadget.appendMessage(message);
})
.push(function () {
return gadget.storeArchive(message);
})
.push(null, logError);
})
// Store message in the archive
.declareMethod('storeArchive', function (message) {
const gadget = this;
const id = message.folder + "_" + message.room + "_"
+ message.name + "_" + getTime(message).toString();
return new RSVP.Queue()
.push(function () {
return logQueue(gadget.wrapJioAccess('put', id, {
const id = message.folder + "_" + message.room + "_"
+ message.name + "_" + getTime(message).toString();
return gadget.wrapJioAccess('put', id, {
portal_type: "Text Post",
parent_relative_url: "post_text_module",
reference: id,
author: message.name,
date_ms: getTime(message),
content: JSON.stringify(message),
}));
});
});
})
.push(null, logError);
})
// Ask a peer to send over a request
.declareMethod('requestRequest', function () {
// Add message to the list
.declareMethod('storeList', function (message) {
const gadget = this;
return logQueue(gadget.sendMessage(createMessage(
gadget, {type: "doubler", content: ""})));
return new RSVP.Queue()
.push(function () {
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_dict[message.room].push(message);
})
.push(null, logError);
})
// Send a request to update the local archive
.declareMethod('sendRequest', function () {
// Appends a message to the chat box
.declareMethod('appendMessage', function (message) {
const gadget = this;
return pollUntilNotNull(gadget, 1000, 30000, function () {
return gadget.state_parameter_dict.initialized;
}, function () {
return gadget.sendMessage(createMessage(gadget, {
type: "request",
content: {
room_set: gadget.state_parameter_dict.room_set,
dict: gadget.state_parameter_dict.last_message_dict,
},
}));
});
return new RSVP.Queue()
.push(function () {
if (message.room === gadget.state_parameter_dict.room) {
const container = gadget.state_parameter_dict
.element.querySelector(".chat-list");
container.appendChild(messageToChat(message));
container.scrollTop = container.scrollHeight;
}
})
.push(null, logError);
})
// Listen for new chats
.declareService(function () {
// Sort the list, dedupe, and overwrite the chat box,
// efficient because the archive is originally sorted
.declareMethod('refreshChat', function () {
const gadget = this;
function handleSubmit(event) {
return new RSVP.Queue()
.push(function () {
switch (event.target.className) {
case "manage-form":
switch (event.target
.querySelector("input[type=submit]:focus").name) {
case "sync":
return new RSVP.Queue()
.push(function () {
return gadget.wrapJioAccess('repair');
})
.push(function () {
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(null, logError);
case "new":
return createContact(gadget, "guest", "room=guest&role=guest&auth=dropbox&dropbox_url=/Apps/OfficeJS Chat/&erp5_url=https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/");
}
case "send-form":
const content = event.target.elements.content.value;
event.target.elements.content.value = "";
if (content.indexOf("/") === 0) {
return parseCommand(gadget, content);
return new RSVP.Queue()
.push(function () {
const container = gadget.state_parameter_dict
.element.querySelector(".chat-list");
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);
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;
new_list.push(message);
container.appendChild(messageToChat(message));
}
container.scrollTop = container.scrollHeight;
gadget.state_parameter_dict.message_list_dict
[gadget.state_parameter_dict.room] = new_list;
})
.push(null, logError);
})
// Notify when a new message appears and denotify when it is seen
.declareMethod('notifyStatus', function (room, notify) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
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 = gadget.state_parameter_dict.element
.querySelector("#chat-contact-" + room);
if (contact.className === "current") {
contact.className = class_name || "current";
} else {
contact.className = class_name;
}
})
.push(null, logError);
})
// Join a different room in the same folder
.declareMethod('changeRoom', function (room) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
styleElementByQuery(gadget,
"#webrtc-gadget-" + gadget.state_parameter_dict.room, "none");
styleElementByQuery(gadget, ".chat-right-panel-chat", "flex");
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)) {
return new RSVP.Queue()
.push(function () {
gadget.state_parameter_dict.room_set[room] = false;
gadget.state_parameter_dict.message_list_dict[room] = [];
gadget.state_parameter_dict.last_message_dict[room] = {};
})
.push(function () {
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(null, logError);
}
})
.push(function () {
return gadget.refreshChat();
})
.push(null, logError);
})
// Create a new contact
.declareMethod('createContact', function (param_dict) {
const gadget = this;
return new RSVP.Queue()
// XXX: load params here too
.push(function () {
return gadget.appendContact(param_dict.room);
})
.push(function () {
return gadget.chooseRoom(param_dict);
})
.push(function () {
return gadget.editContact(param_dict.room);
})
.push(null, logError);
})
// Add a contact as some HTML element
.declareMethod('appendContact', function (room) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
if (!room.trim()) {
throw "An invisible name is not allowed! You couldn't click on it!";
}
if (gadget.state_parameter_dict.element
.querySelector("#chat-contact-" + room)) {
throw "A contact with the same name already exists!";
}
const contact = document.createElement("li");
contact.appendChild(document.createTextNode(room));
contact.setAttribute("id", "chat-contact-" + room);
gadget.state_parameter_dict.element
.querySelector(".contact-list").appendChild(contact);
gadget.state_parameter_dict.element.querySelector(
"#chat-contact-" + gadget.state_parameter_dict.room).className = "";
contact.className = "current";
contact.addEventListener('click', function (event) {
return new RSVP.Queue()
.push(function () {
return gadget.notifyStatus(room, false);
})
.push(function () {
gadget.state_parameter_dict.element.querySelector(
"#chat-contact-" + gadget.state_parameter_dict.room)
.className = "";
gadget.state_parameter_dict.element
.querySelector("#chat-contact-" + room).className = "current";
})
.push(function () {
if (gadget.state_parameter_dict.room_set[room]) {
return gadget.changeRoom(room);
} else {
return gadget.deployMessage({content: content});
return gadget.editContact(room);
}
}
})
.push(null, logError);
})
.push(null, logError);
}, false);
return;
})
})
// Edit a contact in the right panel
.declareMethod('editContact', function (room) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
styleElementByQuery(gadget, ".chat-right-panel-chat", "none");
styleElementByQuery(gadget,
"#webrtc-gadget-" + gadget.state_parameter_dict.room, "none");
styleElementByQuery(gadget, "#webrtc-gadget-" + room, "block");
gadget.state_parameter_dict.room = room;
gadget.state_parameter_dict.element.querySelector(".chat-title")
.textContent = "Contact: " + room;
})
.push(null, logError);
})
// Parse chat commands
.declareMethod('parseCommand', function (chat) {
const gadget = this;
return new RSVP.Queue()
.push(function () {
const split = chat.slice(1).split(" ");
const command = split.shift();
const argument = split.join(" ");
switch (command) {
case "join":
if (gadget.state_parameter_dict.room_set[argument]) {
return gadget.changeRoom(argument);
} else {
return gadget.deployNotification({
content: 'You must first be connected to room "'
+ argument + '" via WebRTC to join it!',
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:",
"/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",
];
const promise_list = [];
return new RSVP.Queue()
.push(function () {
for (let i = 0, i_len = help_message_list.length;
i < i_len; i++) {
promise_list.push(gadget.deployNotification({
content: help_message_list[i],
colour: "green",
}));
}
return RSVP.all(promise_list);
})
.push(null, logError);
}
});
})
.onEvent('submit', function (event) {
const gadget = this;
switch (event.target.className) {
case "sync-form":
return new RSVP.Queue()
.push(function () {
return gadget.wrapJioAccess('repair');
})
.push(function () {
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(null, logError);
case "edit-form":
return gadget.editContact(gadget.state_parameter_dict.room);
case "join-form":
const contact = resetInputValue(event.target.elements.content);
return gadget.createContact({room: contact, role: "guest"});
case "make-form":
const room = resetInputValue(event.target.elements.content);
return gadget.createContact({room: room, role: "host"});
case "auth-form":
return gadget.connectContact();
case "send-form":
const content = resetInputValue(event.target.elements.content);
if (content.indexOf("/") === 0) {
return gadget.parseCommand(content);
} else {
return gadget.deployMessage({content: content});
}
}
return loopEventListener(gadget.state_parameter_dict.element
.querySelector(".send-form"), "submit", false, handleSubmit);
})
.declareService(function () {
......@@ -682,20 +757,24 @@
})
.push(function () {
if (gadget.state_parameter_dict.initialized) {
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",
})));
const promise_list = [];
for (let room in gadget.state_parameter_dict.room_set) {
if (gadget.state_parameter_dict.room_set.hasOwnProperty(room)
&& gadget.state_parameter_dict.room_set[room]) {
promise_list.push(gadget.deployMessage({
type: "notification",
content: gadget.state_parameter_dict.name + " has quit.",
room: room,
colour: "orange",
}));
}
}
return RSVP.all(promise_list);
} else {
return;
}
});
})
.push(null, logError);
});
}(window, document, FastPriorityQueue, loopEventListener, rJS, RSVP));
\ No newline at end of file
......@@ -11,40 +11,53 @@
<script src="gadget_erp5_chat_webrtc.js"></script>
</head>
<body>
<h3 class="webrtc-heading"></h3>
<p class="error"></p>
<p class="status"></p>
<form class="contact-form">
<label>Name:</label>
<input type="text" name="name" />
<label>Folder:</label>
<input type="text" name="folder" />
<label>Dropbox folder:</label>
<input type="text" name="dropbox_url" placeholder="/Apps/OfficeJS Chat" />
<label>ERP5 URL:</label>
<input type="text" name="erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
<label>Hateoas URL:</label>
<input type="text" name="hateoas_url" placeholder="https://softinst75770.host.vifib.net/erp5/web_site_module/hateoas" />
<br />
<input type="submit" value="Update information!" />
</form>
<form class="auth-form">
<label>
<input type="radio" name="auth" value="erp5" required="required" />
ERP5, URL:
<input type="text" name="erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
ERP5
</label>
<label>
<input type="radio" name="auth" value="dropbox" required="required" />
Dropbox, Folder:
<input type="text" name="dropbox_url" placeholder="/Apps/OfficeJS Chat" />
Dropbox
</label>
<input type="submit" value="Authenticate!" />
</form>
<form class="host-offer-form">
<label>Paste your guest's offer in this box:</label>
<textarea rows="10" cols="80" name="send"></textarea>
<textarea rows="5" cols="80" name="send"></textarea>
<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>
<textarea rows="5" cols="80" name="receive" readonly></textarea>
</form>
<form class="guest-offer-form">
<p>This is your new offer. Send it to your host!</p>
<p class="receive"></p>
<textarea rows="5" cols="80" name="receive" readonly></textarea>
<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>
<textarea rows="10" cols="80" name="send"></textarea>
<textarea rows="5" cols="80" name="send"></textarea>
<input type="submit" value="Paste it!" />
</form>
</body>
......
(function (window, document, rJS, RSVP) {
// Miscellaneous utility functions
function logError(error) {
console.log(error);
}
function logQueue(action) {
return new RSVP.Queue()
.push(function () {
return action;
})
.push(null, logError);
}
function showElementByClass(gadget, query) {
gadget.state_parameter_dict.element
.querySelector(query).style.display = "block";
}
function hideElementByClass(gadget, query) {
gadget.state_parameter_dict.element
.querySelector(query).style.display = "none";
}
function pollUntilNotNull(
gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) {
if (callbackFunction === undefined) {
callbackFunction = function () {};
}
return RSVP.any([
RSVP.Queue()
.push(function () {
return RSVP.delay(timeout_ms);
})
.push(function () {
gadget.state_parameter_dict.element
.querySelector(".error").textContent =
"Timed out after " + timeout_ms + " ms.";
return;
})
.push(null, logError),
promiseDoWhile(function () {
return new RSVP.Queue()
.push(function () {
return RSVP.delay(delay_ms);
})
.push(function () {
return nullableFunction();
})
.push(function (result) {
if (result === null) {
return null;
} else {
return callbackFunction(result);
}
})
.push(function (nullable) {
return nullable === null;
})
.push(null, logError);
})
]);
}
// jIO utility functions
function createDropboxJio(gadget, folder) {
......@@ -102,8 +37,10 @@
.push(function (attachment_list) {
const promise_list = [];
for (let file_name in attachment_list) {
promise_list.push(dropbox_gadget
.removeAttachment(param_dict.folder, file_name));
if (attachment_list.hasOwnProperty(file_name)) {
promise_list.push(dropbox_gadget
.removeAttachment(param_dict.folder, file_name));
}
}
return RSVP.all(promise_list);
});
......@@ -116,7 +53,8 @@
})
.push(function (attachment_list) {
for (let file_name in attachment_list) {
if (file_name.indexOf("offer_" + param_dict.room + "_") === 0) {
if (attachment_list.hasOwnProperty(file_name)
&& file_name.indexOf("offer_" + param_dict.room + "_") === 0) {
return file_name.slice(6);
}
}
......@@ -200,7 +138,7 @@
erp5_gadget = jio_gadget;
return erp5_gadget.createJio({
type: "erp5",
url: (new URI("hateoas")).absoluteTo(url).toString(),
url: url,
default_view_reference: "view",
});
})
......@@ -227,9 +165,6 @@
parent_relative_url: "webrtc_rooms_module",
});
})
.push(function (result) {
return result;
})
.push(null, logError);
}
......@@ -290,14 +225,14 @@
.push(function () {
return createErp5Jio(
gadget, gadget.state_parameter_dict.login_dict.folder,
gadget.state_parameter_dict.auth_dict.erp5_url);
gadget.state_parameter_dict.auth_dict.hateoas_url);
})
.push(function (folder_id) {
gadget.state_parameter_dict.auth_dict.erp5_folder =
folder_id.slice(folder_id.indexOf("/") + 1);
gadget.state_parameter_dict.auth_dict.erp5_folder_url =
gadget.state_parameter_dict.auth_dict.erp5_url
+ gadget.state_parameter_dict.erp5_folder + "/";
+ gadget.state_parameter_dict.auth_dict.erp5_folder + "/";
return gadget.getDeclaredGadget("erp5_gadget");
})
.push(function (erp5_gadget) {
......@@ -392,6 +327,7 @@
return pollUntilNotNull(gadget, 500, 10000, function () {
return gadget.state_parameter_dict.candidate;
}, function (host_answer) {
gadget.state_parameter_dict.candidate = null;
param_dict.function_param_dict.file_name = "answer_" + file_name;
param_dict.function_param_dict.content = host_answer;
return param_dict.function_dict.putContent(
......@@ -400,15 +336,18 @@
})
.push(null, logError)
.push(function () {
param_dict.function_param_dict.file_name = "offer_" + file_name;
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
if (file_name) {
param_dict.function_param_dict.file_name = "offer_" + file_name;
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
} else {
return;
}
})
.push(null, logError)
.push(function () {
return authenticateHost(param_dict);
})
.push(null, logError);
}
function authenticateGuest(param_dict) {
......@@ -442,13 +381,16 @@
}, function (host_answer) {
return sendAnswer(gadget, host_answer);
});
}, function () {
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
})
.push(null, logError)
.push(function () {
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
if (file_name) {
param_dict.function_param_dict.file_name = "answer_" + file_name;
return param_dict.function_dict.removeContent(
jio_gadget, param_dict.function_param_dict);
} else {
return;
}
})
.push(null, logError);
}
......@@ -467,15 +409,15 @@
if (gadget.state_parameter_dict.login_dict.role === "host") {
gadget.state_parameter_dict.element
.querySelector(".host-offer-form textarea").value = "";
hideElementByClass(gadget, ".host-answer-form");
showElementByClass(gadget, ".host-offer-form");
styleElementByQuery(gadget, ".host-answer-form", "none");
styleElementByQuery(gadget, ".host-offer-form", "block");
peer_list.push(peer_connection);
return setupPeerConnection(
gadget, peer_connection, ".host-answer-form .receive");
gadget, peer_connection, ".host-answer-form textarea");
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
peer_list[0] = peer_connection;
return setupPeerConnection(
gadget, peer_connection, ".guest-offer-form .receive");
gadget, peer_connection, ".guest-offer-form textarea");
}
})
.push(function () {
......@@ -521,18 +463,26 @@
const candidate = JSON.stringify(peer_connection.localDescription);
gadget.state_parameter_dict.candidate = candidate;
gadget.state_parameter_dict.element
.querySelector(form_selector).textContent = candidate;
.querySelector(form_selector).value = candidate;
};
if (gadget.state_parameter_dict.login_dict.role === "host") {
peer_connection.oniceconnectionstatechange = function() {
if (peer_connection.iceConnectionState === "connected") {
gadget.state_parameter_dict.guest_amount += 1;
} else if (peer_connection.iceConnectionState === "disconnected") {
gadget.state_parameter_dict.guest_amount -= 1;
}
peer_connection.oniceconnectionstatechange = function () {
return new RSVP.Queue()
.push(function () {
if (peer_connection.iceConnectionState === "connected") {
gadget.state_parameter_dict.guest_amount += 1;
} else if (peer_connection.iceConnectionState === "disconnected") {
gadget.state_parameter_dict.guest_amount -= 1;
}
return;
})
};
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
peer_connection.oniceconnectionstatechange = function() {
peer_connection.oniceconnectionstatechange = function () {
gadget.state_parameter_dict.element
.querySelector(".status").textContent =
"WebRTC connection status: "
+ peer_connection.iceConnectionState + ".";
if (peer_connection.iceConnectionState === "failed") {
gadget.state_parameter_dict.element
.querySelector(".error").textContent =
......@@ -556,8 +506,8 @@
return createInitialOffer(gadget);
} else if (
gadget.state_parameter_dict.login_dict.role === "guest") {
hideElementByClass(gadget, ".guest-offer-form");
hideElementByClass(gadget, ".auth-form");
styleElementByQuery(gadget, ".guest-offer-form", "none");
styleElementByQuery(gadget, ".auth-form", "none");
return;
}
})
......@@ -671,16 +621,15 @@
const gadget = this;
return new RSVP.Queue()
.push(function () {
hideElementByClass(gadget, ".host-answer-form");
hideElementByClass(gadget, ".guest-answer-form");
gadget.state_parameter_dict.element.setAttribute("id",
"webrtc-gadget-" + gadget.state_parameter_dict.login_dict.room);
styleElementByQuery(gadget, ".host-answer-form", "none");
styleElementByQuery(gadget, ".guest-answer-form", "none");
if (gadget.state_parameter_dict.login_dict.role === "host") {
hideElementByClass(gadget, ".guest-offer-form");
styleElementByQuery(gadget, ".guest-offer-form", "none");
} else if (gadget.state_parameter_dict.login_dict.role === "guest") {
hideElementByClass(gadget, ".host-offer-form");
styleElementByQuery(gadget, ".host-offer-form", "none");
}
gadget.state_parameter_dict.element.querySelector(".webrtc-heading")
.textContent = "WebRTC Negotiations for: "
+ gadget.state_parameter_dict.login_dict.room;
return createInitialOffer(gadget);
})
.push(null, logError);
......@@ -723,11 +672,13 @@
.declareMethod('authenticate', function () {
const gadget = this;
const auth = gadget.state_parameter_dict.element
.querySelector(".auth-form").elements.auth.value;
const fields = gadget.state_parameter_dict.element
.querySelector(".auth-form").elements;
const auth = fields.auth.value;
.querySelector(".contact-form").elements;
const dropbox_url = fields.dropbox_url.value;
const erp5_url = fields.erp5_url.value;
const hateoas_url = fields.hateoas_url.value;
return new RSVP.Queue()
.push(function () {
let folder = gadget.state_parameter_dict.login_dict.folder;
......@@ -743,6 +694,7 @@
erp5_url: erp5_url,
erp5_folder: null,
erp5_folder_url: null,
hateoas_url: hateoas_url,
};
switch (auth) {
case "dropbox":
......@@ -755,31 +707,24 @@
});
})
.declareService(function () {
.onEvent('submit', function (event) {
const gadget = this;
function handleSubmit(event) {
switch (event.target.className) {
case "auth-form":
return gadget.authenticate();
case "host-offer-form":
hideElementByClass(gadget, ".host-offer-form");
showElementByClass(gadget, ".host-answer-form");
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");
showElementByClass(gadget, ".guest-answer-form");
break;
case "guest-answer-form":
hideElementByClass(gadget, ".guest-answer-form");
let answer = event.target.elements.send.value;
event.target.elements.send.value = "";
return sendAnswer(gadget, answer);
}
switch (event.target.className) {
case "auth-form":
return gadget.authenticate();
case "host-offer-form":
styleElementByQuery(gadget, ".host-offer-form", "none");
styleElementByQuery(gadget, ".host-answer-form", "block");
return sendOffer(gadget, resetInputValue(event.target.elements.send));
case "guest-offer-form":
styleElementByQuery(gadget, ".guest-offer-form", "none");
styleElementByQuery(gadget, ".guest-answer-form", "block");
break;
case "guest-answer-form":
styleElementByQuery(gadget, ".guest-answer-form", "none");
return sendAnswer(
gadget, resetInputValue(event.target.elements.send));
}
return loopEventListener(
gadget.state_parameter_dict.element, "submit", false, handleSubmit);
});
}(window, document, rJS, RSVP));
\ No newline at end of file
/*global window, RSVP, FileReader */
/*jslint indent: 2, maxerr: 3, unparam: true */
(function (window, RSVP, FileReader) {
"use strict";
window.loopEventListener = function (target, type, useCapture, callback,
prevent_default) {
//////////////////////////
// Infinite event listener (promise is never resolved)
// eventListener is removed when promise is cancelled/rejected
//////////////////////////
var handle_event_callback,
callback_promise;
if (prevent_default === undefined) {
prevent_default = true;
}
function cancelResolver() {
if ((callback_promise !== undefined) &&
(typeof callback_promise.cancel === "function")) {
callback_promise.cancel();
}
}
function canceller() {
if (handle_event_callback !== undefined) {
target.removeEventListener(type, handle_event_callback, useCapture);
}
cancelResolver();
}
function itsANonResolvableTrap(resolve, reject) {
var result;
handle_event_callback = function (evt) {
if (prevent_default) {
evt.stopPropagation();
evt.preventDefault();
}
cancelResolver();
try {
result = callback(evt);
} catch (e) {
result = RSVP.reject(e);
}
callback_promise = result;
new RSVP.Queue()
.push(function () {
return result;
})
.push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
reject(error);
}
});
};
target.addEventListener(type, handle_event_callback, useCapture);
}
return new RSVP.Promise(itsANonResolvableTrap, canceller);
};
window.promiseEventListener = function (target, type, useCapture) {
//////////////////////////
// Resolve the promise as soon as the event is triggered
// eventListener is removed when promise is cancelled/resolved/rejected
//////////////////////////
var handle_event_callback;
function canceller() {
target.removeEventListener(type, handle_event_callback, useCapture);
}
function resolver(resolve) {
handle_event_callback = function (evt) {
canceller();
evt.stopPropagation();
evt.preventDefault();
resolve(evt);
return false;
};
target.addEventListener(type, handle_event_callback, useCapture);
}
return new RSVP.Promise(resolver, canceller);
};
window.promiseReadAsText = function (file) {
return new RSVP.Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function (evt) {
resolve(evt.target.result);
};
reader.onerror = function (evt) {
reject(evt);
};
reader.readAsText(file);
});
};
window.promiseDoWhile = function (loopFunction, input) {
// calls loopFunction(input) until it returns a non positive value
// this queue is to protect the inner loop queue from the
// `promiseDoWhile` caller, avoiding it to enqueue the inner
// loop queue.
return new RSVP.Queue()
.push(function () {
// here is the inner loop queue
var loop_queue = new RSVP.Queue();
function iterate(previous_iteration_result) {
if (!previous_iteration_result) {
return input;
}
loop_queue.push(iterate);
return loopFunction(input);
}
return loop_queue
.push(function () {
return loopFunction(input);
})
.push(iterate);
});
};
window.logError = function (error) {
console.log(error);
};
window.logQueue = function (action) {
return new RSVP.Queue()
.push(function () {
return action;
})
.push(null, logError);
};
window.styleElementByQuery = function (gadget, query, style) {
gadget.state_parameter_dict.element
.querySelector(query).style.display = style;
}
window.resetInputValue = function (element) {
const value = element.value;
element.value = "";
return value;
};
window.pollUntilNotNull = function (
gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) {
if (callbackFunction === undefined) {
callbackFunction = function () {};
}
return new RSVP.Queue()
.push(function () {
return nullableFunction();
})
.push(function (result) {
if (result !== null) {
return callbackFunction(result);
} else {
return RSVP.any([
RSVP.timeout(timeout_ms),
promiseDoWhile(function () {
return new RSVP.Queue()
.push(function () {
return RSVP.delay(delay_ms);
})
.push(function () {
return nullableFunction();
})
.push(function (result) {
if (result === null) {
return null;
} else {
return callbackFunction(result);
}
})
.push(function (nullable) {
return nullable === null;
})
.push(null, logError);
})
]);
}
});
};
}(window, RSVP, FileReader));
\ 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>contributor/person_module/1</string>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_global.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>rjs_gadget_global_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>gadget_global.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/rjs_gadget_global_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