Electron & ReactJS, Use BrowserWindow for GitHub oAuth authentication - oauth

I have set up github's Electron with ReactJs. So I got a BrowserWindow and a react app playing nicely in that window. What I'm trying to achieve is to get authenticated with GitHub. So when a user presses the Login with Github button, a new BrowserWindow opens and goes to the github authorize app url. The issue I have has to do with the callback and how I will get the code returned from the callback. I've done it with Apache Cordova and the InAppBrowser but it was different since I was able to use localhost as a callback.
What I've done so far with electron is opening the new BrowserWindow but after the authorization I cannot get the code from the callback.
var authWindow = new BrowserWindow({ width: 800, height: 600, show: true, 'always-on-top': true });
var githubUrl = 'https://github.com/login/oauth/authorize?';
var authUrl = githubUrl + 'client_id=' + options.client_id + '&scope=' + options.scope;
authWindow.loadUrl(authUrl);
authWindow.setVisibleOnAllWorkspaces(true);
authWindow.setResizable(false);
authWindow.addListener('page-title-updated', function(stream) {
console.log("LOADED");
console.log(JSON.stringify(stream));
console.log(stream);
var url = (typeof stream.url !== 'undefined' ? stream.url : stream.originalEvent.url),
raw_code = /code=([^&]*)/.exec(stream.url) || null,
code = (raw_code && raw_code.length > 1) ? raw_code[1] : null,
error = /\?error=(.+)$/.exec(strean.url);
if (code || error) {
authWindow.close();
}
// If there is a code in the callback, proceed to get token from github
if (code) {
// requestToken(code);
} else if (error) {
alert("Oops! Couldn't log authenticate you with using Github.");
}
});
Where I'm doing console.log(JSON.stringify(stream)); I get {} so it's something that has to do the the eventListener? Any ideas or better approaches?

So what I was missing was the right event. The correct approach is:
// Build the OAuth consent page URL
var authWindow = new BrowserWindow({ width: 800, height: 600, show: false, 'node-integration': false });
var githubUrl = 'https://github.com/login/oauth/authorize?';
var authUrl = githubUrl + 'client_id=' + options.client_id + '&scope=' + options.scopes;
authWindow.loadUrl(authUrl);
authWindow.show();
// Handle the response from GitHub
authWindow.webContents.on('did-get-redirect-request', function(event, oldUrl, newUrl) {
var raw_code = /code=([^&]*)/.exec(newUrl) || null,
code = (raw_code && raw_code.length > 1) ? raw_code[1] : null,
error = /\?error=(.+)$/.exec(newUrl);
if (code || error) {
// Close the browser if code found or error
authWindow.close();
}
// If there is a code in the callback, proceed to get token from github
if (code) {
requestGithubToken(options, code);
} else if (error) {
alert("Oops! Something went wrong and we couldn't log you in using Github. Please try again.");
}
});
// Reset the authWindow on close
authWindow.on('close', function() {
authWindow = null;
}, false);
I also wrote a tutorial that describes the full implementation which can be found at http://manos.im/blog/electron-oauth-with-github/

Related

Ajax calls working in Android but not iOS

