Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OTA updates support for testflight channel #3291

Merged
merged 13 commits into from
Apr 3, 2024

Conversation

haileyok
Copy link
Contributor

@haileyok haileyok commented Mar 20, 2024

Why

This PR adds OTA update functionality to the app. Updates will use the updates.bsky.app service, which is based on the Expo Updates protocol. https://github.com/expo/custom-expo-updates-server

There are two channels for updates: production and testflight. In this PR, we are only enabling the testflight channel. Users that are using the production app will not receive OTA updates, and the production client will not make any requests to the server at this time.

After some time of testing the internal channel, we can enable the production channel to start getting a sense of how many requests are going to the server and to see if there are any changes we need to make there. The good news is that even if the server goes down or starts returning errors, the client will simply ignore the requests/errors and continue to operate normally.

We should also update the workflow at a later point to support automatic deployments to the staging server, as well as manual workflow for releasing to web.

Workflow

Untitled

How Channels Work

There is an environemtn variable inside of .env called EXPO_PUBLIC_ENV that can be either set to production or testflight depending on how we run the release. Using eas build --profile testflight for example will automatically set EXPO_PUBLIC_ENV to testflight. This will set the app to download updates from the testflight channel. Likewise, using eas build --profile production will set the app to download updates from the production channel.

There should not be any changes to our current workflow when submitting new releases to the app store. The GitHub action is configured to either use the testflight or production channel now. @pfrazee notably it might be worth taking a moment to properly add the credentials for Google Play to make Android releases easier, but the workflow that you are using for submitting Android builds right now will also continue to be supported.

How Updates Work

New builds (i.e. a build that we are submitting to the App Store) will not immediately have any OTA updates available for it. In that case, the JS bundle included in the .ipa/.aab will be used by the client. Whenever an OTA update is released however, the .ipa/.aab's JS bundle will not be used in favor of the JS bundle downloaded from our updates server.

In the event that the downloaded JS bundle causes any issues, the client will fall back to using the .ipa/.aab's JS bundle.

We use expo-fingerprint in the GitHub action as well to prevent any accidental deployments that are not compatible with the current runtime version. I.e., if we updated a package that uses native code such as react-navigation, there's a chance that the JS code used by react-navigation would not be compatible with the native code the library uses. Therefore, we will cancel and fail these deployments.

Copy link

github-actions bot commented Mar 20, 2024

The Pull Request introduced fingerprint changes against the base commit: 02b2ab4

