I'm new to shopify-App developmet and came across Polaris, react library provided by Shopify for consistent user-interface development. My ideas is to build a Node.js Express App to authenticate and install the app and to process data and have a react app for the User Interface for the shop-admin.
I searched through the web, but couldn't find a standard or recommended way of connecting react app to the shopify app.
What I could figure out was to redirect to the React app from the Node app when a shop admin select app from Apps section and is authenticated successfully as follow.
app.get('/shopify/callback', (req, res) => {
const { shop, hmac, code, state } = req.query;
const stateCookie = cookie.parse(req.headers.cookie).state;
if (state !== stateCookie) {
return res.status(403).send('Request origin cannot be verified');
}
if (shop && hmac && code) {
const map = Object.assign({}, req.query);
delete map['signature'];
delete map['hmac'];
const message = querystring.stringify(map);
const generatedHash = crypto
.createHmac('sha256', apiSecret)
.update(message)
.digest('hex');
if (generatedHash !== hmac) {
return res.status(400).send('HMAC validation failed');
}
const accessTokenRequestUrl = 'https://' + shop + '/admin/oauth/access_token';
const accessTokenPayload = {
client_id: apiKey,
client_secret: apiSecret,
code,
};
request.post(accessTokenRequestUrl, { json: accessTokenPayload })
.then((accessTokenResponse) => {
const accessToken = accessTokenResponse.access_token;
const shopRequestUrl = 'https://' + shop + '/admin/shop.json';
const shopRequestHeaders = {
'X-Shopify-Access-Token': accessToken,
};
res.redirect([URL to the react app built with Polaris]);
})
.catch((error) => {
res.status(error.statusCode).send(error.error.error_description);
});
} else {
res.status(400).send('Required parameters missing');
}
});
I got this working, but I'm not sure whether this is the recommended way to do it.
They are 3 different things: Node.JS, React, and Polaris and you can choose any of them. Polaris is a library and if you want to use it just run yarn add #shopify-polaris and read its documentation. That's all.
With or without Polaris, you could still create a Shopify app, with various stacks.
Related
I had started a project a little while ago and have been busy lately so I have not been able to work on it. I am out of practice with web development because I had recently joined the military. Right now the project consists of a create-react-app app with auth0 integrated. What I am trying to do is get the plaid link integrated into the page it takes you after logging in using auth0. I am requesting help on what code from the plaid docs I use in order for this to work. Their documentation is a little confusing to me, maybe because I'm so out of practice. Any help would be much much appreciated.
https://github.com/CollinChiz/SeeMyCash
Have you taken a look at the Quickstart at https://github.com/plaid/quickstart/? It contains a full React implementation that does this. Here's the relevant excerpt:
// APP COMPONENT
// Upon rendering of App component, make a request to create and
// obtain a link token to be used in the Link component
import React, { useEffect, useState } from 'react';
import { usePlaidLink } from 'react-plaid-link';
const App = () => {
const [linkToken, setLinkToken] = useState(null);
const generateToken = async () => {
const response = await fetch('/api/create_link_token', {
method: 'POST',
});
const data = await response.json();
setLinkToken(data.link_token);
};
useEffect(() => {
generateToken();
}, []);
return linkToken != null ? <Link linkToken={linkToken} /> : <></>;
};
// LINK COMPONENT
// Use Plaid Link and pass link token and onSuccess function
// in configuration to initialize Plaid Link
interface LinkProps {
linkToken: string | null;
}
const Link: React.FC<LinkProps> = (props: LinkProps) => {
const onSuccess = React.useCallback((public_token, metadata) => {
// send public_token to server
const response = fetch('/api/set_access_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ public_token }),
});
// Handle response ...
}, []);
const config: Parameters<typeof usePlaidLink>[0] = {
token: props.linkToken!,
onSuccess,
};
const { open, ready } = usePlaidLink(config);
return (
<button onClick={() => open()} disabled={!ready}>
Link account
</button>
);
};
export default App;
I have some problems with authentication with Google OAuth2 in my react-native app. I'm using 'expo-auth-session' library for my authentification. I need get access token and then get Youtube profile. But i'm stuck with error "Invalid parameter value for redirect_uri: Invalid scheme"
My scheme in app.json:
"scheme": "com.liga.online"
My code is below:
import {
makeRedirectUri,
useAuthRequest,
ResponseType,
} from "expo-auth-session";
const discoveryYoutube = {
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenEndpoint: 'https://oauth2.googleapis.com/token',
revocationEndpoint: 'https://oauth2.googleapis.com/revoke'
};
/// Inside my React component
const [requestYoutube, responseYoutube, promptAsyncYoutube] = useAuthRequest(
{
responseType: ResponseType.Code,
clientId: YOUTUBE_CLIENT_ID,
scopes: ["https://www.googleapis.com/auth/youtube.readonly"],
redirectUri: makeRedirectUri({
native: "com.liga.online/callback",
}),
},
discoveryYoutube
);
When I press the button, callback is starting
const signInYoutube = async () => {
const response = await promptAsyncYoutube();
console.log(response.data);
}
But I get error
Any idea how I can fix it?
P.S. I tried fix it with library "expo-google-app-auth". I get access token, but when I try to get Youtube profile and get "Request failed with status code 403".
UPDATE 1
By the way about my connection to Youtube Profile.
I change something to get access token.
For example:
import * as Google from 'expo-google-app-auth';
import { startAsync } from 'expo-auth-session';
// Inside my React component
// When I press the button, callback is starting
const signInYoutube = async () => {
const config = {
androidClientId: YOUTUBE_CLIENT_ID
};
const { type, accessToken, user } = await Google.logInAsync(config);
// I need again open my Browser for get Youtube data
const response = await startAsync({
authUrl: `https://www.googleapis.com/youtube/v3/channels?access_token=${accessToken}&part=snippet&mine=true&scope=https://www.googleapis.com/auth/youtube.readonly`,
showInRecents: true
});
console.log(response.data);
}
But I get error
UPDATE 2
I wanted to see which data is loaded from AuthRequest. And I see pretty weird log. Redirect_uri is different from the set.
RESOLUTION
When I add "https://www.googleapis.com/auth/youtube.readonly" in my scopes - i can get profile data. Another words below is my code.
import axios from 'axios';
import * as Google from 'expo-google-app-auth';
// Inside my React component
// Callback function
const signInYoutube = async () => {
const config = {
androidClientId: YOUTUBE_CLIENT_ID,
scopes: ['https://www.googleapis.com/auth/youtube.readonly']
};
const { type, accessToken, user } = await Google.logInAsync(config);
if (type === 'success') {
const response = await axios.get(
`https://www.googleapis.com/youtube/v3/channels?part=id&mine=true&key=${encodeURI(YOUTUBE_API_KEY)}`,
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
setYoutubeData({ accessToken, user, youtubeId: response.data.items[0].id });
}
};
IMPORTANT
Don't remember add in your project Youtube API v3
It looks like your expo environment is using the development redirect URI instead of the native one. Check out these docs for setting up the environment that will give you the native scheme you're looking for: https://docs.expo.io/guides/authentication/#standalone-bare-or-custom
Also make sure that you register your custom scheme with Google: https://developers.google.com/identity/protocols/oauth2/native-app#redirect-uri_custom-scheme
As for your Youtube example, you should be specifying the scopes in the call to Google.loginAsync, not the call to the Youtube API. scopes are requested during the authorization step, and your current authorization request doesn't include any. The relevant docs are here: https://docs.expo.io/versions/latest/sdk/google/#loginasync
REDIRECT URI
Your code looks roughly right though your redirect URI is a private URI scheme and should include a colon character:
com.liga.online:/callback
TRACING MESSAGES
For people to help you with your YouTube profile request you'll need to be able to tell us what is being sent over HTTP/S. Could you try to trace messages as in this write up of mine, then paste the request and response details into your question above
This is a react-native application and I am currently writing some end-to-end testing.
A token is stored in the redux store shown below and I am testing the login functionality using detox/jest. I need to detect if the token exists in the store in my login.spec.js . If the token exists I want to wipe it from the store so the user is not logged in automatically when i reload the app to take the user back to another scene. The main function in question is the refreshUserToken() and line:-
const { refresh_token } = yield select(token);
Here is the redux saga file User.js located at:-MyApp/App/Sagas/User.js
import { call, put, takeEvery, select } from "redux-saga/effects";
import Config from "MyApp/App/Config";
import API from "MyApp/App/Services/API";
import { when } from "MyApp/App/Helpers/Predicate";
import Credentials from "MyApp/App/Helpers/Credentials";
import ActionCreator from "MyApp/App/Actions";
const appendPayload = payload => {
return {
...payload,
// Removed because no longer needed unless for testing purposes.
// username: Config.TEST_USERNAME,
// password: Config.TEST_PASSWORD,
client_id: Config.CLIENT_ID,
client_secret: Config.CLIENT_SECRET,
};
};
const token = state => state.token;
const user = state => state.user;
const attemptUserLogin = function*(action) {
const { payload } = action;
const login = "/oauth/token";
const grant_type = "password";
const loginPayload = appendPayload(payload);
action.payload = {
...loginPayload,
grant_type,
};
yield attemptUserAuthorisation(login, action);
};
const attemptUserRegister = function*(action) {
const register = "/api/signup";
const { payload } = action;
yield Credentials.save(payload);
yield put(ActionCreator.saveUserCredentials(payload));
yield attemptUserAuthorisation(register, action);
};
const refreshUserToken = function*(action) {
const login = "/oauth/token";
const grant_type = "refresh_token";
const { refresh_token } = yield select(token);
action.payload = {
...action.payload,
grant_type,
refresh_token,
};
yield attemptUserAuthorisation(login, action);
};
const watchExampleSaga = function*() {
yield takeEvery(ActionCreator.AUTO_USER_LOGIN, autoUserLogin);
yield takeEvery(ActionCreator.USER_LOGIN, attemptUserLogin);
yield takeEvery(ActionCreator.USER_REGISTER, attemptUserRegister);
yield takeEvery(ActionCreator.USER_REFRESH_TOKEN, refreshUserToken);
};
export default watchExampleSaga;
Here is my detox/jest spec file located at:-MyApp/App/e2e/login.spec.js
describe('Login Actions', () => {
it('Should be able to enter an email address', async () => {
await element(by.id('landing-login-btn')).tap()
const email = 'banker#dovu.io'
await element(by.id('login-email')).tap()
await element(by.id('login-email')).replaceText(email)
});
it('Should be able to enter a password', async () => {
const password = 'secret'
await element(by.id('login-password')).tap()
await element(by.id('login-password')).replaceText(password)
});
it('Should be able to click the continue button and login', async () => {
await element(by.id('login-continue-btn')).tap()
await waitFor(element(by.id('dashboard-logo'))).toBeVisible().withTimeout(500)
// If token exists destroy it and relaunch app. This is where I need to grab the token from the redux saga!
await device.launchApp({newInstance: true});
});
})
This is how I handled a similar scenario:
in package.json scripts:
"start:detox": "RN_SRC_EXT=e2e.tsx,e2e.ts node node_modules/react-native/local-cli/cli.js start",
In my detox config:
"build": "ENVFILE=.env.dev;RN_SRC_EXT=e2e.tsx,e2e,ts npx react-native run-ios --simulator='iPhone 7'",
That lets me write MyFile.e2e.tsx which replaces MyFile.tsx whilst detox is running
In the test version of that component I have buttons which are tapped in the tests and the buttons dispatch redux actions
Looks like this actually cant be done unless someone can give me a solution other than mocking the state which still wouldn't work in this case my app checks for real states to auto login.
I did get to the stage of creating a new action getUserToken and exporting that into my jest file. However the action returns undefined because the jest file requires a dispatch method like in containers.js. If anyone could provide me with a method of this using jest I would be very happy.
I need to pick files from onedrive and dropbox. Is there any npm modules available.
This is an old question, but I ran into the same issue. Spent a while finding the best solution.
Using an in-browser package react-native-inappbrowser and the deepLink functionality. I was able to solve the issue.
You will have to look at the OneDrive/Dropbox documentation for allowing a RedirectUI for a react-native app.
const redirectUrl = 'com.******.*******://auth'
export const tryDeepLinking = async () => {
const loginUrl =
'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
const redirectUrl = getDeepLink();
const url = `${loginUrl}?redirect_url=
${encodeURIComponent(redirectUrl,)}
&client_id=${encodeURIComponent('**********',)}
&response_type=${encodeURIComponent('token')}
&scope=${encodeURIComponent(
'openid Files.ReadWrite.All offline_access',
)}`;
try {
if (await InAppBrowser.isAvailable()) {
const result: any = await InAppBrowser.openAuth(url, redirectUrl, {
// iOS Properties
ephemeralWebSession: false,
// Android Properties
showTitle: false,
enableUrlBarHiding: true,
enableDefaultShare: false,
});
const paramsString = result.url.split('#')[1];
let params: any = {};
paramsString.split('&').forEach(kv => {
const keyValue = kv.split('=', 2);
params[keyValue[0]] = keyValue[1];
});
await setStorageAzureToken(params.access_token);
Alert.alert('Response', 'Success');
} else {
Alert.alert('InAppBrowser is not supported :/');
}
} catch (error) {
console.error(error);
Alert.alert('Something’s wrong with the app :(');
}
};
This is what I am using to get the OneDrive access token, from there you are able to use the https://graph.microsoft.com/v1.0/ api's to manage files.
You can try to use react-native-document-picker.
I have a React Redux application with a Node back end for my API's (also using Thunk). One of the API's I a connecting to is Strava, which requires OAuth authentication.
The Strava npm module I am using in the back end has an OAuth feature so it makes it pretty simple.
I have a "Connect to Strava" link on the app which sends a request to the API via my Node back end and sends back the Strava OAuth URL:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClickAuth: () => {
store.dispatch(getStravaOauthUrl())
}
}
}
When clicking the link to connect to Strava (as per above), Node sends back a generated URL:
//action
export const getStravaOauthUrl = () => {
return function(dispatch) {
dispatch(loadStravaBegin())
return FitnessApi.fetchStravaOauthUrl().then(strava => {
dispatch(receivedStravaOauthUrl(strava));
}).catch(error => {
throw(error);
})
}
}
export const receivedStravaOauthUrl = (response) => {
return{
type: 'RECEIVED_STRAVA_OAUTH_URL',
response,
receivedAt: Date.now()
}
}
//reducer
export const fitnessReducer = (state = {
fitness: {},
isFetching: true
}, action) => {
switch (action.type) {
case 'RECEIVED_STRAVA_OAUTH_URL':
return Object.assign({}, state, {
fitness: {
oAuthUrl: action.response,
isFetching: false
}
});
default:
return state
}
}
So I have the response in my Store. My question is, how do I go about directing the browser to the URL and getting the response? I am not sure if it's best to use native JS redirect or make use of some sort of established process by using React Router?
I have seen a lot of what look like client side OAuth modules, but don't know if they'll be useful in my situation.
Thanks