How to access a registered workbox queue in my React application - reactjs

I'm using the create-react-app built in service worker for my PWA. I'm using the Queue class from workbox to add failed api calls to the queue. All the registration of the service worker is done in my service-worker.ts file.
My question: How can I access an already registered queue in my React component? I want to display a counter stating the amount of unsycned api calls. It is not clear to me how I can achieve this.
service-worker.ts
// add failed fetch requests to queue
const queue = new Queue('fetch-requests');
self.addEventListener('fetch', (event) => {
if (!self.navigator.onLine) {
const promiseChain = fetch(event.request.clone()).catch(() => {
if (event.request.method === 'POST' || event.request.method === 'PATCH') {
return queue.pushRequest({ request: event.request });
}
});
event.waitUntil(promiseChain);
}
});
App.tsx
const App: React.FC = () => {
// how can I access the queue from here and do some stuff with it?
return <Router />;
};
export default App;

Your queues are stored in indexedDB as "workbox-background-sync".
So, it's just a matter of querying the store. See example below
function getQueuesMeta() {
return new Promise(resolve => {
const connection = indexedDB.open("workbox-background-sync")
const res = []
connection.onsuccess = (event) => {
const db = event.target.result
try{
const tx = db.transaction(["requests"])
const reqIndex = tx.objectStore('requests').index('queueName')
reqIndex.openCursor().onsuccess = (e) => {
const cursor = e.target.result
let meta = {}
if (cursor) {
meta = {
// if you got meta, time to get it
timestamp: cursor.value.timestamp
}
if ('undefined' == typeof res[cursor.key]) {
res[cursor.key] = [meta]
} else {
res[cursor.key].push(meta)
}
cursor.continue()
} else {
resolve(res)
}
}
} catch (error){
console.log('no ‘request’ objectStore')
resolve(null)
}
}
})
}

Related

Intercepting Auth0 getSession with MSW.js and Cypress

I'm building NextJS app with SSR. I've written the getServerSideProps function that makes a call to supabase. Before making the call I'm trying to get user session by calling getSession function from #auth0/nextjs-auth0 package.
I'm trying to mock it in the handlers.ts file:
import { rest } from 'msw';
export const handlers = [
// this is the endpoint called by getSession
rest.get('/api/auth/session', (_req, res, ctx) => {
return res(ctx.json(USER_DATA));
}),
rest.get('https://<supabase-id>.supabase.co/rest/v1/something', (_req, res, ctx) => {
return res(ctx.json(SOMETHING));
}),
];
My mocks file: requestMocks/index.ts:
export const initMockServer = async () => {
const { server } = await import('./server');
server.listen();
return server;
};
export const initMockBrowser = async () => {
const { worker } = await import('./browser');
worker.start();
return worker;
};
export const initMocks = async () => {
if (typeof window === 'undefined') {
console.log('<<<< setup server');
return initMockServer();
}
console.log('<<<< setup browser');
return initMockBrowser();
};
initMocks();
Finally, I'm calling it in the _app.tsx file:
if (process.env.NEXT_PUBLIC_API_MOCKING === 'true') {
require('../requestMocks');
}
Unfortunately, it does work for me. I'm getting no user session data in the getServerSideProps function in my page component:
import { getSession } from '#auth0/nextjs-auth0';
export const getServerSideProps = async ({ req, res }: { req: NextApiRequest; res: NextApiResponse }) => {
const session = getSession(req, res);
if (!session?.user.accessToken) {
// I'm constantly falling here
console.log('no.session');
return { props: { something: [] } };
}
// DO something else
};
Any suggestions on how to make it working in Cypress tests would be great.
I'm expecting that I will be able to mock requests made in getServerSideProps function with MSW.js library.
I made it finally. Looks like I don't have to mock any calls. I need to copy my user appSession cookie and save it in cypress/fixtures/appSessionCookie.json file:
{
"appSession": "<cookie-value>"
}
Then use it in tests as follows:
before(() => {
cy.fixture('appSessionCookie').then((cookie) => {
cy.setCookie('appSession', cookie.appSession);
});
});
This makes a user automatically logged in with Auth0.

How do I get the data from an API call, in a different file, in React