Fingerprint diff
[
  {
    "type": "file",
    "filePath": ".gitignore",
    "reasons": [
      "bareGitIgnore"
    ],
    "hash": "34d7809b467aae19b90d91248c1967f54b5613e3"
  },
  {
    "type": "file",
    "filePath": "eas.json",
    "reasons": [
      "easBuild"
    ],
    "hash": "991e1f84ddcc82b0fb25e2f46b6bdfcf088690d7"
  },
  {
    "type": "file",
    "filePath": "package.json",
    "reasons": [
      "expoConfigPlugins"
    ],
    "hash": "2868ff27167ee7ac22c4a5df4bd251302ea9b7f8"
  },
  {
    "type": "dir",
    "filePath": "node_modules/expo-updates/ios",
    "reasons": [
      "expoAutolinkingIos"
    ],
    "hash": "d46bff4299ab6eed3a7e3a2488215fad8eaa164d"
  },
  {
    "type": "dir",
    "filePath": "patches",
    "reasons": [
      "patchPackage"
    ],
    "hash": "e62e799b66ac996950d154bba61d4df5f2171b46"
  },
  {
    "type": "contents",
    "id": "expoConfig",
    "contents": "{\"android\":{\"adaptiveIcon\":{\"backgroundColor\":\"#1185FE\",\"backgroundImage\":\"./assets/icon-android-background.png\",\"foregroundImage\":\"./assets/icon-android-foreground.png\",\"monochromeImage\":\"./assets/icon-android-foreground.png\"},\"googleServicesFile\":\"./google-services.json\",\"icon\":\"./assets/icon.png\",\"intentFilters\":[{\"action\":\"VIEW\",\"autoVerify\":true,\"category\":[\"BROWSABLE\",\"DEFAULT\"],\"data\":[false,{\"host\":\"bsky.app\",\"scheme\":\"https\"}]}],\"package\":\"xyz.blueskyweb.app\",\"splash\":{\"backgroundColor\":\"#0c7cff\",\"dark\":{\"backgroundColor\":\"#0f141b\",\"image\":\"./assets/splash-dark.png\",\"resizeMode\":\"cover\"},\"image\":\"./assets/splash.png\",\"resizeMode\":\"cover\"}},\"androidStatusBar\":{\"backgroundColor\":\"#00000000\",\"barStyle\":\"light-content\"},\"assetBundlePatterns\":[\"**/*\"],\"extra\":{\"eas\":{\"build\":{\"experimental\":{\"ios\":{\"appExtensions\":[{\"bundleIdentifier\":\"xyz.blueskyweb.app.Share-with-Bluesky\",\"entitlements\":{\"com.apple.security.application-groups\":[\"group.app.bsky\"]},\"targetName\":\"Share-with-Bluesky\"}]}}},\"projectId\":\"55bd077a-d905-4184-9c7f-94789ba0f302\"}},\"hooks\":{\"postPublish\":[{\"config\":{\"dist\":\"undefined.1.76.0.undefined\",\"organization\":\"blueskyweb\",\"project\":\"react-native\",\"release\":\"1.76.0\"},\"file\":\"sentry-expo/upload-sourcemaps\"}]},\"icon\":\"./assets/icon.png\",\"ios\":{\"associatedDomains\":[\"applinks:bsky.app\",\"applinks:staging.bsky.app\"],\"bundleIdentifier\":\"xyz.blueskyweb.app\",\"config\":{\"usesNonExemptEncryption\":false},\"entitlements\":{\"com.apple.security.application-groups\":\"group.app.bsky\"},\"infoPlist\":{\"NSCameraUsageDescription\":\"Used for profile pictures, posts, and other kinds of content.\",\"NSMicrophoneUsageDescription\":\"Used for posts and other kinds of content.\",\"NSPhotoLibraryAddUsageDescription\":\"Used to save images to your library.\",\"NSPhotoLibraryUsageDescription\":\"Used for profile pictures, posts, and other kinds of content\",\"UIBackgroundModes\":[\"remote-notification\"]},\"splash\":{\"backgroundColor\":\"#ffffff\",\"dark\":{\"backgroundColor\":\"#001429\",\"image\":\"./assets/splash-dark.png\",\"resizeMode\":\"cover\"},\"image\":\"./assets/splash.png\",\"resizeMode\":\"cover\"},\"supportsTablet\":false},\"name\":\"Bluesky\",\"orientation\":\"portrait\",\"owner\":\"blueskysocial\",\"platforms\":[\"android\",\"ios\",\"web\"],\"plugins\":[\"./plugins/shareExtension/withShareExtensions.js\",\"./plugins/withAndroidManifestFCMIconPlugin.js\",\"./plugins/withAndroidManifestPlugin.js\",\"./plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js\",\"./plugins/withAndroidStylesWindowBackgroundPlugin.js\",\"expo-localization\",[\"expo-build-properties\",{\"android\":{\"buildToolsVersion\":\"34.0.0\",\"compileSdkVersion\":34,\"kotlinVersion\":\"1.8.0\",\"newArchEnabled\":false,\"targetSdkVersion\":34},\"ios\":{\"deploymentTarget\":\"13.4\",\"newArchEnabled\":false}}],[\"expo-notifications\",{\"color\":\"#1185fe\",\"icon\":\"./assets/icon-android-notification.png\"}]],\"scheme\":\"bluesky\",\"sdkVersion\":\"50.0.0\",\"slug\":\"bluesky\",\"splash\":{\"backgroundColor\":\"#ffffff\",\"image\":\"./assets/splash.png\",\"resizeMode\":\"cover\"},\"updates\":{\"channel\":\"production\",\"checkAutomatically\":\"NEVER\",\"codeSigningCertificate\":\"./code-signing/certificate.pem\",\"codeSigningMetadata\":{\"alg\":\"rsa-v1_5-sha256\",\"keyid\":\"main\"},\"enabled\":false,\"fallbackToCacheTimeout\":30000,\"url\":\"https://updates.bsky.app/manifest\"},\"userInterfaceStyle\":\"automatic\",\"version\":\"1.76.0\",\"web\":{\"favicon\":\"./assets/favicon.png\"}}",
    "reasons": [
      "expoConfig"
    ],
    "hash": "ec8665883ccf4478a9d5b3dd09e452af616f67ad"
  },
  {
    "type": "contents",
    "id": "packageJson:scripts",
    "contents": "{\"prepare\":\"is-ci || husky install\",\"postinstall\":\"patch-package && yarn intl:compile\",\"prebuild\":\"expo prebuild --clean\",\"android\":\"expo run:android\",\"ios\":\"expo run:ios\",\"web\":\"expo start --web\",\"use-build-number\":\"./scripts/useBuildNumberEnv.sh\",\"use-build-number-with-bump\":\"./scripts/useBuildNumberEnvWithBump.sh\",\"build-web\":\"expo export:web && node ./scripts/post-web-build.js && cp -v ./web-build/static/js/*.* ./bskyweb/static/js/\",\"build-all\":\"yarn intl:build && yarn use-build-number-with-bump eas build --platform all\",\"build-ios\":\"yarn use-build-number-with-bump eas build -p ios\",\"build-android\":\"yarn use-build-number-with-bump eas build -p android\",\"build\":\"yarn use-build-number-with-bump eas build\",\"start\":\"expo start --dev-client\",\"start:prod\":\"expo start --dev-client --no-dev --minify\",\"clean-cache\":\"rm -rf node_modules/.cache/babel-loader/*\",\"test\":\"NODE_ENV=test jest --forceExit --testTimeout=20000 --bail\",\"test-watch\":\"NODE_ENV=test jest --watchAll\",\"test-ci\":\"NODE_ENV=test jest --ci --forceExit --reporters=default --reporters=jest-junit\",\"test-coverage\":\"NODE_ENV=test jest --coverage\",\"lint\":\"eslint --cache --ext .js,.jsx,.ts,.tsx src\",\"typecheck\":\"tsc --project ./tsconfig.check.json\",\"e2e:mock-server\":\"./jest/dev-infra/with-test-redis-and-db.sh ts-node --project tsconfig.e2e.json __e2e__/mock-server.ts\",\"e2e:metro\":\"NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios\",\"e2e:build\":\"NODE_ENV=test detox build -c ios.sim.debug\",\"e2e:run\":\"NODE_ENV=test detox test --configuration ios.sim.debug --take-screenshots all\",\"perf:test\":\"NODE_ENV=test maestro test\",\"perf:test:run\":\"NODE_ENV=test maestro test __e2e__/maestro/scroll.yaml\",\"perf:test:measure\":\"NODE_ENV=test flashlight test --bundleId xyz.blueskyweb.app --testCommand 'yarn perf:test' --duration 150000 --resultsFilePath .perf/results.json\",\"perf:test:results\":\"NODE_ENV=test flashlight report .perf/results.json\",\"perf:measure\":\"NODE_ENV=test flashlight measure\",\"intl:build\":\"yarn intl:extract && yarn intl:compile\",\"intl:check\":\"yarn intl:extract && git diff-index -G'(^[^\\\\*# /])|(^#\\\\w)|(^\\\\s+[^\\\\*#/])' HEAD || (echo '\\n⚠️ i18n detected un-extracted translations\\n' && exit 1)\",\"intl:extract\":\"lingui extract\",\"intl:compile\":\"lingui compile\",\"nuke\":\"rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android\",\"update-extensions\":\"bash scripts/updateExtensions.sh\",\"export\":\"npx expo export\"}",
    "reasons": [
      "packageJson:scripts"
    ],
    "hash": "4247ebb9a76cda7a18bb1e346a43d96908ffde6a"
  }
]

