Add active connections to channel when it's created - node.js

I am trying to prevent who can see events for a particular page, so I am determining and storing the ids of which users have access to a particular page. In channels.js, I can add users to the new page-level channel in app.on('connection') by iterating through the pages like this: app.channel('page-' + pageId).join(connection)
The problem is that a user doesn't start getting broadcasts from that page until after they refresh the browser and re-connect.
What I want to happen is have all connections for a an allowed user start getting broadcasts when the page is created. Is there a way to do that in channels.js, or can I tell it who to start broadcasting to in a hook for the page creation?
Editing to add the last thing I tried. "Users-pages" is an associative entity that links Users and Pages.
app.service('users-pages').on('created', userPage => {
const { connections } = app.channel(app.channels).filter(connection =>
connection.user.id === userPage.userId
)
app.channel('page-' + userPage.pageId).join(connections)
})

The keeping channels updated documentation should have the answer you are looking for. It uses the users service but can be updated for pages accordingly which could look like this:
app.service('pages').on('created', page => {
// Assuming there is a reference of `users` on the page object
page.users.forEach(user => {
// Find all connections for this user
const { connections } = app.channel(app.channels).filter(connection =>
connection.user._id === user._id
);
app.channel('page-' + pageId).join(connections);
});
});

Using app.channel('page-' + pageId).join(connections) wasn't working for me. But looping through each connection works fine:
connections.forEach(connection => {
app.channel('page-' + userPage.pageId).join(connection)
})

Related

How to reflect changes in client when database changes?

So here's my scenario: I have a client page, where any one can fill and submit a form. the form data is stored in a database. There is a separate admin pc which is on an admin page, where every new form entry is displayed. How to update the front end of the admin everytime a new form is submitted, without refreshing/re hitting the API?
I am using React with express and MongoDB.
You can use TanStack Query (also called react query) ,
it's useQuery (a data fetching method) has a build-in property called isFetching which allow automatic fetching in a specific interval you want, like :
const { data,isLoading,isFetching } = useQuery(
['random-data'],
async () => {
const { data } = await axios.get(
'https://random-data-api.com/api/v1/random_data'
);
return data;
},
{
// refetch data every sec.
refetchInterval: 1000,
}
);
you can check more here

How to load selected item from database?

So I have a default model set up for viewing my data, and a form for inputting the data. I want to know what the best practice is for retrieving the one item of selected data? it's for a MERN stack
Currently I am using window hash and adding the id onto the url and retrieving from database that way, I feel this is janky though and trying to add update functionality it seems like it might get confusing.
I've thought about adding a currentID to redux, but then I can see problems occurring when that is persisted and you go to create a recipe after viewing and end up editing instead of creating.
retrieving id from url
const recipeId = window.location.hash.substr(1);
const recipe = useSelector((state) =>
state.recipes.find((r) => r._id === recipeId)
);
I get my recipes from mongo
export const recipeList = async (req, res) => {
try {
const recipes = await recipeSheet.find();
res.status(200).json(recipes);
} catch (error) {
res.status(404).json({ message: error.message });
}
};
and store to redux
export const getRecipes = () => async (dispatch) => {
try {
const { data } = await api.fetchRecipes();
dispatch({ type: "FETCH_ALL_RECIPES", payload: data });
} catch (error) {
console.log(error.message);
}
};
It depends on how large is your data. It'd better define a new GET path to retrieve a single record, like BASE_URL/api/recipes/123 or you can add query acceptance for the current endpoint to find a specific id in DB and return it, like BASE_URL/api/recipes?id=123. The reason for that is besides the optimization (for large data sets), the record may change after you store all records to the redux store, and by the current solution, you show the old data to the user. Best practices tell us to choose the first way as your solution, the second way is usually for filtering the data. Then simply by sending the new URL by the user, trigger a new API call to the new endpoint and get the single record.

how to prevent multiple user input due to refreshes (Node JS)