I'm trying to update some code, taking into account new sdk versions. I have the new api call in one file:
import { CognitoIdentityProviderClient, ListUsersCommand } from "#aws-sdk/client-cognito-identity-provider";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
await Auth.currentCredentials().then((data) => {
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id
};
const command = new ListUsersCommand(params);
client.send(command).then(
(data) => {
return data
},
(error) => {
console.log(error)
}
);
});
}
I'm trying to retrive the data in another file:
import ListUsers from "../../../API/cognito/ListUsers";
import ListUsersInGroup from "../../../API/cognito/ListUsersInGroup";
import { useState, useEffect, useRef } from "react";
import PortalUsersTable from "../../../components/tables/PortalUsersTable";
export default function ManageUsers() {
const [userDetails, setUserDetails] = useState("");
const refUsers = useRef();
const refUsersExec = useRef();
const refUsersAdmin = useRef();
const refUsersGroups = useRef();
useEffect(() => {
function getUsers() {
refUsers.current = ListUsers();
refUsersExec.current = ListUsersInGroup("usersAdmin");
refUsersAdmin.current = ListUsersInGroup("usersExec");
//setUsersTloOfficers(apiTloOfficers);
refUsersGroups.current = ListUsersInGroup("usersGroups");
let userData = [];
let arrUsersExec = [];
for (let a in refUsersExec.current.Users) {
arrUsersExec.push(refUsersExec.current.Users[a].Username);
}
let arrUsersAdmin = [];
for (let b in refUsersAdmin.current.Users) {
arrUsersAdmin.push(refUsersAdmin.current.Users[b].Username);
}
let arrUsersGroups = [];
for (let b in refUsersNtigGroups.current.Users) {
arrUsersGroups.push(refUsersGroups.current.Users[b].Username);
}
for (let i in refUsers.current.Users) {
let email = null;
for (let x in refUsers.current.Users[i].Attributes) {
if (refUsers.current.Users[i].Attributes[x].Name === "email") {
email = refUsers.current.Users[i].Attributes[x].Value;
break;
}
}
let memberExec = arrUsersExec.includes(refUsers.current.Users[i].Username);
let memberAdmin = arrUsersAdmin.includes(refUsers.current.Users[i].Username);
let memberGroups = arrUsersGroups.includes(refUsers.current.Users[i].Username);
userData.push({
id: i,
Username: refUsers.current.Users[i].Username,
AccountStatus: refUsers.current.Users[i].UserStatus,
Email: email,
Users: memberGroups,
Exec: memberExec,
Admin: memberAdmin,
});
}
setUserDetails(userData);
}
getUsers();
}, []);
return (
<>
<h2>Manage Portal Users</h2>
<PortalUsersTable userDetails={userDetails} />
</>
);
}
The logic to handle the API data is sound.
This is the old API call:
import AWS from "aws-sdk";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
let idToken = "";
await Auth.currentAuthenticatedUser().then((user) => {
idToken = user.signInUserSession.idToken.getJwtToken();
});
AWS.config.region = awsmobile.aws_cognito_region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: awsmobile.aws_cognito_identity_pool_id,
RoleArn: "arn:aws:iam::xxxxxxxxx:role/xxxxxxxxxxxxx",
Logins: { "xxxxxxxxxxxxxxxxxxxx": idToken }
});
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
let params = {
UserPoolId: awsmobile.aws_user_pools_id,
AttributesToGet: ["email"]
};
return new Promise((resolve, reject) => {
cognitoidentityserviceprovider.listUsers(params, function (err, result) {
if (err) {
console.log(err);
//onError(err);
reject(err);
return;
}
if (result) {
resolve(result);
}
});
});
}
I can see the new API call is returning the correct data in the console. I think I'm not passing the data between files correctly.
I've tried various ways of changing the API call function, reading the cognito sdk description but it's not the API call that is incorrect.
How can I use the API call data in the separate file?
Even if your API call if correct, it looks like you are not returning anything from your function ListUsers. You are mixing async/await pattern with the then. I assume you have added a console.log right before the return data. Refactoring your function using async/await would look like this :
export default async function ListUsers() {
try {
const data = await Auth.currentCredentials();
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data,
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id,
};
const command = new ListUsersCommand(params);
const commandData = await client.send(command);
return commandData;
} catch (error) {
console.log(error);
}
}

Accessing AsyncStorage react native

Hello I am new to react Native and I would like to access the user id that was stored to react Native storage so that I can pass it to the WebSocket connection but it is not returning the id
here is my sample code
import { io } from "socket.io-client/dist/socket.io";
import env from "../utils/env";
import AsyncStorage from "#react-native-async-storage/async-storage";
const getUserData = async () => {
try {
const value = await AsyncStorage.getItem("UserData");
if (value !== null) {
return value;
}
} catch (e) {
// remove error
}
console.log("Done.");
};
getUserData().then((res) => {
let response = JSON.parse(res);
console.log(response._id);
});
let socket = io(`${env.DEV_SERVER_URL}`, {
transports: ["websocket"],
query: `mobileId=${getUserData().then((res) =>{
let response = JSON.parse(res);
return response._id
})}`,
});
export default socket;
const storeData = async (data) =>{
await AsyncStorage.setItem('UserData', JSON.stringify(UserData));
}
try this to store your data you might have forgot to stringify your object before storing it

