I have 2 firestore collections - users/{user}/clients and users/{user}/pros. If a new client registers and a new document is created, I want to search collection pros for professionals working in the matching field and living within 5 km (of the new client), send notification to the pros filtered, and redirect them to a new page that shows the new client's details. In order to implement that in cloud functions,
I installed geofirestore and wrote code like this;
exports.sendNotification = functions.firestore
.document("users/{user}/clients/{client}")
.onCreate(async snapshot => {
try {
const clientfield = snapshot.data().field;
const clientaddress = snapshot.data().address;
const clientgeopoint = snapshot.data().g.geopoint;
const geocollection = geofirestore.collection('users/{user}/pros');
const query = geocollection.near({center: clientgeopoint, radius: 5}).where('sector', '==', clientsector);
const tokenArray = [];
query.get().then(querySnapshot => {
querySnapshot.forEach(doc => {
let token = doc.data().fcmToken
tokenArray.push(token);
})
}).catch ((error) => console.log(error));
const message = {
"notification": {
title: 'blah',
body: 'blah'
},
}
return await admin.messaging().sendToDevice(tokenArray, message);
} catch (error) {
console.log(error)
}
})
Notification part works now, but I still have this problem of redirecting to a page that shows a new client's request details for the pros to see when they click and open the app. How can I redirect users to a page when they click the notification and open the app? I appreciate any help.
I posted a new question about redirecting to a page on notification click, and got the answer there. Please refer to here -> how to redirect users to a page on notification click
Related
I'm scraping data from Google using Puppeteer. But before Puppeteer gets to the google page, an annoying popup appears (screenshot).
I want to prevent this from happening so that I don't have to click the "reject" / "allow" button in Puppeteer every time. What are the necessary cookies to achieve it?
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto("https://google.com/")
const cookies = [
{
domain: '.google.com',
expirationDate: 9999999999.298648,
name: 'CONSENT',
...
},
]
await page.setCookie(...cookies)
Instead of trying to hard code the cookies, just save them.
Come up with a file path where the cookies file will be saved, e.g. cookiesFilePath.
After launching puppeteer, check to see if a file exists at the path. If it exists load it.
Keep your code for navigating the consent form, as you will use it at least once. At the end of the accepting/rejecting, save page.cookies to cookiesFilePath
const cookiesPath = './cookies.json';
const browser = await puppeteer.launch()
const page = await browser.newPage()
if(await fs.existsSync(cookiesPath)){
let cookies = require(cookiesPath)
for(let cookie of cookies){
await page.setCookie(cookie)
}
}
await page.goto("https://google.com/")
const hasConsentForm = async ()=>{
// create query that will return consent form
const consentFormQuerySelector=''
// I dont remember if page.$eval querySelector fails
// throws errors or not
try{
return await page.$eval(consentFormQuerySelector,el=>Boolean(el))
}catch(err){
console.log(err)
return false
}
}
const navigateConsentForm = async ()=>{
// add your existing code that navigates the consent form
// ...
// after the consent form is cleared, save cookies
let cookies = await page.cookies()
fs.writeFileSync(cookiesPath,JSON.stringify(cookies,null,2))
}
if(await hasConsentForm()){
await navigateConsentForm()
}
Assuming that the lack of cookies or the lack of a certain cookie property is the reason that the consent form pops up, this is will make it so that after running once, the form doesnt show again.
I trying to code a simple customize statistic with command trought data base "mysql" and i have problem about define guildID in "Ready" function is the anyway to define it or i need search other solutions
const Discord = require("discord.js")
const { bot } = require('../index');
const { mysql } = require('../index')
bot.on('ready', async () => {
setInterval(function() {
let sql = 'SELECT * FROM stats WHERE guildID = ?'
mysql.query(sql, [guild.id], (err, results) => {
let allchannels = results.channelID
let guildid = results.guildID
setInterval(() => {
const guild = bot.guild.get(`${guildid}`)
var userCount = guild.memberCount;
const totalUsers = bot.channels.get(`${allchannels}`)
totalUsers.setName(`total members = ${userCount}`)
}, 20000);
})
}, 15000);
})
connection.query(sql, [guild.id], (err, results) => {
ReferenceError: guild is not defined
i want to code a statistic like StartIT v4 bot but idk is it possible in ready? i dont want on any restart my bot use command like !start statistic etc,
im glad if someone know how to fix it or have any solution
You problem is that guild is not defined, the bot Object has no guild only guilds a Collection where all guilds are listed. You can either fetch the id of the specific server and filter the collection, or what i think is better for your case loop throw the hole collection and set the statistics.
As for if its possisble, you can use bot.on guildCreate and guildDelete to listen when a bot joines a new server or leaves a server. I dont know what values you want to have in your statistics, but if you want an overview above all servers your bot is running on i would use the create and delete event.
Here you can see that the client (your bot) has no attribute guild
discord.js Client
I'm currently developing an App in Flutter and I came across one problem which I'm trying to solve for a while without any success. After someone downloads the app he/she will have to register first in order to use it. User can choose between Driver or Promoter and depending on that, the user will fill up different registration forms. After that process is done (all the data is stored in RealTime database in 2 different folders, driverdata or promoterdata), User will be redirected to the Welcome Page (Authentication Page). After User types in their e-mail and password and click on Log In button, I want to program the path for users to open different Pages (DriverFeed or PromoterFeed).
I had an idea to write a function that will check if an id from user is stored in driverdata or promoterdata, but without any success.
Then I was searching some other solutions on the internet and I came across this: https://www.youtube.com/watch?v=3hj_r_N0qMs but I don't think that this is the good way to solve my problem. I don't want that any User has Moderator claims.
Here is the solution that is working at the moment but not the way I wanted to. I am using ScopedModels and In my connected class / UserModel I wrote:
Future<String> getUserStatus() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String userStatus = prefs.getString('userStatus');
return userStatus;
}
Future<bool> setUserStatus(String userStatus) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('userStatus', userStatus);
return true;
}
Then in the registration Page after the user enters all his data and click on submit button, depending on which user is registering I set their value to the 'driver' or 'promoter' with the following funciton:
setUserStatus('driver');
or
setUserStatus('promoter');
Then in my AuthPage where the user is Loging in, when the user enter his e-mail and password and click on the LogIn button I'm calling log in Function and it looks like this:
Future<void> _submitForm(Function login) async {
if (!_formKey.currentState.validate()) {
return;
}
_formKey.currentState.save();
final Map<String, dynamic> successInformation =
await login(_formData['email'], _formData['password']);
if (successInformation['success']) {
model.getUserStatus().then((a) {
if (a == 'driver') {
Navigator.of(context).pushReplacementNamed('/driverfeed');
} else if (a == 'promoter') {
Navigator.of(context).pushReplacementNamed('/promotions');
} else {
return Container();
}
});
} else {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('An Error Ocurred'),
content: Text(successInformation['message']),
actions: <Widget>[
FlatButton(
child: Text('Okay'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
}
}
I am new to chrome-extension development. I followed some tutorials and later I was playing with the extension . there is something bugging me this extension.
steps to reproduce:
open the devtools in two pages side by side
follow the steps mentioned in github page.
when you click on(any one of the page) 'Insert button to send a message from page to devtools' button in panel, you can see that Html page changes.
then click on the button displayed in page. You can see that in both the panels, button/text is changed.
Is there any filter to restrict this and only change the button/text on clicked page only? I know this is happening because of port.postMessage(message); in background.js page.
I found this but I was not able to make it work.
Any help would be appreciated :)
The communication scheme is simple:
your devtools panel opens a port to the background page
the background page listens on that port and stores a lookup table of tabId-to-port mappings
the background page also listens for messages from content scripts and uses the above lookup table to route the message to a corresponding port channel
devtools-panel.js
var port = chrome.runtime.connect();
port.onMessage.addListener(message => {
$id('insertmessagebutton').innerHTML = message.content;
});
$id('executescript').onclick = () =>
runContentScript({code: "console.log('Content script executed')"});
$id('insertscript').onclick = () =>
runContentScript({file: "inserted-script.js"});
$id('insertmessagebutton').onclick = () => {
runContentScript({code: "document.body.innerHTML='<button>Send to panel</button>'"});
runContentScript({file: "messageback-script.js"});
};
function runContentScript(params) {
port.postMessage(
Object.assign({
tabId: chrome.devtools.inspectedWindow.tabId,
}, params)
);
}
function $id(id) {
return document.getElementById(id);
}
background.js
var tabPorts = {};
chrome.runtime.onConnect.addListener(port => {
let tabId;
port.onMessage.addListener(message => {
if (!tabId) {
// this is a first message from devtools so let's set the tabId-port mapping
tabId = message.tabId;
tabPorts[tabId] = port;
}
if (message.code || message.file) {
delete message.tabId;
chrome.tabs.executeScript(tabId, message);
}
});
port.onDisconnect.addListener(() => {
delete tabPorts[tabId];
});
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
const port = sender.tab && tabPorts[sender.tab.id];
if (port) {
port.postMessage(message);
}
});
chrome.tabs.onRemoved.addListener(tabId => {
delete tabPorts[tabId];
});
chrome.tabs.onReplaced.addListener((newTabId, oldTabId) => {
delete tabPorts[oldTabId];
});
I have a Chrome extension that requests a user to login using the chrome.identity.getAuthToken route. This works fine, but when you login you can only use the users that you have accounts in Chrome for.
The client would like to be able to login with a different Google account; so rather than using the.client#gmail.com, which is the account Chrome is signed in to, they want to be able to login using the.client#company.com, which is also a valid Google account.
It is possible for me to be logged in to Chrome with one account, and Gmail with a second account, and I do not get the option to choose in the extension.
Is this possible?
Instead of authenticating the user using the chrome.identity.getAuthToken , just implement the OAuth part yourself.
You can use libraries to help you, but the last time I tried the most helpful library (the Google API Client) will not work on a Chrome extension.
Check out the Google OpenID Connect documentation for more info. In the end all you have to do is redirect the user to the OAuth URL, use your extension to get Google's answer (the authorization code) and then convert the authorization code to an access token (it's a simple POST call).
Since for a Chrome extension you cannot redirect to a web server, you can use the installed app redirect URI : urn:ietf:wg:oauth:2.0:oob. With this Google will display a page containing the authorization code.
Just use your extension to inject some javascript code in this page to get the authorization code, close the HTML page, perform the POST call to obtain the user's email.
Based on David's answer, I found out that chrome.identity (as well as generic browser.identity) API now provides a chrome.identity.launchWebAuthFlow method which can be used to launch an OAuth workflow. Following is a sample class showing how to use it:
class OAuth {
constructor(clientId) {
this.tokens = [];
this.redirectUrl = chrome.identity.getRedirectURL();
this.clientId = clientId;
this.scopes = [
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.send"
];
this.validationBaseUrl = "https://www.googleapis.com/oauth2/v3/tokeninfo";
}
generateAuthUrl(email) {
const params = {
client_id: this.clientId,
response_type: 'token',
redirect_uri: encodeURIComponent(this.redirectUrl),
scope: encodeURIComponent(this.scopes.join(' ')),
login_hint: email
};
let url = 'https://accounts.google.com/o/oauth2/auth?';
for (const p in params) {
url += `${p}=${params[p]}&`;
}
return url;
}
extractAccessToken(redirectUri) {
let m = redirectUri.match(/[#?](.*)/);
if (!m || m.length < 1)
return null;
let params = new URLSearchParams(m[1].split("#")[0]);
return params.get("access_token");
}
/**
Validate the token contained in redirectURL.
This follows essentially the process here:
https://developers.google.com/identity/protocols/OAuth2UserAgent#tokeninfo-validation
- make a GET request to the validation URL, including the access token
- if the response is 200, and contains an "aud" property, and that property
matches the clientID, then the response is valid
- otherwise it is not valid
Note that the Google page talks about an "audience" property, but in fact
it seems to be "aud".
*/
validate(redirectURL) {
const accessToken = this.extractAccessToken(redirectURL);
if (!accessToken) {
throw "Authorization failure";
}
const validationURL = `${this.validationBaseUrl}?access_token=${accessToken}`;
const validationRequest = new Request(validationURL, {
method: "GET"
});
function checkResponse(response) {
return new Promise((resolve, reject) => {
if (response.status != 200) {
reject("Token validation error");
}
response.json().then((json) => {
if (json.aud && (json.aud === this.clientId)) {
resolve(accessToken);
} else {
reject("Token validation error");
}
});
});
}
return fetch(validationRequest).then(checkResponse.bind(this));
}
/**
Authenticate and authorize using browser.identity.launchWebAuthFlow().
If successful, this resolves with a redirectURL string that contains
an access token.
*/
authorize(email) {
const that = this;
return new Promise((resolve, reject) => {
chrome.identity.launchWebAuthFlow({
interactive: true,
url: that.generateAuthUrl(email)
}, function(responseUrl) {
resolve(responseUrl);
});
});
}
getAccessToken(email) {
if (!this.tokens[email]) {
const token = await this.authorize(email).then(this.validate.bind(this));
this.tokens[email] = token;
}
return this.tokens[email];
}
}
DISCLAIMER: above class is based on open-source sample code from Mozilla Developer Network.
Usage:
const clientId = "YOUR-CLIENT-ID"; // follow link below to see how to get client id
const oauth = new OAuth();
const token = await oauth.getAccessToken("sample#gmail.com");
Of course, you need to handle the expiration of tokens yourself i.e. when you get 401 from Google's API, remove token and try to authorize again.
A complete sample extension using Google's OAuth can be found here.