How to get Firebase reference when using Polymerfire? - firebase

I am new to Polymer and I am stuck on setting the database data. I manged to make email authentication work and I need to save user data after user creation. I initialize the app with firebase-app element.
Here is the important part:
this.$.auth.createUserWithEmailAndPassword(email, pass).then(function (user) {
user.sendEmailVerification();
document.getElementById("emaildialog").toggle();
var view = document.getElementById("r_view");
firebase.database().ref('/user/' + user['uid']).set({
name: view.name,
surname: view.surName
}).catch(function (err) {
console.log(err.message);
});
})
User is successfully created but the user data won't get saved and
firebase.database is not a function"
error is thrown. I guess it's because I don't have access to firebase.database function in the scope. I found many ways how to solve the issue using pure JavaScript, but I'm not sure what is the official "Polymer way".
EDIT:
I still can't get it to work. i managed to get a reference of app object but it seems like there is no database method available. I wrote a simple function for debugging:
debugFunction: function () {
if (!!this.user) {
var fb = this.$.auth.app;
console.log(!!fb); // Output is true,
var database = fb.database();
}
}
I get the "Uncaught TypeError: fb.database is not a function(…)" once more.
Thanks in advance, Jan

You can get the reference of the firebase app inside your firebase-auth element. Make sure you do this outside of the callback function so you won't have to deal with getting the proper scope of this. If you must, you can do .bind or arrow functions.
var app = this.$.auth.app;
Then after that you can do app.database() as a replacement for the firebase one.

Related

SvelteKit load function error: must return a plain object at the top level

I cannot get SvelteKit load function works when using it with Firebase, I always get this error message:
a load function related to route '/' returned a function, but must return a plain object at the top level (i.e. return {...})
I'm using onSnapshot here with Firestone to get the updated data whenever it changed on the database.
export function load() {
const queryParams = [orderBy('date')];
const q = query(collection(db, 'daily_status'), ...queryParams);
messagesUnsubscribeCallback = onSnapshot(
q,
querySnapshot => {
let data = querySnapshot.docs.map( doc => (
JSON.parse(JSON.stringify(
{
id: doc.id,
status: doc.data().status,
date: doc.data().date.toDate().toLocaleDateString('en-au'),
note: doc.data().note
}
))
))
return { daily_status: data }
})
return messagesUnsubscribeCallback;
}
It looks like your issue is the fact that you are returning the function onSnapshot() inside the load function. The only thing you can return inside a load method is a plain object as the error states. What you might want to do is to run the snapshot code inside an onMount.
Another solution would be creating a svelte store and pass the onSnapshot into the store. An example can be seen in this tutorial:
https://www.captaincodeman.com/lazy-loading-and-querying-firestore-with-sveltekit#introduction
Reference:
https://kit.svelte.dev/docs/load
Your load() function needs to run asynchronous code, so it can't return back the data directly. Instead, you need to change it to return a promise that resolves to the loaded data. For an example using fetch(), see:
https://kit.svelte.dev/docs/load#making-fetch-requests
In your case, you need to create your own promise.
Further more, the purpose of the load() function is to load the initial data the page needs to be able to render. As suggested by another, to subscribe to updates in the future, do that in the page component in onMount(), so you only subscribe to future updates when the component is rendered in the web browser.

Cloud Functions database event always contains empty data

I have an issue with my cloud functions where in all my database events all return empty. For example, in the following event the event.data.val() would return null. I am doing an update operation and have tested the update by testing the cloud function using the shell as well as after deploying.
export const createSubscription = functions.database.ref('/users/{userId}/subscription').onWrite( event => {
if(!event.data.val()) {
return;
}
});
But I can easily hook into the auth.user() events like the following and receive the data.
export const createStripeUser = functions.auth.user().onCreate(event => {
const user = event.data;
});
Edit: Passing data into the collection for example like the one below on the emulator console
createSubscription({
testKey: 'testValue'
})
or the following on from my frontend
db.ref(`/users/23213213213/subscription`).update({ testKey: 'testValue'});
would return null on the function.
DougStevenson is correct. For the .onCreate() you would be doing it correct wit a myDatabaseFunction('new_data').
With the .onWrite() you need to pass in the before and after like my example below.
You may have got stuck were I did. Note that I have a curly bracket around the before and after the final JSON. It didn't work properly without them.
addComment({before: {"comment":"before comment","role":"guest"}, after:{"comment":"After Comment","role":"guest"}})
Hope my example helps a bit more than a generic string parameter.
Good Luck!