Generated by PR labeler 🤖

another adjustment, testing

another adjustment, testing

fix again

fix again

set default runtime version

fix

test this script

test this script

test this script

add build numbers to the deployment url

clean

give script access to build number

add `useBuildNumberEnv` without a bump

new line

fix missing async

add channel name to deployment url

add updates check on launch for testflight users

ver bump

init updates on launch for native

add `testflight` as default in build submit

add is_testflight check
@haileyok haileyok force-pushed the hailey/configure-builds-and-bundles branch from a2c0019 to f1cdc4e Compare March 21, 2024 00:03
@haileyok haileyok force-pushed the hailey/configure-builds-and-bundles branch 2 times, most recently from 134adb3 to 2b5056a Compare March 31, 2024 07:28
drop check down to every 15 minutes

adjustments

change to 15 mins

use jq to get version if necessary

rm test on push

figured it out

remove nightly testflight releases

test again again again again again AGAIN ONCE MORE

test again again again again again AGAIN

test again again again again again AGAIN

test again again again again again

test again again again again

test again again again

test again again

test again

test

test

test

run deploy if necessary

run deploy if necessary

run deploy if necessary

run deploy if necessary

run deploy if necessary

remove test message

fix environment

oops

cleanup

merge in changes
@haileyok haileyok force-pushed the hailey/configure-builds-and-bundles branch from 0772a63 to e3065c8 Compare April 1, 2024 22:54
@haileyok haileyok marked this pull request as ready for review April 1, 2024 22:54
@haileyok haileyok changed the title [Wait for 1.73.0 release] Add configuration for testflight vs production channels for expo-updates Add OTA updates support for testflight channel Apr 2, 2024
@haileyok haileyok force-pushed the hailey/configure-builds-and-bundles branch from 0d67811 to e011399 Compare April 2, 2024 20:46
@@ -69,7 +68,7 @@ jobs:
echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json

