Meteorjs - Online users - meteor

Is there a way to get a list of all currently connected users? I have checked many of the chatroom tutorials and none of them provide this methodology. Is it even possible and if so how would one go about implementing it correctly the Meteor way?

I've managed to figure out a way to do this without using inefficient keep-alives. Basically, user logins or reconnects set the profile.online to true, and logouts or disconnects set it to false. You can add it as a Meteorite smart package and try it out here:
https://github.com/mizzao/meteor-user-status
Fixes or improvements are appreciated.

We accomplished this by setting an online property when a user logs in, and then periodically pinging (every 10 seconds) and setting any inactive users to offline. Not ideal, but it works. Would love to see this feature in Meteor. Here is the pinging function.
Meteor.setInterval(function () {
var now = (new Date()).getTime();
OnlineUsers.find({'active':true,'lastActivity': {$lt: (now - 30 * 1000)}}).forEach(function (onlineActivity) {
if(onlineActivity && onlineActivity.userId) {
OnlineUsers.update({userId:onlineActivity.userId},{$set: {'active': false}});
Meteor.users.update(onlineActivity.userId,{$set: {'profile.online': false}});
}
});
}, 10000);

Old question, but for anyone looking into this there is now a meteor package that monitors client to server connections. It's called meteor user status and can be found on github.

