EAS Update 在生产构建中未下载,尽管通道匹配且已成功发布

发布: (2026年2月9日 GMT+8 10:26)
4 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的文章正文(包括任何代码块、列表或其他 Markdown 内容),我将按照要求保留原始格式并将文本翻译成简体中文。谢谢!

问题概述

  • 我在 bare React Native 项目(非 Managed 工作流)中使用 EAS Update。更新已成功发布并出现在 Expo 仪表板上,但通过 TestFlight 安装的实体设备从未收到更改。

  • 更新在仪表板中列在正确的分支/通道下。

  • 我已经多次重启应用。

项目配置

app.config.ts

import { ExpoConfig, ConfigContext } from 'expo/config';
import * as dotenv from 'dotenv';
import path from 'path';
import pkg from './package.json';

const APP_VARIANT = process.env.APP_VARIANT || 'prod';

dotenv.config({ path: path.resolve(__dirname, `.env.${APP_VARIANT}`) });

interface CustomExpoConfig extends ExpoConfig {
  'react-native-google-mobile-ads'?: {
    android_app_id?: string;
    ios_app_id?: string;
  };
}

const convertVersionToNumber = (version: string) => {
  const [major, minor, patch] = version.split('.').map(Number);
  return major * 1_000_000 + minor * 1_000 + patch + 2;
};

export default ({ config }: ConfigContext): CustomExpoConfig => ({
  ...config,
  name: 'GoalWith',
  slug: 'goalwith',
  version: pkg.version,
  runtimeVersion: pkg.version,

  ios: {
    ...config.ios,
    bundleIdentifier: 'com.goalwith.goalwith',
    buildNumber: convertVersionToNumber(pkg.version).toString(),
    googleServicesFile: './ios/GoogleService-Info.plist',
  },

  android: {
    package: 'com.goalwith',
    versionCode: convertVersionToNumber(pkg.version),
  },

  extra: {
    env: process.env.ENV,
    apiUrl: process.env.API_URL,
    kakaoAppKey: process.env.KAKAO_APP_KEY,
    googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID,
    admobIdAndroid: process.env.ADMOB_ID_ANDROID,
    admobIdIos: process.env.ADMOB_ID_IOS,
    eas: {
      projectId: '8475b304-e536-458b-aa6a-6aea6e3e6939',
    },
  },

  'react-native-google-mobile-ads': {
    android_app_id: process.env.ADMOB_ID_ANDROID,
    ios_app_id: process.env.ADMOB_ID_IOS,
  },

  updates: {
    url: 'https://u.expo.dev/8475b304-e536-458b-aa6a-6aea6e3e6939',
    requestHeaders: {
      'expo-channel-name': 'production',
    },
  },
});

eas.json

{
  "build": {
    "development": {
      "channel": "dev",
      "env": {
        "APP_VARIANT": "dev"
      }
    },
    "production": {
      "channel": "production",
      "env": {
        "APP_VARIANT": "prod"
      }
    }
  }
}

AppDelegate.swift

import UIKit
import Expo
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import GoogleSignIn
import RNBootSplash
import KakaoSDKCommon
import KakaoSDKAuth
import FirebaseCore
import EXUpdates

@main
class AppDelegate: ExpoAppDelegate {
  var window: UIWindow?

  var reactNativeDelegate: ReactNativeDelegate?
  var reactNativeFactory: RCTReactNativeFactory?

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    AppController.initializeWithoutStarting()
    FirebaseApp.configure()

    let delegate = ReactNativeDelegate()
    let factory = ExpoReactNativeFactory(delegate: delegate)
    delegate.dependencyProvider = RCTAppDependencyProvider()

    reactNativeDelegate = delegate
    reactNativeFactory = factory
    bindReactNativeFactory(factory)

    self.window = UIWindow(frame: UIScreen.main.bounds)

    factory.startReactNative(
      withModuleName: "main",
      in: self.window,
      launchOptions: launchOptions
    )

    if let rootView = self.window?.rootViewController?.view {
      RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView)
    }

    GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
      if error != nil || user == nil {
        // Show the app's signed-out state.
      } else {
        // Show the app's signed-in state.
      }
    }

    if let kakaoAppKey = Bundle.main.object(forInfoDictionaryKey: "KAKAO_APP_KEY") as? String {
      KakaoSDK.initSDK(appKey: kakaoAppKey)
    } else {
      print("Warning: KAKAO_APP_KEY not found in Info.plist")
    }

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  override func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey : Any] = [:]
  ) -> Bool {
  var handled = false

    handled = GIDSignIn.sharedInstance.handle(url)
    if handled { return true }

    if AuthApi.isKakaoTalkLoginUrl(url) {
      handled = AuthController.handleOpenUrl(url: url)
      if handled { return true }
    }

    return super.application(app, open: url, options: options)
  }
}

class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
  override func sourceURL(for bridge: RCTBridge) -> URL? {
    // needed to return the correct URL for expo-dev-client.
    bridge.bundleURL ?? bundleURL()
  }

  override func bundleURL() -> URL? {
#if DEBUG
    RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
#else
    if let updatesUrl = AppController.sharedInstance.launchAssetUrl() {
      return updatesUrl
    }
    return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
  }
}

Expo.plist

<dict>
  <key>EXUpdatesEnabled</key>
  <true/>
  <key>EXUpdatesCheckOnLaunch</key>
  <string>ALWAYS</string>
  <key>EXUpdatesLaunchWaitMs</key>
  <integer>0</integer>
  <key>EXUpdatesRequestHeaders</key>
  <dict>
    <key>expo-channel-name</key>
    <string>production</string>
  </dict>
  <key>EXUpdatesURL</key>
  <string>https://u.expo.dev/8475b304-e536-458b-aa6a-6aea6e3e6939</string>
  <key>EXUpdatesRuntimeVersion</key>
  <string>1.0.5</string>
</dict>

我已经验证的内容

  1. 更新出现在 Expo Dashboard 的 production 渠道下。
  2. 应用已在设备上 强制退出并重新启动 多次。

目标

找出为何在 TestFlight 安装的设备上 OTA 更新未被应用,尽管仪表盘显示更新已发布。希望能获得关于缺失的配置、缓存或所需原生更改的任何见解。

如果您需要查看其他配置文件或特定日志,请告诉我,我会立即更新帖子。

0 浏览
Back to Blog

相关文章

阅读更多 »