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

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"
}
}
}
}

Related

TestCafe config setup for multiple environments

I'm wondering if it's possible to have a single .testcaferc.json file that provides config for all environments?
We've got a lot of config that's shared, so I don't really want to create individual config files for each environment if I don't have to.
Something like this would be great, but can't seem to see anything in the tc docs that mentions anything about environments
{
browsers: "chrome",
concurrency: 5,
baseUrl: "http://localhost:3000",
...
env: {
dev: {
baseUrl: "https://dev.example.com",
...
},
test: {
baseUrl: "https://test.example.com",
...
},
prod: {
baseUrl: "https://example.com",
...
},
}
}
Currently we pass a number of extra args into the scripts as below, which is getting really messy and difficult to maintain in our package file.
{
"test:e2e": "npm run test:cafe",
"test:e2e:dev": "npm run test:cafe -- --base-url=https://dev.example.com",
"test:e2e:test": "npm run test:cafe -- --base-url=https://test.example.com",
"test:e2e:prod": "npm run test:cafe -- --base-url=https://example.com","
}
Thanks!
You can use the JavaScript config file instead of a JSON config file. In this case, you can write your own logic right inside the config. For example, you can check the environment variable as follows:
.testcaferc.js:
module.exports = {
baseUrl: process.env.dev ? 'http://localhost:3000' : 'http://localhost:3005',
customActions: {},
}
Moreover, you can create different configuration files for different environments, and one for common settings. In this case, you can use the common settings from the common config file and import specific environment settings from other config files.
Common: .testcaferc.js
function resolveEnv () {
if (process.env.dev)
return './.testcaferc-dev.js'
else if (process.env.test)
return './.testcaferc-test.js'
else
return './.testcaferc-prod.js'
}
const commonConfig = {
skipJsErrors: true,
customActions: {}
}
const envConfigName = resolveEnv();
const envConfig = require(envConfigName);
module.exports = {
...commonConfig,
...envConfig,
}
.testcaferc-dev.js
module.exports = {
baseUrl: 'http://localhost:3000',
skipJsErrors: false,
}

Why debug on Webpack v5 Vue is no longer working?

I recently updated #vue's webpack and submodules to v5 following this. This was necessary because web workers were not working with v4. Now my problem is that I'm not able to debug the code and I don't know where to start. I accept any suggestion that helps me diagnose the problem
The code runs normally, it just doesn't stop on breakpoints anymore. My vue.config.js has a configuration in the devtoolModuleFilenameTemplate that if I'm not mistaken it was supposed to work with .ts files, I've tried to remove it and it didn't help.
I searched around here and couldn't find anyone with a similar problem. My current version is at 5.0.8 but I also tried 5.0.1.
vue.config.js:
/* eslint-disable */
const CompilerSfc = require('#vue/compiler-sfc')
const parse = CompilerSfc.parse
CompilerSfc.parse = (source, options) => {
return parse(source, Object.assign({ pad: true }, options))
}
module.exports = {
devServer: {
allowedHosts: 'all',
port: 5000,
host: '0.0.0.0',
},
configureWebpack: {
devtool: 'source-map',
output: {
devtoolModuleFilenameTemplate: (info) => {
if (info.allLoaders === '') {
// when allLoaders is an empty string the file is the original source
// file and will be prefixed with src:// to provide separation from
// modules transpiled via webpack
const filenameParts = ['src://']
if (info.namespace) {
filenameParts.push(info.namespace + '/')
}
filenameParts.push(info.resourcePath.replace(/^\.\//, ''))
return filenameParts.join('')
} else {
// otherwise we have a webpack module
const filenameParts = ['webpack://']
if (info.namespace) {
filenameParts.push(info.namespace + '/')
}
filenameParts.push(info.resourcePath.replace(/^\.\//, ''))
const isVueScript =
info.resourcePath.match(/\.vue$/) &&
info.query.match(/\btype=script\b/) &&
!info.allLoaders.match(/\bts-loader\b/)
if (!isVueScript) {
filenameParts.push('?' + info.hash)
}
return filenameParts.join('')
}
},
},
},
}
launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "vue.js: chrome",
"request": "launch",
"type": "chrome",
"url": "http://localhost:5000",
"webRoot": "${workspaceFolder}/src"
}
]
}
You can put debugger key directly in your code, instand of breakpoint in vscode. Then, debug with your browser.
Here is an example :
Hope it hopes :)

