How to implement Google API with React, Redux and Webpack - node.js

I'm trying to get google calendar events into my React Redux app.
I've tried using googleapis and google-auth-library but webpack is throwing errors because googleapis was built to run server side and bundle.js is referenced from client.
So I've read a few forums about these errors and they all point to using Google's js client library instead.
I understand how to implement this in a java or php app (I'm old... 35 ;) but I'm new to React Redux and I'm looking for the best way to implement this.
I'm trying to fetch the events from my calendar in my actions.js. I tried including <script src="https://apis.google.com/js/api.js"></script> in my html header and then using gapi.load() from actions.js. I also tried creating a api.js file and referencing that with require('./api'). I also tried to use the cli commands from the Node.js Quickstart guide to get an access_token and then just use axios to call Google API directly but I'm getting a 403. I'm thinking I'm just not providing the proper headers but that wouldn't be best practice anyway.
My question is basically how do I reference Google's js client library from my actions.js file while adhering to Redux standards?

You're on the right track by including the official Google client API in the HTML header. It's less than ideal -- it would be nice if Google provided the (browser) client API as an npm module that you could import. But they don't (that I see), so I think what you're doing is fine.
Then there's the question of "how do I use it in a way that's React/Redux friendly?" Redux is a mechanism for managing the state of your application. The Google API is not part of your application (though what you do with it may inform the state of your application).
It's easy to verify that you have access to the Google API: you can just make a call from the componentDidMount method of one of your components, and do a console log:
class MyComp extends React.Component {
componentDidMount() {
// this is taken directly from Google documentation:
// https://developers.google.com/api-client-library/javascript/start/start-js
function start() {
// 2. Initialize the JavaScript client library.
gapi.client.init({
'apiKey': 'YOUR_API_KEY',
// clientId and scope are optional if auth is not required.
'clientId': 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com',
'scope': 'profile',
}).then(function() {
// 3. Initialize and make the API request.
return gapi.client.request({
'path': 'https://people.googleapis.com/v1/people/me',
})
}).then(function(response) {
console.log(response.result);
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
};
// 1. Load the JavaScript client library.
gapi.load('client', start);
},
}
If you don't see what you expect on the console, somehow gapi isn't getting loaded as you expect. If that happens, you'll have a more specific question you can ask!
If you do get a response, you now know how to call GAPI...but then how to make use of it in a Redux-friendly way?
When you make a GAPI call, you probably want to modify your application's state in some way (otherwise why would you be doing it?) For example, you might invoke the auth flow, and when GAPI returns success, your application state now has loggedIn: true or similar (possibly with lots of other state changes). Where you make the GAPI call is up to you. If you want to do it when the component loads, you should do it in componentDidMount. You also may commonly be making the GAPI call in response to a user action, such as clicking on a button.
So the typical flow would be something like this:
// either in componentDidMount, or a control handler, usually:
someGapiCall()
.then(result => {
this.props.onGapiThing(result.whatever)
})
Where this.props.onGapiThing is a function that dispatches an appropriate action, which modifies your application state.
I hope this overview helps...feel free to follow up with more specific questions.

Can you try this library which I used to load external libraries and modules in my React app when I couldn't find a NPM module for it:
https://github.com/ded/script.js/
So your code will be like this:
import $script from 'scriptjs';
$script('https://apis.google.com/js/api.js', function () {
//Put your google api functions here as callback
});

I'm going to answer my own question despite some very good correct answers.
#MattYao answered my actual question of how to get a js script available for reference in my actions.js file.
#Ethan Brown gave a very detailed answer that showed some excellent flow possibilities.
#realseanp changed the scope but a valid answer.
I tried all of the above and they worked.
So I'm not sure what I was doing wrong but I was finally able to access the gapi object from actions.js by just adding <script src="https://apis.google.com/js/api.js"></script> to my index head.
I'm using pug so it looks like this:
doctype
html
head
title MyTitle
link(rel='stylesheet' href='/static/css/main.css')
link(rel='stylesheet' href='/static/css/react-big-calendar.css')
script(src='https://apis.google.com/js/api.js' type='text/javascript')
body
div(id='app')
script(src='/static/bundle.js' type='text/javascript')
Here is my component file:
import React from 'react'
import BigCalendar from 'react-big-calendar';
import moment from 'moment';
import { connect } from 'react-redux'
import { fetchEvents } from '../actions/actions'
BigCalendar.momentLocalizer(moment);
#connect((store) => {
return {
events: store.events.events
}
})
export default class Calendar extends React.Component
{
componentWillMount()
{
this.props.dispatch(fetchEvents())
}
render()
{
return (
<div>
<BigCalendar
events={this.props.events}
startAccessor='startDate'
endAccessor='endDate'
style={{height: 800}}
/>
</div>
)
}
}
And then I was able to get this working in my actions.js file
export function fetchEvents()
{
return (dispatch) =>
{
function start()
{
// 2. Initialize the JavaScript client library.
gapi.client.init({
'apiKey': API_KEY,
// clientId and scope are optional if auth is not required.
'clientId': CLIENT_ID,
'scope': 'profile',
}).then(function() {
// 3. Initialize and make the API request.
return gapi.client.request({
'path': 'https://www.googleapis.com/calendar/v3/calendars/MY_EMAIL#gmail.com/events?timeMax=2017-06-03T23:00:00Z&timeMin=2017-04-30T00:00:00Z',
})
}).then( (response) => {
let events = response.result.items
dispatch({
type: 'FETCH_EVENTS_FULFILLED',
payload: events
})
}, function(reason) {
console.log(reason);
});
};
// 1. Load the JavaScript client library.
gapi.load('client', start)
}}
I had to make my calendar public to access it this way. So now I'm going to work on the oauth2 stuff :/

I would load all the google stuff in my index file before i loaded my webpack bundle (Option 1) . Then I would use redux sagas to call the google apis. Loading the google code before your webpack bundle will ensure everything is ready to go when you call the api from the saga

Try this package.
It looks like it is updated.
https://github.com/QuodAI/tutorial-react-google-api-login

Related

Next.js Protect Routes Which Use Static Site Generation

In Next.js you have the option of server side rendering (SSR) or static site generation (SSG). Throughout the Next.js docs and community, SSG is recommended over SSR for performance reasons.
I have a Next.js build that uses SSG throughout the application, using getStaticProps() etc. to generate the content/pages at build time by integrating with an external CMS (Prismic). I prefer this because as mentioned it gives a performance boost and also most of the codebase can then use the same data-fetching strategy (at build time).
However, some of these pages need to be protected - meaning they should only be accessed by authenticated users. We are using Auth0 to generate a JWT token and have a React context provider save the status of the user (logged in or not) after validating the token in an API call.
But, I am struck that I don't seem to have a good way to protect SSG pages with this token. The recommended way here strikes me as odd because as far as I can tell this is a client-side redirect that could be manipulated by the client (for example - the client could manipulate it's local state/context or else tamper with whatever is returned from notloggedincondition) to show the static content or otherwise short-circuit the redirect.
For reference, here is a paste of that code:
import {useEffect} from 'react'
import {useRouter} from 'next/router'
export async function getStaticProps() {
return {
props: {
hello: 'Hello World'
}
}
}
export default (props) => {
const router = useRouter()
useEffect(() => {
if(notloggedincondition) {
router.push('/login')
}
}, [])
return <h1>Rest of the page</h1>
}
Note the <h1>Rest of the page</h1> could still be accessed by manipulating the client... so I want to secure the SSG at the request/response level and do a server side redirect (if need be), or something like that.
Short of something like this, is there no way to securely protect a SSG page without having to rely on client-side routing? Do I need to SSR the content even though it is no different really from the rest of the content, save for the requirement that only authenticated users can see it?
Perhaps I am missing something obvious, but it seems to me that even with a static site there should be a way to protect it without relying on client side routing. That is to say, it does not seem intrinsic to the concept of a statically generated site that every page must be public, so I'm wondering about a way to do this in Next.js that is secure.
The best way I could find to accomplish this is via SWR fetches, statically generating a skeleton of the page with initial unprotected static data and then hydrating it with the refresh, if the refresh returns content.
This does require that you move logic gathering data for the protected page behind an API or CMS (anything which would clear your view of permissions), and converting existing routes to use API calls isn't a trivial task, so YMMV.
Important note: your redirect would still need to be client-side, but you can avoid having anything protected shown to an unauthorized user as that'd still be controlled at the server level. Since your biggest concern appears to be a user actively trying to compromise content by manipulating code, this seems to meet your risk remediation criteria (they would still be unable to access protected content).
Example page code:
import {useEffect} from 'react'
import {useRouter} from 'next/router'
import useSWR from 'swr'
export async function getStaticProps() {
return {
props: {
hello: 'Hello World'
}
}
}
export default (props) => {
const router = useRouter()
// Access the protected content via an API route,
// provide the initial unprotected static content via the initialData param
const { data } = useSWR('/api/protected-content', fetcher, { initialData: props })
useEffect(() => {
if(notloggedincondition) {
router.push('/login')
}
}, [])
return <h1>{ data.hello }</h1>
}
Then, an example API implementation at pages/api/protected-content:
export default async function ProtectedContent(req, res) {
// Get a session object based on request cookies
let session = await initUserSession(req, res);
// If a session is available, return the protected content
if (session.props.userSession) {
return res.status(200).json({hello: 'This is protected content'});
} else {
return res.status(401).send("UNAUTHENTICATED");
}
}
Next js now has middleware that can redirect request based on the context.
Encountered the same problem at work, and opted for a custom server that sits in front of Next.js.
We're using Express, but any Node framework would work really. This lets us move authentication concerns to a separate server framework, which in turn forwards the requests to Next.js.
https://nextjs.org/docs/advanced-features/custom-server

