Right way of accessing Meteor.users from the client - meteor

I have defined some useful fields in the users collection for my convenience. What would be the right way of allowing a client to access the corresponding field? I'm using the autopublish package, but Meteor.user() from the client side only reveals the emails array.

You have to explicitly tell Meteor which fields from users to include when querying users collection.
For example to publish custom "avatar" field on client:
// Client only code
if (Meteor.isClient) {
Meteor.subscribe("currentUserData");
...
}
// Server-only code
if (Meteor.isServer) {
Meteor.publish("currentUserData", function() {
return Meteor.users.find({}, {
fields : {
'avatar' : 1
}
});
});
...
}

Related

Meteor.user with Additional Fields on Client

In Meteor, one can add additional fields to the root-level of the new user document like so:
// See: https://guide.meteor.com/accounts.html#adding-fields-on-registration
Accounts.onCreateUser((options, user) =>
// Add custom field to user document...
user.customField = "custom data";
return user;
});
On the client, one can retrieve some data about the current user like so:
// { _id: "...", emails: [...] }
Meteor.user()
By default, the customField does not exist on the returned user. How can one retrieve that additional field via the Meteor.user() call such that we get { _id: "...", emails: [...], customField: "..." }? At present, the documentation on publishing custom data appears to suggest publishing an additional collection. This is undesired for reasons of overhead in code and traffic. Can one override the default fields for Meteor.user() calls to provide additional fields?
You have a couple of solutions that you can use to solve this.
Null Publication
Meteor.publish(null, function () {
if (this.userId !== null) {
return Meteor.users.find({ _id: this.userId }, { fields: { customField: 1 } });
} else {
return this.ready();
}
}, { is_auto: true });
This will give you the desired result but will also result in an additional database lookup.. While this is don't by _id and is extremely efficient, I still find this to be an unnecessary overhead.
2.Updating the fields the Meteor publishes for the user by default.
Accounts._defaultPublishFields.projection = { customField: 1, ...Accounts._defaultPublishFields.projection };
This has to be ran outside of any Meteor.startup blocks. If ran within one, this will not work. This method will not result in extra calls to your database and is my preferred method of accomplishing this.
You are actually misunderstanding the documentation. It is not suggesting to populate and publish a separate collection, just a separate publication. That's different. You can have multiple publications/subscriptions that all feed the same collection. So all you need to do is:
Server:
Meteor.publish('my-custom-user-data', function() {
return Meteor.users.find(this.userId, {fields: {customField: 1}});
});
Client:
Meteor.subscribe('my-custom-user-data');

How to populate client-side Meteor.user.services after OAuth with built-in accounts-ui package in Meteor v1.4+?

I'm using accounts-ui and accounts-google in Meteor v1.4.1. I can't get the user.services object to appear scoped in the client code. In particular, I need google's profile picture.
I've configured the server-side code to authenticate with Google like so:
import { Meteor } from 'meteor/meteor';
import { ServiceConfiguration } from 'meteor/service-configuration';
const services = Meteor.settings.private.oauth;
for (let service of Object.keys(services)) {
ServiceConfiguration.configurations.upsert({
service
}, {
$set: {
clientId: services[service].app_id,
secret: services[service].secret,
loginStyle: "popup"
}
});
}
...and the client side code to configure permissions like so:
Accounts.ui.config({
requestPermissions: {
google: ['email', 'profile']
},
forceApprovalPrompt: {
google: true
},
passwordSignupFields: 'EMAIL_ONLY'
});
When users click the 'Sign-In with Google' button, a pop-up appears and they can authenticate. No prompt appears, however, despite forceApprovalPrompt being set to true for google.
The big issue is that when I execute this,
const user = Meteor.user();
console.log(user.services);
anywhere in client code, I do not see the expected user services information. I check my database and it is definitely there for the taking:
$ mongo localhost:27017
> db.users.find({})
> ... "services" : { "google" : { "accessToken" : ... } } ...
I'm curious what I'm missing? Should I explicitly define a publish function in order for user services data to exist in the client?
The services property is intentionally hidden on the client side for security reasons. There are a couple of approaches here :
Suggestions
My preferred one would be to expose a meteor method to bring you the
public keys and avatars you might need in the few places you'd need
them.
On a successful login, you could record the data you need somewhere in the user object, but outside of the services property.
As you said, you could make a new publication which explicitly specifies which fields to retrieve and which ones to hide. You have to be careful what you publish, though.
Code Examples
Meteor methods:
// server
Meteor.methods({
getProfilePicture() {
const services = Meteor.user().services;
// replace with actual profile picture property
return services.google && services.google.profilePicture;
}
});
// client
Meteor.call('getProfilePicture', (err, profilePicture) => {
console.log('profile picture url', profilePicture);
});
Update on successful user creation (you might want to have a login hook as well to reflect any avatar/picture changes in google):
// Configure what happens with profile data on user creation
Accounts.onCreateUser((options, user) => {
if (!('profile' in options)) { options.profile = {}; }
if (!('providers' in options.profile)) { options.profile.providers = {}; }
// Define additional specific profile options here
if (user.services.google) {
options.profile.providers.google = {
picture: user.services.google.picture
}
}
user.profile = options.profile;
return user;
});
Publish only select data...
// Server
Meteor.publish('userData', function () {
if (this.userId) {
return Meteor.users.find({ _id: this.userId }, {
fields: { other: 1, things: 1 }
});
} else {
this.ready();
}
});
// Client
Meteor.subscribe('userData');

Collection subscription but no documents to client

