XMPP: AngularJs + Strophe.js - javascript

I have a basic XMPP client working on strophe.js.
On login I create handlers such as
connect = new Strophe.Connection('http://localhost/http-bind');
...
...
connect.addHandler(on_message, null, "message", "chat");
connect.addHandler(on_presence, null, "presence");
...
...
and then I "listen" to those
function on_presence(presence) {
// handling presence
}
function on_message(presence) {
// handling presence
}
So I am trying to "convert" it into AngularJS. The first part is pretty straight forward. I have a controller which handles the login part just fine:
angular.module('app').controller('loginCtrl', function($scope) {
connect = new Strophe.Connection('http://website.com/http-bind');
connect.connect(data.jid, data.password, function (status) {
if (status === Strophe.Status.CONNECTED) {
connect.addHandler(on_message, null, "message", "chat");
connect.addHandler(on_presence, null, "presence");
}
}
})
But how do I actually begin listening to those events (on_message, on_presence) in the context of angular across all the controllers I have.

Wrap Strophe in an Angular Service. Angular Services are meant to be use as singletons, so you will be able to instantiate the Strophe Service once, and use it everywhere (using Dependency Injection).

As suggested above (or bellow) I wrapped strophe in a service so my login "mechanism" looks like this:
.controller('loginCtrl', function(xmppAuth) {
xmppAuth.auth(login, password);
})
any my service:
.service('xmppAuth', function() {
return {
auth: function(login, password) {
connect = new Strophe.Connection('http://mydomain.net/http-bind');
connect.connect(login, password, function (status) {
if (status === Strophe.Status.CONNECTED) {
// we are in, addHandlers and stuff
}
}
}
}
})

Or may be you can create a module for Strophe and then include the module in your app, and then include strophe as a variable where ever you want to use it.

Related

Unable to bind 'this' to callback function of AcquireTokenSilent - Azure AD MSAL & ReactJS

I am setting up authentication in ReactJS app using AzureAD MSAL. I am able to obtain id_token and access_token. But while getting access token, I am not able tot refer to local variables via this keyword. I tried to bind 'this' to the call back function but that leads to other issues.
I am implementing all the login functionality as a class.
import { UserAgentApplication } from "msal";
export default class AuthService {
constructor() {
this.applicationConfig = {
clientID: "<clientId>",
authority: "<azureADTenantUrl>"
};
this.scopes = [
"openid",
"<Other scopes>"
];
this.client = new UserAgentApplication(
this.applicationConfig.clientID,
this.applicationConfig.authority,
this.authCallback,
{
redirectUri: "http://localhost:3000/"
}
);
}
login = () => {
this.client.loginRedirect(this.scopes);
};
logout = () => {
this.client.logout();
};
authCallback = (erroDesc, token, error, tokenType) => {
if (tokenType == "id_token") {
this.acquireTokenSilent(this.scopes).then(
function(accessToken) {
console.log(accessToken);
},
function(error) {
console.log(error);
}
);
}
};
}
(this is not the actual error message, but a friendly description)
this.scopes is undefined as 'this' is scoped to UserAgentApplication.
to avoid this, I tried to bind the this to the callback function. I have added the following statement in the constructor.
this.authCallback = this.authCallback.bind(this);
this leads to another error.
(this is not the actual error message, but a friendly description)
this.acquireTokenSilent is undefined and 'this' do not have a definition for client to reference using this.client.acquireTokenSilent
So I have hard coded the scopes in the original code and was able to get access token, but again same problem in the call back. This time 'this' is null in the call back.
I tried to move the authCallback to the react component and pass it as a parameter to the service, but that also has same kind of problems.
Any help with how to configure this properly is really appreciated. thanks.
Try this replacement for authCallback. It doesn't entirely solve the problem, but can get you past the UserAgentApplication's hijacking of "this" object.
authCallback = (erroDesc, token, error, tokenType) => {
const client = window.client as Msal.UserAgentApplication;
if (tokenType == "id_token") {
client.acquireTokenSilent(["openid"]).then(
function(accessToken) {
console.log(accessToken);
},
function(error) {
console.log(error);
}
);
}
};
Alternatively, use the loginPopup function instead of loginRedirect as it does not have "this" problem that still exists in current MSAL v0.2.3.
I was able to get it working in msal 1.1.1. Try this way:
this.msalClient = new Msal.UserAgentApplication(config);
this.msalClient.handleRedirectCallback(authCallback.bind(this));
function authCallback(error,response)
{
if (response.tokenType === 'access_token' && response.accessToken)
{
this.accesstoken = response.accessToken;
}
}

Properly retrieve username and useful values (site title, copyright, etc.)

I have a simple web app based on this project ( https://github.com/arthurkao/angular-drywall ), running with NodeJS and AngularJS as the front-end.
I'm trying to set up a simple page that displays a list of all connected users on a map (using Google Maps, Geolocation and PubNub).
Here's how I'm actually doing it:
angular.module('base').controller('TravelCtrl',
function($rootScope, $scope, NgMap, security, $geolocation, PubNub){
$rootScope.extusers = []; //remote users
$scope.initTravel = function() { //declare the init function
PubNub.init({
subscribe_key: $rootScope.security.keys.psk,
publish_key: $rootScope.security.keys.ppk,
uuid: $rootScope.security.currentUser.username,
ssl: true
});
PubNub.ngSubscribe({
channel: "travel",
state: {
position: {},
}
});
console.log("Loaded Travel");
$geolocation.getCurrentPosition({
timeout: 60000
}).then(function(position) { //when location is retreived
$scope.position = position;
PubNub.ngSubscribe({
channel: "travel",
state: {
position: {
lat: Math.floor($scope.position.coords.latitude*1000)/1000, //decrease accuracy
long: Math.floor($scope.position.coords.longitude*1000)/1000,
},
}
});
$rootScope.$on(PubNub.ngPrsEv("travel"), function(event, payload) {
$scope.$apply(function() {
$scope.extusers = PubNub.ngPresenceData("travel");
});
});
PubNub.ngHereNow({ channel: "travel" });
$scope.showInfo = function(evt, marker) { //show user window on map
$scope.extuser = marker;
$scope.showInfoWindow('infoWindow');
};
});
};
if ($rootScope.hasLoaded()) { //if username and keys are already loaded, then init module
$scope.initTravel();
} else { //else, wait for username and keys to be loaded
$rootScope.$on('info-loaded', function(event, args) {
$scope.initTravel();
});
}
}
);
Although it works, it seems like it's very buggy and only loads sometimes. Occasionally, I get this:
Result screenshot
I really don't know what I'm doing wrong, as I simply followed the tutorials on PubNub's AngularJS SDK.
I think this has to do with how I'm initialising the application.
angular.module('app').run(['$location', '$rootScope', 'security', function($location, $rootScope, security) {
// Get the current user when the application starts
// (in case they are still logged in from a previous session)
$rootScope.hasLoaded = function() {
return (security.keys && security.info && security.currentUser); //check if everything is loaded correctly
};
$rootScope.checkLoading = function() {
if ($rootScope.hasLoaded()) {
$rootScope.$broadcast('info-loaded'); //broadcast event to "TravelCtrl" in order to init the module
}
};
security.requestKeys().then($rootScope.checkLoading); //request secret keys
security.requestSiteInfo().then($rootScope.checkLoading); //then templating info (site title, copyright, etc.)
security.requestCurrentUser().then($rootScope.checkLoading); //and finally, current user (name, id, etc.)
$rootScope.security = security;
// add a listener to $routeChangeSuccess
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
$rootScope.title = current.$$route && current.$$route.title? current.$$route.title: 'Default title';
});
}]);
1- Request secret keys, site info and current user with JSON API.
2- Wait until everything's loaded then init the application with the appropriate keys (PubNub, Google Maps)
--
My question is:
How do you instantiate an AngularJS app after retrieving useful information via a RESTful API?
I'm pretty new to AngularJS, and I wouldn't be surprised if my approach is totally ridiculous, but I really need to get some advice on this.
Thanks in advance for your help,
Ulysse
You don't have to wait that the AJAX Query ended to initate the angular APPs.
you can use the $http promise ( details her )
In the controller :
// Simple GET request example:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
// data is now accessible in the html
$scope.data = response ;
// you can call a function to add markers on your maps with the received data
addMarkerOnMap(response);
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
You can also add a watch on some variable to wait modification on them :
// you should have $scope.yourVarName declared.
$scope.$watch('yourVarName', function(newValue, oldValue) {
console.log(newValue);
});
Or watch a list/object
$scope.$watchCollection('[var1,var2]', function () {
},true);

angularfire cannot read property facebook - how do i keep using authData throughout app

I´m working on an android game using ionic framework and firebase.
My plan is to let users login using facebook login with firebase, after this i want to save the game data to the users database key.
The first part is working. the script makes an database array based on the users facebook details. but the problem is after this is made, i cant seem to let angular change any database data. It seems like the authData is stuck in the login function...
Is there a way to keep the authdata for use in different controllers and functions?
app.factory("Auth", function($firebaseAuth) {
var FIREB = new Firebase("https://name.firebaseio.com");
return $firebaseAuth(FIREB);
});
app.controller('HomeScreen', function($scope, Auth, $firebaseArray) {
Auth.$onAuth(function(authData){
$scope.authData = authData;
});
var users = new Firebase("https://name.firebaseio.com/users/");
// create a synchronized array
$scope.users = $firebaseArray(users);
$scope.facebooklogin = function() {
Auth.$authWithOAuthPopup("facebook").then(function(authData){
users.child(authData.facebook.cachedUserProfile.id).set({
Username: authData.facebook.displayName,
Id: authData.facebook.cachedUserProfile.id,
Gender: authData.facebook.cachedUserProfile.gender,
Email: authData.facebook.email,
level: "1"
});
}).catch(function(error){
});
}
$scope.facebooklogout = function() {
Auth.$unauth();
}
$scope.changeLVL = function(authData) {
users.child(authData.facebook.cachedUserProfile.id).set({
level: "2"
});
}
});
And this is the datastructure it creates in firebase
users
998995300163718
Email: "Name#email.com"
Gender: "male"
Id: "998995300163718"
Username: "name lastname"
level: "1"
and after trying to edit i get this error... (using the changelevel function)
TypeError: Cannot read property 'facebook' of undefined
at Scope.$scope.changeLVL (http://localhost:8100/js/controllers.js:35:23)
at fn (eval at <anonymous> (http://localhost:8100/lib/ionic/js/ionic.bundle.js:21977:15), <anonymous>:4:218)
at http://localhost:8100/lib/ionic/js/ionic.bundle.js:57606:9
at Scope.$eval (http://localhost:8100/lib/ionic/js/ionic.bundle.js:24678:28)
at Scope.$apply (http://localhost:8100/lib/ionic/js/ionic.bundle.js:24777:23)
at HTMLButtonElement.<anonymous> (http://localhost:8100/lib/ionic/js/ionic.bundle.js:57605:13)
at HTMLButtonElement.eventHandler (http://localhost:8100/lib/ionic/js/ionic.bundle.js:12103:21)
at triggerMouseEvent (http://localhost:8100/lib/ionic/js/ionic.bundle.js:2870:7)
at tapClick (http://localhost:8100/lib/ionic/js/ionic.bundle.js:2859:3)
at HTMLDocument.tapMouseUp (http://localhost:8100/lib/ionic/js/ionic.bundle.js:2932:5)
The main issue is you're relying on the cachedUserProfile.gender property to exist. This isn't guaranteed to be there for every user. You'll need to find a fallback to avoid an error.
Let's simplify by injecting the user via the resolve() method in the router. Don't mind the structure of the code, it's from the Angular Styleguide (my preferred way of writing Angular apps).
angular.module("app", ["firebase"])
.config(ApplicationConfig)
.factory("Auth", Auth)
.controller("HomeScreen", HomeController);
function Auth() {
var FIREB = new Firebase("https://name.firebaseio.com");
return $firebaseAuth(FIREB);
}
function ApplicationConfig($stateProvider) {
$stateProvider
.state("home", {
controller: "HomeScreen",
templateUrl: "views/home.html"
})
.state("profile", {
controller: "ProfileScreen",
templateUrl: "views/profile.html",
resolve: {
currentUser: function(Auth) {
// This will inject the authenticated user into the controller
return Auth.$waitForAuth();
}
}
});
}
function HomeController($scope, Auth, $state) {
$scope.googlelogin = function() {
Auth.$authWithOAuthPopup("google").then(function(authData) {
users.child($scope.authData.google.cachedUserProfile.id).set({
Username: $scope.authData.google.cachedUserProfile.id,
Gender: $scope.authData.google.cachedUserProfile.gender || ""
});
$state.go("app.next");
});
}
}
function ProfileController(currentUser) {
console.log(currentUser.facebook); // injected from router
}
The benefit of this approach is that you don't have to check for authenticated users in the controller. If the user is injected, you know you have an authenticated user.
Check out the AngularFire docs for more information.

PhoneGap/Cordova with client-side routing?

I've inherited a Cordova/PhoneGap app running Cordova 3.4. My first task was to implement a Client-Side Routing framework to make it easier to navigate between pages. I chose Flatiron Director as my client-side router, but when I went to implement it I started to get weird functionality out of the app.
My first router setup:
var routing = {
testHandler: function(){
console.log('Route ran');
},
routes: function(){
return {
"/testhandler": testHandler
}
}
};
console.log('Routes added');
The routes are added (at least based on the console output). When I attempt to hit the /testhandler hash, I receive a "Failed to load resource: file:///testhandler" error when I set window.location.hash to "/testhandler". I noticed the "Route ran" statement was never printed.
My next attempt was just using the hashchange event with jQuery.
$(window).on('hashchange', function(){ console.log('Ran'); });
On this attempt, regardless of what I change the hash to, I see the 'Ran' output, but I still receive the "Failed to load resource: " error.
Is this a problem with PhoneGap/Cordova? Or our implementation? Is it just not possible to use client-side routing with Cordova? What am I doing wrong?
I know that this doesn't answer your question directly but you may consider making your own provisional router. This may help you to debug your app and to figure out what's the problem.
Something like this for example:
var router = (function (routes) {
var onRouteChange = function () {
// removes hash from the route
var route = location.hash.slice(1);
if (route in routes) {
routes[route]();
} else {
console.log('Route not defined');
}
};
window.addEventListener('hashchange', onRouteChange, false);
return {
addRoute: function (hashRoute, callback) {
routes[hashRoute] = callback;
},
removeRoute: function (hashRoute) {
delete routes[hashRoute];
}
};
})({
route1: function () {
console.log('Route 1');
document.getElementById('view').innerHTML = '<div><h1>Route 1</h1><p>Para 1</p><p>Para 2</p></div>';
},
route2: function () {
console.log('Route 2');
document.getElementById('view').innerHTML = '<div><h1>Route 1</h1><p>Para 1</p><p>Para 2</p></div>';
}
});

XMPP: AngularJS + Strophe

The basic XMPP with strophe and javascript wants to convert to AngularJS.
.controller('loginCtrl', function(xmppAuth) {
xmppAuth.auth(login, password);
})
and in service:
.service('xmppAuth', function() {
.return {
auth: function(login, password) {
connect = new Strophe.Connection(domain);
connect.connect(login, password, function (status) {
if (status === Strophe.Status.CONNECTED) {
connect.addHandler(on_roster_changed,"jabber:iq:roster", "iq", "set");
connect.addHandler(on_iq, null, "iq","");
connect.addHandler(on_presence, null, "presence");
connect.addHandler(on_message, null, 'message', '');
}
}
}
}
})
in js file
var on_presence = function(presence){
code
}
when i run this there is no error. But all handling events like on_presence() method called only once. this is handler event of Strophe Connection object. Is there is any remain in this code or what should i do for handling strophes event with angularJS?
I refered This Link but it not works.
See the Strophe.js docs for addHandler:
The handler should return true if it is to be invoked again; returning false will remove the handler after it returns.
Therefore, your on_presence code should return true if you want it to be invoked again:
var on_presence = function(presence) {
// do some stuff
return true;
};

Categories