Meteor: what is the right way to add custom settings object to users collection?

There are multiple examples on publish/subscribe but not clear on what is the best practice for storing custom data in the in-built "users" collection in Meteor (especially in the new possibility of template specific collections).
For example, I need to store user browse history - something that is accessible through Meteor.user().settings.history.lastvisited[]
The challenge is:
Is any special publish / subscribe required for the above? (the
reason being, I am assuming the users collection is already
published and available on client side - so do we need another?)
How to take care of edge cases where user is new and hence settings.history object may not be defined? Can we have a special publish that automatically takes care of creating an empty object if the settings is undefined? How to do it?
I did this :
// server side
Meteor.publish('userSettings', function (maxRows) {
if (this.userId) {
return Meteor.users.find({ _id: this.userId }, { fields: {'settings':1}});
}
this.ready();
});
//client side
Meteor.subscribe('userSettings');
But I do not see anyway how I can access the published "userSettings" object on the client side - what is missing ??
You can create a field and set it to false/'', on each user you create using the accountsOnCreateUser method.
Accounts.onCreateUser(function(options, user) {
//this function gets called each time a user has been created on the Meteor.user collection
if (options.profile)
user.settings = ''; //this is just and example.
return user;
})
Now the publish looks ok, but in order to get it work im always use a Tracker.autorun function.
Tracker.autorun(function(){
Meteor.subscribe('userSettings');
})
Why the autorun? well if you don't call the auto run here, the subscription get only called 1 time when the apps loads, and not when the user documents.
Take care of yours deny/allow permissions, check this meteor:common mistakes post on the Profile editing section
Also the subscribe function have a callback function. Meteor.subscribe(name, [arg1, arg2...], [callbacks]), so you can do something like this.
var myUserSubscription = Meteor.subscribe('userSettings',function(){
console.log("ok im here on the client side")
console.log("this user subscription is ready " + myUserSubscription.ready())
})
console.log("outside the subscription why not? " + myUserSubscription.ready();
About ready();
True if the server has marked the subscription as ready. A reactive
data source.

angularFireCollection is not returning any data

I have no issues when using implicit updates (angelFire). However I need for some of my data use explicit updating. So I implemented angelFireCollection on the exact same ref I was using previously but despite the console.log explicitly saying that the read was granted and trying it with both with the onloadcallback and without, I don't get data directly into my assigned variable AND once the callback fires I get a strange looking object that DOES contain the data but not in the form I expect. My scope variable ends up with an empty collection. Never gets populated. Here is the code:
var streamController = function ($rootScope, $scope, $log, $location, angularFireCollection, profileService) {
//Wait for firebaseLogin...
$rootScope.$watch('firebaseAuth', init);
function init() {
if ($rootScope.firebaseAuth == false) {
return
};
var refUsers = new Firebase($rootScope.FBURL+'/users/'+$rootScope.uid);
$scope.profile = angularFireCollection(refUsers, function onload(snapshot) {
console.log(snapshot)
});
};
};
myApp.gwWebApp.controller('StreamController', ['$rootScope', '$scope', '$log', '$location', 'angularFireCollection', 'profileService',
streamController]);
}());
Here is what the console.log looks like ( ie; what snapshot looks like ):
>snapshot
T {z: R, bc: J, V: function, val: function, xd: function…}
Here is the earlier message before the snapshot was returned:
Firebase Login Succeeded! fbLoginController.js:16
FIREBASE: Attempt to read /users/529ccc5d1946a93656320b0a with auth={"username":"xxxxxxx#me.com","id":"529ccc5d1946a93656320b0a"} firebase.js:76
FIREBASE: /: "auth.username == 'admin'" firebase.js:76
FIREBASE: => false firebase.js:76
FIREBASE: /users firebase.js:76
FIREBASE: /users/529ccc5d1946a93656320b0a: "auth.id == $user" firebase.js:76
FIREBASE: => true firebase.js:76
FIREBASE:
FIREBASE: Read was allowed.
and finally the desired binding that ends up with an empty array: again from the console:
$scope.profile
[]
Anyone know what I could possibly be doing wrong?? This is like 5 lines of code. Frustrating.
I have put stops in angelFireCollection factory function and can see that the data is getting added to the collection in the callbacks inside that function but my binded variable never gets updated.
UPDATE
Ok experimenting with a plnkr. It seems that angularFireCollection EXPECTS your returning a LIST of items. The snapshot returns properly if you inspect snapshot.val() it will be whatever object structure was stored in firebase. IF you use angularFireCollection it does indeed bind to the variable HOWEVER it turns a non-list object into a garbled mess and you can not access the object user the normal dot operator. This is either a bug or it is a severe limitation of angularFireCollection which will cause me to revaluate how easily I can use firebase as the backend. I can't share my plnkr because it is accessing non-public data but tomorrow if i have time I will create a public firebase with an object store and demonstrate.
Ok. So it appears that indeed angularFireCollection is MEANT to be array based. Which is fine. It would be VERY helpful if the angularFire documentation was updated to make that clear. As such it is not an implicit vs explicit update technique.
For an explicit non-array based approach I have come up with the following code. Had I not been mislead by the documentation I would have gone down this path originally.
var MainCtrl = function($scope, angularFire) {
$scope.test = {};
var _url = 'https://golfwire.firebaseio.com/tmp';
var _ref = new Firebase(_url);
var promise = angularFire(_ref, $scope, 'implicit');
promise.then ( function(data){
$scope.explicit=angular.copy($scope.implicit );
});
}
You then work locally with the 'explicit' copy and when ready just update the 'implicit' by assigning: $scope.implicit = $scope.explicit.
Here is a plnkr: http://plnkr.co/edit/bLJrL1

