async-awaitexpo

How do you use the output from an async result in app.config.ts?


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?


Solution

  • 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.

    package.json

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

    ngrok.cjs

    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}`);
        }
    })();
    

    Sample local-ngrok.cjs

    module.exports = { ngrokUrl: "https://74ea-142-170-84-132.ngrok-free.app"};
    

    app.config.ts

    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.