Trying to use oauth flow in Electron desktop app (with spotify API)?

I have a React app in Electron, and I'm trying to access the spotify API using the spotify-web-api-node library. However, I'm not sure exactly how the oauth flow is meant to work inside of an Electron app... Firstly, for the redirect URL, I used this question and added a registerFileProtocol call to my file. Then I added a specific ipcMain.on handler for receiving the spotify login call from a page, which I've confirmed works with console logs. However, when I get to actually calling the authorizeURL, nothing happens?
This is part of my main.js:
app.whenReady().then(() => {
...
protocol.registerFileProtocol(
"oauthdesktop",
(request, callback) => {
console.log("oauthdesktop stuff: ", request, callback);
//parse authorization code from request
},
(error) => {
if (error) console.error("Failed to register protocol");
}
);
});
ipcMain.on("spotify-login", (e, arg) => {
const credentials = {
clientId: arg.spotifyClientId,
clientSecret: arg.spotifySecret,
redirectUri: "oauthdesktop://test",
};
const spotifyApi = new SpotifyWebApi(credentials);
console.log("spapi: ", spotifyApi);
const authorizeURL = spotifyApi.createAuthorizeURL(
["user-read-recently-played", "playlist-modify-private"],
"waffles"
);
console.log("spurl: ", authorizeURL);
axios.get(authorizeURL);
}
I'd expect the typical spotify login page popup to show up, but that doesn't happen. I'd also expect (possibly) the registerFileProtocol callback to log something, but it doesn't. What am I meant to be doing here? The authorization guide specifically mentions doing a GET request on the auth url, which is what I'm doing here...
In a desktop app it is recommended to open the system browser, and the Spotify login page will render there, as part of creating a promise. The opener library can be used to invoke the browser.
When the user has finished logging in, the technique is to receive the response via a Private URI Scheme / File Protocol, then to resolve the promise, get an authorization code, then swap it for tokens. It is tricky though.
RESOURCES OF MINE
I have some blog posts on this, which you may be able to borrow some ideas from, and a couple of code samples you can run on your PC:
Initial Desktop Sample
Final Desktop Sample
The second of these is a React app and uses a Private URI scheme, so is fairly similar to yours. I use the AppAuth-JS library and not Spotify though.

ReactNative - Proper approach to use Firebase token and making API calls to own server

Am using Firebase PhoneAuth for authentication in my ReactNative app and am coding my own {NodeJS, Express, MongodDB} server application for the API.
So the idea is, my app authenticates the user via the Firebase authentication, and upon successful authentication, will have the Token generated by Firebase. And this Token will be used to make any API calls to my server. And this Token is validated at my server and the corresponding response is provided.
I already asked a question about storing the Token, and Doug told me that I should listen to the onIdTokenChanged in each & every page and use the Token from there to make the API calls to my server.
Here's my minimal code:
import React from 'react'
import {View, Text} from 'react-native'
import auth from '#react-native-firebase/auth'
export default function Home({ navigation })
{
auth().onIdTokenChanged(function(user) {
if (user) {
user.getIdToken().then( token => {
console.log( token )
});
}
});
return (
<View>
<Text>Hello</Text>
</View>
)
}
Am new to ReactNative and NodeJS. So am trying to code everything properly.
My questions are:
This is an asynchronous request isn't it? I mean the fetching of the Token. So I should display a Loader in the screen by default at startup, and inside the user.getIdToken().then( token=>{ //hide the loader }) I should hide the Loader, and make the API call to my server?
If I want to make another(different) API call from the same screen, should I have to listen to this onIdTokenChanged and get the Token and then make the new API call to my server? Or should I store the previously fetched Token to a state variable using useState and then use this stored Token in the subsequent API calls from the same screen?
As you can see in my minimal code, my code for listening to onIdTokenChanged is before the rendering of the screen. Should I have to move it to the useEffect() function instead? If I moved it, do I have to unsubscribe anything inside it?

Redirect from React to an Express route from my API server on the same domain

I have both a React APP and a Express API server on the same server/domain. Nginx is serving the React APP and proxying the express server to /api.
Nginx configuration
https://gist.github.com/dvriv/f4cff6e07fe6f0f241a9f57febd922bb
(Right now I am using the IP directly instead of a domain)
From the React APP, when the user does something I want him to download a file. I used a express route on my API server that serve the file. This works fine when the user put the URL.
This is my express route:
donwloadFile.route('/')
.get((req, res) => {
const file = '/tmp/PASOP180901.txt';
res.download(file);
});
This is my react redirect:
if (this.state.downloadFile === true) {
this.setState({ downloadFile: false });
setTimeout(() => {
window.location.href = '/api/downloadFile';
}, 100);
}
The address changes but the download don't start. If I press F5 then the download starts just fine. If I use a external URL to host my file, the download start just fine too.
Thanks
First things first. Don't use setTimeout, but rather use the callback function of setState to execute code after the state is set ensuring it has been modified. Calling the callback function will guarantee the state is changed before that code in the callback is executed.
from the official docs:
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied.
setState(stateChange[, callback])
The second parameter to setState() is an optional callback function
that will be executed once setState is completed and the component is
re-rendered. Generally we recommend using componentDidUpdate() for
such logic instead.
So, instead of:
if (this.state.downloadFile === true) {
this.setState({ downloadFile: false });
setTimeout(() => {
// execute code, or redirect, or whatever
}, 100);
}
you should do:
if (this.state.downloadFile === true) {
this.setState({ downloadFile: false }, () => {
// execute code, or redirect, or whatever
});
}
Now, for your specific problem
Set headers in your server side
You can set the Content-Disposition header to tell the browser to download the attachment:
from here:
In a regular HTTP response, the Content-Disposition response header is
a header indicating if the content is expected to be displayed inline
in the browser, that is, as a Web page or as part of a Web page, or as
an attachment, that is downloaded and saved locally.
Set it like this:
('Content-Disposition: attachment; filename="/tmp/PASOP180901.txt"');
Force download from the client
There are multiple ways to force the download from the client, but this is the only one I've tried.
For this to work, you have to have the content of text somehow in the client (your express route can return it for example) and create a filename for the file that will be downloaded.
let element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
Basically you are creating an empty link component, setting the data attribute to it with the text's content, attaching the link to the body, clicking the link and then removing the link from the body.
Open the link in a new tab
Opening the link in a new tab will trigger the download as well:
window.open('/api/downloadFile');
Redirect programatically
Have a look at this question in SO
You can do this:
this.props.history.push("/api/downloadFile")?
If cannot access this.props.history you can import { withRouter } from 'react-router-dom'; and export default withRouter(yourComponent); to access it.

Stripe JS making duplicate requests & new requests on state change

I'm using the stripe JS library on my frontend and just setting the token, not actually using the library for anything. Odd thing is that when I load any page stripe will make a bunch of odd requests and lots of them are duplicates. Often it'll look like this:
https://m.stripe.com/4
https://m.stripe.com/4
https://stripensrq.global.ssl.fastly.net/s/e
https://stripensrq.global.ssl.fastly.net/s/o
https://m.stripe.com/4
Then if I change the page state using the History API it makes all these calls again even though this is a single page webapp. Is this normal?
This behavior caught me by surprise, too. If you have import { loadStripe } from '#stripe/stripe-js anywhere in your SPA, Stripe will begin phoning home on every page load from the moment your app opens.
Option 1: Deferring Stripe's library load
As of #stripe/stripe-js v1.4.0, you can use the /pure import path, which defers the load of Stripe's library until the app actually calls loadStripe:
import { loadStripe } from '#stripe/stripe-js/pure';
Once you call loadStripe, Stripe will continue sending requests to https://m.stripe.com/4 on every URL change until the browser navigates to a new page through an HTTP request (not through a JavaScript route change) or until the browser reloads.
Option 2: Disabling Stripe's fraud detection mechanisms
stripe.js makes requests to https://m.stripe.com/4 as part of its fraud detection mechanisms. As of #stripe/stripe-js v1.5.0, you can disable this behavior by setting {advancedFraudSignals: false}:
import {loadStripe} from '#stripe/stripe-js/pure';
loadStripe.setLoadParameters({advancedFraudSignals: false})
const stripe = await loadStripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
Note that disabling this feature increases your risk of receiving fraudulent transactions.
More details
I wrote a blog post about this if you're interested in additional details: https://mtlynch.io/stripe-recording-its-customers/
EDIT: since writing this, Stripe have updated their library to address these concerns, in large part thanks to #mtlynch's great investigative work. See his answer above for the most up to date answer.
For those wanting to avoid this, it seems that instead of importing the Stripe library as the docs describe:
import { loadStripe } from "#stripe/stripe-js";
// when wanting to actually load Stripe this method is called,
// but `m.stripe.com` was fired on page load, before this was called
const publicKey = "yourPublicKey";
const stripe = await loadStripe(publicKey);
...which will automatically call the m.stripe.com endpoint, you can dynamically import the library so that this is only called when you actually want Stripe functionality (not on every page, if using redux or vuex, for example):
// create a new async function `stripeJs` that returns the library
const stripeJs = async () => await import("#stripe/stripe-js");
// later, we can call this before we need to use the library
// (e.g. in a vuex/redux action)
// STRIPE's TRACKING SCRIPT WILL BE CALLED NOW, RATHER THAN ON LOAD
const { loadStripe } = await stripeJs();
const publicKey = "yourPublicKey";
const stripe = await loadStripe(publicKey);
// example Stripe call
stripe.redirectToCheckout(...)
Fair warning, I'm not sure what m.stripe.com does, and so there could be unintended side effects to only importing the library before needing to execute it, but this seems to work in my testing.
Agree fully with the other answers here re. importing dynamically.
To take things a step further you can consider running your payment processing in a same-origin frame, which means you can force the library to unload once you're finished with it.
// load this from wherever you want
const html = `
<!-- your external scripts and styles go here -->
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('{STRIPE_KEY}');
/**
* STRIPE IMPLEMENTATION GOES HERE
*/
// Once we've got a token we send it back to our app
stripe.createToken(/*{STRIPE_CARD}*/).then(({ token }) =>
parent.postMessage({ type: 'stripe-token', token }, '*'));
</script>
`;
// wait for the frame to load then write our body
iframe.addEventListener('load', () =>
iframe.contentWindow.document.write(html));
}
// add our iframe to the page and Stripe.js will load (and start pinging its server!)
document.body.append(iframe);
// wait for confirmation from the frame that we're finished with Stripe.js (see the html snippet above)
window.addEventListener('message', ({ data }) => {
if (data.type === 'stripe-token') {
console.log(data.token);
// we've got a token! We can remove our frame (and unload Stripe.js and all its telemetry!)
if (hasSrcdoc) iframe.srcdoc = '';
else iframe.src = '';
document.body.removeChild(iframe);
}
});
Full details here: https://codepen.io/theprojectsomething/full/ExVNEoZ

Resources