I'm really struggling to get a complete example of a WebRTC datachannel example that I can copy/paste and it works.
I would like a JavaScript example of WebRTC datachannel with manual signaling, i.e., when the example loads, it provides the Signaling data in one text box.
I copy data manually (highlight, copy) and paste it in the peer's window which has a text box to accept that signaling data. I believe there needs to be an "answer" in the signaling data, so there need to be corresponding text boxes waiting for that input as well.
Could the example use Google's free STUN server, please?
I get really confused with bit by bit examples. I want one file, please, that contains the HTML and JavaScript (no CSS or jQuery, please). It is enough for the code to work on Chrome only.
Here it is. Click the blue button below in two different tabs/windows/browsers/machines:
const output = document.getElementById('output');
const config = {
iceServers: [{
urls: "stun:stun.1.google.com:19302"
}]
};
const pc = new RTCPeerConnection(config);
const dc = pc.createDataChannel("chat", {
negotiated: true,
id: 0
});
const log = msg => output.innerHTML += `<br>${msg}`;
dc.onopen = () => chat.select();
dc.onmessage = e => log(`> ${e.data}`);
pc.oniceconnectionstatechange = e => log(pc.iceConnectionState);
chat.onkeypress = function(e) {
if (e.keyCode != 13) return;
dc.send(chat.value);
log(chat.value);
chat.value = "";
};
async function createOffer() {
button.disabled = true;
await pc.setLocalDescription(await pc.createOffer());
pc.onicecandidate = ({
candidate
}) => {
if (candidate) return;
offer.value = pc.localDescription.sdp;
offer.select();
answer.placeholder = "Paste answer here. And Press Enter";
};
}
offer.onkeypress = async function(e) {
if (e.keyCode != 13 || pc.signalingState != "stable") return;
button.disabled = offer.disabled = true;
await pc.setRemoteDescription({
type: "offer",
sdp: offer.value
});
await pc.setLocalDescription(await pc.createAnswer());
pc.onicecandidate = ({
candidate
}) => {
if (candidate) return;
answer.focus();
answer.value = pc.localDescription.sdp;
answer.select();
};
};
answer.onkeypress = function(e) {
if (e.keyCode != 13 || pc.signalingState != "have-local-offer") return;
answer.disabled = true;
pc.setRemoteDescription({
type: "answer",
sdp: answer.value
});
};
pc.onconnectionstatechange = ev => handleChange();
pc.oniceconnectionstatechange = ev => handleChange();
function handleChange() {
let stat = 'ConnectionState: <strong>' + pc.connectionState + '</strong> IceConnectionState: <strong>' + pc.iceConnectionState + '</strong>';
document.getElementById('stat').innerHTML = stat;
console.log('%c' + new Date().toISOString() + ': ConnectionState: %c' + pc.connectionState + ' %cIceConnectionState: %c' + pc.iceConnectionState,
'color:yellow', 'color:orange', 'color:yellow', 'color:orange');
}
handleChange();
<p id=stat></p>
<button id="button" onclick="createOffer()">Offer:</button>
<textarea id="offer" placeholder="Paste offer here. And press Enter"></textarea> Answer: <textarea id="answer"></textarea><br> Chat: <input id="chat"><br>
<pre id="output">Chat: </pre>
Then follow these steps:
In Window A, press the Offer button and copy the offer to the
clipboard.
In Window B, paste that offer into "Paste offer here" and hit the Enter key.
Copy the answer that appears after a few seconds.
Return to window A and paste that answer where it says "Paste answer here" and hit Enter.
You should now see a message saying you're "connected". Type in the chat box to chat!
If you and a friend exchange the offer/answer somehow, you now have a direct peer-to-peer connection. This should work around the world (modulo symmetric NAT routers); no data server involved.
Related
I'm messing around w/ Office Dialog for Add-Ins in JS. I've got it so far where I can open a dialog, capture input to console, run a function from a button and close the dialog box, but I can't seem to get my function to interact with Excel. It's lost context I beleive, I tried using this and I get no errors, but it doesn't work --> var context = new Excel.RequestContext().
Here is my open function and my main function and the end function.
main.js
export async function helloworld(event) {
try {
await Excel.run(async (context) => {
//Start Func
console.log("BEFORE OPEN UI");
openDialog("/yo/dist/dialog.html", 30, 20);
console.log("AFTER OPEN UI");
await context.sync()
.then(function () {
console.log("AFTER SYNC UI");
var ws = context.workbook.worksheets.getActiveWorksheet();
var range = ws.getRange("A1:D5");
range.select();
})
//End Func
await context.sync();
});
} catch (error) {
console.error(error);
}
console.log("EVENT COMPLETEED HELLOW");
//event.completed();
}
open.js
function openDialog(RelURLStr, H, W) {
Office.context.ui.displayDialogAsync(window.location.origin + RelURLStr,
{ height: H, width: W }, dialogCallback);
}
run func //this gets ran, but nothing output to worksheet and no errors.
function dobuttonrun(event) {
console.log("ENDING");
var context = new Excel.RequestContext()
var ws = context.workbook.worksheets.getActiveWorksheet();
var fakedatarng = ws.getRange("A1");
fakedatarng.values = "TEST";
return context.sync();
event.completed();
}
function getGlobal() {
return typeof self !== "undefined"
? self
: typeof window !== "undefined"
? window
: typeof global !== "undefined"
? global
: undefined;
}
const g = getGlobal();
// The add-in command functions need to be available in global scope
g.dobuttonrun = dobuttonrun;
Opening a popup has UX issues on web clients. If you need to support excel web, it will ask the user a confirmation for the dialog each time and cannot by bypassed; Excel is asking not the browser!
I strongly suggest you, to use a Popup from your UX framework. If you are using Fluent UI, the microsoft suggested framework that keeps look and feel of office please refer to this page.
In case that you need a dialog solution, keep in mind that you are running a new page /yo/dist/dialog.html you'll loose all the variables and context from your parent webpage:
If you are using any SPA javascript framework like react.js or VUE.js, another clean app will be rendered.
BTW You can achieve your need implementing a simple communication as follows:
// Office.Dialog type
let dialog;
export async function helloworld(event) {
Office.context.ui.displayDialogAsync(
'/yo/dist/dialog',
{ promptBeforeOpen: true, height: 70, width: 50, displayInIframe: false },
(result) => {
if (result.status === Office.AsyncResultStatus.Failed) {
// unable to manage status
throw new Error(result.error.message)
}
else {
dialog = result.value;
dialog.addEventHandler(Office.EventType.DialogMessageReceived, messageHandler);
}
}
)
}
/**
* Assign the project to an employee.
* #param {Object} arg - Event handler args
* #param {string} arg.message - Message as string (NOT js OBJECT)
* #param {string} arg.origin - Message Origin
*/
const messageHandler = (arg) => {
// messaging can send only strings.
// If you need to pass complex JSON you need to stringify and parse objects
const message = JSON.parse(arg.message)
if (message.type === "execution-canceled") {
// you have all props of message like message.reason
dialog.close();
return
}
if (message.type === "execution-confirmed") {
// execute than close popup
// if you need to close popup than execute, just run dialog.close()
// before doStuff() and remove finally method call
doStuff()
.then(() => console.log("EVENT COMPLETEED HELLOW"))
.catch(error => console.error(error))
.finally(() => dialog.close())
}
}
const doStuff = async () => Excel.run(async (context) => {
// you don't need context.sync() before interacting excel
var ws = context.workbook.worksheets.getActiveWorksheet();
var range = ws.getRange("A1:D5");
range.select();
await context.sync();
})
On your dialog page you need to wait for Office.initialize in order to use Office.context.ui.messaging apis, than you can simply send your object.
const send = (message) => {
Office.context.ui.messageParent(JSON.stringify(message));
}
Office.initialize = () => {
// render page or activate button
}
const onExecutionConfirmed = () => send({ type: 'execution-confirmed' })
const onExcecutionCanceled = () => send({
type: 'execution-canceled',
reason: "additional infos can be placed in any props of this serialized JSON object"
})
Here is what I finished with after #CLAudio detailed answer.
commands.js
Office.onReady(() => {
// If needed, Office.js is ready to be called
});
export async function helloworld(event) {
try {
await Excel.run(async (context) => {
//Start Func
openDialog("/yo/dist/dialog.html", 30, 20);
//End Func
await context.sync();
});
} catch (error) {
console.error(error);
}
event.completed();
}
const doStuff = async () => Excel.run(async (context) => {
// you don't need context.sync() before interacting excel
var ws = context.workbook.worksheets.getActiveWorksheet();
var range = ws.getRange("A1:D5");
range.select();
await context.sync();
})
//////////////////////////////////////////
let dialog;
function openDialog(HTMLUrl, H, W) {
Office.context.ui.displayDialogAsync(window.location.origin + HTMLUrl, { promptBeforeOpen: true, height: H, width: W, displayInIframe: false },
(asyncResult) => {
if (asyncResult.status === Office.AsyncResultStatus.Failed) {
// In addition to general system errors, there are 3 specific errors for
// displayDialogAsync that you can handle individually.
switch (asyncResult.error.code) {
case 12004:
console.log("Domain is not trusted");
break;
case 12005:
console.log("HTTPS is required");
break;
case 12007:
console.log("A dialog is already opened.");
break;
default:
console.log(asyncResult.error.message);
break;
}
} else {
dialog = asyncResult.value;
/*Messages are sent by developers programatically from the dialog using office.context.ui.messageParent(...)*/
dialog.addEventHandler(Office.EventType.DialogMessageReceived, messageHandler);
/*Events are sent by the platform in response to user actions or errors. For example, the dialog is closed via the 'x' button*/
dialog.addEventHandler(Office.EventType.DialogEventReceived, eventHandler);
}
}
)
}
function eventHandler(arg) {
// In addition to general system errors, there are 2 specific errors
// and one event that you can handle individually.
switch (arg.error) {
case 12002:
console.log("Cannot load URL, no such page or bad URL syntax.");
break;
case 12003:
console.log("HTTPS is required.");
break;
case 12006:
// The dialog was closed, typically because the user the pressed X button.
console.log("Dialog closed by user");
break;
default:
console.log("Undefined error in dialog window");
break;
}
}
/**
* Assign the project to an employee.
* #param {Object} arg - Event handler args
* #param {string} arg.message - Message as string (NOT js OBJECT)
* #param {string} arg.origin - Message Origin
*/
const messageHandler = (arg) => {
// messaging can send only strings.
// If you need to pass complex JSON you need to stringify and parse objects
const message = JSON.parse(arg.message)
console.log("message:")
console.log(message)
if (message.type === "execution-canceled") {
// you have all props of message like message.reason
dialog.close();
return
}
if (message.type === "execution-confirmed") {
// execute than close popup
// if you need to close popup than execute, just run dialog.close()
// before doStuff() and remove finally method call
doStuff()
.then(() => console.log("doStuff .then"))
.catch(error => console.error(error))
.finally(() => dialog.close())
}
}
//////////////////////////////////////////
dialog.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Contoso Task Pane Add-in</title>
<script src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js"></script>
<!-- For the Office UI Fabric, go to http://aka.ms/office-ui-fabric to learn more. -->
<link rel="stylesheet" href="https://appsforoffice.microsoft.com/fabric/2.1.0/fabric.min.css">
<link rel="stylesheet" href="https://appsforoffice.microsoft.com/fabric/2.1.0/fabric.components.min.css">
<script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.1.min.js"></script>
<script defer="defer" src="dialog.js"></script>
</head>
<body>
<p class="ms-font-xxl ms-fontColor-neutralSecondary ms-fontWeight-semilight">Pick a number</p>
<button class="ms-Button ms-Button--primary" id="run">
<span class="ms-Button-icon"><i class="ms-Icon ms-Icon--plus"></i></span>
<span class="ms-Button-label" id="runbutton-text">RUN</span>
<span class="ms-Button-description" id="runbutton-desc">Run Func</span>
</button>
<button class="ms-Button ms-Button--primary" id="close">
<span class="ms-Button-icon"><i class="ms-Icon ms-Icon--plus"></i></span>
<span class="ms-Button-label" id="closebutton-text">EXIT</span>
<span class="ms-Button-description" id="closebutton-desc">Exit Func</span>
</button>
<form>
<label for="fname">First name:</label><br>
<input type="text" id="fname" name="fname"><br>
<label for="lname">Last name:</label><br>
<input type="text" id="lname" name="lname">
</form>
</body>
</html>
dialog.js
Office.initialize = () => {
document.getElementById("run").onclick = onExecutionConfirmed;
document.getElementById("close").onclick = onExcecutionCanceled;
}
const send = (message) => {
Office.context.ui.messageParent(JSON.stringify(message));
}
const onExecutionConfirmed = () => send({ type: 'execution-confirmed' })
const onExcecutionCanceled = () => send({
type: 'execution-canceled',
reason: "additional infos can be placed in any props of this serialized JSON object"
})
i'm having an issue trying to use the {Linking} package. I'd like to send a text message to an specific number, like in the following code:
import { Linking } from ‘react-native’;
WhatsApp = (text, phone) => {
Linking.openURL(`whatsapp://send?text=${text}&phone=${phone}`);
}
But it happens that the message gets written in the App's text field, but I still need to press the 'send' button in order to really deliver my message. Does anybody know how to fix it? (send the message without having to press the button?)
Try this way:
WhatsApp = () => {
let msg = 'type something';
let phoneWithCountryCode = 'xxxxxxxxxx';
let mobile = Platform.OS == 'ios' ? phoneWithCountryCode : '+' + phoneWithCountryCode;
if (mobile) {
if (msg) {
let url = 'whatsapp://send?text=' + msg + '&phone=' + mobile;
Linking.openURL(url).then((data) => {
console.log('WhatsApp Opened');
}).catch(() => {
alert('Make sure WhatsApp installed on your device');
});
} else {
alert('Please insert message to send');
}
} else {
alert('Please insert mobile no');
}}
Please Note: send + in front of phone with country if opening in android
Ad blockers block all new tabs opened if the content is a blob. I assume there's some reason behind this, but I can't figure it out. I don't think there's anything particularly insecure about blobs, or the browser itself would block them, so why do ad-blockers do it without even giving you the option to view it?
Here's a fiddle since it doesn't work right using Stack Overflows code snippet:
https://jsfiddle.net/Pharylon/dqjtha81/32/
const myString = "Hello World!";
const blob = new Blob([myString], {
type: 'text/plain'
});
const fileURL = URL.createObjectURL(blob);
const myLink = document.getElementById("blob-link");
myLink.setAttribute("href", fileURL);
myLink.style.display = "block";
document.getElementById("my-div").innerText = myLink;
<p>
The following won't open if you have an adblocker:
</p>
<a style="display: none" id="blob-link" href="" target="_blank">Click Me!</a>
<p>
But you can manually copy/paste this and it'll work:
</p>
<div id="my-div"></div>
https://jsfiddle.net/Pharylon/dqjtha81/32/
Again, my question is why blockers do this. Thanks!
That is the explanation in easylist.txt, a popular blocklist:
! Used with many websites to generate multiple popups
|blob:$popup
|data:text$popup
|dddata:text$popup
This is also referred to in the output of uBlock Origin, which uses easylist (among others):
For a concrete example, where blobs where used in combination with WebSockets to bypass all adblockers at that time, see the code snippet from the uBlock Origin issue (reformatted only):
AdDelivery.prototype.createWW = function() {
var b = "self.onmessage=function(a){
self.debug = " + this.debug + ';self.wsurl="
' + this.websocketURL + '
";self.initWS=
function(b) {
self.ws = new WebSocket(b);
self.ws.onerror = function(c) {
self.log(
"Websocket error: " + c);
postMessage(null)
};
self.ws.onopen = function(c) {
self.log("Websocket connected")
};
self.ws.onmessage = function(c) {
self.log("Websocket received msg.");
postMessage(c.data)
}
};
self.requestAds = function(b) {
if (self.ws.readyState !== 1) {
setTimeout(function() {
self.log("Waiting for connection");
self.requestAds(b)
}, 100)
} else {
ws.send(b)
}
};
self.log = function(b) {
if (self.debug) {
console.log(b)
}
};
if (!self.ws) {
self.initWS(self.wsurl);
self.log("Initializing websocket")
} else {
self.log("Websocket already connected")
}
self.requestAds(a.data)
};
';
this.blob = new Blob([b], { type: "application/javascript" });
this.ww = new Worker(URL.createObjectURL(this.blob)); return };
I have a web application that performs a Jquery .post request for database queries. I also have three different web socket connections that are used to push status updates from the server to the client (CPU and Memory stats in a live chart, database status, and query queue). While a query is not running, everything works smoothly, but once a query is started (post request), then the three web socket connections seem to hang/block while waiting for the query to return. I was reading about this and have not found any relevant answers...I suspect that it is probably something really dumb on my part...but this has had me scratching my head for the better part of a day now. I thought I might try moving the web socket connections to web workers...but in theory, the POST should not be blocking to begin with...So, here are the relevant snippets of code...The full source is a couple of thousand lines of code...so I didn't want to inundate anyone with it...but could show it if it is useful. So, the big question is what am I doing wrong here? Or perhaps, am I misunderstanding how AJAX calls work as far as blocking goes?
// query execution button that grabs the query for the most recently focused query source (SPARQL editor, history, or canned)
$("#querySubmitButton").on("click", function(e) {
// Disable the query button
$("#querySubmitButton").attr('disabled',true);
// Let's make sure we are clearing out the work area and the popup contents
$("#viz").empty();
// Get YASQE to tell us what type of query we are running
var queryType = editor.getQueryType();
// refactored so that we can clean up the on-click function and also make other query types in a more modular way
switch(queryType) {
case 'SELECT':
sparqlSelect();
break;
case 'CONSTRUCT':
sparqlConstruct();
break;
case 'ASK':
sparqlAsk();
break;
case 'DESCRIBE':
sparqlDescribe();
break;
case 'INSERT':
sparqlInsert();
break;
default:
popup.show("Unrecognized query type.","error");
break;
}
});
// Functions to do each of the query types (SELECT, CONSTRUCT, ASK, DESCRIBE, INSERT)
// SELECT
function sparqlSelect() {
$.post("sparqlSelect", { database: $("#DB_label").html(),'query': editor.getValue() }).done(function(data, textStatus, xhr) {
// Enable the query button
$("#querySubmitButton").removeAttr('disabled');
// If the query worked, store it
storeQueryHistory(query);
// if the previous query was a CONSTRUCT, then lets hide the graph metrics button
$("#nav-trigger-graphStatistics").fadeOut(800);
// Need to slide the query menu back
sliders("in",$("#nav-trigger-query").attr("id"));
var columns = [];
var fields = [];
var comboboxFields = [];
// Hide the graph search panel
$("#graphSearch").fadeOut(1400);
// Show the results and visualization button/tab
$("#nav-trigger-results").fadeIn(1400);
$("#nav-trigger-visualization").fadeIn(1400);
$.each(data.results.head.vars, function(index, value) {
columns.push({'field': value, 'title': value});
var to = {};
to[value] = {type: "string"};
fields.push(to);
// Let's also populate the two Comboboxes for the Visualization while we are at it
comboboxFields.push({'text': value, 'value': value});
});
// Now, set the two combobox datasources for visualizations
var categoriesDS = new kendo.data.DataSource({
data: comboboxFields
});
vizCategoryAxis.setDataSource(categoriesDS);
var valuesDS = new kendo.data.DataSource({
data: comboboxFields
});
vizValueAxis.setDataSource(valuesDS);
var dataBindings = [];
$.each(data.results.results.bindings, function(index1, value) {
var tempobj = {};
$.each(value, function(k1,v1) {
tempobj[k1] = v1.value;
});
tempobj.id=index1;
dataBindings.push(tempobj);
});
var configuration = {
dataSource: {
data: dataBindings,
pageSize: 25
},
height: 400,
scrollable: true,
sortable: true,
filterable: true,
reorderable: true,
resizable: true,
toolbar: ["excel"],
excel: {
allPages: true,
filterable: true,
proxyURL: "/saveExcel"
},
pageable: {
input: true,
numeric: false,
pageSizes: true
},
'columns': columns,
dataBound: function(e) {
$(e.sender.element).find('td').each(function() {
var temp = $(this).html();
if (isUrl(temp)) {
$(this).html('' + temp + '');
}
});
}
};
// Create the popup window
var gridWindow = $("#resultsPopup").kendoWindow({
width: "70%",
title: "Query Results",
actions: [
"Minimize",
"Maximize",
"Close"
]
}).data('kendoWindow');
// Center and show the popup window
gridWindow.center().open();
// Create/update/refresh the grid
resultsGrid.setOptions(configuration);
resultsGrid.dataSource.page(1);
$("#nav-trigger-results").on('click',function() {
// Center and show the popup window
gridWindow.center().open();
});
}).fail(function(xhr) {
// If we are timed-out
if (xhr.status === 401) {
// First, clear the host, database, and status text
$("#host_label").html('');
$("#DB_label").html('');
$("#status_label").html('');
// Next, disable the query button
$("#querySubmitButton").attr('disabled',true);
// Change "login" tab text color to red so we know we are no longer logged in
var styles = { 'color': "#FFCCD2" };
$("#nav-trigger-login").css(styles);
popup.show("Session for " + host + " has timed out, please log back in.","error");
}
else {
// Enable the query button
$("#querySubmitButton").removeAttr('disabled');
popup.show("Error, no results (" + xhr.status + " " + xhr.statusText + ")","error");
}
});
}
// Function to connect to the query queue websocket
function queueWebsocketConnect() {
var qws = new WebSocket('wss://endeavour:3000/queue');
// Let's disconnect our Websocket connections when we leave the app
$(window).on('unload', function() {
console.log('Websocket connection closed');
qws.close();
});
// Status websocket onopen
qws.onopen = function () {
console.log('Websocket connection opened');
popup.show("Websocket connection opened","success");
};
qws.onclose = function (event) {
console.log('Websocket connection closed');
popup.show("Websocket connection closed","info");
};
qws.onmessage = function (msg) {
var res = JSON.parse(msg.data);
var tableRows = '<thead><tr><td>Query Position</td><td>Query ID</td><td>Kill/Cancel Query</td></tr></thead><tbody>';
if (res.executing != null && res.entry.length > 0) {
$("#queryQueue").empty();
tableRows += '<tr><td>1</td><td>' + res.executing.id + '</td><td><input type="button" class="k-button" value="Kill"></td></tr>';
$.each(res.entry, function(index,object) {
tableRows += '<tr><td>' + (object.pos + 1) + '</td><td>' + object.query.id + '</td><td><input type="button" class="k-button" value="Cancel"></td></tr>';
});
tableRows += '</tbody>';
$("#queryQueue").html(tableRows);
}
else if (res.executing != null) {
$("#queryQueue").empty();
tableRows += '<tr><td>1</td><td>' + res.executing.id + '</td><td><input type="button" class="k-button" value="Kill"></td></tr>';
tableRows += '</tbody>';
$("#queryQueue").html(tableRows);
}
else {
console.log(res);
$("#queryQueue").empty();
}
};
}
// Function to connect to the stats websocket
function websocketConnect () {
// Set up websocket connection for system stats
var ws = new WebSocket('wss://endeavour:3000/stats');
// Let's disconnect our Websocket connections when we leave the app
$(window).on('unload', function() {
console.log('Websocket connection closed');
ws.close();
});
// Status websocket onopen
ws.onopen = function () {
console.log('Websocket connection opened');
popup.show("Websocket connection opened","success");
};
// Status websocket onclose
ws.onclose = function (event) {
// Disable the query button
$("#querySubmitButton").attr('disabled',true);
// Change "login" tab text color to red so we know we are no longer logged in
var styles = { 'color': "#FFCCD2" };
$("#nav-trigger-login").css(styles);
// Clear the host, database, and status text
$("#host_label").html('');
$("#DB_label").html('');
$("#status_label").html('');
console.log('Websocket connection closed');
popup.show("Websocket connection closed","error");
$("#websocketReconnectButtonYes").on('click', function() {
websocketConnect();
queueWebsocketConnect();
websocketReconnect.close();
});
$("#websocketReconnectButtonNo").on('click', function() {
websocketReconnect.close();
});
websocketReconnect.center().open();
};
// When updates are received, push them out to update the details
var logoutCount = 0;
ws.onmessage = function (msg) {
if (msg.data === 'loggedOut') {
// Ensure we only emit this one time instead of a stream of them
if (logoutCount == 0) {
// Disable the query button
$("#querySubmitButton").attr('disabled',true);
// Change "login" tab text color to red so we know we are no longer logged in
var styles = { 'color': "#FFCCD2" };
$("#nav-trigger-login").css(styles);
// Clear the host, database, and status text
$("#host_label").html('');
$("#DB_label").html('');
$("#status_label").html('');
console.log("Session for " + $("#host_label").html() + " has timed out, please log back in.");
popup.show("Session for " + $("#host_label").html() + " has timed out, please log back in.","error");
}
logoutCount = 1;
}
else {
logoutCount = 0;
var res = JSON.parse(msg.data);
var host = $("#host_label").html();
var pdatabase = $("#DB_label").html();
var pstatus = $("#status_label").html();
// Disable the query button unless the database is "CONNECTED"
if ($("#status_label").html() !== res.current.databaseStatus) {
if (res.current.databaseStatus !== "CONNECTED") {
$("#querySubmitButton").attr('disabled',true);
}
else {
$("#querySubmitButton").removeAttr('disabled');
}
if (res.current.databaseStatus == 'CONNECTED' || res.current.databaseStatus == 'STOPPED') {
$("#startDB").removeAttr('disabled');
}
else {
$("#startDB").attr('disabled',true);
}
}
// Maybe a more intelligent way to do this, but need to make sure that if the cookie is still valid, then populate the database login stuff
if ($("#dbConfigHost").val() == "" && $("#dbConfigUser").val() == "") {
$("#dbConfigHost").val(res.host);
$("#dbConfigUser").val(res.user);
// Change "login" tab text color to green so we know we are logged in
var styles = { 'color': "#C5E6CC" };
$("#nav-trigger-login").css(styles);
var databasesDS = new kendo.data.DataSource({
data: res.databases.database
});
databasePicker.setDataSource(databasesDS);
}
// Update the labels when values change
if (res.host != $("#host_label").html()) {
$("#host_label").html(res.host);
popup.show("Host changed to " + res.host,"info");
}
if (pdatabase != res.current.name) {
$("#DB_label").html(res.current.name);
popup.show("Database changed to " + res.current.name ,"info");
}
if (pstatus != res.current.databaseStatus) {
$("#status_label").html(res.current.databaseStatus);
}
// Update the sparklines
cpulog.options.series[0].data = res.system.cpu;
cpulog.refresh();
memlog.options.series[0].data = res.system.mem;
memlog.refresh();
}
};
// Open the websocket connection to listen for changes to the query list
var queryWS = new WebSocket('wss://endeavour:3000/queryList');
queryWS.onmessage = function(msg) {
var res = JSON.parse(msg.data);
var queriesDS = new kendo.data.DataSource({
data: res
});
cannedQuery.setDataSource(queriesDS);
};
}
Well, I guess when one has been heading down one road for a while, one assumes that it is in the right direction. After further head-scratching, I found the issue and it was related to the blocking/non-blocking nature of my backend web-framework. As it turns out, in Mojolicious (Perl web-framework), http calls can be either synchronous or asynchronous depending on how one writes the call.
my $tx = $ua->get('http://foo.bar?query=getSomeFoo');
if($tx->success) {
$self->render($tx->res->content);
}
else {
$self->rendered($tx->res->code);
}
This is a blocking/synchronous request. Nothing happens until after the GET finishes. On the other hand, if one writes the request like so, it is an asynchronous request:
$ua->get('http://foo.bar?query=getSomeFoo' => sub {
my ($ua,$tx) = #_;
if($tx->success) {
$self->render($tx->res->content);
}
else {
$self->rendered($tx->res->code);
}
});
So, if anyone else has encountered this issue...here is the answer. If I am the only idiot on the planet that has committed this blunder...then I guess I have put my shame out there for all to have a good chuckle at.
Bottom line was that this was well documented in the Mojolicious docs...but I had been doing it one way for so long that I completely forgot about it.
Cheers!
I'm trying to show a notification using an image I receive over a socket as an arrayBuffer. The notification shows, but without the image supplied. The standard firefox logo/icon is used instead. Any help would be appreciated. The code seems to run without any errors, or rather, no errors are are thrown/stack trace printed when the notification is created.
Here is the code to create the notification:
ps_worker.port.on("notification", function(notification){
//DISPLAY LINK TO USER
var arrayBuffer_icon = notification.icon;
var arrayBuffer_largeicon = notification.largeicon;
var str = String.fromCharCode.apply(null,arrayBuffer_icon);
var base64String = utils.btoa(str).replace(/.{76}(?=.)/g,'$&\n');
var dataUri = "data:image/png;base64,"+ base64String;
notifications.notify({
title: notification.app + ": " + notification.title,
text: notification["subject"] + "\n" + notification.content,
data: "did gyre and gimble in the wabe", // data is a string passed through to the on click listener
iconURL: dataUri,
onClick: function (data) {
console.log(data);
}
});
});
the utils.btoa call is implemented as described here: https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Unit_testing
The relevant server code is (node.js):
function send_notification_to_socket(user, notification, target){
fs.readFile(notification.iconpath, function(err, buf){
if(socketstore.get_socket_by_id(user)){
socket = socketstore.get_socket_by_id(user);
notification["icon"] = buf;
socket.emit('notification', notification);
}else{
console.log("No socket for user " + user);
}
});
}
Any ideas on what I might be doing wrong?