Is there a post createUser hook in meteor when using accounts-ui package?

Let's say I have a todo app, and I want to make sure that every user that registers has at least one todo to start with, something like "First todo to cross off!", how would I do that in meteor?
In general, the way I see it, I can do it when the user is created for the first time (ideal), or check to see whether they need a new todo every time they log in (less ideal). In the latter case, I can do a check for Todos.findOne(), and if the count is 0, add one. However, seems that whether I do this in my router when the page loads, or on some template's .rendered function, the collection I'm checking hasn't been loaded yet, so I always create a new todo, even if one really does exist. So it'd be great if someone could explain how to get around that.
But, what I'd ideally want is the ability to just create a new Todo when the user is created. There is a Accounts.onCreateUser method, but that is used to add additional info to user profile, not a post-create hook. There's also a method to programmatically create the user using Accounts.createNewUser with a callback, but I'm using the accounts-ui package so am not programmatically adding users. In a less ideal case, I could check for the Todo whenever the user logs in, but even in that case, there seems to be a federated Accounts.loginWithXService login, so not sure how to handle the callback when any user logs in, regardless of service type.
I think I must be missing something simple, so apologies if this is super obvious. Any help is appreciated.
The Meteor API now has the hook onCreateUser:
Accounts.onCreateUser(function (options, user) {
Todos.insert({
owner: user._id,
text: "First todo to cross off!",
});
// We still want the default hook's 'profile' behavior.
if (options.profile)
user.profile = options.profile;
return user;
});
I used the _.wrap method described above but wanted to include an additional suggestion. It's a good idea to call the original callback from your new custom callback. Meteor does some things on the callback that we don't want to miss.
Modified code that worked like a champ for me:
Accounts.createUser = _.wrap(Accounts.createUser, function(createUser) {
// Store the original arguments
var args = _.toArray(arguments).slice(1),
user = args[0];
origCallback = args[1];
var newCallback = function(error) {
// do my stuff
origCallback.call(this, error);
};
createUser(user, newCallback);
});
If you are using the UserAccounts package: postSignUpHook now exists.
Splendido just merged my pull request for exactly this issue.
AccountsTemplates.configure({
/*...*/
postSignUpHook: /*[callback with your actions post full user creation goes here]*/,
/*...*/
}
Documentation (You'll need to scroll down it's the last hook):
func(userId, info) Called, server side only, just after a successfull user account creation, post submitting the pwdForm for sign-up: allows for custom actions on the data being submitted after we are sure a new user was successfully created. A common use might be applying roles to the user, as this is only possible after fully completing user creation in alanning:roles. The userId is available as the first parameter, so that user object may be retrieved. The password is not available as it's already encrypted, though the encrypted password may be found in info if of use.
You can piggyback onto functions that are called by Meteor by wrapping them. I'm also using the accounts-ui and accounts-password packages and I use Underscore's _.wrap method to redefine the loginWithPassword function. Underscore is included in Meteor by default.
I use something like this for logging in:
Meteor.loginWithPassword = _.wrap(Meteor.loginWithPassword, function(login) {
// Store the original arguments
var args = _.toArray(arguments).slice(1),
user = args[0],
pass = args[1],
origCallback = args[2];
// Create a new callback function
// Could also be defined elsewhere outside of this wrapped function
var newCallback = function() { console.info('logged in'); }
// Now call the original login function with
// the original user, pass plus the new callback
login(user, pass, newCallback);
});
In this specific case, the code above would go in your client code somewhere.
For Accounts.createUser, it might look something like this (also somewhere in client code):
Accounts.createUser = _.wrap(Accounts.createUser, function(createUser) {
// Store the original arguments
var args = _.toArray(arguments).slice(1),
user = args[0],
origCallback = args[1];
// Create a new callback function
// Could also be defined elsewhere outside of this wrapped function
// This is called on the client
var newCallback = function(err) {
if (err) {
console.error(err);
} else {
console.info('success');
}
};
// Now call the original create user function with
// the original user object plus the new callback
createUser(user, newCallback);
});
Hope that's helpful.
One of the Meteor devs answered this question in Meteor google group: https://groups.google.com/forum/?fromgroups=#!topic/meteor-talk/KSz7O-tt4w8
Basically, right now, there is no createUser hook when using accounts-ui, only when programmatically doing so via Accounts.createUser. Also, there are no hooks for login, unless using the lower-level login functions like loginWithFacebook, etc. I haven't figured out an ideal way around this yet, but a few ways of handling it:
if needing to enter a default value into a collection, in that collection's subscription, use the onComplete argument. In this callback, if there are no entries in collection, add one. This avoids the first problem I mentioned in my post about not knowing when a collection was loaded, though not ideal since collection could be empty because user already removed first default one:
Meteor.subscribe 'todos', user: Meteor.userId(), () ->
todo = Todos.findOne()
unless todo
Todos.insert user: Meteor.userId()
you can set up a login hook by using the Meteor.autorun reactive method to check for a change in Meteor.userId(). That'll only get called when the user logs in/reloads the page. This is more useful for non-collection stuff since the collection is not guaranteed to be loaded when Meteor.userId is set:
Meteor.autorun () ->
if Meteor.userId()
console.log 'Do some post login hook'
So I think the efficient solution is still out there somewhere, but wanted to update this post with workarounds I had found in the meantime.
I think this answer this question better: How can I create users server side in Meteor?
in resume:
Accounts.createUser({
username: username,
email : email,
password : password,
profile : {
//publicly visible fields like firstname goes here
}
});
check the meteor docs for more: http://docs.meteor.com/#/full/accounts_createuser

Resources