I'm developing an app in Nativescript for the first time and running into an issue where AJAX calls work on Android but not iOS. I have a login.js file which requires a user-view-model (user-view-model.js), and when I test the code on Android it takes me to the "home" page but it hits the catch function on iOS.
login.js:
var dialogsModule = require("ui/dialogs");
var UserViewModel = require("../../shared/view-models/user-view-model");
var applicationSettings = require("application-settings");
var user = new UserViewModel({
email: "aaa#aaa.com",
password: "aaa"
});
var frameModule = require("ui/frame");
var page;
exports.loaded = function(args) {
page = args.object;
page.bindingContext = user;
};
exports.login = function () {
user.login().catch(function(error) {
dialogsModule.alert({
message: "Unfortunately we could not find your account.",
okButtonText: "OK"
});
return Promise.reject();
}).then(function(response) {
console.dir(response)
console.log("past response")
applicationSettings.setString("user_id", response.user_id);
applicationSettings.setString("first_name", response.first_name);
applicationSettings.setString("last_name", response.last_name);
applicationSettings.setString("user_type", response.user_type);
var topmost = frameModule.topmost();
topmost.navigate("views/home/home");
});
};
user-view-model.js:
var config = require("../../shared/config");
var fetchModule = require("fetch");
var observableModule = require("data/observable");
var http = require("http");
function User(info) {
info = info || {};
var viewModel = new observableModule.fromObject({
email: info.email || "",
password: info.password || ""
});
viewModel.login = function() {
let loginEmail = JSON.stringify(this.get("email")).replace(/['"]+/g, '');
let loginPassword = JSON.stringify(this.get("password")).replace(/['"]+/g, '');
console.log(loginEmail, loginPassword);
let loginUrl = config.serverPHPServiceUrl + "Login.php?user_id=" + loginEmail + "&password=" + loginPassword;
console.log(loginUrl);
// I tried this way first and wasn't able to login on iOS, which made me try the second method below.
// return fetchModule.fetch(loginUrl, {
// method: "POST",
// headers: {
// "Content-Type": "application/json"
// }
// }).then(handleErrors).then(function(response) {
// return response.json();
// }).then(function(data) {
// console.dir(data);
// console.log(data["results"][0]["user_id"])
// return data["results"][0];
// });
// This method works on Android but not iOS.
return http.getJSON(loginUrl).then(function(response) {
console.dir(response);
return response.results[0];
})
};
return viewModel;
};
function handleErrors(response) {
console.log("in errors")
if (!response.ok) {
console.log(JSON.stringify(response));
throw Error(response.statusText);
}
return response;
}
module.exports = User;
Is there anything fundamentally wrong with my code, or do asynchronous calls work differently on iOS vs Android in Nativescript? I did the Grocery tutorial and didn't run into this issue, so I didn't think this was the case. Does it matter that the backend is using PHP?
I fixed my issue: I started a new project with Angular 2 and ran into the same error, but then it gave me the error message "Error: The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." I solved it by adding "https" to my url call, but this post has another solution.

Azure Media Player on iOS devices

I'm streaming O365 videos using Azure Media Player in a web app that must be used only in mobile devices. It works with WP and Android, but the player stuck on iOS.
This is my code
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + bearer);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync($"{url}/GetPlaybackUrl('1')");
var content = await response.Content.ReadAsStringAsync();
var firstVal = JsonConvert.DeserializeObject<VideoToken>(content);
response = await client.GetAsync($"{url}/GetStreamingKeyAccessToken");
content = await response.Content.ReadAsStringAsync();
var secondVal = JsonConvert.DeserializeObject<VideoToken>(content);
Client Side
<video id="newsVideoAMP" class="azuremediaplayer amp-default-skin amp-big-play-centered" tabindex="0"></video>
var initVideoPlayer = function (playbackUrl, streamingKeyAccessToken) {
try {
var myOptions = {
"nativeControlsForTouch": false,
controls: true,
autoplay: false,
techOrder: ["azureHtml5JS", "flashSS", "html5FairPlayHLS", "silverlightSS", "html5"],
logo: { enabled: false }
}
newsVideoPlayer = amp("newsVideoAMP", myOptions,
function () {
this.addEventListener(amp.eventName.error, function () {
window.alert('error');
console.log('Error: amp init');
var errorDetails = newsVideoPlayer.error();
window.alert(errorDetails);
var code = errorDetails.code;
var message = errorDetails.message;
$("#log").append("<li><span>code: " + code + " - detail: " + message + "</span></li>')");
});
});
newsVideoPlayer.src([
{
"src": playbackUrl,
"type": "application/vnd.ms-sstr+xml",
"protectionInfo": [
{
"type": "AES",
"authenticationToken": streamingKeyAccessToken
}
]
}]);
}
catch (err) {
console.log(err);
}
}
I think the issue is related to video encoding. So I tried to use GetPlaybackUrl('0') (and avoid the next token request), but the player stops to work on WP and Android and still not work on iOS.
The logger in callback function doesn't tell me some useful and I have also tried to change the tech order.
Is there a console to manage video encoding in order to avoid the AES algorithm and the decrypt token? Because this doc explain that iOS works with HTML5 tech will no token request. How can I solve? Thanks
I found a workaround to reproduce videos on iOS devices.
Instead of use REST API I put an iframe element in my page with the video embedded.
Like this (using MVC Razor):
var url = "{YourWebsiteUrl}/portals/hub/_layouts/15/VideoEmbedHost.aspx?chId={YourChannelId}&vId={YourVideoId}&width=853&height=480&autoPlay=false&showInfo=false";
<iframe width=853 height=480 id="videoframe" src="#url" allowfullscreen data-spresponsive style='position: absolute; top: 0; left: 0; right: 0; bottom: 0; height: 100%; max-width: 100%;'></iframe>
I get this code from the popup of "embed" menu in the video page of Office365 Video.
If somebody else knows another (and better) method, please let me know. Thanks

Cordova / Ionic - Download file from InAppBrowser

The scenario goes like this: I open a website in InAppBrowser, after the user ends with the work over there, the site generates a .pdf for the user to download, the problem is that the pdf does not download, it opens it in the browser.
Is there a way to make it download from the InAppBrowser? I'm currently working on an iOS app, so the solution would be better for iOS.
Thanks in advance.
Following #jcesarmobile advices this is what I came up with:
First I had to install the cordova-plugin-file-transfer
Open URL
var url = "http://mi-fancy-url.com";
var windowref = window.open(url, '_blank', 'location=no,closebuttoncaption=Cerrar,toolbar=yes,enableViewportScale=yes');
Create a listener on that windowref for a loadstart event and check if what's being loaded is a pdf (that's my case).
windowref.addEventListener('loadstart', function(e) {
var url = e.url;
var extension = url.substr(url.length - 4);
if (extension == '.pdf') {
var targetPath = cordova.file.documentsDirectory + "receipt.pdf";
var options = {};
var args = {
url: url,
targetPath: targetPath,
options: options
};
windowref.close(); // close window or you get exception
document.addEventListener('deviceready', function () {
setTimeout(function() {
downloadReceipt(args); // call the function which will download the file 1s after the window is closed, just in case..
}, 1000);
});
}
});
Create the function that will handle the file download and then open it:
function downloadReceipt(args) {
var fileTransfer = new FileTransfer();
var uri = encodeURI(args.url);
fileTransfer.download(
uri, // file's uri
args.targetPath, // where will be saved
function(entry) {
console.log("download complete: " + entry.toURL());
window.open(entry.toURL(), '_blank', 'location=no,closebuttoncaption=Cerrar,toolbar=yes,enableViewportScale=yes');
},
function(error) {
console.log("download error source " + error.source);
console.log("download error target " + error.target);
console.log("upload error code" + error.code);
},
true,
args.options
);
}
The problem i'm facing now is the path where it downloads, I just can't open it. But well, at least file is now downloaded. I will have to create a localStorage item to save the paths for different files.
Many validations are missing in this steps, this was just an example I made quickly to check if it works. Further validations are needed.
Open you window using IAB plugin and add an event listener
ref = window.open(url, "_blank");
ref.addEventListener('loadstop', loadStopCallBack);
In the InAppBrowser window call the action using https://xxx.pdf">documentName
Implement the loadStopCallBack function
function loadStopCallBack(refTemp) {
if(refTemp.url.includes('downloadDoc')) {
rtaParam = getURLParams('downloadDoc', refTemp.url);
if(rtaParam != null)
downloadFileFromServer(rtaParam);
return;
}
}
function getURLParams( name, url ) {
try {
if (!url)
url = location.href;
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(url);
return results == null ? null : results[1];
} catch (e) {
showSMS(e);
return null;
}
}
After create a download method
function downloadFileFromServer(fileServerURL){
try {
var Downloader = window.plugins.Downloader;
var fileName = fileServerURL.substring(fileServerURL.lastIndexOf("/") + 1);
var downloadSuccessCallback = function(result) {
console.log(result.path);
};
var downloadErrorCallback = function(error) {
// error: string
console.log(error);
};
//TODO cordova.file.documentsDirectory for iOS
var options = {
title: 'Descarga de '+ fileName, // Download Notification Title
url: fileServerURL, // File Url
path: fileName, // The File Name with extension
description: 'La descarga del archivo esta lista', // Download description Notification String
visible: true, // This download is visible and shows in the notifications while in progress and after completion.
folder: "Download" // Folder to save the downloaded file, if not exist it will be created
};
Downloader.download(options, downloadSuccessCallback, downloadErrorCallback);
} catch (e) {
console.log(e);
}
}
you can get the plugin here https://github.com/ogarzonm85/cordova-plugin-downloader
it Works and was too easy

Firefox addon which refreshes the tap and auto resend data

I'm trying make a firefox add-on, which reloads a page automatically under some conditions.
First I add a contentScript to a tab to get some information on the page in my addon
tab.attach({
contentScript:self.port.emit...
I have it working to the point of the refresh with
tab.reload();
but then an alert pops up every time "if it should resend data".
I want to resend the data automatically.
How and where do I do it? In the add-on or in the contentScript?
Has it to do with the Load Flags constant?
This clicks the accept button when the prompt opens. But you see the prompt window for like a couple hundred ms.
i had to use setTimeout with 0 wait time other wise the aDOMWindow.args property and aDOMWindow.Dialog and a bunch of other stuff would be undefined or null so weird. But this works:
var stringBundle = Services.strings.createBundle('chrome://browser/locale/appstrings.properties');
try {
windowListener.unregister();
} catch (ignore) {}
var windowListener = {
//DO NOT EDIT HERE
onOpenWindow: function(aXULWindow) {
// Wait for the window to finish loading
let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
aDOMWindow.addEventListener('load', function() {
aDOMWindow.removeEventListener('load', arguments.callee, false);
windowListener.loadIntoWindow(aDOMWindow, aXULWindow);
}, false);
},
onCloseWindow: function(aXULWindow) {},
onWindowTitleChange: function(aXULWindow, aNewTitle) {},
register: function() {
// Load into any existing windows
let XULWindows = Services.wm.getXULWindowEnumerator(null);
while (XULWindows.hasMoreElements()) {
let aXULWindow = XULWindows.getNext();
let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
windowListener.loadIntoWindow(aDOMWindow, aXULWindow);
}
// Listen to new windows
Services.wm.addListener(windowListener);
},
unregister: function() {
// Unload from any existing windows
let XULWindows = Services.wm.getXULWindowEnumerator(null);
while (XULWindows.hasMoreElements()) {
let aXULWindow = XULWindows.getNext();
let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
windowListener.unloadFromWindow(aDOMWindow, aXULWindow);
}
//Stop listening so future added windows dont get this attached
Services.wm.removeListener(windowListener);
},
//END - DO NOT EDIT HERE
loadIntoWindow: function(aDOMWindow, aXULWindow) {
if (!aDOMWindow) {
return;
}
if (aDOMWindow.location == 'chrome://global/content/commonDialog.xul') {
var repostString = stringBundle.GetStringFromName('confirmRepostPrompt');
var repostStringFormatted = stringBundle.formatStringFromName('confirmRepostPrompt', [aDOMWindow.Application.name], 1);
aDOMWindow.setTimeout(function() {
console.log('setimeout val 00:', aDOMWindow.args)
//aDOMWindow.args and aDOMWindow.Dialog is not available till after setTimeout of 0 so weird
if (aDOMWindow.args.text == repostString || aDOMWindow.args.text == repostStringFormatted) {
console.log('this is resend prompt so accept it');
//aDOMWindow.Dialog.ui.button0.click(); //doesnt work
//aDOMWindow.Dialog.onButton0(); // doesnt work
//aDOMWindow.ondialogaccept(); //doesnt work
var dialog = aDOMWindow.document.getElementById('commonDialog');
var btnAccept = aDOMWindow.document.getAnonymousElementByAttribute(dialog, 'dlgtype', 'accept');
btnAccept.click();
console.log('clicked');
}
}, 0);
}
},
unloadFromWindow: function(aDOMWindow, aXULWindow) {
if (!aDOMWindow) {
return;
}
}
};
windowListener.register();
open scratchpad. set environemnt to browser. run the code. refresh a page to get prompt you'll see it get clicked.

Loading script after page fully loaded

I am building a firefox addon that loads javascript at every page load. I'm using progress listener function I found on this page: https://developer.mozilla.org/en/Code_snippets/Progress_Listeners
My problem is that the code seems to execute to early before the page is fully loaded which causes my script to not run. Here is my code.
var PageLoad = {
browser: null,
domain: null,
oldURL: null,
init: function() {
gBrowser.addProgressListener(urlBarListener,Components.interfaces.nsIWebProgress.NOTIFY_LOCATION);
},
uninit: function() {
gBrowser.removeProgressListener(urlBarListener);
},
processNewURL: function(aURI) {
//if (aURI.spec == this.oldURL)
//return;
MyObject.function();
this.oldURL = aURI.spec;
}
};
var urlBarListener = {
locChange: false,
QueryInterface: function(aIID) {
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
},
onLocationChange: function(aProgress, aRequest, aURI) {
PageLoad.processNewURL(aURI);
},
onStateChange: function(aWebProgress, aRequest, aFlag, aStatus) {},
onProgressChange: function(a, b, c, d, e, f) {},
onStatusChange: function(a, b, c, d) {},
onSecurityChange: function(a, b, c) {}
};
window.addEventListener("load",
function() {
PageLoad.init()
}, false);
var MyObject = {
function : function() {
var script = PageLoad.browser.createElement('script');
script.src = 'url_to_script.js';
PageLoad.browser.getElementsByTagName('head')[0].appendChild(script);
}
};
With this code I get this error message on the console:
PageLoad.browser.getElementByTagName("head")[0] is undefined
If I add a timeout then the script does work intermittently but if the page loads slow I get the same error, here's what works sometimes setTimeout(MyObject.function, 1000);
I need a more reliable way of making sure it's executing the script after the page is loaded.
Unrelated, and it doesn't seem to cause any problems but I also see this error message:
gBrowser.addProgressListener was called with a second argument, which is not supported. See bug 608628.
If you want to load javascript at every page load - the best way is subscribing to DOMContentLoaded event:
var MyObject = {
processDOMContentLoaded: function(doc) {
var script = doc.createElement('script');
script.src = 'url_to_script.js';
script.type = 'text/javascript';
doc.getElementsByTagName('head')[0].appendChild(script);
}
};
window.addEventListener('load', function() {
var appcontent = document.getElementById('appcontent');
if(appcontent != null) {
appcontent.addEventListener('DOMContentLoaded', function(event) {
var doc = event.originalTarget;
if(doc instanceof HTMLDocument) {
MyObject.processDOMContentLoaded(doc);
}
}, true);
}
}, false);
Have not tested, but should work.
You are using onLocationChange method - but if you look at how the browser behaves, the location in the address bar changes as soon as a connection with the server is established. You should look at state changes instead:
onStateChange: function(aWebProgress, aRequest, aFlag, aStatus)
{
if ((aFlag & Components.interfaces.nsIWebProgressListener.STATE_STOP) &&
(aFlag & Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW))
{
// A window finished loading
PageLoad.windowLoaded(aWebProgress.DOMWindow);
}
},
Note that the window that finished loading is explicitly passed to PageLoad.windowLoaded() - you will be receiving notifications from different tabs and you cannot assume that the notification comes from the foreground tab.
As to the warning you are getting, just leave out the second parameter in the call to gBrowser.addProgressListener():
gBrowser.addProgressListener(urlBarListener);
tabbrowser.addProgressListener() only accepts one parameter, unlike nsIWebProgress.addProgressListener() which has a second parameter.
Actually its a great question.
You should use event listener, but carefully, because if you trigger for every page load its can trigger you more than one time (in the worst case about dozens of times).
So how you can do that?
window.addEventListener("load", function load(event){
window.removeEventListener("load", load, false); //remove listener, no longer needed
myExtension.init();
},false);
var myExtension = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent){
appcontent.addEventListener("DOMContentLoaded", myExtension.onPageLoad, true);
}
},
onPageLoad: function(aEvent) {
var doc = aEvent.originalTarget; // doc is document that triggered the event
var win = doc.defaultView; // win is the window for the doc
if (doc.nodeName != "#document") return;
if (win != win.top) return;
if (win.frameElement) return;
alert("the main page has been loaded");
},
};
get notice that we check for the type of the trigger every pageload triggering to prevent multi load.
The answers that were provided were acceptable but I found yet another solution that works perfectly.
var PageLoad = {
init: function() {
if(gBrowser) gBrowser.addEventListener("DOMContentLoaded", this.onPageLoad, false);
},
onPageLoad: function(aEvent) {
var doc = aEvent.originalTarget; // doc is document that triggered the event
var win = doc.defaultView; // win is the window for the doc
if (doc.nodeName != "#document") return;
if (win != win.top) return;
if (win.frameElement) return;
MyAddon.function();
}
}
window.addEventListener("load", function load(event){
window.removeEventListener("load", load, false); //remove listener, no longer needed
PageLoad.init();
},false);

Resources