I'm building a mobile app using Expo. I've switched to using a dynamic app.config.ts
file in order to leverage environment variables and have different configurations for dev, staging and production.
When in 'dev' configuration, I'd like to be able to set a config value to the output of a web request (basically, the ngrok public_url obtained from localhost:4040/api/tunnels
).
However, as I understand it from the Expo documentation, the dynamic configuration in app.config.ts
does NOT support Promises (and there's an error when I try to use it). This is very unfortunate as it would allow me to solve my problem immediately. As such, I'm now looking for a workaround.
What options are there for me to use the output of an async (Promise) function such as this within my Expo configuration file?
In the end, I found no way of doing this directly in the app.config.ts
file. I resorted to scripting the commands in my package.json
file instead.
Whenever I want to run the project locally, I now call npm run dev
and all of the necessary scripts are run to properly initialize the ngrok URL.
I modified the dev
script to sequentially run dev:get-local-ngrok
followed by start
. This executes a script that recovers the ngrok URL and exports it to a cjs
file that then gets imported by the app.config.ts
{
...
"scripts": {
"start": "expo start --dev-client",
"dev:get-local-ngrok": "node ./ngrok.cjs",
"dev": "run-s dev:get-local-ngrok start",
},
...
}
This script recovers the ngrok public URL and writes it to the local-ngrok.cjs
file.
const axios = require("axios");
const fs = require("node:fs");
const modulePath = "./local-ngrok.cjs";
(async () => {
try
{
if (fs.existsSync(modulePath)) {
fs.unlinkSync(modulePath);
}
const resp = await axios.get("http://localhost:4040/api/tunnels");
const url = resp.data.tunnels[0]?.public_url;
if (!url) {
throw new Error("Response did not return expected public_url value.");
}
fs.writeFileSync(modulePath, `module.exports = { ngrokUrl: \"${url}\"};`);
} catch (ex) {
throw new Error(`Could not obtain ngrok tunnel url. Are you certain that ngrok is running?\n${ex}`);
}
})();
local-ngrok.cjs
module.exports = { ngrokUrl: "https://74ea-142-170-84-132.ngrok-free.app"};
The following changes were added to the app.config.ts
file to require
the local-ngrok.cjs
file and use the output to set a config variable.
// if APP_ENV is not set, means that we are in dev mode
if (!process.env.APP_ENV) {
// determine local ngrok url and use that to for config
let ngrokUrl : string | undefined;
try {
const ngrokModule = require('./local-ngrok.cjs');
if (ngrokModule) {
ngrokUrl = ngrokModule.ngrokUrl;
}
} catch (ex) {}
if (!ngrokUrl) {
throw new Error("Could not determine ngrok url. Are you running with the 'npm run dev' command? Or have you run the 'npm run dev:get-local-ngrok' first?");
}
config.API_BASE_URL = ngrokUrl + "/";
}
I don't love the solution since I feel that it's hacky but until there's support for Promises in app.config.ts
, this works and I think this is as good as it's going to get.