In the previous chapter, Eligibility Gatekeeper, we set up a "Bouncer" to check if a user is even allowed to request remote settings.
Now that the Bouncer has opened the door, we have to actually go get the data. But the internet is a messy place. Wi-Fi drops, servers get overloaded, and sometimes data gets corrupted in transit.
This chapter introduces the API Transport & Schema Validation layer. Think of this as a Secure Courier Service.
Use Case: You are a developer on a train with spotty Wi-Fi. You open the CLI tool.
We use a library called axios to make HTTP requests. However, we don't just send a request blindly. We need to attach "Authentication" (proof of who we are) and "User-Agent" (what tool we are using).
// index.ts (Simplified)
const headers = {
'Authorization': `Bearer ${userToken}`, // Our ID Badge
'User-Agent': 'ClaudeCode/1.0', // Our Uniform
'If-None-Match': `"${cachedChecksum}"` // "Only give me new stuff"
}
// Send the request
const response = await axios.get(endpoint, { headers })
Imagine ordering a book you already own. Itβs a waste of money and shipping. In our system, before we ask the server for settings, we look at the file we already have on disk. We create a Checksum (a unique fingerprint) of that file.
When we talk to the server, we say: "I have the settings with fingerprint abc-123. Do you have anything newer?"
If the server doesn't respond, we shouldn't give up immediately. We use Exponential Backoff. This means we wait a little longer after each failure.
JavaScript is loosely typed, which can be dangerous. If the server sends us { "theme": "blue" } but our code expects { "theme": { "color": "blue" } }, the app will crash.
We use a library called Zod to define a "Schema"βa strict blueprint of what the data must look like. If the data doesn't fit the blueprint, we throw it away.
// types.ts
// Define the blueprint
const ResponseSchema = z.object({
uuid: z.string(),
settings: z.record(z.string(), z.unknown())
})
// Check the data
const parsed = ResponseSchema.safeParse(incomingData)
Here is what happens when the Remote Settings Lifecycle Manager asks for settings.
Let's look at the code that powers this logic in index.ts.
Before we send a request, we need to know what we currently have. We sort the keys to ensure the fingerprint is consistent.
// index.ts
export function computeChecksumFromSettings(settings: SettingsJson): string {
// Sort keys so {a:1, b:2} produces same hash as {b:2, a:1}
const sorted = sortKeysDeep(settings)
// Create a SHA-256 fingerprint
const normalized = jsonStringify(sorted)
const hash = createHash('sha256').update(normalized).digest('hex')
return `sha256:${hash}`
}
This function wraps the actual network call. It refuses to give up easily.
// index.ts
async function fetchWithRetry(cachedChecksum?: string) {
// Try up to 5 times
for (let attempt = 1; attempt <= DEFAULT_MAX_RETRIES + 1; attempt++) {
const result = await fetchRemoteManagedSettings(cachedChecksum)
// If successful or if error is fatal (like Auth error), stop.
if (result.success || result.skipRetry) {
return result
}
// Wait a bit before trying again
await sleep(getRetryDelay(attempt))
}
return lastResult // Return the last error if all retries failed
}
Why skipRetry? If the server says "401 Unauthorized" (Bad Password), retrying won't help. We fail fast in that case.
This is the core worker function. It handles the specific HTTP status codes and validates the data.
// index.ts - fetchRemoteManagedSettings
// ... setup headers ...
const response = await axios.get(endpoint, { headers, validateStatus: ... })
// Case 1: Server says "You already have the latest version"
if (response.status === 304) {
return { success: true, settings: null, checksum: cachedChecksum }
}
// Case 2: Server sent new data. VERIFY IT!
const parsed = RemoteManagedSettingsResponseSchema().safeParse(response.data)
if (!parsed.success) {
return { success: false, error: 'Invalid format' } // Reject the package
}
return { success: true, settings: parsed.data.settings, checksum: parsed.data.checksum }
In types.ts, we define exactly what we expect the server to send.
// types.ts
export const RemoteManagedSettingsResponseSchema = lazySchema(() =>
z.object({
uuid: z.string(), // Must have an ID
checksum: z.string(), // Must have a checksum
// "settings" must be an object, but we validate contents loosely here
settings: z.record(z.string(), z.unknown()),
}),
)
Note: We use z.unknown() for the values initially to avoid a "Circular Dependency" issue with the full settings schema. We do a deeper check in a second pass inside index.ts.
The API Transport & Schema Validation layer is the muscle of our operation.
At this point, we have:
Now we have a valid settings object in memory. But where do we put it? We need to save it to disk so it works offline next time. However, saving settings can be tricky because the code that reads settings might depend on the code that writes settings, creating a loop.
To solve this, we need a specialized storage mechanism.
Next Chapter: Leaf State Storage (Circular Dependency Breaker)
Generated by Code IQ