In the previous chapter, Feature Gating & Targeting Logic, we built the "Bouncer"βthe logic that decides if a user enters the upsell experience.
One of the key checks the Bouncer performed was looking at Remote Configuration:
if (!getDesktopUpsellConfig().enable_startup_dialog) return false;
But where does getDesktopUpsellConfig come from?
Imagine you release your CLI tool. 10,000 users download it. Suddenly, you realize there is a typo in the dialog, or worse, the upsell is causing the app to crash.
If you hardcoded enable_startup_dialog = true in your code, you are stuck. You have to:
npm update (which they might not do for months).Dynamic Configuration (often called Feature Flags) acts like a remote control for your application. Even though the code is running on the user's computer, you can flip a switch on a server (like GrowthBook, LaunchDarkly, or a custom backend) to change how the app behaves instantly.
In this project, we use a specific function with a very honest name:
getDynamicConfig_CACHED_MAY_BE_STALE
Let's build the connection to our remote control.
First, we need to tell TypeScript what switches are available on our remote control.
// Define what the config looks like
type DesktopUpsellConfig = {
enable_shortcut_tip: boolean; // Switch 1
enable_startup_dialog: boolean; // Switch 2
};
Explanation: This acts as a contract. We expect the server to send us an object with exactly these two true/false switches.
What if the user is offline? or the server crashes? We must always have a fallback plan.
// Default values if the internet is down
const DESKTOP_UPSELL_DEFAULT: DesktopUpsellConfig = {
enable_shortcut_tip: false,
enable_startup_dialog: false
};
Explanation:
Safety First: We default everything to false. It is better to show nothing than to crash the app or show a broken feature. If the remote fetch fails, the app behaves as if the feature doesn't exist.
Now, we call the magic function to get the values.
import { getDynamicConfig_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js';
export function getDesktopUpsellConfig(): DesktopUpsellConfig {
// Ask for the config named 'tengu_desktop_upsell'
return getDynamicConfig_CACHED_MAY_BE_STALE(
'tengu_desktop_upsell',
DESKTOP_UPSELL_DEFAULT
);
}
Input:
'tengu_desktop_upsell' (The unique ID we use on the server).DESKTOP_UPSELL_DEFAULT (Our safety net).Output:
{ enable_startup_dialog: true, ... }{ enable_startup_dialog: false, ... }
You might be wondering about the long name: getDynamicConfig_CACHED_MAY_BE_STALE.
When you run a command like ls or git status, you expect it to run instantly.
If our CLI tool had to connect to a server, wait 500ms for a response, and then run the command, it would feel incredibly slow and laggy.
To solve this, we use a Caching Strategy.
For an upsell dialog, speed is more important than freshness. It's okay if the user gets the feature flag 1 hour later than everyone else, as long as their CLI command runs instantly.
Here is what happens when the user runs the app:
While the actual implementation of getDynamicConfig... is complex, here is a simplified version of what it does internally:
function getDynamicConfig_CACHED_MAY_BE_STALE(key, defaultValue) {
// 1. Read from a hidden file on your hard drive
const cached = readJsonFile('.growthbook_cache.json');
// 2. Trigger a background refresh (fire and forget)
// This runs silently and doesn't block the UI
refreshConfigInBackground(key);
// 3. Return the cached value immediately
return cached[key] || defaultValue;
}
Why this matters:
Now our logic flow is complete:
enable_startup_dialog to true.getDesktopUpsellConfig reads the cached config (which might say false initially).true value and saves it.getDesktopUpsellConfig reads the cache -> returns true.true and allows the Ink UI (Chapter 1) to render.In this chapter, we learned:
We have the UI (Chapter 1), the Logic (Chapter 2), and the Remote Control (Chapter 3). But there is one piece missing.
If a user clicks "Don't ask again," we need to remember that choice forever. We can't store that on a remote server (privacy/offline issues). We need to store it on their computer.
Next Chapter: Global User State Persistence
Generated by Code IQ