Ctrl K
ComunidadGuías…

apps/control-center/src/config.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import Constants from 'expo-constants';
import * as Updates from 'expo-updates';

/** What the update server needs to answer this device, all of it already on hand. */
export type SurfConfig = {
  /** Where /branch_lists lives: the update URL with its last segment dropped. */
  baseUrl: string;
  appId: string;
  channel: string;
  runtimeVersion: string;
  /**
   * The request headers baked at build time. setUpdateRequestHeadersOverride
   * REPLACES the whole set rather than merging into it, and expo-updates only
   * accepts keys that were declared at build time — so every override has to be
   * rebuilt from this, or the poll loses its channel and its app id.
   */
  requestHeaders: Record<string, string>;
};

export const BRANCH_HEADER = 'xprem-branch';

/** State the manifest already carries, so nothing has to be stored on the side. */
export type LoadedState = {
  /** The branch actually served. Not necessarily the one that was asked for. */
  branch: string | null;
  /** Set when the server refused a branch because its update crashed here. */
  refusedBranch: string | null;
};

function extra(): Record<string, unknown> {
  return ((Updates.manifest as { extra?: Record<string, unknown> } | undefined)?.extra ??
    {}) as Record<string, unknown>;
}

export function readLoadedState(): LoadedState {
  const manifestExtra = extra();
  return {
    branch: typeof manifestExtra.branch === 'string' ? manifestExtra.branch : null,
    refusedBranch:
      typeof manifestExtra.branchSurfingRefused === 'string' &&
      manifestExtra.branchSurfingRefused.length > 0
        ? manifestExtra.branchSurfingRefused
        : null,
  };
}

/**
 * The keys the override cannot be built without. expo-updates only accepts an
 * override whose keys were declared at build time, and the override replaces the
 * whole header set — so a build missing any of these can be sent into a state
 * where every poll is answered 400 and no update can reach it to fix that.
 */
const REQUIRED_BUILD_HEADERS = ['expo-app-id', 'expo-channel-name', BRANCH_HEADER];

function declared(requestHeaders: Record<string, string>, key: string): boolean {
  return typeof requestHeaders[key] === 'string';
}

/**
 * Returns null when this build cannot surf at all: no update config, a runtime too
 * old for the header override, or a build-time header set the override could not
 * be rebuilt from. The component renders nothing in that case rather than offering
 * a switch that would strand the device.
 */
export function readConfig(): SurfConfig | null {
  const updates = Constants.expoConfig?.updates as
    | { url?: string; requestHeaders?: Record<string, string> }
    | undefined;
  if (!updates?.url || typeof Updates.setUpdateRequestHeadersOverride !== 'function') {
    return null;
  }

  const requestHeaders = updates.requestHeaders ?? {};
  // A key whose value came from an unset env var is dropped from the config
  // entirely, so this checks presence rather than truthiness — the empty string
  // is a declaration, undefined is not.
  const missing = REQUIRED_BUILD_HEADERS.filter(key => !declared(requestHeaders, key));
  if (missing.length > 0) {
    // Loud, because the symptom is a panel that never appears and the cause is
    // three lines away in app config.
    console.warn(
      `[xprem] Branch surfing is unavailable: ${missing.join(', ')} ${
        missing.length === 1 ? 'is' : 'are'
      } missing from updates.requestHeaders. A header can only be overridden at ` +
        `runtime if it was declared at build time.`
    );
    return null;
  }

  const appId = requestHeaders['expo-app-id'];
  const channel = Updates.channel;
  const runtimeVersion = Updates.runtimeVersion;
  if (!appId || !channel || !runtimeVersion) {
    return null;
  }

  let baseUrl: string;
  try {
    // Sibling of the manifest route, not root of the host: a self-hosted server
    // at https://host/ota/manifest serves https://host/ota/branch_lists. Using
    // the origin alone sent every request to the wrong path, and the 404 that
    // came back was indistinguishable from "this channel does not allow it".
    const parsed = new URL(updates.url);
    // Trailing slashes go first: on ".../manifest/" the segment removal would
    // otherwise eat the empty part after the slash and leave "manifest" in the
    // path, sending every request one level too deep.
    baseUrl = parsed.origin + parsed.pathname.replace(/\/+$/, '').replace(/\/[^/]*$/, '');
  } catch {
    return null;
  }

  return { baseUrl, appId, channel, runtimeVersion, requestHeaders };
}