This Meteor code gives full records to the client for one of the collections but the client browser gives:
MyCol.findOne({});
undefined
I also have a server side code which inserts documents into the collection and mongo terminal verifies documents exist.
Any idea why? Thanks
//app.lib.js
MyCol = new Mongo.Collection('myCol');
//server
Meteor.publish('myCol', function() {
return MyCol.find({});
});
//client main.js
Meteor.subscribe('myCol');
Template.checks.helpers({
'values': function() {
return MyCol.find({}); // no documents found,
}
});

meteor.js : find users by email

In my meteor.js app, I'm trying to write a simple admin page which can find a user by his/her email address.
I can see that in the Meteor.users collection there is an 'emails' array, which has objects like so
{ address : 'foo#foo.com',
verified : false
}
Normally in Mongodb I can search inside this 'emails' array like so :
Meteor.users.find({ emails.address : 'foo#foo.com' });
But this query is throwing an error :
While building the application:
client/admin.js:224:41: Unexpected token .
Aka Meteor doesn't like the nested query...
Any ideas on how to query the Meteor.users collection by email address ?
You can also use what you had, just put it in quotes:
Meteor.users.find({ "emails.address" : 'foo#foo.com' });
If on the server, Meteor has a special function for this :
Accounts.findUserByEmail(email).
I believe this is the recommended way.
Emails holds an array of emails. Each email has an address.
Try { emails: { $elemMatch: { address: "foo#foo.com" } } }.
Information on $elemMatch is here.
Information on emails as an array is here.
By default, Meteor only publishes the logged in user and you can, as you mention, run queries against that user. In order to access the other users you have to publish them on the server:
Meteor.publish("allUsers", function () {
return Meteor.users.find({});
});
And subscribe to them on the client:
Meteor.subscribe('allUsers');
And run the following command
Meteor.users.find({"emails": "me#example.com"}).fetch()
OR
Meteor.users.find({"emails.0": "me#example.com"}).fetch()
Refer this
If you want to find all emails inside Accounts array, and do an insensitive query:
const hasUser = Meteor.users.findOne({
emails: {
$elemMatch: {
address: {
$regex : new RegExp(doc.email, "i")
}
}
}
});
One possible workaround, if this works on the server but not the client, is to use a users_by_email method on the server:
if (Meteor.isServer) {
Meteor.methods({
'get_users_by_email': function(email) {
return Users.find({ emails.address: email }).fetch();
}
});
}
if (Meteor.isClient) {
foo_users = Meteor.call('get_users_by_email', 'foo#bar.baz');
}

Invoke a client js function in Meteor after getting results from the server

I'm trying to see how can I invoke a js function after the client gets a result from a Meteor method call. The only thing I was able to get is to invoke the function myFunc only on the client that made the actual method call.
Any thoughts how i can invoke the function on all the currently subscribed clients?
here is the code:
function myFunc(error, result) {
alert(result);
}
if (Meteor.is_client) {
Template.container.events = {
'click input' : function () {
Meteor.call('someMethod',myFunc);
if (typeof console !== 'undefined')
console.log("You pressed the button");
}
};
}
if (Meteor.is_server) {
Meteor.startup(function () {
// code to run on server at startup
});
}
Meteor.methods({
someMethod: function() {
//console.log(!this.is_simulation);
return "something";
}
})
Thanks
Currently you can't broadcast a method call to all clients directly. At least as far as I can tell. But a work around would be to create a collection called Alerts and monitor it for changes. Then when you want to send a message to all your users you can change the document in Alerts:
Client:
Alerts = new Meteor.Collection("alerts")
Meteor.autosubscribe(function() {
Alerts.find().observe({
added: function(item){
alert(item.message);
}
});
});
Server:
Alerts = new Meteor.Collection("alerts")
Meteor.publish("alerts", function(){
Alerts.find();
});
Alerts.remove({}); // remove all
Alerts.insert({message: "Some message to show on every client."});
Another option is using Meteor Stream package which purpose is to avoid using a mongodb collection on the server side. It does supports client to clients, server to clients, client to server AND server to servers messaging, including a support for Meteor Cluster
If you want to stay with meteor only using collections, the following code allows you to either broadcast a message from the client to all the clients or a message from the server to all the subscribed clients. Just use this mechanism to then fire a function on the client side once the right message is received. The code is made in such a way that you will never have useless items remaining into the collection.
Messages = new Meteor.Collection("messages");
if (Meteor.isClient) {
Meteor.subscribe("messages");
var query = Messages.find({});
var handle = query.observe({
added: function(document)
{
console.log(document.message);
}
});
// Test the mechanism from the client side
Meteor.call("client talked");
}
if (Meteor.isServer) {
Meteor.startup(function() {
Messages.remove({});
});
Meteor.publish("messages", function()
{
// you might add an optional filter in order to broadcast only the messages you want to the client
return Messages.find();
});
function talk(message)
{
var id = Messages.insert({"message":message});
Messages.remove(id);
}
Meteor.methods(
{
talk: function(message)
{
// you might filter here if the clients can talk using this.userId
talk(message);
}
});
// test the mechanism from the server side
talk("server talked");
}
I like what Zeke said, but for people who uses Meteor 0.5.0+, use Deps.autorun instead of autosubscribe... details at:
https://groups.google.com/forum/#!topic/meteor-core/mTa81RLvhbY
and
http://www.meteor.com/blog/2013/02/14/meteor-055-devshop-code-and-community-contributions
I simple approach to call a JavaScript client-side function would be to add a script tag in your html template that is bound by your collection. Anytime a new item is inserted, this tag would be inserted into the client would run your function. I have a collection call uploads with some properties such as name. The following template triggers drawpoints() client-side function upon receipt of a new item in Uploads collection:
{{#each uploads}}
<tr>
<td>{{name}}</td>
<td>
<div class="alert alert-success">Download Here</div>
</td>
</tr>
<script>drawpoints();</script>
{{/each}}

Resources