I have ended up with this.
Server code:
Meteor.startup ->
Meteor.methods
keepalive: (params) ->
return false unless #userId
Meteor.keepalive ?= {}
Meteor.clearTimeout Meteor.keepalive[#userId] if Meteor.keepalive[#userId]
Meteor.users.update #userId, $set: {'profile.online': true}
Meteor.keepalive[#userId] = Meteor.setTimeout (=>
delete Meteor.keepalive[#userId]
Meteor.users.update #userId, $set: {'profile.online': false}
), 5000
return true
Client code:
Meteor.startup ->
Meteor.setInterval (->
Meteor.call('keepalive') if Meteor.userId()
), 3000

What I am doing is to record the user's online status when the browser tab of the meteor app has or loses the focus. Then filter the users who are online. There must be better solutions but this one works.
if Meteor.is_client
Meteor.startup ->
$(window).focus ->
Meteor.call 'online', true
$(window).blur ->
Meteor.call 'online', false
else
Meteor.methods
online: (isOnline=true) ->
Meteor.users.update Meteor.userId(), $set: online: isOnline
Then you can use
Meteor.users.find online: true
to filter online users.
BTW, don't forget to publish users with online field.
Meteor.publish null, ->
Meteor.users.find {}, fields: online: 1

My recommendation for this case is danimal:userpresence because there are some more benefits:
Track user online status over multiple servers
Contain httpHeaders so if you want to check if receiver has checked the going messages or not
Very simple code to read

Related

Firebase: Delete user analytics data - userdeletionRequests:upsert - GDPR

My question is, how can I delete a users analytics data from firebase using "Google User Deletion API" and its method: userdeletionRequests:upsert? This is important for me to fully fulfill GDPR.
I tried searching for this, but didn't a solution for using it in combination with "NodeJS" and "firebase-cloud-functions".
My biggest problem is, how I get the access, token, this is what I have for now:
const accessToken = (await admin.credential.applicationDefault().getAccessToken()).access_token;
return ky.post(constants.googleUserDeletionURL, {
body: JSON.stringify({
kind: "analytics#userDeletionRequest",
id: {
type: "USER_ID",
userId: uid,
},
propertyId: constants.googleUserDeletionPropertyId,
}),
headers: {
"Authorization": `Bearer ${accessToken}`,
},
}).catch((err) => {
functions.logger.log(`An Error happened trying to delete user-anayltics ${(err as Error).message}`);
});
But I always get An Error happened trying to delete user-anayltics Request failed with status code 403 Forbidden
Okay, after some painful and long days (literally took me like >20 hours), I've figured out how to achieve this request. Here is a step for step guide:
Step 1 - Needed Dependencies
To send a post-request to google, we need a http-client-library. I've choosen "ky", so we need to install it first with:
npm install ky
Furthermore, we need to create or OWN oAuth2 token, otherwise the post request will be denied with "error 403". To create our own oAuth token, we need another dependency:
npm install #googleapis/docs
Step 2 - Needed Google Property ID
With your request, Google needs to know which property-id / project you are targeting (where it should later delete the user-analytics-data). To get this property-id, we need to log in into GCP via Firebase (not the "real" GCP, the one via Firebase).
For this, go into your "console.firebase.google.com" → Select your project → Analytics Dashboard → "View more in Google Analytics (button at the right corner)"
Write "property-id" into the search field and then save it somewhere (we need it later)
Step 3 - Creating Client-Secret
The third step is to create a service-account, which we will later add into our functions-folder, in order to create our oAuthClient (don't worry, you will see what I mean to a later point)
To create your new service.json, log in to google cloud platform via "https://cloud.google.com/" and then follow the pictures:
FIRST:
SECOND:
THIRD:
FOURTH:
FIFTH
Step 4 - Download JSON
After we created our "oAuth-Deleter service-account", we need to manually download the needed JSON, so we can paste it into our functions-folder.
For this, select "oauth-deleter#your-domain.iam.gserviceaccount.com" under "Service Account"
Then click on "Keys" and "Add key", which will automagically download you a service-json (SELECT Key type → JSON → Create).
Step 5 - Paste JSON file into your functions-folder
To loosen up the mood a bit, here is an easy step. Paste the downloaded JSON-File into your functions-folder.
Step 6 - Grant Access to our new created oAuth-Delelter-Account
Creating the service-account and giving it access in the normal GCP is not enough for Google, so we also have to give it access in our Firebase project. For this, go back into your "GCP via Firebase (see Step 2)" → Click on Setting → "User Access for Account" → Click on the "plus"
Then click on "Add user" and write the email we copied before into the email field (the email from Step 3, Picture FOURTH "Service-Account ID). In our case, it is "oAuth-Deleter#your-domain.iam.gserviceaccount.com". Furthermore, it needs admin-access:
Step 6 - The code
Now, after these million unnecessary but necessary steps, we get to the final part. THE DAMN CODE. I've written this in typescript with "compilerOptions" → "module": "esnext", "target": "esnext". But I am sure that you are smart enough to change the code after completing this many steps :)
import admin from "firebase-admin";
import functions from "firebase-functions";
import ky from "ky";
import docs from "#googleapis/docs";
import { UserRecord } from "firebase-functions/v1/auth";
export const dbUserOnDeleted = functions.
.auth
.user()
.onDelete((user) => doOnDeletedUser(user))
----------------------------
export asnc function doOnDeletedUser/user: UserRecord) {
try {
const googleDeletionURL = "https://www.googleapis.com/analytics/v3/userDeletion/userDeletionRequests:upsert"
// Step 1: Paste propertyID: (See Step 2)
const copiedPropertyID = "12345678"
// Step 2: Create oAuthClient
const oAuthClient = new docs.auth.GoogleAuth({
keyFile: "NAME-OF-THE-FILE-YOU-COPIED-INTO-YOUR-FUNCTIONS-FOLDER",
scopes: ["https://www.googleapis.com/auth/analytics.user.deletion"]
});
// Step 3: Get user uid you want to delete from user-analytics
const uid = user.uid
// Step 4: Generate access token
// (this is the reason why we needed the 5 steps before this)
// yup, a single line of code
const accessToken = await oAuthClient.getAccessToken() as string;
// Step 5: Make the request to google and delete the user
return ky.post(googleDeletionURL, {
body: JSON.stringify({
kind: "analytics#userDeletionRequest",
id: {
type: "USER_ID",
userid: uid
},
propertyId: copiedPropertyID
}),
headers: {
"Authorization": "Bearer " + accessToken,
}
});
} catch (err) {
functions.logger.error(`Something bad happened, ${(err as Error).message)`
}
}
Afterthoughts
This was and probably will be my longest post at stack overflow forever. I have to say that it was a pain in the a** to get this thing to working. The amount of work and setup that is needed to simply delete a data from one endpoint is just ridiculous. Google, please fix.

Disable web security in Cypress just for one test

After reading the Cypress documentation on web security and when to disable it, I've decided I indeed need to do it. Is there a way to disable this just for one particular test/test suite? I'm using version 3.4.1 and this config is being set in cypress.json - therefore it's global for all tests.
Is there a way to disable web security just for one test? Thanks!
Original answer:
Does this work for you?
describe("test the config json", function () {
it("use web security false here", function () {
Cypress.config('chromeWebSecurity',false);
cy.visit("https://www.google.com");
console.log(Cypress.config('chromeWebSecurity'));
});
it("use web security true here", function () {
Cypress.config('chromeWebSecurity',true);
cy.visit("https://www.google.com");
console.log(Cypress.config('chromeWebSecurity'));
});
});
The config is changed as you can see from the console log.
See document here https://docs.cypress.io/guides/references/configuration.html#Cypress-config
Updates:
After I saw DurkoMatKo's comment I managed to find an URL to test this 'chromeWebSecurity' option. It did not work as expected.
I think changing this config might not work during running the same browser as this is more like a browser feature which will determine when start.
In this case what I can think of is only to run Cypress with different configurations.
The cypress doc here shows clear steps to do this.
hope this helps.
In my case it worked as follows.
the first thing was to set chromeWebSecurity to false
//cypress.json
{
"chromeWebSecurity": false
}
Then what I do is with a before assign it to true with Cypress.config
//cypress/integration/testing.spec.js
context('DEMO-01', () => {
beforeEach(function () {
Cypress.config('chromeWebSecurity', true);
});
describe('CP001 - start dasboard', () => {
it('P01: open dashboard', () => {
cy.visit(URL);
});
});
});

How to delete a large node in firebase

I have a Firebase child node with about 15,000,000 child objects with a total size of about 8 GB of data.
exampele data structure:
firebase.com/childNode/$pushKey
each $pushKey contains a small flat dictionary:
{a: 1.0, b: 2.0, c: 3.0}
I would like to delete this data as efficiently and easy as possible. How?
What i Tried:
My first try was a put request:
PUT firebase.com/childNode.json?auth=FIRE_SECRET
data-raw: null
response: {
"error": "Data requested exceeds the maximum size that can be accessed with a single request. Contact support#firebase.com for help."
}
So that didn't work, let's do a limit request:
PUT firebase.com/childNode.json?auth=FIRE_SECRET&orderBy="$key"&limitToFirst=100
data-raw: null
response: {
"error": "Querying related parameters not supported on this request type"
}
No luck so far :( What about writing a script that will get the first X number of keys and then create a patch request with each value set to null?
GET firebase.com/childNode.json?auth=FIRE_SECRET&shallow=true&orderBy="$key"&limitToLast=100
{
"error" : "Mixing 'shallow' and querying parameters is not supported"
}
It's really not going to be easy this one? I could remove the shallow requirement and get the keys, and finish the script. I was just hoping there would be a easier/more efficient way???
Another thing i tried were to create a node script that listen for childAdded and then directly tries to remove those children?
ref.authWithCustomToken(AUTH_TOKEN, function(error, authData) {
if (error) {console.log("Login Failed!", error)}
if (!error) {console.log("Login Succeeded!", authData)}
ref.child("childNode").on("child_added", function(snap) {
console.log(`found: ${snap.key()}`)
ref.child("childNode").child(snap.key()).remove( function(err) {
if (!err) {console.log(`deleted: ${snap.key()}`)}
})
})
})
This script actually hangs right now, but earlier I did receive somethings like a max stack limit warning from firebase. I know this is not a firebase problem, but I don't see any particular easy way to solve that problem.
Downloading a shallow tree, will download only the keys. So instead of asking the server to order and limit, you can download all keys.
Then you can order and limit it client-side, and send delete requests to Firebase in batches.
You can use this script for inspiration: https://gist.github.com/wilhuff/b78e7391396e09f6c614
Use firebase cli tool for this: firebase database:remove --project .
In Browser Console this is fastest way
database.ref('data').limitToFirst(10000).once('value', snap => {
var updates = {};
snap.forEach(snap => {
updates[snap.key] = null;
});
database.ref('data').update(updates);
});

Meteor & account-base - how to get data for different users

I have basic project in Meteor created from Meteor-admin stub: (https://github.com/yogiben/meteor-admin)
I need to display avatars for all users, not only current one.
For displaying user's avatar I need his email address. (I am using utilities:avatar https://atmospherejs.com/utilities/avatar)
Question: what adjustments to project should I make to be able to access other users' data?
It probably has something to do with publishing users.
At the moment I have:
{{> avatar user=getAuthor shape="circle" size="small"}}
getAuthor: ->
console.log 'Owner:'
console.log #owner
user = Meteor.users.findOne(#owner)
console.log user
user
This correctly prints Owner: #owner (id) for all users, but user object is only populated for current user.
I also have this code in server-side:
Meteor.publishComposite 'user', ->
find: ->
Meteor.users.find _id: #userId
children: [
find: (user) ->
_id = user.profile?.picture or null
ProfilePictures.find _id: _id
]
(children / ProfilePicture are irrelevent)
I think account-base library turns publishing off or something? Thanks for help!
Bonus question: I would like to access only some info about an user (email address).
If you remove the package autopublish, you need to specify explicitly what the server sends to the client. You can do this via Meteor.publish and Meteor.subscribe.
For instance, to publish the email addresses of all users you could do:
if (Meteor.isServer) {
Meteor.publish('emailAddresses', function() {
return Meteor.users.find({}, {
fields: {
'email': 1
}
});
});
}
After that, you need to subscribe to the publication on the client:
if (Meteor.isClient) {
Meteor.subscribe("emailAddresses");
}
Read more about Meteor's publish and subscribe functionality.
Having collection: Meteor.users
To access other users data just publish it on the server-side:
Meteor.publish 'userData', ->
Meteor.users.find()
On client side you don't have to use any userData reference. Just access it:
Meteor.users.findOne(someId)
To allow access to only specific information, publish it with fields parameter:
Meteor.publish 'userData', ->
Meteor.users.find({},{fields: {'_id', 'emails', 'username'}})

Publishing different data for different users

I'm trying to publish all users to admins only but ommitting certain data (In this case an API key which is supposed to be "private" to each user, I realize that the admin can most likely check the database but let's ignore the security implications for now).
So the basic idea is that a user can see his own profile completely and no one else. An admin can see his own complete profile and a censored version of all other user's profiles. For this I have the following publish code:
Meteor.publish('currentUser', function() {
return Meteor.users.find({_id: this.userId}, {fields: {'profile.apiKey': true}});
});
Meteor.publish('allUsers', function() {
var currentUser = Meteor.users.findOne(this.userId);
return currentUser && currentUser.profile.admin ?
Meteor.users.find({}, {sort: ['username', 'asc'], fields: {'profile.apiKey': false}}) : null;
});
The problem is that the apiKey field doesn't get published after logging in. Ie. if I simply login as an admin the admin's apiKey won't be available until the page is reloaded. Removing the restriction from the 'allUsers' publish function solves the issue so it must have something to do with this. Is there any way to force Meteor to reload the subscriptions after a login?

Resources