- name: 🏗️ EAS Build
run: yarn use-build-number eas build -p ios --profile production --local --output build.ipa --non-interactive
run: yarn use-build-number eas build -p ios --profile ${{ inputs.profile || 'testflight' }} --local --output build.ipa --non-interactive
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the --profile flag here determines what EXPO_PUBLIC_ENV gets set to.

Copy link
Member

@estrattonbailey estrattonbailey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! So pumped for this 🔥

As you mentioned in Slack, let's do another quick look at Android too

Comment on lines 24 to 34
"preview": {
"extends": "base",
"distribution": "internal",
"channel": "preview",
"channel": "production",
"ios": {
"resourceClass": "large"
},
"env": {
"EXPO_PUBLIC_ENV": "production"
}
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this preview profile was only added for APK builds (happens bc of distribution: 'internal'). Could maybe remove if we can build APKs with the testflight profile.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah interesting. I think what I'll do for now then is set this one to use testflight channel, since we'll still need to do Android builds locally until we figure out the Google Play alpha builds stuff.

@@ -0,0 +1,7 @@
# Expo-Updates Patch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super appreciate this md 🙏

haileyok added 2 commits April 3, 2024 15:11
update git ignore

rm test stuff from the bundle action

remove test message

test message

fix

test message

test message

few android fixes

few android fixes

fix jq

add a test message

fix slack webhook

create android deployments test 2

create android deployments

add `testflight-android` profile to eas.json

more cleanup

some more cleanup

simplify some logic

remove unnecessary channel

rename to `useOTAUpdates`
@haileyok haileyok force-pushed the hailey/configure-builds-and-bundles branch from f60bdae to 4e581b7 Compare April 3, 2024 22:11
@haileyok haileyok merged commit 73df7e5 into main Apr 3, 2024
5 checks passed
@haileyok haileyok deleted the hailey/configure-builds-and-bundles branch June 8, 2024 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants