I need to fetch the client local IP address from a web application.
For which I am using a standard RTCPeerConnection implementation to fetch. But the ice candidate that is returned does not carry the IP V4 address, but an address that look like a guid: asdf-xxxx-saass-xxxx.local
But surprisingly this chrome extension is able to fetch the same on same machine and browser.
Note: code that I used in web application is same as of the extension
This is the html code for same:
<html>
<head>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script type="text/javascript">
function logit(msg) {
var dt = new Date(); var time = dt.getHours() + ":" + dt.getMinutes() + ":"
+ dt.getSeconds();
console.log(time + " " + msg);
};
function getChromeVersion() {
try {
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
return raw ? parseInt(raw[2], 10) : false;
} catch (e) {
return null;
}
}
function getChromeManifest() {
return chrome.runtime && typeof chrome.runtime === "function" ? chrome.runtime.getManifest() : {}
}
function getUserIP(callback) {
logit(" getting user local ip ")
getLocalIPs(function (ips) {
logit(" got user local ip : " + ips)
if (ips && ips.length) return callback(ips[0]);
logit(" getting user local ip with stun ")
getLocalIPs(function (ips) {
logit(" got user local ip with stun : " + ips)
if (ips && ips.length) return callback(ips[0])
logit(" cannot get user local ip, returning null ")
callback(null)
}, true, 2000)
})
}
function getLocalIPs(callback, withStun, timeout) {
var ips = [];
var RTCPeerConnection = window.RTCPeerConnection ||
window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
var pc = new RTCPeerConnection({
// Don't specify any stun/turn servers, otherwise you will
// also find your public IP addresses.
// iceServers: [],
iceServers: withStun ? [{ urls: "stun:stun.services.mozilla.com" }] : []
});
var closeAndCallback = function () {
clearTimeout(waitTimeout)
try {
if (pc && pc.close) {
pc.close();
}
} catch (e) { console.log("exception while closing pc, err: %s", err) }
callback(ips);
}
var waitTimeout = timeout ? setTimeout(closeAndCallback, timeout) : null;
// Add a media line, this is needed to activate candidate gathering.
pc.createDataChannel('');
// onicecandidate is triggered whenever a candidate has been found.
pc.onicecandidate = function (e) {
console.log(e)
if (!e.candidate) { // Candidate gathering completed.
pc.close();
closeAndCallback();
return;
}
var ip = /^candidate:.+ (\S+) \d+ typ/.exec(e.candidate.candidate)[1];
if (ips.indexOf(ip) == -1) // avoid duplicate entries (tcp/udp)
ips.push(ip);
};
pc.createOffer(function (sdp) {
pc.setLocalDescription(sdp);
}, function onerror() { });
};
function callThirdParty(server, name) {
var api = server;
logit("Connecting " + server + " ...");
$.ajax({
type: "GET",
url: api,
success: function (data) {
if (data && data['ip']) {
logit("Public IP: " + data['ip']);
}
}, error:
function (request, status, error) {
logit('Response: ' + request.responseText);
logit(' Error: ' + error);
logit(' Status: ' + status);
},
complete: function (data) {
logit(' API Finished: ' + name + " Server!");
}
});
}
document.addEventListener('DOMContentLoaded', function () {
getUserIP(function (ip) { //
ipaddress = ip;
$('#ip2').html(ipaddress);
var manifest = getChromeManifest();
logit(manifest.name);
logit("Version: " + manifest.version);
logit("Chrome Version: " + getChromeVersion());
callThirdParty("https://api.ipify.org?format=json", "ipify.org");
}, 100);
}, false);
</script>
</head>
<p>Public IPs</p>
<div id="ip"></div>
<p>Local IP</p>
<div id="ip2"></div>
<p>Logs</p>
<div id="log"></div>
<div id="log1"></div>
<div id="log2"></div>
</html>
TL;DR
It looks like local addresses are/will be anonymized using mDNS and default setting for the flag would be gradually set to Enabled for all Chrome users.
For local development take a look here (set to Disable): chrome://flags/#enable-webrtc-hide-local-ips-with-mdns
Unless someone finds out some clever hack for it, you probably won't be able to revert the change for users of your webapp.
That guid is actually mDNS address. Quick search in newest WebRTC bugs in Chromium https://bugs.chromium.org/p/chromium/issues/list?can=2&q=component%3ABlink%3EWebRTC+
revealed few interesting entries, and there are few StackOverflow questions regarding anonymization not working (like this one: mDNS Support for WebRTC at Google Chrome M74).
Right now I see the effect in Chrome 75 on few computers with Windows 10 - some sites which previously were able to detect local IP flawlessly (http://net.ipcalf.com, https://ipleak.net, https://browserleaks.com/webrtc) now don't display it or show mDNS url instead.
As a sidenote: after enabling the mDNS flag, the extension you've linked wasn't able to detect my exact local IP. Instead, it showed few candidates from /24 address group. Even then, the extension could be privileged in some way, so it wouldn't be affected so much by mDNS anonymization.
EDIT (Mar 2020): it looks like Firefox could be anonymizing local IPs as well.
As of March 2020, there are two settings in about:config page:
media.peerconnection.ice.obfuscate_host_addresses - when set to true, it changes local IP to {uuid}.local
media.peerconnection.ice.obfuscate_host_addresses.whitelist - string with URLs, which are able to retrieve real IP, even with obfuscation enabled
I've checked Firefox 73 and Developer Edition 74 (without any extension that could have changed the setting), first had obfuscate_host_addresses set to false, while dev edition had it enabled.
EDIT (Oct 2020): since Chrome 86 the mDNS setting is enabled and cannot be disabled via chrome://flags anymore (there is no such option available).
Related
OBJECTIVE:
I'm building a FPV robot, I want to control it with a with a webbrowser over a local wi-fi connection.
I'm using a raspberry pi 3B+ with Raspbian Stretch. I built my own motor control and power regulator hat.
After lots of research testing, I decided to use node.JS as http server and socket.io to provide a low latency bidirectional communication with my robot. This stack achieve about 7ms of latency.
Picture of the robot
PROBLEM:
I need to stream low latency video from an USB camera attached to the RPI to the browser. My target is to achieve at least 640x480 resolution at 10FPS with 50ms of latency or better. I'm happy sacrificing visual fedelity to get a quicker response from my robot.
If possible I would like to stream in UDP to improve the reliability of the stream.
If possible I would like to stream a video that modern webbrowsers can natively decode. I'd like to use a H264 codec and the HTML5 video tag.
I can fall back to use a javascript player if there is no other option.
WHAT I TRIED:
I did an extensive research and tried many tools.
Amongst other, I tried VLC, mjpg streamer, gstreamer and raspivid. A few times I got to a stream the webbrowser could view, but at best I got a latency of 700ms at 320x240. Very very far from my target.
Currently I'm looking into WebRTC solutions.
QUESTION:
I'd like suggestions for NODE.JS packages or other solutions to provide a UDP H264 video stream that can be decoded by an HTML5 video tag with a target latency of 50ms.
Thanks
UPDATE:
Thanks for your answers! I'll keep updating this question and I'll post the solution once it works.
PUSH INDIVIDUAL FRAMES
I tried a different approach by pushing individual 200KB 640x480 jpg frame through websocket and I got a latency of about 190ms. I can probably do a lot better by reusing objects but I'm putting this attempt in hold for now.
UPDATE2:
While researching WebRTC I found a stack that looked easy enough.
Server side it uses V4L2 as driver, FFMPEG to transcode into an MPEG1 http stream with TS encapsulation locally, node js to flip the stream into a websocket.
Client side there is a javascript that decode the MPEG1 TS stream and paint a canvas object into the HTML page.
It achieves 640x480#20FPS with 240mS of latency.
Good enough for an MVP, but I'll keep working to get it down.
Code in the answer.
I adapted code from here and integrated it with an http server and socket.io controls:
https://github.com/phoboslab/jsmpeg
Server:
V4L2 -> FFMPEG (MPEG1 TS) -> NODE HTTP Server -> NODE Websocket broadcast
Client:
Websocket -> Javascript (Decode MPEG1 TS and paint to html canvas) -> Html Canvas
This stack achieve 640x480#20FPS with 240ms of latency. Still far from my target but good enough as MVP. The controls in both directions have a latency of 7ms, which is excellent.
This stack is held back by the transcoding and decoding stage, and the RPI gets really hot. The transport of raw data through websocket looks good, I'm going to profile the latency of each steps in the future.
Execution:
pi#MazeRunner:~ $ node node.js &
pi#MazeRunner:~ $ ffmpeg -f v4l2 -framerate 20 -video_size 640x480 -i /dev/video0 -f mpegts -codec:v mpeg1video -s 640x480 -b:v 600k -bf 0 http://localhost:8080/mystream
Server side NODE.JS
//operating system library. Used to get local IP address
var os = require("os");
//file system library. Used to load file stored inside back end server (https://nodejs.org/api/fs.html)
var fs = require("fs");
//http system library. Handles basic html requests
var http = require("http").createServer(http_handler);
//url library. Used to process html url requests
var url = require("url");
//Websocket
var io = require("socket.io")(http);
//Websocket used to stream video
var websocket = require("ws");
//-----------------------------------------------------------------------------------
// CONFIGURATION
//-----------------------------------------------------------------------------------
//Port the server will listen to
var server_port = 8080;
var websocket_stream_port = 8082;
//Path of the http and css files for the http server
var file_index_name = "index.html";
var file_css_name = "style.css";
var file_jsplayer_name = "jsmpeg.min.js";
//Http and css files loaded into memory for fast access
var file_index;
var file_css;
var file_jsplayer;
//Name of the local video stream
var stream_name = "mystream";
//-----------------------------------------------------------------------------------
// DETECT SERVER OWN IP
//-----------------------------------------------------------------------------------
//If just one interface, store the server IP Here
var server_ip;
//Get local IP address of the server
//https://stackoverflow.com/questions/3653065/get-local-ip-address-in-node-js
var ifaces = os.networkInterfaces();
Object.keys(ifaces).forEach
(
function (ifname)
{
var alias = 0;
ifaces[ifname].forEach
(
function (iface)
{
if ('IPv4' !== iface.family || iface.internal !== false)
{
// skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
return;
}
if (alias >= 1)
{
// this single interface has multiple ipv4 addresses
console.log('INFO: Server interface ' +alias +' - ' + ifname + ':' + alias, iface.address);
}
else
{
server_ip = iface.address;
// this interface has only one ipv4 adress
console.log('INFO: Server interface - ' +ifname, iface.address);
}
++alias;
}
);
}
);
//-----------------------------------------------------------------------------------
// HTTP SERVER
//-----------------------------------------------------------------------------------
// Fetch and serves local files to client
//Create http server and listen to the given port
http.listen
(
server_port,
function( )
{
console.log('INFO: ' +server_ip +' listening to html requests on port ' +server_port);
//Pre-load http, css and js files into memory to improve http request latency
file_index = load_file( file_index_name );
file_css = load_file( file_css_name );
file_jsplayer = load_file( file_jsplayer_name );
}
);
//-----------------------------------------------------------------------------------
// HTTP REQUESTS HANDLER
//-----------------------------------------------------------------------------------
// Answer to client http requests. Serve http, css and js files
function http_handler(req, res)
{
//If client asks for root
if (req.url == '/')
{
//Request main page
res.writeHead( 200, {"Content-Type": detect_content(file_index_name),"Content-Length":file_index.length} );
res.write(file_index);
res.end();
console.log("INFO: Serving file: " +req.url);
}
//If client asks for css file
else if (req.url == ("/" +file_css_name))
{
//Request main page
res.writeHead( 200, {"Content-Type": detect_content(file_css_name),"Content-Length" :file_css.length} );
res.write(file_css);
res.end();
console.log("INFO: Serving file: " +req.url);
}
//If client asks for css file
else if (req.url == ("/" +file_jsplayer_name))
{
//Request main page
res.writeHead( 200, {"Content-Type": detect_content(file_jsplayer_name),"Content-Length" :file_jsplayer.length} );
res.write(file_jsplayer);
res.end();
console.log("INFO: Serving file: " +req.url);
}
//Listening to the port the stream from ffmpeg will flow into
else if (req.url = "/mystream")
{
res.connection.setTimeout(0);
console.log( "Stream Connected: " +req.socket.remoteAddress + ":" +req.socket.remotePort );
req.on
(
"data",
function(data)
{
streaming_websocket.broadcast(data);
/*
if (req.socket.recording)
{
req.socket.recording.write(data);
}
*/
//console.log("broadcast: ", data.length);
}
);
req.on
(
"end",
function()
{
console.log("local stream has ended");
if (req.socket.recording)
{
req.socket.recording.close();
}
}
);
}
//If client asks for an unhandled path
else
{
res.end();
console.log("ERR: Invalid file request" +req.url);
}
}
//-----------------------------------------------------------------------------------
// WEBSOCKET SERVER: CONTROL/FEEDBACK REQUESTS
//-----------------------------------------------------------------------------------
// Handle websocket connection to the client
io.on
(
"connection",
function (socket)
{
console.log("connecting...");
socket.emit("welcome", { payload: "Server says hello" });
//Periodically send the current server time to the client in string form
setInterval
(
function()
{
socket.emit("server_time", { server_time: get_server_time() });
},
//Send every 333ms
333
);
socket.on
(
"myclick",
function (data)
{
timestamp_ms = get_timestamp_ms();
socket.emit("profile_ping", { timestamp: timestamp_ms });
console.log("button event: " +" client says: " +data.payload);
}
);
//"ArrowLeft"
socket.on
(
"keyboard",
function (data)
{
timestamp_ms = get_timestamp_ms();
socket.emit("profile_ping", { timestamp: timestamp_ms });
console.log("keyboard event: " +" client says: " +data.payload);
}
);
//profile packets from the client are answer that allows to compute roundway trip time
socket.on
(
"profile_pong",
function (data)
{
timestamp_ms_pong = get_timestamp_ms();
timestamp_ms_ping = data.timestamp;
console.log("Pong received. Round trip time[ms]: " +(timestamp_ms_pong -timestamp_ms_ping));
}
);
}
);
//-----------------------------------------------------------------------------------
// WEBSOCKET SERVER: STREAMING VIDEO
//-----------------------------------------------------------------------------------
// Websocket Server
var streaming_websocket = new websocket.Server({port: websocket_stream_port, perMessageDeflate: false});
streaming_websocket.connectionCount = 0;
streaming_websocket.on
(
"connection",
function(socket, upgradeReq)
{
streaming_websocket.connectionCount++;
console.log
(
'New websocket Connection: ',
(upgradeReq || socket.upgradeReq).socket.remoteAddress,
(upgradeReq || socket.upgradeReq).headers['user-agent'],
'('+streaming_websocket.connectionCount+" total)"
);
socket.on
(
'close',
function(code, message)
{
streaming_websocket.connectionCount--;
console.log('Disconnected websocket ('+streaming_websocket.connectionCount+' total)');
}
);
}
);
streaming_websocket.broadcast = function(data)
{
streaming_websocket.clients.forEach
(
function each(client)
{
if (client.readyState === websocket.OPEN)
{
client.send(data);
}
}
);
};
//-----------------------------------------------------------------------------------
// FUNCTIONS
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
// SERVER DATE&TIME
//-----------------------------------------------------------------------------------
// Get server time in string form
function get_server_time()
{
my_date = new Date();
return my_date.toUTCString();
}
//-----------------------------------------------------------------------------------
// TIMESTAMP
//-----------------------------------------------------------------------------------
// Profile performance in ms
function get_timestamp_ms()
{
my_date = new Date();
return 1000.0* my_date.getSeconds() +my_date.getMilliseconds()
}
//-----------------------------------------------------------------------------------
// FILE LOADER
//-----------------------------------------------------------------------------------
// Load files into memory for improved latency
function load_file( file_name )
{
var file_tmp;
var file_path = __dirname +"/" +file_name;
//HTML index file
try
{
file_tmp = fs.readFileSync( file_path );
}
catch (err)
{
console.log("ERR: " +err.code +" failed to load: " +file_path);
throw err;
}
console.log("INFO: " +file_path +" has been loaded into memory");
return file_tmp;
}
//-----------------------------------------------------------------------------------
// CONTENT TYPE DETECTOR
//-----------------------------------------------------------------------------------
// Return the right content type to give correct information to the client browser
function detect_content( file_name )
{
if (file_name.includes(".html"))
{
return "text/html";
}
else if (file_name.includes(".css"))
{
return "text/css";
}
else if (file_name.includes(".js"))
{
return "application/javascript";
}
else
{
throw "invalid extension";
}
}
Client Side html
<!DOCTYPE html>
<meta charset="utf-8"/>
<html>
<head>
<title>Maze Runner</title>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var host_ip = document.location.hostname;
console.log("connecting to host: ", host_ip);
//Get references to the html controls
textbox_input1 = window.document.getElementById("my_text_box")
//Connect to the server via websocket
var mysocket = io("http://" +host_ip +":8080");
//Long lived frame object
var last_frame;
//-----------------------------------------
// CONNESSION ACKNOWLEDGE
//-----------------------------------------
// Link is initiated by the client
// Server sends a welcome message when link is estabilished
// Server could send an auth token to keep track of individual clients and login data
mysocket.on
(
"welcome",
(message) =>
{
console.log("Server websocket connession acknoweldged... " +message.payload);
}
)
//-----------------------------------------
// SERVER->CLIENT CONTROLS
//-----------------------------------------
// Server can send an async message to dinamically update the page without reloading
// This is an example message with the server local date and time in string form
mysocket.on
(
"server_time",
(message) =>
{
fill_label( message.server_time );
console.log("Server sent his local time... " +message.server_time);
}
)
function fill_label( payload )
{
textbox_input1.value=payload;
}
//-----------------------------------------
// CLIENT->SERVER CONTROLS
//-----------------------------------------
// Controls inside the webpage can emit async events to the server
// In this example I have a push button and I catch keyboard strokes
//Handler for a pushbutton
function socket_button_handler()
{
mysocket.emit("myclick", { payload: "button was clicked" });
console.log("Button was clicked...");
}
//Listen for keystrokes
window.document.addEventListener
(
"keypress",
function onEvent(event)
{
//Inform the server that a key has been pressed
mysocket.emit("keyboard", { payload: event.key });
console.log("Key press...");
}
);
//-----------------------------------------
// PING-PONG
//-----------------------------------------
// Server sends ping messages with a timestamp
// Client answers with pongs to allow server to profile latency of the channel
//profile messages means the server wants to compute roundway trip
mysocket.on
(
"profile_ping",
(message) =>
{
//Answer back with the received timestamp so that server can compute roundway trip
mysocket.emit("profile_pong", { timestamp: message.timestamp });
console.log( "server wants a pong. server absolute timestamp[ms]: " +message.timestamp );
}
);
</script>
</head>
<body>
<h1>Html+Css Server +low latency Websocket server</h1>
<!-- button control with socket emitter as handler -->
<p> This button will emit a websocket event. The server will be informed in real time of the event. </p>
<button id="my_button" type="button" onclick="socket_button_handler()">Websocket Button!</button>
<!-- input text control -->
<p> This input can be filled through websockets directly by the server in real time </p>
<input id="my_text_box" type="text" value="" size="40">
<!-- canvas object, it's painted by the javascript video decoder -->
<p> This canvas is painted by the javascript player and shows the live stream.'</p>
<canvas id="video-canvas" width=640 height=480></canvas>
<!-- Javascript video decoder, take in a data stream from a websocket and paint on a canvas -->
<script type="text/javascript" src="jsmpeg.min.js"></script>
<script type="text/javascript">
var mycanvas = document.getElementById("video-canvas");
var url = "ws://" + host_ip +":8082/";
var player = new JSMpeg.Player(url, {canvas: mycanvas});
</script>
</body>
</html>
Javascript Player
You can get the javascript player I used from here:
https://github.com/phoboslab/jsmpeg/blob/master/jsmpeg.min.js
I’d like suggestions for NODE.JS packages or other solutions to provide a UDP H264 video stream that can be decoded by an HTML5 video tag with a target latency of 50ms.
That’s Almost certainly not possible in that configuration.
If you drop the video tag requirement, and use just straight WebRTC in the browser, you may be able to get down to about 150ms.
I'm currently unsuccessfully trying to make my PWA installable. I have registered a SertviceWorker and linked a manifest as well as I am listening on the beforeInstallPromt event.
My ServiceWorker is listening to any fetch event.
My problem is, that the created beforeInstall banner is just being shown on Chrome desktop but on mobile I get a warning in Chrome inspection tab "Application" in the "Manifest" section:
Installability
Service worker does not have the 'fetch' handler
You can check the message on https://dev.testapp.ga/
window.addEventListener('beforeinstallprompt', (e) => {
// Stash the event so it can be triggered later.
deferredPrompt = e;
mtShowInstallButton();
});
manifest.json
{"name":"TestApp","short_name":"TestApp","start_url":"https://testapp.ga/loginCheck","icons":[{"src":"https://testapp.ga/assets/icons/launcher-ldpi.png","sizes":"36x36","density":0.75},{"src":"https://testapp.ga/assets/icons/launcher-mdpi.png","sizes":"48x48","density":1},{"src":"https://testapp.ga/assets/icons/launcher-hdpi.png","sizes":"72x72","density":1.5},{"src":"https://testapp.ga/assets/icons/launcher-xhdpi.png","sizes":"96x96","density":2},{"src":"https://testapp.ga/assets/icons/launcher-xxhdpi.png","sizes":"144x144","density":3},{"src":"https://testapp.ga/assets/icons/launcher-xxxhdpi.png","sizes":"192x192","density":4},{"src":"https://testapp.ga/assets/icons/launcher-web.png","sizes":"512x512","density":10}],"display":"standalone","background_color":"#ffffff","theme_color":"#0288d1","orientation":"any"}
ServiceWorker:
//This array should NEVER contain any file which doesn't exist. Otherwise no single file can be cached.
var preCache=[
'/favicon.png',
'/favicon.ico',
'/assets/Bears/bear-standard.png',
'/assets/jsInclude/mathjax.js',
'/material.js',
'/main.js',
'functions.js',
'/material.css',
'/materialcolors.css',
'/user.css',
'/translations.json',
'/roboto.css',
'/sw.js',
'/'
];
//Please specify the version off your App. For every new version, any files are being refreched.
var appVersion="v0.2.1";
//Please specify all files which sould never be cached
var noCache=[
'/api/'
];
//On installation of app, all files from preCache are being stored automatically.
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(appVersion+'-offline').then(function(cache) {
return cache.addAll(preCache).then(function(){
console.log('mtSW: Given files were successfully pre-cached')
});
})
);
});
function shouldCache(url) {
//Checking if url is market as noCache
var isNoCache=noCache.includes(url.substr(8).substr(url.substr(8).indexOf("/")))||noCache.includes((url.substr(8).substr(url.substr(8).indexOf("/"))).substr(0,(url.substr(8).substr(url.substr(8).indexOf("/"))).indexOf("?")));
//Checking of hostname of request != current hostname
var isOtherHost=url.substr(8).substr(0,url.substr(8).indexOf("/"))!=location.hostname&&url.substr(7).substr(0,url.substr(7).indexOf("/"))!=location.hostname;
return((url.substr(0,4)=="http"||url.substr(0,3)=="ftp") && isNoCache==false && isOtherHost==false);
}
//If any fetch fails, it will look for the request in the cache and serve it from there first
self.addEventListener('fetch', function(event) {
//Trying to answer with "online" version if fails, using cache.
event.respondWith(
fetch(event.request).then(function (response) {
if(shouldCache(response.url)) {
console.log('mtSW: Adding file to cache: '+response.url);
caches.open(appVersion+'-offline').then(function(cache) {
cache.add(new Request(response.url));
});
}
return(response);
}).catch(function(error) {
console.log( 'mtSW: Error fetching. Serving content from cache: ' + error );
//Check to see if you have it in the cache
//Return response
//If not in the cache, then return error page
return caches.open(appVersion+'-offline').then(function (cache) {
return cache.match(event.request).then(function (matching) {
var report = !matching || matching.status == 404?Promise.reject('no-match'): matching;
return report
});
});
})
);
})
I checked the mtShowInstallButton function. It's fully working on desktop.
What does this mean? On the Desktop, I never got this warning, just when using a handheld device/emulator.
Fetch function is used to fetch JSon manifest file. Try reading google docs again.
For adding PWA in Mobile you need manifest file to be fetched which is fetched using service-worker using fetch function.
Here is the code :
fetch('examples/example.json')
.then(function(response) {
// Do stuff with the response
})
.catch(function(error) {
console.log('Looks like there was a problem: \n', error);
});
for more about fetch and manifest try this.
I'm wanting to use BrowserSync for some testing and development on a COTS (commercial, of the shelf) system - think like Sharepoint, but it's not Sharepoint.
As this is a COTS system, one of the security features that we cannot disable is that it will only allow one active session per user id. Having multiple browsers synced and trying to login will fail as the COTS system detects more than one user login.
Is there any way to have browser sync treat a window/browser as the 'master' session and simply re-draw the 'slaves' using the response from the master window? As opposed to copying all actions across and causing multiple requests to be sent from different browsers?
I had the same issue here is my solution (I save cookies in BS and use it all the time, its works well for me):
gulp.task('browser-sync', function () {
var cookies = {};
browserSync.init({
proxy: {
target: "localhost",
proxyReq: [
function (proxyReq) {
var cookieText = Object.keys(cookies).map(function (name) {
return name + '=' + cookies[name];
}).join('; ')
if (proxyReq._headers.cookie) {
proxyReq.setHeader('cookie', cookieText);
}
}
],
proxyRes: [
function (proxyRes, req, res) {
if (proxyRes.headers && proxyRes.headers['set-cookie']) {
proxyRes.headers['set-cookie'].forEach(function (cookie) {
var name, value;
var t = cookie.split(';')[0].split('=');
name = t[0];
value = t[1];
cookies[name] = value;
});
}
}
]
}
});
});
I have a little node application that runs and gets stats from an httpprovider
Its returning the value every second in this format:
{ WOWZA_CONNECTIONS_CURRENT: 21,
WOWZA_CONNECTIONS_TOTAL: 4879,
WOWZA_CONNECTIONS_BYTES_IN: 303242,
WOWZA_CONNECTIONS_BYTES_OUT: 96372 }
but I need it to be returning in a different format, exactly like this:
WOWZA_CONNECTIONS_CURRENT 21 Rem-East-v4-Edge-1
WOWZA_CONNECTIONS_TOTAL 4879 Rem-East-v4-Edge-1
WOWZA_CONNECTIONS_BYTES_IN 303242 Rem-East-v4-Edge-1
WOWZA_CONNECTIONS_BYTES_OUT 96372 Rem-East-v4-Edge-1
How would I accomplish this?
Here is my Code:
#!/usr/bin/env node
// Requires.
// request, jsdom, optimist, all of which can be install with npm.
// NOTE: -----------------------------------------------------------------------
// Uses jsdom 0.2.13, 0.2.14 has a bug
// https://github.com/tmpvar/jsdom/issues/436
// Install w/ npm via `npm install jsdom#0.2.13`
// -----------------------------------------------------------------------------
// Require modules.
var request = require('request'),
jsdom = require('jsdom'),
argv = require('optimist').argv;
// Make sure that at least the --uri argument was passed.
if (argv.uri.length == 0) {
console.log('URI Required! Script should be called with one argument which is the URI of the connectioncounts HTTP provider to query.');
return;
}
/**
*
*/
var Collector = {
stats: {},
get_stats: function(uri, callback) {
request({ uri: uri }, function (error, response, body) {
if (error && response.statusCode !== 200) {
console.log('Error when contacting ' + uri);
}
jsdom.env({
html: body,
scripts: [
'http://code.jquery.com/jquery.min.js'
]
}, function (err, window) {
// User jQuery to Gather some stats from the connectioncounts HTTP
// provider.
var $ = window.jQuery;
Collector.stats['WOWZA_CONNECTIONS_CURRENT'] = parseInt($('ConnectionsCurrent').html());
Collector.stats['WOWZA_CONNECTIONS_TOTAL'] = parseInt($('ConnectionsTotal').html());
Collector.stats['WOWZA_CONNECTIONS_BYTES_IN'] = parseFloat($('MessagesInBytesRate').html());
Collector.stats['WOWZA_CONNECTIONS_BYTES_OUT'] = parseFloat($('MessagesOutBytesRate').html());
callback(window);
});
});
},
}
/**
* Wrap Collector.get_stats call in a closure it works better with setInterval.
*/
var callDelay = function() {
Collector.get_stats(argv.uri, function(response) {
// Print out collected stats.
console.log(Collector.stats);
});
}
// Get stats once right away.
callDelay();
// Allo repeating at specified interval if --repeat is set.
if (argv.repeat != undefined) {
// Default to every 30 seconds if no delay is specified.
if (argv.delay == undefined) {
argv.delay = 30000;
}
setInterval(callDelay, argv.delay);
}
EDIT:
I achieved the output i needed by changing the output section of the code link so.
// Print out collected stats.
//console.log(Collector.stats);
console.log('WOWZA_CONNECTIONS_CURRENT ' + Collector.stats.WOWZA_CONNECTIONS_CURRENT + ' Rem-East-v4-Edge-1');
console.log('WOWZA_CONNECTIONS_TOTAL ' + Collector.stats.WOWZA_CONNECTIONS_TOTAL + ' Rem-East-v4-Edge-1')
console.log('WOWZA_CONNECTIONS_BYTES_IN ' + Collector.stats.WOWZA_CONNECTIONS_BYTES_IN + ' Rem-East-v4-Edge-1')
console.log('WOWZA_CONNECTIONS_BYTES_OUT ' + Collector.stats.WOWZA_CONNECTIONS_BYTES_OUT + ' Rem-East-v4-Edge-1')
You don't need to parse this as JSON, it is already there in an object. Just construct a string the way you want it by accessing it via Collector.stats.
I am trying to implement a Voice-only WebRTC app. I am running it on Chrome Version 29.0.1547.0 dev. My app uses Socket.IO for the signaling mechanism.
peerConnection.addIceCandidate() is giving me this error: Uncaught SyntaxError: An invalid or illegal string was specified.
and separately, peerConnection.setRemoteDescription(); is giving me this error: Uncaught TypeMismatchError: The type of an object was incompatible with the expected type of the parameter associated to the object.
Here's my code:
SERVER (in CoffeeScript)
app = require("express")()
server = require("http").createServer(app).listen(3000)
io = require("socket.io").listen(server)
app.get "/", (req, res) -> res.sendfile("index.html")
app.get "/client.js", (req, res) -> res.sendfile("client.js")
io.sockets.on "connection", (socket) ->
socket.on "message", (data) ->
socket.broadcast.emit "message", data
CLIENT (in JavaScript)
var socket = io.connect("http://localhost:3000");
var pc = new webkitRTCPeerConnection({
"iceServers": [{"url": "stun:stun.l.google.com:19302"}]
});
navigator.getUserMedia = navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
navigator.getUserMedia({audio: true}, function (stream) {
pc.addStream(stream);
}, function (error) { console.log(error); });
pc.onicecandidate = function (event) {
if (!event || !event.candidate) return;
socket.emit("message", {
type: "iceCandidate",
"candidate": event.candidate
});
};
pc.onaddstream = function(event) {
var audioElem = document.createElement("audio");
audioElem.src = webkitURL.createObjectURL(event.stream);
audioElem.autoplay = true;
document.appendChild(audioElem);
console.log("Got Remote Stream");
};
socket.on("message", function(data) {
if (data.type === "iceCandidate") {
console.log(data.candidate);
candidate = new RTCIceCandidate(data.candidate);
console.log(candidate);
pc.addIceCandidate(candidate);
} else if (data.type === "offer") {
pc.setRemoteDescription(data.description);
pc.createAnswer(function(description) {
pc.setLocalDescription(description);
socket.emit("message", {type: "answer", description: description});
});
} else if (data.type === "answer") {
pc.setRemoteDescription(data.description);
}
});
function offer() {
pc.createOffer( function (description) {
pc.setLocalDescription(description);
socket.emit("message", {type: "offer", "description": description});
});
};
The HTML just contains a button that calls offer().
I can confirm the ICECandidates and SessionDescriptions are transferring successfully from one client to the other.
What am I doing wrong? And how should I fix these and any other errors so that I can transfer audio from one client to the other?
PS: If you know about a good source documenting the WebRTC API (except the W3C documentation), please tell me about it!
Thanks!
For that error the point is, ICE Candidates must be added only after successfully setting remote description.
Note that just after creating Offer (by Offerer), ice candidates are produced immediately. So, if somehow the answerer receives those candidates, before setting remote description (which in theory would arrive before candidates), you get error.
The same is true for offerer. It must set remote description before adding any ice candidate.
I see that in your javascript code you are not guaranteeing that remote description is set before adding ice candidates.
First of all you can check just before pc.addIceCandidate(candidate); if pc's remoteDescription is set. If you see that it is null (or undefined), you can locally store received ice candidates to add them after setting remoteDescription (or wait in offerer to send them in proper time.)