Firefox Extension/Addon : Reading a text file from remote server(URL) - javascript

After wasting my two days to find out what's going wrong with this script, finally I decide to ask it.
What I am trying to do
I am trying to read a text file from remote server. Then storing all text file updates to an SQLITE database at the time of my Firefox Extension/Addon get loaded.
What I tried
var updatereader = {
start: function () {
//alert('reading update');
var fURL = null;
var ioService = null;
var fURI = null;
var httpChannel = null;
fURL = "http://www.example.com/addon/mlist.txt";
ioService = Components.classes["#mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
fURI = ioService.newURI(fURL, null, null);
httpChannel = ioService.newChannelFromURI(fURI).QueryInterface(Components.interfaces.nsIHttpChannel);
httpChannel.asyncOpen(updatereader.StreamReader, null);
},
onUpdateCompleted: function () {
},
StreamReader:
{
fOutputStream: null,
fPointer: null,
tempFile: "mlist.txt",
onStartRequest: function (aRequest, aContext) {
//alert('onStart');
updatereader.StreamReader.fOutputStream = Components.classes["#mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
updatereader.StreamReader.fPointer = Components.classes["#mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties).get("ProfD", Components.interfaces.nsIFile);
updatereader.StreamReader.fPointer.append(updatereader.StreamReader.tempFile);
updatereader.StreamReader.fOutputStream.init(updatereader.StreamReader.fPointer, 0x02 | 0x20 | 0x08, 0644, 0);
},
onDataAvailable: function (aRequest, aContext, aInputStream, aOffset, aCount) {
//control flow is not entering here - may be here is somehting missing
var sStream = null;
var tempBuffer = null;
sStream = Components.classes["#mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
sStream.init(aInputStream);
tempBuffer = sStream.read(aCount);
updatereader.StreamReader.fOutputStream.write(tempBuffer, aCount);
},
onStopRequest: function (aRequest, aContext, aStatusCode) {
//alert('onStop');
var currentDate = new Date();
if (aStatusCode == 0) {
fileInputStream = Components.classes["#mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
updatereader.StreamReader.fOutputStream.close();
fileInputStream.init(updatereader.StreamReader.fPointer, 0x01, 0, 0);
lineInputStream = fileInputStream.QueryInterface(Components.interfaces.nsILineInputStream);
//pass data to somewhere
var dbH = new dbstore();
dbH.updateData(lineInputStream);
lineInputStream.close();
updatereader.StreamReader.fPointer.remove(false);
updatereader.onUpdateCompleted();
} else {
}
}
}
}
Problem:
Getting nothing in lineInputStream which passes the read data to somewhere else for storing it.
Area of problem:
Program control flow is not entring to this section
onDataAvailable:
Not getting any error.

First of all, there doesn't really seem to be any need to read the file to the disk first (unless it is really, really big).
I'd just use XMLHttpRequest to get the file, which when run from a privileged context (e.g. add-on code, but not a website) can access any and every valid URI.
XMLHttpRequest will simplify almost everything, e.g. no more onDataAvailable, (usually) no more manual text converting, etc.
Also, no need to ever hit the disk during the transfer.
Code would look something like this:
var req = new XMLHttpRequest();
req.open("GET", "http://www.example.com/addon/mlist.txt"); // file:/// would work too, BTW
req.overrideMimeType("text/plain");
req.addEventListener("load", function() {
// Do something with req.responseText
}, false);
req.addEventListener("error", function() {
// Handle error
}, false);
req.send();
If you want to use XMLHttpRequest in a non-window, e.g. js code module or js components, then you need to first initialize a constructor. This is not required for windows, including XUL windows and by that XUL overlays.
// Add XMLHttpRequest constructor, if not already present
if (!('XMLHttpRequest' in this)) {
this.XMLHttpRequest = Components.Constructor("#mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest");
}
SDK users should use the request module, or net/xhr if a lower-level API is required.
PS: If you're still interested in using raw channels, here is a minimal example I coded up in a Scratchpad (to run, open a Scratchpad for a privileged location, e.g. about:newtab).
You shouldn't alert from your own implementation: alert() will spin the event loop and causes reentrant code, which is not supported in this context.
var {
classes: Cc,
interfaces: Ci,
results: Cr,
utils: Cu
} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm")
Cu.import("resource://gre/modules/Services.jsm");
var ConverterStream = Components.Constructor(
"#mozilla.org/intl/converter-input-stream;1",
"nsIConverterInputStream",
"init");
var RC = Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
function Listener() {
this.content = "";
}
Listener.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener]),
onStartRequest: function(req, ctx) {
console.log("start");
},
onDataAvailable: function(req, ctx, stream, offset, count) {
console.log("data", count);
try {
var cs = new ConverterStream(stream, null /* utf-8 */, 4096, RC);
try {
var str = {};
while (cs.readString(4096, str)) {
this.content += str.value;
}
}
finally {
cs.close();
}
}
catch (ex) {
console.error("data", ex.message, ex);
}
},
onStopRequest: function(req, ctx, status) {
console.log("stop", status,
this.content.substr(0, 20), this.content.length);
}
};
var uri = Services.io.newURI("http://example.org", null, null);
Services.io.newChannelFromURI(uri).asyncOpen(new Listener(), null);

Related

Using MP4box.js and onSegment callback is not called

Base problem: display a H264 live stream in a browser.
Solution: let's just convert it to fragmented mp4 and load chunk-by-chunk via websocket (or XHR) into MSE.
Sounds too easy. But I want to do the fragmentation on client side with pure JS.
So I'm trying to use MP4Box.js. On its readme page it states: it has a demo: "A player that performs on-the-fly fragmentation".
That's the thing I need!
However the onSegment callbacks which should feed MSE are not called at all:
var ws; //for websocket
var mp4box; //for fragmentation
function startVideo() {
mp4box = MP4Box.createFile();
mp4box.onError = function(e) {
console.log("mp4box failed to parse data.");
};
mp4box.onMoovStart = function () {
console.log("Starting to receive File Information");
};
mp4box.onReady = function(info) {
console.log(info.mime);
mp4box.onSegment = function (id, user, buffer, sampleNum) {
console.log("Received segment on track "+id+" for object "+user+" with a length of "+buffer.byteLength+",sampleNum="+sampleNum);
}
var options = { nbSamples: 1000 };
mp4box.setSegmentOptions(info.tracks[0].id, null, options); // I don't need user object this time
var initSegs = mp4box.initializeSegmentation();
mp4box.start();
};
ws = new WebSocket("ws://a_websocket_server_which_serves_h264_file");
ws.binaryType = "arraybuffer";
ws.onmessage = function (event) {
event.data.fileStart = 0; //tried also with event.data.byteOffset, but resulted error.
var nextBufferStart = mp4box.appendBuffer(event.data);
mp4box.flush(); //tried commenting out - unclear documentation!
};
}
window.onload = function() {
startVideo();
}
Now putting this into an HTML file would result this in the JavaScript console:
Starting to receive File Information
video/mp4; codecs="avc1.4d4028"; profiles="isom,iso2,avc1,iso6,mp41"
But nothing happens afterwards. Why is the onSegment not called here? (the h264 file which the websocket-server serves is playable in VLC - however it is not fragmented)
The problem was using the nextBufferStart in a wrong way.
This should be the correct one:
var nextBufferStart = 0;
...
ws.onmessage = function (event) {
event.data.fileStart = nextBufferStart;
nextBufferStart = mp4box.appendBuffer(event.data);
mp4box.flush();
};

Creating global VAR in functions

So I'm having trouble with getting a VAR in a function to be global, I have tried the following resources:
What is the scope of variables in JavaScript?
My previous question was marked as a duplicate but after reviewing the link above it did not help with my issue.
Here is my previous question:
So I'm using OpenTok to create a online conferencing tool and need to grab the session details from an API on a different server. I've created a php script on the other server that grabs session information based on the session id provided by a URL parameter. I know that the php script and most of the JavaScript is working correctly because when I console.log data from the parsed JSON it prints the correct information. However when I try to put the variables into the credentials area I get the following error:
ReferenceError: thesession is not defined
Here is the code used to get the JSON from a PHP script on a separate server:
var url_string = window.location.href;
var url = new URL(url_string);
var session = url.searchParams.get("s");
if (session == '') {
window.location.replace("http://www.google.com");
}
var getJSON = function(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
var status = xhr.status;
if (status === 200) {
callback(null, xhr.response);
} else {
callback(status, xhr.response);
}
};
xhr.send();
};
getJSON('http://192.168.64.2/api/meeting/?uid=' + session,
function(err, data) {
if (err !== null) {
console.log('Error');
}
var thesession = data.sessionID;
var thetoken = data.token;
console.log(thesession);
console.log(thetoken);
});
let otCore;
const options = {
credentials: {
apiKey: "####",
sessionId: thesession,
token: thetoken
},
And here is a screenshot of the console:
The top console log is "thesession" and the second console log is "thetoken". I have tried looking up the error but can't quite find one with the same usage as mine.
The desired outcome would be that I could using the data from the parsed JSON and use the result as the credentials e.g. data.sessionID which is bound the the VAR thesession.
I know this might be a scope issue, but I'm not sure how I could alter the code to make it work as intended.
Any help would be much appreciated, this one has really got me stumped :)
How would I alter the scope to get the desired function? I have reviewed the link that was given on the previous question, but this didn't help me with my issue.
var thesession = data.sessionID;
Is defined within its execution context, which is the callback function you've passed to getJSON.
One step in the right direction is to reverse the assignment. Assign 'thesession' to the options object within the scope where 'thesession' exists.
const options = {
credentials: {
apiKey: "####",
sessionId: null,
token: thetoken
}
};
getJSON('http://192.168.64.2/api/meeting/?uid=' + session,
function(err, data) {
if (err !== null) {
console.log('Error');
}
var thesession = data.sessionID;
var thetoken = data.token;
console.log(thesession);
console.log(thetoken);
options.credentials.sessionId = thesession;
});
However, it's important to realize that your program is not going to wait for this assignment. It will send the getJSON request, and then continue processing. Your options object won't have a sessionId until the getJSON call finishes and its callback has been invoked.
This would be a good opportunity to delve into Promises, which will help you better understand how to handle the non-blocking nature of javascript.
Your problem is that this line var thesession = data.sessionID is scoped within the function function(err, data) { ... }. In order to allow two functions to use the same variable, you need to make sure that the variable isn't declared somewhere they don't have access to.
It's the difference between this:
function func1() {
var x = 3
}
function func2() {
console.log(x)
}
func1();
func2();
and this:
var x;
function func1() {
x = 3
}
function func2() {
console.log(x)
}
func1();
func2();
Similarly, if you declare var thesession; at the start of your script (or at least outside that other function) then just set it with thesession = data.sessionID, your final part will have access to your variable thesession.
Edit
In context:
var url_string = window.location.href;
var url = new URL(url_string);
var session = url.searchParams.get("s");
var thesession;
var thetoken;
if (session == '') {
window.location.replace("http://www.google.com");
}
var getJSON = function(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
var status = xhr.status;
if (status === 200) {
callback(null, xhr.response);
} else {
callback(status, xhr.response);
}
};
xhr.send();
};
getJSON('http://192.168.64.2/api/meeting/?uid=' + session,
function(err, data) {
if (err !== null) {
console.log('Error');
}
thesession = data.sessionID;
thetoken = data.token;
console.log(thesession);
console.log(thetoken);
});
let otCore;
const options = {
credentials: {
apiKey: "####",
sessionId: thesession,
token: thetoken
},
As a side-note - I'd also recommend not using var and instead just using let of const, depending on if you want your variable to be mutable or not.

Firefox add-on get the DOM window which made an HTTP request

I'm capturing the HTTP requests in a Firefox Add-on SDK extension. I need to get the DOM window associated with the request. However, I'm getting an NS_NOINTERFACE error.
Here is my code:
var httpRequestObserver = {
observe: function (subject, topic, data) {
var httpRequest = subject.QueryInterface(Ci.nsIHttpChannel);
var requestUrl = subject.URI.host;
var domWin;
var assWindow;
console.log('URL: ', requestUrl);
try {
domWin = httpRequest.notificationCallbacks.getInterface(Ci.nsIDOMWindow);
assWindow = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext)
.associatedWindow;
console.log(domWin);
} catch (e) {
console.log(e);
}
// console.log('TAB: ', tabsLib.getTabForWindow(domWin.top));
var hostName = wn.domWindow.getBrowser().selectedBrowser.contentWindow.location.host;
console.log('HOST: ', hostName);
},
get observerService() {
return Cc['#mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
},
register: function () {
this.observerService.addObserver(this, 'http-on-modify-request', false);
},
unregister: function () {
this.observerService.removeObserver(this, 'http-on-modify-request');
}
};
httpRequestObserver.register();
I've tried both nsIDOMWindow and nsILoadContext, but NS_NOINTERFACE error always appears on an attempt to get the window object.
I have finally managed to get the data I need via
httpRequest.notificationCallbacks.getInterface(Ci.nsILoadContext).topFrameElement
For example, to get url of the document which started the request, I used
httpRequest.notificationCallbacks.getInterface(Ci.nsILoadContext).topFrameElement._documentURI.href
Since you already found how to get the <browser> from the request you can do the following to get back to SDK APIs:
let browser = ....topFrameElement
let xulTab = browser.ownerDocument.defaultView.gBrowser.getTabForBrowser(browser)
let sdkTab = modelFor(xulTab)
modelFor() is documented in the tabs module.

Ping with javascript and read the errors

I'm basically following the accepted answer to this question (Is it possible to ping a server from Javascript?)
Update
It seems to work as expected when the domain is 15 characters long (actually, http:// + 15, but 16 or more causes it to bomb. More details at the bottom.
The issue I'm seeing is that if you're using something that seems like a valid domain, for example http://thisisdefinitelynotarealdomainname.com, it returns an error but the code mentioned considers errors okay (because most should be). Looking at the error event, I'm not sure I see where I could get the HTTP response code (i.e., if it's a 404, consider it invalid).
Here is a jsFiddle showing the problem -- they all display "responded". If you look in the console, the invalid domain returns a 404 error, and the two valid ones (if in chrome console, not sure about the others) show that they were interpreted as an image but transferred as text/html -- is there any way to read either the 404 error, or the mime type?
var pinger = function () {
var ping = function (ip, callback) {
if (!this.inUse) {
this.status = 'unchecked';
this.inUse = true;
this.callback = callback;
this.ip = ip;
var _that = this;
this.img = new Image();
this.img.onload = function () {
_that.inUse = false;
_that.callback('responded');
};
this.img.onerror = function (e) {
if (_that.inUse) {
_that.inUse = false;
_that.callback('responded', e);
}
console.log(e);
};
this.start = new Date().getTime();
this.img.src = "http://" + ip + "/?now=" + this.start; // add the current time to work around caching
this.time = setTimeout(function () {
if (_that.inUse) {
_that.inUse = false;
_that.callback('timeout');
}
}, 1500);
}
}
return {
ping: ping
};
}();
(function () {
var output = document.getElementById('output');
var servers = [
'localhost',
'google.com',
'okthisreallydoesntmakeanysense',
'okthisreallydoe',
'thisisashortone',
'thisisabitlonger'
];
servers.forEach(function (server) {
new pinger.ping(server, function (status, e) {
output.innerHTML += server + ': ' + status + '<br />';
});
});
})();
Update
What's even more weird is that it seems to be fine up until 15 characters. I've updated the jsFiddle. See below on ones that respond how I'd expect vs ones that don't. What might cause this?
'localhost',
'google.com',
'okthisreallydoesntmakeanysense', // doesn't work
'okthisreallydoe', // works (15 characters)
'thisisashortone', // works (15 characters)
'thisisabitlonger' // doesn't work (16 characters)
This might help.
function Pinger_ping(ip, callback) {
if(!this.inUse) {
this.inUse = true;
this.callback = callback
this.ip = ip;
var _that = this;
this.img = new Image();
this.img.onload = function() {_that.good();};
this.img.onerror = function() {_that.good();};
this.start = new Date().getTime();
this.img.src = "http://" + ip;
this.timer = setTimeout(function() { _that.bad();}, 1500);
}
}
Let me know if it works

Post header in extension firefox

Iam building an extension where i catch all the post requests. But in the httpChannel.originalURI.spec there aren't any attributes from the post. How can i get the post's atrtibutes?
myObserver.prototype = {
observe: function(subject, topic, data) {
if("http-on-modify-request"){
var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
if(httpChannel.requestMethod=="POST")
alert(httpChannel.originalURI.spec);
}
}
},
register: function() {
var observerService = Components.classes["#mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "http-on-modify-request", false);
},
unregister: function() {
var observerService = Components.classes["#mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this, "http-on-modify-request");
}
}
Any ideas?
nsIHttpChannel only provides access to HTTP headers. POST data is sent as part of the request body so you need to change your object interface to nsIUploadChannel and read the binary upload data into a string.
var uploadChannel = httpChannel.QueryInterface(Ci.nsIUploadChannel);
var uploadStream = uploadChannel.uploadStream;
uploadStream.QueryInterface(Ci.nsISeekableStream).
seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
var binStream = Cc["#mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
binStream.setInputStream(uploadStream);
var postBytes = binStream.readByteArray(binStream.available());
var postString = String.fromCharCode.apply(null, postBytes);
The code from Luckyrat did not work properly for me. I had to deal with some requests timing out. Noticing nmaiers comment this code is working correctly (as far as I can tell):
function getPostString(httpChannel) {
var postStr = "";
try {
var uploadChannel = httpChannel.QueryInterface(Ci.nsIUploadChannel);
var uploadChannelStream = uploadChannel.uploadStream;
if (!(uploadChannelStream instanceof Ci.nsIMultiplexInputStream)) {
uploadChannelStream.QueryInterface(Ci.nsISeekableStream).seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
var stream = Cc["#mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
stream.setInputStream(uploadChannelStream);
var postBytes = stream.readByteArray(stream.available());
uploadChannelStream.QueryInterface(Ci.nsISeekableStream).seek(0, 0);
postStr = String.fromCharCode.apply(null, postBytes);
}
}
catch (e) {
console.error("Error while reading post string from channel: ", e);
}
finally {
return postStr;
}
}

Categories

Resources