XMPP: AngularJS + Strophe - javascript

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;
};

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;
}
}

Meteor - How to find out if Meteor.user() can be used without raising an error?

I'm looking for a way to determine if Meteor.user() is set in a function that can be called both from the server and client side, without raising an error when it is not.
In my specific case I use Meteor server's startup function to create some dummy data if none is set. Furthermore I use the Collection2-package's autoValue -functions to create some default attributes based on the currently logged in user's profile, if they are available.
So I have this in server-only code:
Meteor.startup(function() {
if (Tags.find().fetch().length === 0) {
Tags.insert({name: "Default tag"});
}
});
And in Tags-collection's schema:
creatorName: {
type: String,
optional: true,
autoValue: function() {
if (Meteor.user() && Meteor.user().profile.name)
return Meteor.user().profile.name;
return undefined;
}
}
Now when starting the server, if no tags exist, an error is thrown: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
So in other words calling Meteor.user() on the server startup throws an error instead of returning undefined or null or something. Is there a way to determine whether it will do so prior to calling it?
I cannot solve this simply by wrapping the call with if (Meteor.isServer) within the autoValue function, as the autoValue functions are normally called from server side even when invoked by the user, and in these cases everything in my code works fine.
Note that this is related to How to get Meteor.user() to return on the server side?, but that does not address checking if Meteor.user() is available in cases where calling it might or might not result in an error.
On the server, Meteor.users can only be invoked within the context of a method. So it makes sense that it won't work in Meteor.startup. The warning message is, unfortunately, not very helpful. You have two options:
try/catch
You can modify your autoValue to catch the error if it's called from the wrong context:
autoValue: function() {
try {
var name = Meteor.user().profile.name;
return name;
} catch (_error) {
return undefined;
}
}
I suppose this makes sense if undefined is an acceptable name in your dummy data.
Skip generating automatic values
Because you know this autoValue will always fail (and even if it didn't, it won't add a useful value), you could skip generating automatic values for those inserts. If you need a real name for the creator, you could pick a random value from your existing database (assuming you had already populated some users).
Been stuck with this for two days, this is what finally got mine working:
Solution: Use a server-side session to get the userId to prevent
"Meteor.userId can only be invoked in method calls. Use this.userId in publish functions."
error since using this.userId returns null.
lib/schemas/schema_doc.js
//automatically appended to other schemas to prevent repetition
Schemas.Doc = new SimpleSchema({
createdBy: {
type: String,
autoValue: function () {
var userId = '';
try {
userId = Meteor.userId();
} catch (error) {
if (is.existy(ServerSession.get('documentOwner'))) {
userId = ServerSession.get('documentOwner');
} else {
userId = 'undefined';
}
}
if (this.isInsert) {
return userId;
} else if (this.isUpsert) {
return {$setOnInsert: userId};
} else {
this.unset();
}
},
denyUpdate: true
},
// Force value to be current date (on server) upon insert
// and prevent updates thereafter.
createdAt: {
type: Date,
autoValue: function () {
if (this.isInsert) {
return new Date;
} else if (this.isUpsert) {
return {$setOnInsert: new Date};
} else {
this.unset();
}
},
denyUpdate: true
},
//other fields here...
});
server/methods.js
Meteor.methods({
createPlant: function () {
ServerSession.set('documentOwner', documentOwner);
var insertFieldOptions = {
'name' : name,
'type' : type
};
Plants.insert(insertFieldOptions);
},
//other methods here...
});
Note that I'm using the ff:
https://github.com/matteodem/meteor-server-session/ (for
ServerSession)
http://arasatasaygin.github.io/is.js/ (for is.existy)

How to turn off eventlistener in factory method which is returning promise to controllers?

