Expo App environments for Dev, UAT and Production - react-native

I have a React Native app built in Expo that connects to a Rest API. There are three environments for the rest api - dev, uat and production as below (example).
dev = https://dev.myapi.com/api
uat = https://uat.myapi.com/api
prod = https://prod.myapi.com/api
Depending on where the app is being used it needs to connect to the correct environment.
Running in the Expo Client = Dev API
Running in TestFlight or Internal Testing for the Play Store = UAT API
Running in the App Store or Play Store = Production API
What is the simplest way to achieve this?

Follow below Steps
Install expo-constants package. To install the package run the below command.
npm i expo-constants
Add environment.js file and paste below code.
import Constants from 'expo-constants';
import { Platform } from 'react-native';
const localhost = Platform.OS === 'ios' ? 'localhost:8080' : '10.0.2.2:8080';
const ENV = {
dev: {
apiUrl: 'https://dev.myapi.com/api',
amplitudeApiKey: null,
},
staging: {
apiUrl: 'https://uat.myapi.com/api',
amplitudeApiKey: '[Enter your key here]',
// Add other keys you want here
},
prod: {
apiUrl: 'https://prod.myapi.com/api',
amplitudeApiKey: '[Enter your key here]',
// Add other keys you want here
},
};
const getEnvVars = (env = Constants.manifest.releaseChannel) => {
// What is __DEV__ ?
// This variable is set to true when react-native is running in Dev mode.
// __DEV__ is true when run locally, but false when published.
if (__DEV__) {
return ENV.dev;
} else if (env === 'staging') {
return ENV.staging;
} else if (env === 'prod') {
return ENV.prod;
}
};
export default getEnvVars;
Accessing Environment Variables
// Import getEnvVars() from environment.js
import getEnvVars from '../environment';
const { apiUrl } = getEnvVars();
/******* SESSIONS::LOG IN *******/
// LOG IN
// credentials should be an object containing phone number:
// {
// "phone" : "9876342222"
// }
export const logIn = (credentials, jsonWebToken) =>
fetch(`${apiUrl}/phone`, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + jsonWebToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
To create the builds use the below commands.
Dev - expo build:ios --release-channel dev
Staging - expo build:ios --release-channel staging
Production - expo build:ios --release-channel prod
Now that Expo supports config file as app.config.js or app.config.ts, we can use the dotenv. Check this: https://docs.expo.io/guides/environment-variables/#using-a-dotenv-file
Refer link

This can be done using different Release Channel names,
lets say you have created 3 release channels this way:
expo publish --release-channel prod
expo publish --release-channel staging
expo publish --release-channel dev
then you can have a function to set environment vars accordingly:
import * as Updates from 'expo-updates';
function getEnvironment() {
if (Updates.releaseChannel.startsWith('prod')) {
// matches prod*
return { envName: 'PRODUCTION', dbUrl: 'ccc', apiKey: 'ddd' }; // prod env settings
} else if (Updates.releaseChannel.startsWith('staging')) {
// matches staging*
return { envName: 'STAGING', dbUrl: 'eee', apiKey: 'fff' }; // stage env settings
} else {
// assume any other release channel is development
return { envName: 'DEVELOPMENT', dbUrl: 'aaa', apiKey: 'bbb' }; // dev env settings
}
}
Refer to expo documentation for more info!

For those who are using Expo sdk 46(or any newer version), you can do the following way
Rename the app.json to app.config.js
Add API URL under extra property
export default () => ({
expo: {
name: '',
slug: ''
extra: {
API_URL: process.env.API_URL || null,
},
// ...
},
});
We can access this API using expo constants like this(wherever we want).
Don't forget to import constants from Expo.
const myApi = Constants.expoConfig.extra.API_URL
axios.get(myApi).... // using API END POINT
For Local development to access API you can do it in two ways
API_URL="http:// localhost:3000" expo start
Just comment the Contants.expoConfig..... and directly paste local URL
like const myApi = "http:// localhost:3000"
And in eas.json
{
"production": {
"env": {
"API_URL": "https://prod.example.com"
}
},
"staging": {
"env": {
"API_URL": "https://staging.example.com"
}
}
}
Once we run eas build the appropriate API endpoint will be set.
Refer to the same in Expo documentation
https://docs.expo.dev/eas-update/environment-variables/

Related

Can't deploy smart contract to hardhat network

I set up a project with hardhat for an NFT app. I modify hardhat.config.js like this:
const { ALCHEMY_KEY, ACCOUNT_PRIVATE_KEY } = process.env;
module.exports = {
solidity: "0.8.0",
defaultNetwork: "hardhat",
networks: {
hardhat: {},
rinkeby: {
url: `https://eth-rinkeby.alchemyapi.io/v2/${ALCHEMY_KEY}`,
accounts: [`0x${ACCOUNT_PRIVATE_KEY}`]
},
// ethereum: {
// chainId: 1,
// url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_KEY}`,
// accounts: [`0x${ACCOUNT_PRIVATE_KEY}`]
// },
},
}
then I created a deploy script in the scripts folder with the deploy task
// scripts/deploy.js
const { task } = require("hardhat/config");
const { getAccount } = require("./helpers");
task("deploy", "Deploys the TokenV2.sol contract").setAction(async function (taskArguments, hre) {
const tokenFactory = await hre.ethers.getContractFactory("TokenV2", getAccount());
const token = await tokenFactory.deploy();
await token.deployed()
console.log(`Contract deployed to address: ${token.address}`);
});
The problem it's when I run npx hardhat deploy it's shows this error in terminal: Error: unsupported getDefaultProvider network (operation="getDefaultProvider", network="hardhat", code=NETWORK_ERROR, version=providers/5.5.3) What I missed? I will appreciate any help.
I never used defaultNetwork in my works so I just had the following hardhat.config File and had no issues at all:
{
"solidity":"0.8.4",
"networks":{
"rinkeby":{
"url":"`https://eth-rinkeby.alchemyapi.io/v2/${ALCHEMY_KEY}`",
"accounts":[
"`0x${ACCOUNT_PRIVATE_KEY}`"
]
}
}
}

adding cloudinary to strapi

Can someone tell me how to install Cloudinary to my Strapi app, I installed the plugin like the documentation said but the plugin doesn't show up at all in my project. Can someone tell me what im doing wrong
There is an example on the strapi documentation:
https://strapi.io/documentation/3.0.0-beta.x/plugins/upload.html#using-a-provider
To enable the provider for Cloudinary, create or edit the file at ./extensions/upload/config/settings.json
{
"provider": "cloudinary",
"providerOptions": { "cloud_name":"PROVIDER_CLOUD_NAME",
"api_key": "PROVIDER_API_KEY",
"api_secret":"PROVIDER_API_SECRET"
}
}
Of course you should replace PROVIDER_CLOUD_NAME, PROVIDER_API_KEY, PROVIDER_API_SECRET with appropriate values that can be found on your Cloudinary account.
If you want a specific configuration by environment you can edit the file at ./extensions/upload/config/settings.js like this:
if (process.env.NODE_ENV === 'production') {
module.exports = {
provider: 'providerName',
providerOptions: {
cloud_name: process.env.PROVIDER_CLOUD_NAME,
api_key: process.env.PROVIDER_API_KEY,
api_secret: process.env.PROVIDER_API_SECRET
}
};
} else {
// to use the default local provider you can return an empty configuration
module.exports = {};
}

How to keep my keys in safe in react native (expo)?

I have 3 release channels - dev, qa, prod.
const ENV_MODES = {
dev: {
},
prod: {
},
qa: {
}
}
ENV_MODES.default = ENV_MODES.prod
const getEnvVars = channel => {
if (process.env.NODE_ENV !== 'production') return ENV_MODES.dev
if (channel === null || channel === undefined || channel === '') return ENV_MODES.prod
if (channel.startsWith(ENV_DEV)) return ENV_MODES.dev
if (channel.startsWith(ENV_QA)) return ENV_MODES.qa
if (channel.startsWith(ENV_PROD)) return ENV_MODES.prod
return ENV_MODES.default
}
const ENV = getEnvVars(Constants.manifest.releaseChannel)
But I don't want to put keys into the repo.
How should I handle this? As I understand I can't expect that I will have NODE_ENV === 'qa' when I will publish in QA channel
You could use react-native-dotenv and add your keys to a .env file and add it to .gitignore. This way you won't be pushing keys to your repo and you can change your variables depending on the environment your code is running on.
To use the lib you only need to add it to your devDependencies and add it to you babel.config.js file, like so:
module.exports = function (api) {
api.cache(true);
return {
presets: [
'babel-preset-expo',
'module:react-native-dotenv',
],
};
};
EDIT:
NODE_ENV won't be the same as your release channel. If you want to load configs based on the release channel use Expo.Constants.manifest.releaseChannel.
However have in mind this variable doesn't exist in Dev mode, as per expo's docs.
Expo.Constants.manifest.releaseChannel does NOT exist in dev mode. It does exist, however when you explicitly publish / build with it.
EDIT 2:
Here's an example on how you can achieve both individual configurations for each release channel and use react-native-dotenv to avoid pushing secrets to your Git repo (since this is a big no no).
Remember: add your .env file to your .gitignore.
Constants.js
// eslint-disable-next-line import/no-extraneous-dependencies
import { AWS_KEY } from 'react-native-dotenv';
import { Constants as ExpoConstants } from 'expo';
const getChannelConfigs = (releaseChannel = '') => {
switch (releaseChannel) {
case 'qa':
return {
API_URL: 'https://qa.com/api',
...
};
case 'prod':
return {
API_URL: 'https://prod.com/api/',
...
};
default:
return {
API_URL: 'https://dev.com/api/',
...
};
}
};
const CHANNEL_CONFIGS = Object.freeze(getChannelConfigs(ExpoConstants.manifest.releaseChannel));
export default { AWS_KEY, CHANNEL_CONFIGS };
.env
AWS_KEY='superSecretKeyDoNOTStealThx'
In this example we are configuring which API URL the app will call based on its release channel. We're also avoiding commiting keys to our Git repo, since we now have them in a cozy .env file.
It's also worth mentioning this setup works when building a standalone app in your CI, but handing secret keys to your users might not be the best idea.
Expo now has eas.json file that sits in root of your app. That file can define all the build profiles and variables if needed. The submit feature of the configuration helps manage all the app store related configs.
Sensitive secrets (prod ids and paths) , feature flags are all possible to configure.
https://docs.expo.dev/build-reference/variables/
{
"build": {
"production": {
"node": "16.13.0",
"env": {
"API_URL": "https://company.com/api"
}
},
"preview": {
"extends": "production",
"distribution": "internal",
"env": {
"API_URL": "https://staging.company.com/api"
}
}
}
"submit": {
"production": {
"android": {
"serviceAccountKeyPath": "../path/to/api-xxx-yyy-zzz.json",
"track": "internal"
},
"ios": {
"appleId": "john#turtle.com",
"ascAppId": "1234567890",
"appleTeamId": "AB12XYZ34S"
}
}
}
}

How do I run selenium after serving create react app application?

I am trying to make some e2e tests with selenium over my Create React App application.
The problem is that before selenium can even run, I must run the react application itself (using for example npm start).
The problem: serving the app will stay in 'watch' mode in any autumation process I am trying to build.
Is it possible to run a create react app, and only after that selenium? (using grunt, npm or whatever?)
The current flow:
Run react development server with grunt (using npm start)
Run selenium
Is there a way to make them work one after the other?
grunfile:
require('dotenv').config();
module.exports = function(grunt) {
const API = process.env.REACT_APP_API_PATH + ':' + process.env.REACT_APP_API_PORT + '/api';
const DEV_PORT = 3001;
grunt.initConfig({
http: {
prepare_e2e_tests: {
options: {
url: API + '/prepare_e2e_tests'
}
}
},
exec: {
start_dev_server: {
cmd: 'REACT_APP_IS_TESTING=true PORT=' + DEV_PORT + ' npm start'
},
e2e: {
cmd: 'DEV_SERVER_PORT=3001 npm run e2e'
}
},
concurrent: {
test: {
tasks: ['http:prepare_e2e_tests','exec:start_dev_server','exec:e2e'],
options: {
logConcurrentOutput: true
}
}
}
});
grunt.registerTask('run_e2e', function() {
console.log('running e2e tests');
grutn.task.run('exec:e2e')
});
grunt.registerTask('prepare_server', function() {
console.log('preparing e2e tests on server:');
grunt.task.run('http:prepare_e2e_tests');
});
grunt.registerTask('setup_dev_server', function() {
console.log('running development server on port ' + DEV_PORT);
grunt.task.run('exec:start_dev_server');
});
grunt.registerTask('test', 'run e2e tests', ['concurrent:test']);
grunt.loadNpmTasks('grunt-http');
grunt.loadNpmTasks('grunt-exec');
grunt.loadNpmTasks('grunt-concurrent');
grunt.registerTask('default', []);
};
Thanks!

What is the best approach to test a HapiJS plugin, with Lab?

What is the best way to test a HapiJS plugin, for example one plugin that add routes and handlers.
Since I have to create an instance of Hapi.Server to run the plugins, should I define all the tests from the app's root, for all the plugins ?
or
should I manage to get THE instance of Hapi.Server in my plugin's local tests ?
If I go for the second option, my server will have registered all the plugins, including those that the plugin to be tested doesn't depends on.
What is the best way to approach this ?
Thanks in advance.
If you're using Glue (and I highly recommend it), you can create a manifest variable for each test (or group of tests) you want to execute. The manifest only needs to include plugins required for that test to execute properly.
And expose some sort of init function to actually start your server. Small example:
import Lab = require("lab");
import Code = require('code');
import Path = require('path');
import Server = require('../path/to/init/server');
export const lab = Lab.script();
const it = lab.it;
const describe = lab.describe;
const config = {...};
const internals = {
manifest: {
connections: [
{
host: 'localhost',
port: 0
}
],
registrations: [
{
plugin: {
register: '../http_routes',
options: config
}
},
{
plugin: {
register: '../business_plugin',
options: config
}
}
]
},
composeOptions: {
relativeTo: 'some_path'
}
};
describe('business plugin', function () {
it('should do some business', function (done) {
Server.init(internals.manifest, internals.composeOptions, function (err, server) {
// run your tests here
});
});
});
init function:
export const init = function (manifest: any, composeOptions: any, next: (err?: any, server?: Hapi.Server) => void) {
Glue.compose(manifest, composeOptions, function (err: any, server: Hapi.Server) {
if (err) {
return next(err);
}
server.start(function (err: any) {
return next(err, server);
});
});
};