I want to be able to create some firebase references when the first user connects to a socket.io room, and then listen to changes on those firebase references, and write to files.
Now my approach was to have a global Map object, then assign firebase references and filenames to arrays as part of an object. Then store the object, to the global map.
Unfortunately, the references are lost as soon as the function for first connection ends. I can see this by letting another user connect / refreshing the page and logging the global map.
Here is what my code looks like.
io.on('connect', (socket) => {
io.to(socket.id).emit('create-room');
socket.on('join-room', (roomId: string) => {
socket.join(roomId);
try {
console.log(activeWorkers)
if (!activeWorkers.get(roomId)) {
// if not in global map, tell user he is first in room so that we can get additional info
io.to(socket.id).emit('first-in-room');
} else {
console.log(activeWorkers.get(roomId));
}
} catch(err) {
console.log(err);
}
});
socket.on('create-worker', async (roomId: string, additionalInfo: string) => {
// first user sent back additional info;
let firebaseRefObj = new FirebaseRefObj(roomId, additionalInfo); // an object that stores the references and file paths
activeWorkers.set(roomId, {
id: roomId,
worker: firebaseRefObj
}); // store in global map
firebaseRefObj.createRefs(); // create references, the references add initial values to firebase, then watch for changes to update local files.
})
socket.on("disconnect", () => {
socket.removeAllListeners();
});
})
This is because socket.io functions don't persist their variables from within on events, if you are trying to access the firebaseRefObj at a later point, you must store it into an array or an object map so you can re-associate that user with it.
You could use the Room ID as you have and pass it as a name of the value in your list of references {[roomId]: firebaseRefObj}
Personally, I highly recommend an enmap for this use case, an enmap is like a C# dictionary for node.js. When you initiate your script and declare your variables, add a new enmap at the top to store your ref objects.
I believe the following will work:
const Enmap = require("enmap");
const FirebaseRefs = new Enmap();
// ...
FirebaseRefs.set(RoomId, FirebaseRefsObj);
You can read the basics here: https://enmap.evie.dev/usage/basic
Source: https://www.npmjs.com/package/enmap
Enmap was a really good solution, but my problem lied with what I was using my firebase references with. I was internally using firepad, and it took itself along with the firebase reference out of scope.
Removing firepad fixed my issue.
Related
I know there is an old post about this functionality in javascript: Getting All Variables In Scope
But I am desperately looking and thought I'd just ask in case there might be transpile magic in typescript (or a way we can extend it) that may acheve this..(e.g.)
.ts
() => {
const a = 123;
console.log(scope)
}
.js transpile
var scope = {};
() => {
scope.a = 123;
console.log(scope);
}
The reason I'm asking is that I'm looking for a node backend solution to be able to log a function's scope state and save it in a debug database for review. (So any time an error occurs the state is never lost but recorded instead)
#k0pernikus
Getting specific on my issue, I'm trying to get more context on what went wrong with my handler for a firebase event. functions.firestore.document('users/{uid}').onUpdate
// ====== Pretend code I wish would work =====
const logScopeSomewhere = (anonymousScope) => (err) => {
console.log(anonymousScope) // { pie: 'pie', apple: 'apple' }
// or write error to database..
// Main goal is that all variables in the function that errored are logged with the Type Error..
}
const handleUpdate = (change: Change<QueryDocumentSnapshot>, context: EventContext) => {
let anonymousScope;
return (async () => {
anonymousScope = scope; // special reserved - // Possible JS transpile manipulation can have a closure on top..
const pie = 'pie'; // anonymousScope.pie
const apple = 'apple'; // anonymousScope.apple
// apple.core.seed - // TypeError (will go to catch..)
})().catch(logScopeSomewhere(anonymousScope))
}
functions.firestore.document('users/{uid}').onUpdate(handleUpdate)
Trying to be more clear, I want to have an object that has a snapshot of the state of the executing function when the error occurred. I plan to catch it and will use it as logging information.
I don't think V8 exposes scope information in any other way than through DevTools.
You can parse your own source code, which lets you do any analysis you want. See this answer to the old question you linked for an example.
The TypeScript compiler must be doing the same analysis already internally, so I'm pretty sure it could be extended to dump that information. For example, it should be possible to extend the TypeScript language with a scope keyword (or whatever) that gets compiled to a JS object containing all in-scope variables and their values. That said, I have no idea whether something like that already exists, or how one would go about adding it.
I'm trying to use a mongodb find items and stored in ReactiveDict, but I'm just getting the error:
{{#each}} currently only accepts arrays, cursors or falsey...
What am I doing wrong here?
Template.body.onCreated(function(){
Meteor.subscribe('itemFind');
this.global = new ReactiveDict();
this.global.set('items',Items.find());
});
Template.body.helpers({
items(){
console.log(Template.instance().global.get('items'));
return Template.instance().global.get('items');
}
});
Further, I figured if I added a .fetch() to the original find statement this would be fixed, but apparently not.
I'm new to Meteor, so what am I doing wrong here?
On the onCreated the subscription is not ready yet. A good practice is to define your Reactive vars on onCreated and assign them on onRendered.
In any case, you need to wait for the subscription to be ready. To do so you can use an autorun(). The autorun() will rerun every time its dependencies are updated.
Template.body.onCreated(function() {
this.global = new ReactiveDict({});
});
Template.body.onRendered(function()
this.autorun(() => {
const subs = Meteor.subscribe('itemFind');
if(subs.ready()) {
this.global.set('items', Items.find({}));
}
});
});
Mainly out of curiosity, but also for a better understanding of Meteor security, what is the reason(ing) behind Meteor.user() not working inside publish functions?
The reason is in this piece of code (from meteor source code)
Meteor.user = function () {
var userId = Meteor.userId();
if (!userId)
return null;
return Meteor.users.findOne(userId);
};
Meteor.userId = function () {
// This function only works if called inside a method. In theory, it
// could also be called from publish statements, since they also
// have a userId associated with them. However, given that publish
// functions aren't reactive, using any of the infomation from
// Meteor.user() in a publish function will always use the value
// from when the function first runs. This is likely not what the
// user expects. The way to make this work in a publish is to do
// Meteor.find(this.userId()).observe and recompute when the user
// record changes.
var currentInvocation = DDP._CurrentInvocation.get();
if (!currentInvocation)
throw new Error("Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.");
return currentInvocation.userId;
};
I follow a tutorial with Meteor I try to create a collection, both for client and server. Here is my code:
var lists = new Meteor.Collection("Lists");
if (Meteor.isClient) {
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
As tutorial I have read, when run on server, if I open chrome console and type lists I will receive Meteor.Collection. But when I tried on my machine, I received error:
Reference error. lists is not define
Have I done something wrong? Please tell me.
Thanks :)
Also you can put all your collections inside the /lib/collection.js route (for better practices).
So with that we ensure that meteor loads first the collections, and they will be available on both client/server.
you should remove Autopublish/insecure package, to avoid meteor sends all the collections when load and to control who can or not insert/remove/update on the collections.
meteor remove autopublish
meteor remove insecure.
So a simple collection will look like this.
//lib/collection.js
Example = new Mongo.Collection("Example") //we create collection global
if(Meteor.isClient) {
Meteor.subscribe('Example') //we subscribe both after meteor loads client and server folders
}
now on /server/collections.js
Meteor.publish('Example', function(){
return Example.find(); //here you can control whatever you want to send to the client, you can change the return to just return Example.find({}, {fields: {stuff: 1}});
});
// Here we control the security of the collections.
Example.allow({
insert: function(userId, doc) {
if(Meteor.userId()){
return true; //if the user is connected he can insert
} else{
return false// not connected no insert
}
},
update: function(userId, doc, fields, modifier) { //other validation },
remove: function(userId, doc) { //other validation },
});
Just to try to explain a little more deep the Collection here on meteor, hope it help you GL
I think you have autopulish/autosubscribe turned off. Try
if (Meteor.isClient) {
Meteor.subscribe('lists');
}
if (Meteor.isServer){
Meteor.publish('lists',function(){
return Lists.find();
});
}
For your naming, I'd also recommend you reverse the way you're capitalizing your collections. So instead it would be
var Lists = new Meteor.Collection("lists");
And finally, look at https://github.com/matteodem/meteor-boilerplate for your directory structure so you don't have to do the if meteor.is stuff anymore.
Edit
Full code should look like:
var Lists = new Meteor.Collection("lists");
if (Meteor.isClient) {
Meteor.subscribe('lists');
}
if (Meteor.isServer){
Meteor.publish('lists',function(){
return Lists.find();
});
}
All of your script source files are wrapped in a function closure as part of the build process. In order for your collection to be visible outside of that file (or in your case - attached to the window object) you will need to declare it as a global variable:
Lists = new Meteor.Collection('lists');
Note the lack of var. As #thatgibbyguy pointed out, the accepted pattern is to capitalize collection variables, and camelcase collection names.
I am trying to get all the variables that have been defined, i tried using the global object
but it seems to be missing the ones defined as var token='44'; and only includes the ones defined as token='44';. What i am looking for idealy is something like the get_defined_vars() function of php. I need to access the variables because i need to stop the node process and then restart at the same point without having to recalculate all the variables, so i want to dump them somewhere and access them later.
It's impossible within the language itself.
However:
1. If you have an access to the entire source code, you can use some library to get a list of global variables like this:
var ast = require('uglify-js').parse(source)
ast.figure_out_scope()
console.log(ast.globals).map(function (node, name) {
return name
})
2. If you can connect to node.js/v8 debugger, you can get a list of local variables as well, see _debugger.js source code in node.js project.
As you stated
I want to dump them somewhere and access them later.
It seems like you should work towards a database (as Jonathan mentioned in the comments), but if this is a one off thing you can use JSON files to store values. You can then require the JSON file back into your script and Node will handle the rest.
I wouldn't recommend this, but basically create a variable that will hold all the data / variables that you define. Some might call this a God Object. Just make sure that before you exit the script, export the values to a JSON file. If you're worried about your application crashing, perform backups to that file more frequently.
Here is a demo you can play around with:
var fs = require('fs');
var globalData = loadData();
function loadData() {
try { return require('./globals.json'); } catch(e) {}
return {};
}
function dumpGlobalData(callback) {
fs.writeFile(
__dirname + '/globals.json', JSON.stringify(globalData), callback);
}
function randomToken() {
globalData.token = parseInt(Math.random() * 1000, 10);
}
console.log('token was', globalData.token)
randomToken();
console.log('token is now', globalData.token)
dumpGlobalData(function(error) {
process.exit(error ? 1 : 0);
});