Serving a modified asset-manifest.json in CRA using CRACO doesn't work

I have just created a new CRA app. In our organization we have a micro frontend framework which has certain requirements when it comes to the the asset file of each micro frontend app. CRA will by default, create a asset-manifest.json file.
https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/config/webpack.config.js#L656
Now I need to change this file to assets.json and make some structural changes as well. To achieve this I use CRACO and add the WebpackManifestPlugin.
const ManifestPlugin = require('webpack-manifest-plugin');
module.exports = {
webpack: {
plugins: {
// tried removing CRA definition for ManifestPlugin.
// It worked, but had no impact on my problem
// remove: ['ManifestPlugin'],
add: [
new ManifestPlugin({
fileName: 'assets.json',
generate: (seed, files, entrypoints) => {
const js = [],
css = [];
files.forEach((file) => {
if (file.path.endsWith('.js') && file.isInitial) {
js.push({ value: file.path, type: 'entry' });
}
if (file.path.endsWith('.css') && file.isInitial) {
css.push({ value: file.path, type: 'entry' });
}
});
return { js, css };
},
})
]
}
}
};
Whenever I build the application, my new assets.json file is generated as expected.
However, I can't get CRA, or webpack-dev-server I assume, to serve this file while I run my CRA app in development mode. It only resolves to the index.html file. I have looked through CRA source code and can't really find any relevant place where asset-manifest.json is mentioned.
So how do I get webpack-dev-server to serve my assets.json file?
You need to add the ManifestPlugin to webpack.plugins.remove array to receive only the configuration from WebpackManifestPlugin:
...
webpack: {
alias: {},
plugins: {
add: [
new WebpackManifestPlugin(webpackManifestConfig)
],
remove: [
"ManifestPlugin"
],
},
configure: (webpackConfig, { env, paths }) => { return webpackConfig; }
},
...

Expo App environments for Dev, UAT and Production

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/

Removing log from production mode is not working - react native

React native documentation state that
"You can also use this babel plugin that removes all the console.* calls. You need to install it first with npm i babel-plugin-transform-remove-console --save-dev, and then edit the .babelrc file under your project directory like this"
{
"env": {
"production": {
"plugins": ["transform-remove-console"]
}
}
}
I didn't find .babelrc file. Hence I made the following changes in babel.config.js file.
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
env: {
production: {
plugins: ["transform-remove-console"]
}
}
};
But it's not working for me. The logs are displayed when I've tested them in Android Studio. What went wrong here?
The documented approach didn't work for my existing project either, but it worked in a new test project. I tried numerous dependency upgrades, adding/removing deps, plugins, etc. trying to figure out the source of the problem, but no luck. So finally I went with this workaround:
module.exports = {
plugins: ['#babel/plugin-proposal-numeric-separator', 'lodash'].concat(
process.env.NODE_ENV === 'production' ? ['transform-remove-console'] : []
),
presets: [
'module:metro-react-native-babel-preset',
['#babel/env', { targets: { node: 6 } }]
]
};
This should work (recommended method)
const presets = ['module:metro-react-native-babel-preset'];
const plugins = ['module:react-native-dotenv'];
if (process.env.ENV === 'prod') {
plugins.push('transform-remove-console');
}
module.exports = {presets, plugins};
Or if you don't want to use any package for this you can do this.
Place this in index.js
if(!__DEV__){
console.log = () => {}
}