How to return boolean value from custom function in react

I have an app that takes in an object named user. The user object has userId information which is needed to get information from firestore database if the person is a paid member or not, membership is either true or false. If the person is a non-paid member than i want to display a button, and if he is a paid member, than i want the button to not be displayed. The problem i am having is how to return a boolean from the PaidMembership() function?
const App = ({ user, database }) => {
const PaidMembership = () => {
var test = null;
docRef.get().then(function(doc) {
if (doc.exists) {
test = doc.data().membership;
//console.log(paidMembership);
} else {
console.log("Error: no such document exists")
test = false;
}
})
return test;
}
return (
{ PaidMembership() ? render : dont render}
)
}
Make test variable inside state and check
const [test, setTest] = useState(null);
const App = ({ user, database }) => {
const PaidMembership = () => {
docRef.get().then(function(doc) {
if (doc.exists) {
setTest( doc.data().membership);
//console.log(paidMembership);
} else {
console.log("Error: no such document exists")
setTest(null);
}
})
return test;
}
return (
{ test ? "" : <button>show button</button>}
)
}
This is because docRef.get returns promise and you are treating it as a normal function call. Try using this :
const App = async ({ user, database }) => {
const PaidMembership = async () => {
const doc = await docRef.get();
return doc.exists;
};
return (await PaidMembership()) ? "render" : "dont render";
};

React and reCAPTCHA v3

Is there any easy way to use reCAPTCHA v3 in react? Did a google search an can only find components for v2. And only react-recaptcha-v3 for v3.
But I get an error Invalid site key or not loaded in api.js when I try to use the component.
Hey you don't need a package, its just an unnecessary package you don't need.
https://medium.com/#alexjamesdunlop/unnecessary-packages-b3623219d86
I wrote an article about why you shouldn't use it and another package.
Don't rely on some package! Rely on google instead :)
const handleLoaded = _ => {
window.grecaptcha.ready(_ => {
window.grecaptcha
.execute("_reCAPTCHA_site_key_", { action: "homepage" })
.then(token => {
// ...
})
})
}
useEffect(() => {
// Add reCaptcha
const script = document.createElement("script")
script.src = "https://www.google.com/recaptcha/api.js?render=_reCAPTCHA_site_key"
script.addEventListener("load", handleLoaded)
document.body.appendChild(script)
}, [])
return (
<div
className="g-recaptcha"
data-sitekey="_reCAPTCHA_site_key_"
data-size="invisible"
></div>
)
I am teaching myself React + TypeScript and this is what I came up with to implement recaptcha v3.
I wanted a simple solution that would allow me to:
get the token dynamically only when the form is submitted to avoid timeouts and duplicate token errors
use recaptcha only on some components for privacy reasons (eg. login, register, forgot-password) instead of globally defining recaptcha api.js in index.html
require the least code possible to implement in a component
reCAPTCHA.ts
declare global {
interface Window {
grecaptcha: any;
}
}
export default class reCAPTCHA {
siteKey: string;
action: string;
constructor(siteKey: string, action: string) {
loadReCaptcha(siteKey);
this.siteKey = siteKey;
this.action = action;
}
async getToken(): Promise<string> {
let token = "";
await window.grecaptcha.execute(this.siteKey, {action: this.action})
.then((res: string) => {
token = res;
})
return token;
}
}
const loadReCaptcha = (siteKey: string) => {
const script = document.createElement('script')
script.src = `https://www.recaptcha.net/recaptcha/api.js?render=${siteKey}`
document.body.appendChild(script)
}
To use this class declare it as a property in the component:
recaptcha = new reCAPTCHA((process.env.REACT_APP_RECAPTCHA_SITE_KEY!), "login");
And on form submit get the token that you need to pass to backend:
let token: string = await this.recaptcha.getToken();
To verify the token on the backend:
recaptcha.ts
const fetch = require("node-fetch");
const threshold = 0.6;
export async function validateRecaptcha(recaptchaToken: string, expectedAction: string) : Promise<boolean> {
const recaptchaSecret = process.env.RECAPTCHA_SECRET_KEY;
const url = `https://www.recaptcha.net/recaptcha/api/siteverify?secret=${recaptchaSecret}&response=${recaptchaToken}`;
let valid = false;
await fetch(url, {method: 'post'})
.then((response: { json: () => any; }) => response.json())
.then((data: any)=> {
valid = (data.success && data.score && data.action && data.score >= threshold && data.action === expectedAction);
});
return valid;
}
I have very limited experience with JS/TS and React but this solution does work for me. I welcome any input on improving this code.
You can use react-google-recaptcha3 npm package (size: ~5 KB)
npm i react-google-recaptcha3
Usage
import ReactRecaptcha3 from 'react-google-recaptcha3';
const YOUR_SITE_KEY = '';
function App() {
// load google recaptcha3 script
useEffect(() => {
ReactRecaptcha3.init(YOUR_SITE_KEY).then(
(status) => {
console.log(status);
}
);
}, [])
}
Now on form submit you need to generate token and then append it to your form data
const submit = () => {
const formData = { name: "John", lastname: "Doe" }
ReactRecaptcha3.getToken().then(
(token) => {
console.log(token);
formData.token = token;
// send request to backend
fetch(url, { method: 'POST', body: JSON.stringify(formData) }).then(...)
},
(error) => {
console.log(error);
}
);
};
Now in backend you need to validate token
const request = require('request-promise');
const secretKey = YOUR_RECAPTCHA_SECRET_KEY;
const userIp = 'USER_IP';
request.get({
url: `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptchaToken}&remoteip=${userIp}`,
}).then((response) => {
// If response false return error message
if (response.success === false) {
return res.json({
success: false,
error: 'Recaptcha token validation failed'
});
}
// otherwise continue handling/saving form data
next();
})
Stackblitz example
Try this one!
https://github.com/t49tran/react-google-recaptcha-v3
npm install react-google-recaptcha-v3
You can also create your own custom hook useReCaptcha with React (Typescript):
// hooks/useReCaptcha.ts
import { RECAPTCHA_KEY, RECAPTCHA_TOKEN } from 'config/config'
import { useEffect, useState } from 'react'
const showBadge = () => {
if (!window.grecaptcha) return
window.grecaptcha.ready(() => {
const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
if (!badge) return
badge.style.display = 'block'
badge.style.zIndex = '1'
})
}
const hideBadge = () => {
if (!window.grecaptcha) return
window.grecaptcha.ready(() => {
const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
if (!badge) return
badge.style.display = 'none'
})
}
const useReCaptcha = (): { reCaptchaLoaded: boolean; generateReCaptchaToken: (action: string) => Promise<string> } => {
const [reCaptchaLoaded, setReCaptchaLoaded] = useState(false)
// Load ReCaptcha script
useEffect(() => {
if (typeof window === 'undefined' || reCaptchaLoaded) return
if (window.grecaptcha) {
showBadge()
setReCaptchaLoaded(true)
return
}
const script = document.createElement('script')
script.async = true
script.src = `https://www.google.com/recaptcha/api.js?render=${RECAPTCHA_KEY}`
script.addEventListener('load', () => {
setReCaptchaLoaded(true)
showBadge()
})
document.body.appendChild(script)
}, [reCaptchaLoaded])
// Hide badge when unmount
useEffect(() => hideBadge, [])
// Get token
const generateReCaptchaToken = (action: string): Promise<string> => {
return new Promise((resolve, reject) => {
if (!reCaptchaLoaded) return reject(new Error('ReCaptcha not loaded'))
if (typeof window === 'undefined' || !window.grecaptcha) {
setReCaptchaLoaded(false)
return reject(new Error('ReCaptcha not loaded'))
}
window.grecaptcha.ready(() => {
window.grecaptcha.execute(RECAPTCHA_KEY, { action }).then((token: string) => {
localStorage.setItem(RECAPTCHA_TOKEN, token)
resolve(token)
})
})
})
}
return { reCaptchaLoaded, generateReCaptchaToken }
}
export default useReCaptcha
Then in the login component for example, you can call this custom hook:
// Login.ts
import React from 'react'
import useReCaptcha from 'hooks/useReCaptcha'
const LoginPageEmail = () => {
const { reCaptchaLoaded, generateReCaptchaToken } = useReCaptcha()
const login = async () => {
await generateReCaptchaToken('login') // this will create a new token in the localStorage
await callBackendToLogin() // get the token from the localStorage and pass this token to the backend (in the cookies or headers or parameter..)
}
return (
<button disabled={!reCaptchaLoaded} onClick={login}>
Login
</button>
)
}
export default LoginPageEmail

Resources