I have a loginService factory used to perform login, logout and provide user data to controllers. Because I need to update userdata in controllers every time loginstate changes, my factory method is returning an update promise:
app.controller('TestCtrl', function ($scope, loginService) {
loginService.currentUserData().then(null, null, function(CurrUserData){
$scope.CurrUserData = CurrUserData;
});
});
In loginService I'm listening to $firebaseSimpleLogin:login/logout events and after they're fired, I pass the userdata object (returned by function based on UID) or null ($fbSimpleLogin:logout event) to $emit.
And finally, in my loginService.currentUserData() method I'm listening to this emitted events and returning deferred.notify(userdata/null).
First issue is that when I change the view (template+ctrl+location), I need to invoke $firebaseSimpleLogin:login/logout event to deliver my userData to new controller. Now, I'm doing it by $locationChangeStart event, but there should be better way...
And last issue: when I'm changing the view, there are more data calls, than I expectet.
Probably every controller add event listeners on $rootScope by calling loginService.currentUserData()? Described code below:
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
findUserByUid(authUser.uid);
});
$rootScope.$on('$firebaseSimpleLogin:logout', function() {
$rootScope.$emit('userLogout', null);
});
$rootScope.$on('$locationChangeStart', function(event, next, current) {
currentUser().then(function(u){
$timeout(function() { // without this same event on viewchange is fired
// by simplelogin, collision (I need to replace this whole block with invoking simpleloginevent)
if (u) {$rootScope.$emit('$firebaseSimpleLogin:login', u);
} else {$rootScope.$emit('$firebaseSimpleLogin:logout', null);};
}, 150);
});
});
function findUserByUid (uid) {
var query = $firebase(usersRef.startAt(uid).endAt(uid));
query.$on('loaded', function () {
var username = query.$getIndex()[0];
setCurrentUser(username);
});
}
function setCurrentUser (username) {
if (username) {$rootScope.$emit('userData', $firebase(usersRef).$child(username));};
}
var currentUserData = function () { // this method is used in CTRLs
var deferred = $q.defer();
var uDl = $rootScope.$on('userData', function(e, FbUserData){deferred.notify(FbUserData); });
var uLl = $rootScope.$on('userLogout', function(){deferred.notify(null); });
return deferred.promise;
};
I recently wrote a demo AngularFire app that has similar functionality. The way I found to handle this is only worry about three points.
When the user logs in $rootScope.$on('$firebaseSimpleLogin:$login')
When the user logs out $rootScope.$on('$firebaseSimpleLogin:$logout')
Calling $getCurrentUser()
This will be able to capture the login life cycle. Since you need to know who the current user is, you can rely on the $firebaseSimpleLogin method rather than trying to $emit your own events.
You also could resolve the current user in the $routeProvider for each view. This way each view won't be rendered until the user has been loaded.
Here's the plunker project and the example Factory:
http://plnkr.co/edit/M0UJmm?p=preview
// Auth factory that encapsulates $firebaseSimpleLogin methods
// provides easy use of capturing events that were emitted
// on the $rootScope when users login and out
.factory('Auth', function($firebaseSimpleLogin, Fb, $rootScope) {
var simpleLogin = $firebaseSimpleLogin(Fb);
return {
getCurrentUser: function() {
return simpleLogin.$getCurrentUser();
},
login: function(provider, user) {
simpleLogin.$login(provider, {
email: user.email,
password: user.password
});
},
logout: function() {
simpleLogin.$logout();
},
onLogin: function(cb) {
$rootScope.$on('$firebaseSimpleLogin:login',
function(e, user) {
cb(e, user);
});
},
onLogout: function(cb) {
$rootScope.$on('$firebaseSimpleLogin:logout',
function(e, user) {
cb(e, user);
});
}
}
})

XMPP: AngularJs + Strophe.js

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.

Calling a method in one JS module and returning data to another

Trying to master the modular programming pattern in JS. I'm having difficulties calling a method in one module -- which returns true or false depending on conditions -- from another module and using the data returned.
This is a cut down version of my first module:
var stAuthentication = (function() {
return {
someMethod: function() {
///
},
isAuthenticated: function() {
var authenticated;
ajax_isAuthenticated = $.ajax({
url: root+'/assets/scripts/php/app/isAuthenticated',
type: 'POST',
dataType: 'text'
});
ajax_isAuthenticated.done(function(response) {
authenticated = response;
console.log("Ajax request returned "+authenticated);
});
ajax_isAuthenticated.fail(function(response) {
console.log("Ajax request (ajax_isAuthenticated) failed.");
});
if (authenticated == 0) {
return false;
} else {
return true;
}
},
someOtherMethod: function() {
///
}
}
})();
As you can see, the method in question is isAuthenticated(). This basically sends an AJAX request to the server where it checks if the user is logged in and returns 1 or 0. I have tested the response from this and it works as it should. My issue is with the next module, where I'll be calling the method in the first example to determine if the user is logged in before running a task in the second module. Here it is:
var stReadLater = (function() {
var elements, quantity;
return {
someMethod: function() {
//
},
saveItem: function() {
if (stAuthentication.isAuthenticated()) {
console.log("Signed in");
// user is signed in so use this feature
} else {
console.log("Not signed in");
// ask user to sign in before using this feature
}
},
someOtherMethod: function() {
//
}
}
})();
The actually location of the issue is this few lines I think, from the first module:
if (authenticated == 0) {
return false;
} else {
return true;
}
First of all, I noticed that it was always returning false. After more playing around, I noticed that authenticated was undefined which led me to move var authenticated around the scope but no luck. I also tried this.authenticated without luck, too.
I know I'm fairly close but I have tried so many different variations I've totally lost it. What is the correct way of doing this?
========================
THE FIX
I was checking the authenticated variable before AJAX had set it. Thanks to #willma for the suggestions. This is what I did if anybody else comes across this:
isAuthenticated: function() {
var deferred = $.Deferred();
ajax_isAuthenticated = $.ajax({
url: root+'/assets/scripts/php/app/isAuthenticated',
type: 'POST',
dataType: 'text'
});
ajax_isAuthenticated.done(function(response) {
deferred.resolve(response)
});
return deferred.promise();
}
and then in the second module:
saveItem: function() {
$.when(stAuthentication.isAuthenticated()).then(function(response) {
if (response == 0) {
console.log("Not signed in");
} else {
console.log("Is signed in");
}
});
}
You're mixing up synchronous and asynchronous code. isAuthenticated will always return false because the function will always return before either of the done or fail callbacks can be called.
Your entire function is basically doing this:
var authenticated;
if (authenticated == 0) { // This is the case because undefined == 0 -> true
return false;
} else {
return true;
}
You have two solutions. Either the stReadLater object can pass a callback function like this:
function done() {
console.log("Signed in");
}
function fail() {
console.log("Not signed in");
}
stReadLater.saveItem = function() {
stAuthentication.isAuthenticated(done, fail);
}
Then is your authentication module:
var stAuthentication.isAuthenticated = function(done, fail) {
ajax_isAuthenticated = $.ajax({
url: root+'/assets/scripts/php/app/isAuthenticated',
type: 'POST',
dataType: 'text'
});
ajax_isAuthenticated.done(done);
ajax_isAuthenticated.fail(fail);
}
Or you can use promises. I find promises more elegant. It's worth reading about them

Categories