I am quite new to web development itself. And I have a problem which I have no idea how to solve or even what to google. The web app is developed with Node JS and MySQL.
So let me explain the situation.
On my website you can create and delete posts.
On "/create", the user enters contents for the post.
Then the user clicks the "submit" button, which is routered to "/create_process"(where the data is actually saved in database)
Occasionally there is some delay in loading "/create_process". So the user keeps refreshing while loading. -> Here is the problem. Everytime the user refreshes at this stage same inputs are sent again and again. The result is multiple posts with exactly same contents.
I am sure that there must be a way to block such trivial inputs.
You can have a throttle function on whatever the use is clicking. Example:
const throttle = (func, limit) => {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
from medium article

Problems making batch request in SPFx

Cheers guys and gals.
Having some problems with $batching requests to SP from SPFx.
Some background: The SP structure has one site collection with lots of subsites. Each subsite has a list whose name is identical on all subsites. I need to access all of those lists.
A normal SPHttpClient call gives me the url of all of the sites. So far so good.
The plan was then to $batch the calls to get the data from the lists. Unfortunatly I only get the answer from one of the calls. The rest of the batched calls gives me "InvalidClientQueryException". If I change the order of the calls it seems like only the first call succeeds.
const spBatchCreationOptions: ISPHttpClientBatchCreationOptions = {
webUrl: absoluteUrl
};
const spBatch: SPHttpClientBatch = spHttpClient.beginBatch(spBatchCreationOptions);
// Add three calls to the batch
const dan1 = spBatch.get("<endpoint1>",SPHttpClientBatch.configurations.v1);
const dan2 = spBatch.get("<endpoint2>",SPHttpClientBatch.configurations.v1);
const dan3 = spBatch.get("<endpoint3>",SPHttpClientBatch.configurations.v1);
// Execute the batch
spBatch.execute().then(() => {
dan1.then((res1) => {
return res1.json().then((res10) => {
console.log(res10);
});
});
dan2.then((res2) => {
return res2.json().then((res20) => {
console.log(res20);
});
});
dan3.then((res3) => {
return res3.json().then((res30) => {
console.log(res30);
});
});
});
So in this case only the call dan1 succeeds. If I however change call2 to have an identical endpoint as the first call they both succeed.
I can't really wrap my head around this, so if someone has any input it would be much appreciated.
//Dan
Make sure that your endpoint is always the same site per one batch. You cannot mix different sites within one batch. In that case only the first call(s) will succeed which are from the same site.
To overcome that you might switch to a search call in case you wanna retrieve information (what you can do over the same site URL).
See my blogpost on that for further information.

Bot Framework Node.js ad hoc message TO A SPECIFIC USER

I have been staring at this for hours and can't find a solution and that is even though by all suggestions it SHOULD be quite easy - https://learn.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-proactive-messages.
I have created a simple code which will "register" the user and save their data in my cosmosDatabse on Azure. That works perfectly.
//ON "register" SAVE USER DATA AND SAY REGISTERED MESSAGE
bot.dialog('adhocDialog', function(session, args) {
var savedAddress = session.message.address;
session.userData.savedAddress = savedAddress;
//REGISTERED MESSAGE
session.endDialog("*Congratulations! You are now registered in our network! (goldmedal)*");
})
.triggerAction({
matches: /^register$/i
})
But how can I then access that specific user and send him a message if, say, a condition is met? (in fact on HTTP request)
I am fairly certain we have to write the conversation ID or user ID somewhere. The question is where?
function startProactiveDialog(address) {
bot.beginDialog(address, "A notification!");
}
This is how simple I think it should be. But where do you specify the user then?
You've saved the address of the user inside of your database by saving it to session.userData.savedAddress. When the event triggers, perform a query to your database that checks for the users that meet two criteria.
They're registered to listen for the event
Their address has been saved inside of the database.
In your case, you can save a property to the session.userData object, a property that lists which events they're listening for. If you just need to send a message to the user, then you can simply use bot.loadSession(savedAddress) to ping the user.
Edit:
So instead of looking specifically by user ID, you should send a query to your CosmosDB that looks for entries that have a "listen-to" Boolean-type flag corresponding to the event.
You're not worrying about the user ID at first, you're just retrieving all entries with a query that would (broadly speaking) look like this:
SELECT * FROM BotState WHERE data LIKE 'listenForEvent=1.
So to setup your session.userData so that the above theoretical query would work, you would need to modify that snippet of code in your question to something like the following:
bot.dialog('adhocDialog', function(session, args) {
var savedAddress = session.message.address;
session.userData.savedAddress = savedAddress;
session.userData.listenForEvent = 1 // Our property we're going to look for.
session.endDialog("*Congratulations! You are now registered in our network! (goldmedal)*");
})
.triggerAction({
matches: /^register$/i
})
Actually, the savedAddress should be an instance of IAddress, and also, the function loadSession(address: IAddress, callback: (err: Error, session: Session) => void): void; and address(adr: IAddress): Message; under Message class all require IAddress as the parameter.
So first of all, you should save the entire address json object in cosmosDB for later using.
As botbuilder for Node.js is built on Restify or Express, you can build an addition route for your user to trigger and send proactive messages. The work flow could be following:
Guide user to register & Save the user's address object with the account mapping in your DB
Create a Route in Restify or Expressjs for trigger the proactive message:
server.get('/api/CustomWebApi', (req, res, next) => {
//find the user's address in your DB as `savedAddress`
var msg = new builder.Message().address(savedAddress);
msg.text('Hello, this is a notification');
bot.send(msg);
res.send('triggered');
next();
}
);
or if you want to leverage loadSession
server.get('/api/CustomWebApi', function (req, res, next) {
bot.loadSession(savedAddress, (err, session) => {
if (!err) {
session.send('Hello, this is a notification')
session.endConversation();
}
})
res.send('triggered');
next();
});
I created a users.json file, to which I save all the users. It works the way I need it to. I guess database would be better, but I don't really have a clue where to begin with that. Database is a whole new chapter I have not encountered yet, so it doesn't make sense to work on it when the project needs are resolved.

Resources