├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE ├── resources │ ├── banner-dark.png │ └── banner-light.png └── workflows │ ├── build.yml │ └── lint-and-tsc.yml ├── .gitignore ├── .prettierrc.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── appcast.xml ├── apps ├── cli │ ├── .gitignore │ ├── codegen.ts │ ├── macos │ │ └── entitlements.plist │ ├── package.json │ ├── pkg.config.json │ ├── src │ │ ├── README.md │ │ ├── api │ │ │ └── GraphqlClient.ts │ │ ├── commands │ │ │ ├── BootDevice.ts │ │ │ ├── CheckTools.ts │ │ │ ├── DetectIOSAppType.ts │ │ │ ├── DownloadBuild.ts │ │ │ ├── InstallAndLaunchApp.ts │ │ │ ├── LaunchExpoGo.ts │ │ │ ├── LaunchUpdate.ts │ │ │ ├── ListDevices.ts │ │ │ └── SetSession.ts │ │ ├── graphql │ │ │ ├── builds.gql │ │ │ └── generated │ │ │ │ └── graphql.ts │ │ ├── index.ts │ │ ├── storage.ts │ │ └── utils.ts │ └── tsconfig.json └── menu-bar │ ├── .bundle │ └── config │ ├── .eas │ └── build │ │ ├── build-preview.yml │ │ ├── build-release.yml │ │ └── configureMacOSCredentials │ │ ├── README.md │ │ ├── build │ │ └── index.js │ │ ├── eas.json │ │ ├── package.json │ │ ├── src │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── yarn.lock │ ├── .eslintrc.js │ ├── .gitignore │ ├── .watchmanconfig │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── app.json │ ├── babel.config.js │ ├── codegen.ts │ ├── eas.json │ ├── electron │ ├── .gitignore │ ├── .vscode │ │ └── launch.json │ ├── assets │ │ └── images │ │ │ ├── icon-linux.png │ │ │ ├── icon-windows.ico │ │ │ └── tray │ │ │ ├── icon-dark.ico │ │ │ ├── icon-light.ico │ │ │ ├── icon.png │ │ │ └── icon@2x.png │ ├── forge.config.ts │ ├── modules │ │ ├── Alert │ │ │ └── main.ts │ │ ├── AutoResizerRootViewManager │ │ │ └── main.ts │ │ ├── DeviceEventEmitter │ │ │ └── preload.ts │ │ ├── Linking │ │ │ ├── main.ts │ │ │ └── preload.ts │ │ ├── WindowManager │ │ │ └── main.ts │ │ ├── mainRegistry.ts │ │ └── preloadRegistry.ts │ ├── package.json │ ├── src │ │ ├── LocalServer.ts │ │ ├── TrayGenerator.ts │ │ ├── main.ts │ │ ├── preload.ts │ │ └── types.d.ts │ ├── tsconfig.json │ ├── vite.cli.config.ts │ ├── vite.main.config.mts │ ├── vite.preload.config.ts │ ├── vite.renderer.config.ts │ └── yarn.lock │ ├── index.d.ts │ ├── index.js │ ├── index.web.js │ ├── ios │ └── ExpoMenuBar.xcodeproj │ ├── macos │ ├── .gitignore │ ├── .xcode.env │ ├── AutoLauncher │ │ ├── AppDelegate.swift │ │ ├── AutoLauncher.entitlements │ │ ├── AutoLauncherRelease.entitlements │ │ └── main.swift │ ├── ExpoMenuBar-macOS │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── mac-1024.png │ │ │ │ ├── mac-128.png │ │ │ │ ├── mac-16.png │ │ │ │ ├── mac-256.png │ │ │ │ ├── mac-32.png │ │ │ │ ├── mac-512.png │ │ │ │ └── mac-64.png │ │ │ ├── AppIconDebug.appiconset │ │ │ │ ├── 1024-mac.png │ │ │ │ ├── 128-mac.png │ │ │ │ ├── 16-mac.png │ │ │ │ ├── 256-mac.png │ │ │ │ ├── 32-mac.png │ │ │ │ ├── 512-mac.png │ │ │ │ ├── 64-mac.png │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── menu-bar-icon-debug.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── menu-bar-icon-debug.png │ │ │ └── menu-bar-icon.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── menu-bar-icon.pdf │ │ ├── AutoResizerRootView.h │ │ ├── AutoResizerRootView.m │ │ ├── AutoResizerRootViewManager.h │ │ ├── AutoResizerRootViewManager.m │ │ ├── Base.lproj │ │ │ └── Main.storyboard │ │ ├── Checkbox.h │ │ ├── Checkbox.m │ │ ├── CheckboxManager.h │ │ ├── CheckboxManager.m │ │ ├── DevViewController.h │ │ ├── DevViewController.m │ │ ├── DragDropStatusItemView.h │ │ ├── DragDropStatusItemView.m │ │ ├── ExpoMenuBar-macOS-Bridging-Header.h │ │ ├── ExpoMenuBar-macOS.entitlements │ │ ├── ExpoMenuBar-macOSRelease.entitlements │ │ ├── ExpoMenuBar.entitlements │ │ ├── Info.plist │ │ ├── PopoverManager.swift │ │ ├── RCTImageView+Private.h │ │ ├── SwifterWrapper.swift │ │ ├── SystemIconViewManager.h │ │ ├── SystemIconViewManager.m │ │ ├── WindowManager.h │ │ ├── WindowManager.m │ │ ├── WindowNavigator.h │ │ ├── WindowNavigator.m │ │ ├── WindowWithDeallocCallback.h │ │ ├── WindowWithDeallocCallback.m │ │ └── main.m │ ├── ExpoMenuBar.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── ExpoMenuBar-macOS.xcscheme │ ├── ExpoMenuBar.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── ExportLocalOptions.plist │ ├── ExportUploadOptions.plist │ ├── Podfile │ ├── Podfile.lock │ ├── Podfile.properties.json │ ├── PrivacyInfo.xcprivacy │ └── scripts │ │ ├── archive_cli.sh │ │ └── build.sh │ ├── metro.config.js │ ├── modules │ ├── auto-updater │ │ ├── README.md │ │ ├── electron │ │ │ ├── Updater.ts │ │ │ ├── main.ts │ │ │ ├── platform │ │ │ │ ├── Linux.ts │ │ │ │ ├── Platform.ts │ │ │ │ ├── Windows.ts │ │ │ │ └── index.ts │ │ │ ├── screens │ │ │ │ ├── downloading-update │ │ │ │ │ └── index.html │ │ │ │ ├── global.css │ │ │ │ └── update-available │ │ │ │ │ ├── index.html │ │ │ │ │ └── preload.js │ │ │ └── utils │ │ │ │ ├── HttpClient.ts │ │ │ │ ├── Logger.ts │ │ │ │ ├── file.ts │ │ │ │ ├── meta.ts │ │ │ │ ├── options.ts │ │ │ │ └── quit.ts │ │ ├── expo-module.config.json │ │ ├── index.ts │ │ ├── ios │ │ │ ├── AutoUpdater.podspec │ │ │ └── AutoUpdaterModule.swift │ │ └── src │ │ │ ├── AutoUpdater.types.ts │ │ │ ├── AutoUpdaterModule.ts │ │ │ └── AutoUpdaterModule.web.ts │ ├── file-handler │ │ ├── electron │ │ │ ├── main.ts │ │ │ └── preload.ts │ │ ├── expo-module.config.json │ │ ├── index.ts │ │ ├── ios │ │ │ ├── FileHandler.podspec │ │ │ └── FileHandlerModule.swift │ │ └── src │ │ │ ├── FileHandler.types.ts │ │ │ ├── FileHandlerModule.ts │ │ │ ├── FileHandlerModule.web.ts │ │ │ └── useFileHandler.ts │ ├── file-picker │ │ ├── electron │ │ │ └── main.ts │ │ ├── expo-module.config.json │ │ ├── index.ts │ │ ├── ios │ │ │ ├── FilePicker.podspec │ │ │ └── FilePickerModule.swift │ │ └── src │ │ │ ├── FilePickerModule.ts │ │ │ ├── FilePickerModule.web.ts │ │ │ └── types.ts │ ├── menu-bar │ │ ├── electron │ │ │ ├── main.ts │ │ │ ├── preload.ts │ │ │ └── spawnCliAsync.ts │ │ ├── expo-module.config.json │ │ ├── index.ts │ │ ├── ios │ │ │ ├── MenuBar.podspec │ │ │ ├── MenuBarExceptions.swift │ │ │ └── MenuBarModule.swift │ │ └── src │ │ │ ├── MenuBarModule.ts │ │ │ ├── MenuBarModule.web.ts │ │ │ └── types.ts │ ├── progress-indicator │ │ ├── expo-module.config.json │ │ ├── index.ts │ │ ├── ios │ │ │ ├── ProgressIndicator.podspec │ │ │ ├── ProgressIndicatorModule.swift │ │ │ └── ProgressIndicatorView.swift │ │ └── src │ │ │ ├── ProgressIndicator.types.ts │ │ │ ├── ProgressIndicatorView.tsx │ │ │ └── ProgressIndicatorView.web.tsx │ ├── rudder │ │ ├── electron │ │ │ ├── main.ts │ │ │ └── preload.ts │ │ ├── expo-module.config.json │ │ ├── index.ts │ │ ├── ios │ │ │ ├── RNRudder.podspec │ │ │ └── RudderModule.swift │ │ └── src │ │ │ ├── Rudder.types.ts │ │ │ ├── RudderModule.ts │ │ │ └── RudderModule.web.ts │ └── web-authentication-session │ │ ├── electron │ │ └── main.ts │ │ ├── expo-module.config.json │ │ ├── index.ts │ │ ├── ios │ │ ├── WebAuthContextProvider.swift │ │ ├── WebAuthenticationSession.podspec │ │ └── WebAuthenticationSessionModule.swift │ │ └── src │ │ ├── WebAuthenticationSession.types.ts │ │ ├── WebAuthenticationSessionModule.ts │ │ └── WebAuthenticationSessionModule.web.ts │ ├── package.json │ ├── public │ ├── fonts │ │ └── Inter │ │ │ ├── Inter-Bold.otf │ │ │ ├── Inter-Medium.otf │ │ │ ├── Inter-Regular.otf │ │ │ └── Inter-SemiBold.otf │ ├── global.css │ └── index.html │ ├── src │ ├── App.tsx │ ├── analytics │ │ └── index.ts │ ├── api │ │ └── ApolloClient.tsx │ ├── assets │ │ ├── icons │ │ │ ├── AlertTriangle.tsx │ │ │ ├── cable-connector.svg │ │ │ ├── check-circle.svg │ │ │ ├── earth-02.svg │ │ │ ├── file-05.svg │ │ │ ├── folder.svg │ │ │ ├── iphone.svg │ │ │ ├── pin-with-outline.svg │ │ │ ├── project-background-icon.svg │ │ │ └── wifi.svg │ │ └── images │ │ │ ├── android-studio.png │ │ │ ├── android-studio@2x.png │ │ │ ├── android-studio@3x.png │ │ │ ├── onboarding │ │ │ ├── background.png │ │ │ ├── background@2x.png │ │ │ ├── background@3x.png │ │ │ ├── expo-orbit-text.svg │ │ │ └── logo.svg │ │ │ ├── xcode.png │ │ │ ├── xcode@2x.png │ │ │ └── xcode@3x.png │ ├── commands │ │ ├── bootDeviceAsync.ts │ │ ├── detectIOSAppTypeAsync'.ts │ │ ├── downloadBuildAsync.ts │ │ ├── installAndLaunchAppAsync.ts │ │ ├── launchExpoGoAsync.ts │ │ ├── launchUpdateAsync.ts │ │ ├── listDevicesAsync.ts │ │ └── setSessionAsync.ts │ ├── components │ │ ├── AutoResizerRootView │ │ │ ├── index.tsx │ │ │ └── index.web.tsx │ │ ├── Avatar.tsx │ │ ├── Button.tsx │ │ ├── Checkbox │ │ │ ├── NativeCheckbox.tsx │ │ │ ├── NativeCheckbox.web.tsx │ │ │ ├── index.tsx │ │ │ └── types.tsx │ │ ├── CommandCheckItem.tsx │ │ ├── DebugLogs │ │ │ ├── DebugLogRow.tsx │ │ │ ├── ObjectInspector.tsx │ │ │ └── index.tsx │ │ ├── DeviceItem.tsx │ │ ├── Image.tsx │ │ ├── NativeColorPalette.tsx │ │ ├── PathInput.tsx │ │ ├── ProjectIcon.tsx │ │ ├── Switch │ │ │ ├── index.tsx │ │ │ └── index.web.tsx │ │ ├── SystemIconView │ │ │ ├── index.tsx │ │ │ └── index.web.tsx │ │ ├── Text.tsx │ │ ├── View.tsx │ │ └── index.ts │ ├── generated │ │ ├── graphql.possibleTypes.json │ │ ├── graphql.tsx │ │ └── schema.graphql │ ├── graphql │ │ ├── apps.gql │ │ └── user.gql │ ├── hooks │ │ ├── useDeepLinking.ts │ │ ├── useDeviceAudioPreferences.ts │ │ ├── useGetPinnedApps.ts │ │ ├── usePopoverFocus.ts │ │ └── useSafeDisplayDimensions.ts │ ├── modules │ │ ├── Alert │ │ │ ├── index.ts │ │ │ └── index.web.ts │ │ ├── DeviceEventEmitter │ │ │ ├── index.ts │ │ │ └── index.web.ts │ │ ├── FilePickerModule.ts │ │ ├── Linking │ │ │ ├── index.ts │ │ │ └── index.web.ts │ │ ├── Logs.ts │ │ ├── MenuBarModule.ts │ │ ├── PlatformColor │ │ │ ├── index.ts │ │ │ └── index.web.ts │ │ ├── Storage.ts │ │ └── WindowManager │ │ │ ├── WindowProvider.tsx │ │ │ ├── index.ts │ │ │ ├── index.web.ts │ │ │ ├── types.ts │ │ │ └── useWindowFocus.ts │ ├── popover │ │ ├── BuildsSection.tsx │ │ ├── Core.tsx │ │ ├── DeviceListSectionHeader.tsx │ │ ├── DevicesListError.tsx │ │ ├── ErrorBoundary.tsx │ │ ├── Footer.tsx │ │ ├── Item.tsx │ │ ├── ProjectsSection.tsx │ │ ├── SectionHeader.tsx │ │ └── index.tsx │ ├── providers │ │ ├── DevicesProvider.tsx │ │ ├── FluentProvider │ │ │ ├── index.tsx │ │ │ └── index.web.tsx │ │ └── ThemeProvider.tsx │ ├── utils │ │ ├── __tests__ │ │ │ └── parseUrl.test.ts │ │ ├── constants.ts │ │ ├── create-component-primitive.tsx │ │ ├── device.ts │ │ ├── helpers.ts │ │ ├── parseUrl.ts │ │ ├── theme.ts │ │ └── useExpoTheme.tsx │ └── windows │ │ ├── DebugMenu.tsx │ │ ├── Onboarding.tsx │ │ ├── Settings.tsx │ │ └── index.ts │ └── tsconfig.json ├── eas.json ├── electron-updates.json ├── lerna.json ├── package.json ├── packages ├── common-types │ ├── .eslintrc.js │ ├── package.json │ ├── src │ │ ├── InternalError.ts │ │ ├── cli-commands │ │ │ ├── checkTools.ts │ │ │ ├── index.ts │ │ │ ├── listDevices.ts │ │ │ └── platform.ts │ │ ├── constants.ts │ │ ├── devices.ts │ │ ├── index.ts │ │ └── storage.ts │ └── tsconfig.json ├── eas-shared │ ├── .eslintrc.js │ ├── babel.config.js │ ├── package.json │ ├── src │ │ ├── api │ │ │ ├── APIV2.ts │ │ │ └── internal │ │ │ │ ├── Config.ts │ │ │ │ ├── ConnectionStatus.ts │ │ │ │ └── index.ts │ │ ├── download.ts │ │ ├── downloadApkAsync.ts │ │ ├── downloadAppAsync.ts │ │ ├── env.ts │ │ ├── fetch.ts │ │ ├── files.ts │ │ ├── index.ts │ │ ├── log.ts │ │ ├── manifest.ts │ │ ├── paths.ts │ │ ├── progress.ts │ │ ├── run │ │ │ ├── android │ │ │ │ ├── aapt.ts │ │ │ │ ├── adb.ts │ │ │ │ ├── emulator.ts │ │ │ │ ├── sdk.ts │ │ │ │ └── systemRequirements.ts │ │ │ ├── index.ts │ │ │ └── ios │ │ │ │ ├── CoreSimulator.ts │ │ │ │ ├── SimControl.ts │ │ │ │ ├── appleDevice │ │ │ │ ├── AppleDevice.ts │ │ │ │ ├── ClientManager.ts │ │ │ │ ├── Prerequisite.ts │ │ │ │ ├── XcodeDeveloperDiskImagePrerequisite.ts │ │ │ │ ├── client │ │ │ │ │ ├── AFCClient.ts │ │ │ │ │ ├── DebugserverClient.ts │ │ │ │ │ ├── InstallationProxyClient.ts │ │ │ │ │ ├── LockdowndClient.ts │ │ │ │ │ ├── MobileImageMounterClient.ts │ │ │ │ │ ├── ServiceClient.ts │ │ │ │ │ └── UsbmuxdClient.ts │ │ │ │ ├── installOnDeviceAsync.ts │ │ │ │ └── protocol │ │ │ │ │ ├── AFCProtocol.ts │ │ │ │ │ ├── AbstractProtocol.ts │ │ │ │ │ ├── GDBProtocol.ts │ │ │ │ │ ├── LockdownProtocol.ts │ │ │ │ │ └── UsbmuxProtocol.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── device.ts │ │ │ │ ├── devicectl.ts │ │ │ │ ├── inspectApp.ts │ │ │ │ ├── simctl.ts │ │ │ │ ├── simulator.ts │ │ │ │ ├── systemRequirements.ts │ │ │ │ ├── xcode.ts │ │ │ │ └── xcrun.ts │ │ ├── timer.ts │ │ ├── tools │ │ │ └── FsCache.ts │ │ ├── ts-declarations │ │ │ └── xcode │ │ │ │ └── index.d.ts │ │ ├── userSettings.ts │ │ ├── utils │ │ │ ├── delayAsync.ts │ │ │ ├── dir.ts │ │ │ ├── errors.ts │ │ │ ├── exit.ts │ │ │ ├── fn.ts │ │ │ ├── parseBinaryPlistAsync.ts │ │ │ └── promise.ts │ │ └── versions.ts │ ├── tsconfig.cjs.json │ └── tsconfig.json └── react-native-electron-modules │ ├── .eslintrc.js │ ├── package.json │ ├── src │ ├── exposeElectronModules.ts │ ├── index.ts │ ├── registerElectronModules.ts │ ├── requireElectronModule.ts │ └── types.ts │ └── tsconfig.json ├── releases.md ├── ts-declarations └── exec-async │ └── index.d.ts ├── tsconfig.base.json └── yarn.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /packages @gabrieldonadel 2 | /apps @gabrieldonadel 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41B Bug Report" 2 | description: "Report a reproducible bug in Expo Orbit" 3 | labels: ["needs review"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: Thanks for taking the time to file a bug report! If you are convinced that you have found a bug in Orbit, this is the right place. Please fill out this entire form. The most important piece is the Steps to reproduce. 8 | - type: markdown 9 | attributes: 10 | value: If you have a question about how to use Orbit, you can get help from the community on the [forums](https://forums.expo.dev/) or on [Discord](https://chat.expo.dev). 11 | - type: textarea 12 | attributes: 13 | label: Summary 14 | description: | 15 | Clearly describe what the expected behavior is and what instead is actually happening. Be concise and precise in your description. Include any affected platforms (iOS, Android). 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Steps to reproduce 21 | description: | 22 | Explain in a few steps how to reproduce the issue reported. 23 | validations: 24 | required: true 25 | - type: textarea 26 | attributes: 27 | label: Environment 28 | description: Run the `npx expo-env-info` command and paste its output in the field below. 29 | validations: 30 | required: true 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F64B Feature Request" 2 | description: "Suggest an idea for this project" 3 | labels: [] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: Thanks for helping us make Orbit even better! 😀 8 | - type: textarea 9 | attributes: 10 | label: Is your feature request related to a problem? Please describe. 11 | description: | 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | validations: 14 | required: false 15 | - type: textarea 16 | attributes: 17 | label: Describe the solution you'd like 18 | description: | 19 | A clear and concise description of what you want to happen. 20 | validations: 21 | required: false 22 | - type: textarea 23 | attributes: 24 | label: Additional context 25 | description: | 26 | Add any other context or screenshots about the feature request here. 27 | validations: 28 | required: false 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | # Why 2 | 3 | 6 | 7 | # How 8 | 9 | 12 | 13 | # Test Plan 14 | 15 | 18 | -------------------------------------------------------------------------------- /.github/resources/banner-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/.github/resources/banner-dark.png -------------------------------------------------------------------------------- /.github/resources/banner-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/.github/resources/banner-light.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.tsbuildinfo 2 | 3 | node_modules 4 | packages/*/node_modules 5 | apps/*/node_modules 6 | npm-debug.log 7 | yarn-error.log 8 | 9 | packages/*/build 10 | apps/*/build 11 | 12 | apps/menu-bar/cli/expo-orbit-cli 13 | apps/menu-bar/cli/orbit-cli* 14 | 15 | .idea 16 | 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | singleQuote: true, 5 | bracketSameLine: true, 6 | trailingComma: "es5", 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-present 650 Industries, Inc. (aka Expo) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/cli/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /apps/cli/codegen.ts: -------------------------------------------------------------------------------- 1 | import type { CodegenConfig } from '@graphql-codegen/cli'; 2 | import { Config } from 'common-types'; 3 | 4 | const config: CodegenConfig = { 5 | overwrite: true, 6 | schema: `${Config.api.origin}/graphql`, 7 | documents: './src/graphql/**/*.gql', 8 | generates: { 9 | './src/graphql/generated/graphql.ts': { 10 | plugins: ['typescript', 'typescript-operations', 'typescript-graphql-request'], 11 | }, 12 | }, 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /apps/cli/macos/entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.disable-library-validation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expo-orbit-cli", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "The command-line tool used internally by Expo Orbit menu bar", 6 | "license": "MIT", 7 | "files": [ 8 | "build" 9 | ], 10 | "scripts": { 11 | "cli": "node ./build/index.js", 12 | "start": "yarn run prepare && yarn run watch", 13 | "build": "tsc", 14 | "archive": "yarn build && pkg build/index.js --config pkg.config.json -o dist/orbit-cli", 15 | "clean": "rimraf build ./tsconfig.tsbuildinfo", 16 | "prepare": "yarn run clean && yarn run build", 17 | "watch": "tsc --watch --preserveWatchOutput", 18 | "codesign": "codesign --options=runtime --sign \"Developer ID Application: 650 Industries, Inc. (C8D8QTF339)\" --entitlements ./macos/entitlements.plist --force ./dist/orbit-cli-arm64 ./dist/orbit-cli-x64", 19 | "typecheck": "tsc", 20 | "gql": "graphql-codegen --config codegen.ts" 21 | }, 22 | "dependencies": { 23 | "commander": "^10.0.1", 24 | "common-types": "1.0.0", 25 | "eas-shared": "*", 26 | "graphql": "^16.8.0", 27 | "graphql-request": "^6.1.0", 28 | "snack-content": "2.0.0-preview.2", 29 | "strip-ansi": "^6.0.0" 30 | }, 31 | "devDependencies": { 32 | "@graphql-codegen/cli": "^5.0.0", 33 | "@graphql-codegen/typescript-graphql-request": "^6.1.0", 34 | "@graphql-codegen/typescript-operations": "^4.0.1", 35 | "pkg": "^5.8.1", 36 | "typescript": "^5.3.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /apps/cli/pkg.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "pkg": { 3 | "assets": ["../../node_modules/macos-export-certificate-and-key/**/*.node"], 4 | "targets": ["node18-macos-arm64", "node18-macos-x64"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/cli/src/api/GraphqlClient.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLClient } from 'graphql-request'; 2 | import { Config } from 'common-types'; 3 | 4 | import { getSdk } from '../graphql/generated/graphql'; 5 | import { getSessionSecret } from '../storage'; 6 | 7 | const endpoint = `${Config.api.origin}/graphql`; 8 | const sessionSecret = getSessionSecret(); 9 | 10 | const client = new GraphQLClient(endpoint, { 11 | headers: sessionSecret 12 | ? { 13 | 'expo-session': sessionSecret, 14 | } 15 | : undefined, 16 | }); 17 | 18 | export const graphqlSdk = getSdk(client); 19 | -------------------------------------------------------------------------------- /apps/cli/src/commands/BootDevice.ts: -------------------------------------------------------------------------------- 1 | import { AndroidEmulator, IosSimulator } from 'common-types/build/devices'; 2 | import { Emulator, Simulator } from 'eas-shared'; 3 | 4 | type BootDeviceAsyncOptions = { 5 | platform: 'android' | 'ios'; 6 | id: string; 7 | noAudio?: boolean; 8 | }; 9 | 10 | export async function bootDeviceAsync({ platform, id, noAudio }: BootDeviceAsyncOptions) { 11 | if (platform === 'ios') { 12 | try { 13 | await Simulator.ensureSimulatorBootedAsync({ 14 | udid: id, 15 | } as IosSimulator); 16 | } catch (error) { 17 | if ( 18 | error instanceof Error && 19 | error.message.includes('Unable to boot device in current state: Booted') 20 | ) { 21 | return; 22 | } 23 | 24 | throw error; 25 | } 26 | 27 | return await Simulator.ensureSimulatorAppOpenedAsync(id); 28 | } 29 | 30 | const runningEmulators = await Emulator.getRunningDevicesAsync(); 31 | if (runningEmulators.some(({ name }) => name === id)) { 32 | return; 33 | } 34 | 35 | await Emulator.bootEmulatorAsync( 36 | { 37 | name: id, 38 | } as AndroidEmulator, 39 | { noAudio } 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /apps/cli/src/commands/DetectIOSAppType.ts: -------------------------------------------------------------------------------- 1 | import { extractAppFromLocalArchiveAsync, detectIOSAppType } from 'eas-shared'; 2 | 3 | export async function detectIOSAppTypeAsync(appPath: string) { 4 | if (!appPath.endsWith('.app') && !appPath.endsWith('.ipa')) { 5 | appPath = await extractAppFromLocalArchiveAsync(appPath); 6 | } 7 | 8 | const appType = await detectIOSAppType(appPath); 9 | 10 | return appType; 11 | } 12 | -------------------------------------------------------------------------------- /apps/cli/src/commands/DownloadBuild.ts: -------------------------------------------------------------------------------- 1 | import { downloadAndMaybeExtractAppAsync } from 'eas-shared'; 2 | 3 | export async function downloadBuildAsync(buildURL: string) { 4 | const buildPath = await downloadAndMaybeExtractAppAsync(buildURL); 5 | return buildPath; 6 | } 7 | -------------------------------------------------------------------------------- /apps/cli/src/commands/SetSession.ts: -------------------------------------------------------------------------------- 1 | import { setSessionSecret } from '../storage'; 2 | 3 | export async function setSessionAsync(sessionSecret: string) { 4 | await setSessionSecret(sessionSecret); 5 | } 6 | -------------------------------------------------------------------------------- /apps/cli/src/graphql/builds.gql: -------------------------------------------------------------------------------- 1 | query getAppBuildForUpdate( 2 | $appId: String! 3 | $platform: AppPlatform! 4 | $distribution: DistributionType! 5 | $runtimeVersion: String 6 | ) { 7 | app { 8 | byId(appId: $appId) { 9 | id 10 | name 11 | buildsPaginated( 12 | first: 1 13 | filter: { 14 | platforms: [$platform] 15 | distributions: [$distribution] 16 | developmentClient: true 17 | runtimeVersion: $runtimeVersion 18 | } 19 | ) { 20 | edges { 21 | node { 22 | __typename 23 | id 24 | ... on Build { 25 | appIdentifier 26 | runtimeVersion 27 | expirationDate 28 | artifacts { 29 | buildUrl 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | query getAppHasDevClientBuilds($appId: String!) { 40 | app { 41 | byId(appId: $appId) { 42 | id 43 | name 44 | hasDevClientBuilds: buildsPaginated(first: 1, filter: { developmentClient: true }) { 45 | edges { 46 | node { 47 | __typename 48 | id 49 | ... on Build { 50 | appIdentifier 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /apps/cli/src/storage.ts: -------------------------------------------------------------------------------- 1 | import { StorageUtils } from 'common-types'; 2 | import os from 'os'; 3 | import JsonFile from '@expo/json-file'; 4 | 5 | export function getSessionSecret(): string | undefined { 6 | const userSettings = userSettingsJsonFile().read(); 7 | return userSettings.sessionSecret; 8 | } 9 | 10 | export async function setSessionSecret(sessionSecret: string): Promise { 11 | await userSettingsJsonFile().setAsync('sessionSecret', sessionSecret); 12 | } 13 | 14 | type UserData = { 15 | sessionSecret?: string; 16 | }; 17 | function userSettingsJsonFile(): JsonFile { 18 | return new JsonFile(StorageUtils.userSettingsFile(os.homedir()), { 19 | jsonParseErrorDefault: {}, 20 | cantReadFileDefault: {}, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /apps/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "outDir": "build", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"], 8 | "exclude": ["**/__mocks__/*", "**/__tests__/*"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/menu-bar/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /apps/menu-bar/.eas/build/build-preview.yml: -------------------------------------------------------------------------------- 1 | build: 2 | name: Build Orbit preview 3 | steps: 4 | - eas/checkout 5 | - configure_macos_credentials: 6 | inputs: 7 | buildCredentials: ${ eas.job.secrets.buildCredentials } 8 | - eas/install_node_modules 9 | - run: 10 | name: Build packages 11 | working_directory: ../../ 12 | command: | 13 | yarn build 14 | - run: 15 | name: Archive CLI 16 | working_directory: ../../apps/cli 17 | command: | 18 | yarn archive 19 | - run: 20 | name: Codesign CLI 21 | working_directory: ../../apps/cli 22 | command: | 23 | yarn codesign 24 | - run: 25 | name: Install menu-bar pods 26 | working_directory: ./macos 27 | command: pod install 28 | - run: 29 | name: Archive menu-bar 30 | working_directory: ../../apps/menu-bar 31 | command: | 32 | yarn archive 33 | - run: 34 | name: Export archive menu-bar 35 | working_directory: ../../apps/menu-bar 36 | command: | 37 | yarn export-local-archive 38 | - eas/find_and_upload_build_artifacts 39 | functions: 40 | configure_macos_credentials: 41 | name: Configure macOS Credentials 42 | inputs: 43 | - name: buildCredentials 44 | type: json 45 | path: ./configureMacOSCredentials 46 | -------------------------------------------------------------------------------- /apps/menu-bar/.eas/build/build-release.yml: -------------------------------------------------------------------------------- 1 | build: 2 | name: Build and notarize Orbit 3 | steps: 4 | - eas/checkout 5 | - configure_macos_credentials: 6 | inputs: 7 | buildCredentials: ${ eas.job.secrets.buildCredentials } 8 | - eas/install_node_modules 9 | - run: 10 | name: Build packages 11 | working_directory: ../../ 12 | command: | 13 | yarn build 14 | - run: 15 | name: Archive CLI 16 | working_directory: ../../apps/cli 17 | command: | 18 | yarn archive 19 | - run: 20 | name: Codesign CLI 21 | working_directory: ../../apps/cli 22 | command: | 23 | yarn codesign 24 | - run: 25 | name: Install menu-bar pods 26 | working_directory: ./macos 27 | command: pod install 28 | - run: 29 | name: Archive menu-bar 30 | working_directory: ../../apps/menu-bar 31 | command: | 32 | yarn archive 33 | - run: 34 | name: Export archive menu-bar 35 | working_directory: ../../apps/menu-bar 36 | command: | 37 | yarn export-upload-archive 38 | - run: 39 | name: Notarize menu-bar 40 | working_directory: ../../apps/menu-bar 41 | command: | 42 | yarn notarize 43 | - eas/find_and_upload_build_artifacts 44 | functions: 45 | configure_macos_credentials: 46 | name: Configure macOS Credentials 47 | inputs: 48 | - name: buildCredentials 49 | type: json 50 | path: ./configureMacOSCredentials 51 | -------------------------------------------------------------------------------- /apps/menu-bar/.eas/build/configureMacOSCredentials/eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 5.4.0" 4 | }, 5 | "build": { 6 | "development": { 7 | "developmentClient": true, 8 | "distribution": "internal" 9 | }, 10 | "preview": { 11 | "distribution": "internal" 12 | }, 13 | "production": {} 14 | }, 15 | "submit": { 16 | "production": {} 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/menu-bar/.eas/build/configureMacOSCredentials/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "configure-macos-credentials", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "./build/index.js", 6 | "type": "commonjs", 7 | "scripts": { 8 | "build": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register", 9 | "watch": "ncc build ./src/index.ts -o build/ --minify --no-cache --no-source-map-register --watch" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "devDependencies": { 14 | "@types/node": "^18.11.18", 15 | "typescript": "^5.1.6", 16 | "@expo/steps": "^1.0.34" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/menu-bar/.eas/build/configureMacOSCredentials/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "inlineSources": true, 7 | "strict": true, 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "esModuleInterop": true, 13 | "skipLibCheck": true, 14 | "noEmit": false, 15 | "forceConsistentCasingInFileNames": true, 16 | "outDir": "build", 17 | "resolveJsonModule": true, 18 | "allowSyntheticDefaultImports": true, 19 | "moduleResolution": "node", 20 | "rootDir": "src", 21 | "declaration": false, 22 | "composite": false 23 | }, 24 | "include": ["src/**/*.ts"], 25 | "exclude": ["**/__mocks__/*", "**/__tests__/*"] 26 | } 27 | -------------------------------------------------------------------------------- /apps/menu-bar/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: 'universe/native', 4 | overrides: [ 5 | { 6 | extends: 'universe/node', 7 | files: ['metro.config.js'], 8 | }, 9 | ], 10 | rules: { 11 | 'react-hooks/exhaustive-deps': 'error', 12 | }, 13 | ignorePatterns: [ 14 | 'electron/.vite/**', 15 | 'electron/dist/**', 16 | 'electron/node_modules/**', 17 | 'electron/out/**', 18 | 'src/generated/**', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /apps/menu-bar/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | ./build/ 4 | macos/build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | ios/.xcode.env.local 21 | macos/.xcode.env.local 22 | 23 | # Android/IntelliJ 24 | # 25 | .idea 26 | .gradle 27 | local.properties 28 | *.iml 29 | *.hprof 30 | .cxx/ 31 | *.keystore 32 | !debug.keystore 33 | 34 | # node.js 35 | # 36 | node_modules/ 37 | npm-debug.log 38 | yarn-error.log 39 | 40 | # fastlane 41 | # 42 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 43 | # screenshots whenever they are needed. 44 | # For more information about the recommended setup visit: 45 | # https://docs.fastlane.tools/best-practices/source-control/ 46 | 47 | **/fastlane/report.xml 48 | **/fastlane/Preview.html 49 | **/fastlane/screenshots 50 | **/fastlane/test_output 51 | 52 | # Bundle artifact 53 | *.jsbundle 54 | 55 | # Ruby / CocoaPods 56 | /ios/Pods/ 57 | /vendor/bundle/ 58 | 59 | # Temporary files created by Metro to check the health of the file watcher 60 | .metro-health-check* 61 | 62 | !.eas/build/ 63 | .expo/ 64 | -------------------------------------------------------------------------------- /apps/menu-bar/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /apps/menu-bar/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby '>= 2.6.10' 5 | 6 | gem 'cocoapods', '>= 1.14' 7 | -------------------------------------------------------------------------------- /apps/menu-bar/README.md: -------------------------------------------------------------------------------- 1 | # Expo Orbit Menu bar 2 | 3 | ## Installation instructions 4 | 5 | - Download the latest release from [menu-bar/releases](https://github.com/expo/eas-menu-bar/releases?q=expo-menu-bar) 6 | - Unzip the file and drag Expo Orbit to the Applications folder. 7 | 8 | ## How to run locally 9 | 10 | At the root of the repo run: 11 | 12 | ```bash 13 | yarn 14 | ``` 15 | 16 | Then inside `apps/cli` run the following command to generate the standalone executable used by the `menu-bar`: 17 | 18 | ```bash 19 | yarn archive 20 | ``` 21 | 22 | Inside `apps/menu-bar` run the following command to update the local cli file: 23 | 24 | ```bash 25 | yarn update-cli 26 | ``` 27 | 28 | Finally, run the following command to start the app: 29 | 30 | ```bash 31 | yarn macos 32 | ``` 33 | -------------------------------------------------------------------------------- /apps/menu-bar/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExpoMenuBar", 3 | "expo": { 4 | "name": "Orbit", 5 | "slug": "orbit", 6 | "description": "Accelerate your development workflow with one-click build launches and simulator management", 7 | "owner": "expo", 8 | "extra": { 9 | "eas": { 10 | "projectId": "6f773599-3109-410f-97c0-fd197cf47995" 11 | } 12 | }, 13 | "ios": { 14 | "bundleIdentifier": "dev.expo.orbit" 15 | }, 16 | "web": { 17 | "bundler": "metro" 18 | }, 19 | "experiments": { 20 | "baseUrl": "./" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/menu-bar/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['babel-preset-expo'], 3 | }; 4 | -------------------------------------------------------------------------------- /apps/menu-bar/codegen.ts: -------------------------------------------------------------------------------- 1 | import type { CodegenConfig } from '@graphql-codegen/cli'; 2 | import { Config } from 'common-types'; 3 | 4 | const config: CodegenConfig = { 5 | overwrite: true, 6 | schema: `${Config.api.origin}/graphql`, 7 | documents: './src/graphql/**/*.gql', 8 | generates: { 9 | './src/generated/graphql.tsx': { 10 | plugins: ['typescript', 'typescript-operations', 'typescript-react-apollo'], 11 | config: { 12 | skipTypename: false, 13 | withHooks: true, 14 | withHOC: false, 15 | withComponent: false, 16 | }, 17 | }, 18 | './src/generated/schema.graphql': { 19 | plugins: ['schema-ast'], 20 | }, 21 | './src/generated/graphql.possibleTypes.json': { 22 | plugins: ['fragment-matcher'], 23 | }, 24 | }, 25 | }; 26 | 27 | export default config; 28 | -------------------------------------------------------------------------------- /apps/menu-bar/eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 3.18.3", 4 | "promptToConfigurePushNotifications": false 5 | }, 6 | "build": { 7 | "preview": { 8 | "config": "build-preview.yml", 9 | "credentialsSource": "remote", 10 | "ios": { 11 | "image": "latest", 12 | "applicationArchivePath": "build/Release/*.app" 13 | } 14 | }, 15 | "production": { 16 | "config": "build-release.yml", 17 | "credentialsSource": "remote", 18 | "ios": { 19 | "image": "latest", 20 | "applicationArchivePath": "build/Notarized/*.app" 21 | } 22 | } 23 | }, 24 | "submit": { 25 | "production": {} 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "node", 5 | "request": "launch", 6 | "name": "Electron Main", 7 | "runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.sh", 8 | "windows": { 9 | "runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.cmd" 10 | }, 11 | // runtimeArgs will be passed directly to your Electron application 12 | "runtimeArgs": [], 13 | "cwd": "${workspaceFolder}", 14 | "console": "integratedTerminal" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/assets/images/icon-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/electron/assets/images/icon-linux.png -------------------------------------------------------------------------------- /apps/menu-bar/electron/assets/images/icon-windows.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/electron/assets/images/icon-windows.ico -------------------------------------------------------------------------------- /apps/menu-bar/electron/assets/images/tray/icon-dark.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/electron/assets/images/tray/icon-dark.ico -------------------------------------------------------------------------------- /apps/menu-bar/electron/assets/images/tray/icon-light.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/electron/assets/images/tray/icon-light.ico -------------------------------------------------------------------------------- /apps/menu-bar/electron/assets/images/tray/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/electron/assets/images/tray/icon.png -------------------------------------------------------------------------------- /apps/menu-bar/electron/assets/images/tray/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/electron/assets/images/tray/icon@2x.png -------------------------------------------------------------------------------- /apps/menu-bar/electron/modules/Alert/main.ts: -------------------------------------------------------------------------------- 1 | import { dialog } from 'electron'; 2 | import type { AlertButton } from 'react-native'; 3 | 4 | const AlertModule: { 5 | name: string; 6 | alert: (title: string, message?: string, buttons?: AlertButton[]) => void; 7 | } = { 8 | name: 'Alert', 9 | alert(title: string, message?: string, buttons?: AlertButton[]) { 10 | dialog.showMessageBox({ 11 | type: 'info', 12 | message: title, 13 | detail: message ?? '', 14 | buttons: buttons?.map((button) => button.text ?? '') ?? ['OK'], 15 | }); 16 | }, 17 | }; 18 | 19 | export default AlertModule; 20 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/modules/AutoResizerRootViewManager/main.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, screen } from 'electron'; 2 | 3 | async function setPopoverSize(width: number, height: number, event: Electron.IpcMainInvokeEvent) { 4 | for (const window of BrowserWindow.getAllWindows()) { 5 | if (event.sender === window.webContents) { 6 | const [oldX, oldY] = window.getPosition(); 7 | const [oldWidth, oldHeight] = window.getSize(); 8 | 9 | const display = screen.getDisplayNearestPoint({ x: oldX, y: oldY }); 10 | 11 | window.setBounds( 12 | { 13 | width, 14 | height, 15 | x: oldX + oldWidth - width, 16 | y: display.size.height / 2 > oldY ? oldY : oldY + oldHeight - height, 17 | }, 18 | true 19 | ); 20 | } 21 | } 22 | } 23 | 24 | const AutoResizerRootViewManager: { 25 | name: string; 26 | setPopoverSize: (width: number, height: number, event: any) => void; 27 | } = { 28 | name: 'AutoResizerRootViewManager', 29 | setPopoverSize, 30 | }; 31 | 32 | export default AutoResizerRootViewManager; 33 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/modules/DeviceEventEmitter/preload.ts: -------------------------------------------------------------------------------- 1 | import { IpcRendererEvent, ipcRenderer } from 'electron'; 2 | import type { DeviceEventEmitterStatic, EmitterSubscription } from 'react-native'; 3 | 4 | const DeviceEventEmitter: { 5 | name: string; 6 | addListener: DeviceEventEmitterStatic['addListener']; 7 | } = { 8 | name: 'DeviceEventEmitter', 9 | addListener: (event: string, callback: (...args: string[]) => void, context) => { 10 | const listener = (event: IpcRendererEvent, ...args: any[]) => { 11 | callback(...args); 12 | }; 13 | ipcRenderer.on(event, listener); 14 | 15 | return { 16 | remove: () => { 17 | ipcRenderer.removeListener(event, listener); 18 | }, 19 | } as EmitterSubscription; 20 | }, 21 | }; 22 | 23 | export default DeviceEventEmitter; 24 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/modules/Linking/main.ts: -------------------------------------------------------------------------------- 1 | import { shell, app, BrowserWindow, WebContents, ipcMain } from 'electron'; 2 | import { LinkingImpl } from 'react-native'; 3 | 4 | const openURLTargets = new WeakSet(); 5 | 6 | function sendOpenURL(url: string) { 7 | for (const window of BrowserWindow.getAllWindows()) { 8 | if (openURLTargets.has(window.webContents)) { 9 | window.webContents.send('open-url', url); 10 | } 11 | } 12 | } 13 | 14 | ipcMain.handle('register-open-url-target', (event) => { 15 | openURLTargets.add(event.sender); 16 | }); 17 | ipcMain.handle('unregister-open-url-target', (event) => { 18 | openURLTargets.delete(event.sender); 19 | }); 20 | 21 | app.on('open-url', (_, url) => { 22 | sendOpenURL(url); 23 | }); 24 | 25 | app.on('second-instance', (_, argv) => { 26 | const lastArg = argv[argv.length - 1]; 27 | if (typeof lastArg === 'string' && lastArg.includes('://')) { 28 | sendOpenURL(lastArg); 29 | } 30 | }); 31 | 32 | async function getInitialURL() { 33 | const lastArg = process.argv[process.argv.length - 1]; 34 | if (typeof lastArg === 'string' && lastArg.includes('://')) { 35 | return lastArg; 36 | } 37 | 38 | return undefined; 39 | } 40 | 41 | async function openURL(url: string) { 42 | await shell.openExternal(url); 43 | } 44 | 45 | const Linking: Partial & { name: string } = { 46 | name: 'Linking', 47 | openURL, 48 | getInitialURL, 49 | }; 50 | 51 | export default Linking; 52 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/modules/Linking/preload.ts: -------------------------------------------------------------------------------- 1 | import { ipcRenderer } from 'electron'; 2 | import { EmitterSubscription, LinkingImpl } from 'react-native'; 3 | 4 | const eventHandlers = new Map<(event: { url: string }) => void, (event: { url: string }) => void>(); 5 | 6 | ipcRenderer.addListener('open-url', (_, url) => { 7 | for (const handler of eventHandlers.values()) { 8 | handler({ url }); 9 | } 10 | }); 11 | 12 | const addEventListener = (_: 'url', handler: (event: { url: string }) => void) => { 13 | if (eventHandlers.size === 0) { 14 | ipcRenderer.invoke('register-open-url-target'); 15 | } 16 | eventHandlers.set(handler, handler); 17 | 18 | return { 19 | remove: () => { 20 | eventHandlers.delete(handler); 21 | if (eventHandlers.size === 0) { 22 | ipcRenderer.invoke('unregister-open-url-target'); 23 | } 24 | }, 25 | } as EmitterSubscription; 26 | }; 27 | 28 | // Apply same behavior as react-native-web 29 | const canOpenURL = (): Promise => Promise.resolve(true); 30 | 31 | const Linking: Partial & { name: string } = { 32 | name: 'Linking', 33 | canOpenURL, 34 | addEventListener, 35 | }; 36 | 37 | export default Linking; 38 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/modules/mainRegistry.ts: -------------------------------------------------------------------------------- 1 | import { Registry } from 'react-native-electron-modules'; 2 | 3 | import AlertModule from './Alert/main'; 4 | import AutoResizerRootViewManager from './AutoResizerRootViewManager/main'; 5 | import Linking from './Linking/main'; 6 | import WindowManager from './WindowManager/main'; 7 | import AutoUpdater from '../../modules/auto-updater/electron/main'; 8 | import FileHandler from '../../modules/file-handler/electron/main'; 9 | import FilePickerModule from '../../modules/file-picker/electron/main'; 10 | import MenuBarModule from '../../modules/menu-bar/electron/main'; 11 | import RudderModule from '../../modules/rudder/electron/main'; 12 | import WebAuthenticationSession from '../../modules/web-authentication-session/electron/main'; 13 | 14 | export const MainModules: Registry = [ 15 | MenuBarModule, 16 | Linking, 17 | AutoResizerRootViewManager, 18 | WindowManager, 19 | RudderModule, 20 | FilePickerModule, 21 | AlertModule, 22 | WebAuthenticationSession, 23 | AutoUpdater, 24 | FileHandler, 25 | ]; 26 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/modules/preloadRegistry.ts: -------------------------------------------------------------------------------- 1 | import { Registry } from 'react-native-electron-modules'; 2 | 3 | import DeviceEventEmitter from './DeviceEventEmitter/preload'; 4 | import Linking from './Linking/preload'; 5 | import FileHandlerModule from '../../modules/file-handler/electron/preload'; 6 | import MenuBarModule from '../../modules/menu-bar/electron/preload'; 7 | import RudderModule from '../../modules/rudder/electron/preload'; 8 | 9 | export const PreloadModules: Registry = [ 10 | MenuBarModule, 11 | DeviceEventEmitter, 12 | Linking, 13 | RudderModule, 14 | FileHandlerModule, 15 | ]; 16 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/src/preload.ts: -------------------------------------------------------------------------------- 1 | import { exposeElectronModules } from 'react-native-electron-modules'; 2 | 3 | import { PreloadModules } from '../modules/preloadRegistry'; 4 | 5 | exposeElectronModules(PreloadModules); 6 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/src/types.d.ts: -------------------------------------------------------------------------------- 1 | // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite 2 | // plugin that tells the Electron app where to look for the Vite-bundled app code (depending on 3 | // whether you're running in development or production). 4 | declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string; 5 | declare const MAIN_WINDOW_VITE_NAME: string; 6 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "commonjs", 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "noImplicitAny": true, 9 | "sourceMap": true, 10 | "baseUrl": ".", 11 | "outDir": "dist", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true 14 | }, 15 | "include": ["src", "../modules/*/electron"] 16 | } 17 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/vite.cli.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // https://vitejs.dev/config 4 | export default defineConfig({ 5 | resolve: { 6 | mainFields: ['module', 'jsnext:main', 'jsnext'], 7 | }, 8 | build: { 9 | outDir: './.vite/build/cli', 10 | commonjsOptions: { 11 | include: [/common-types/, /eas-shared/, /node_modules/], 12 | }, 13 | rollupOptions: { 14 | output: { 15 | inlineDynamicImports: true, 16 | manualChunks: undefined, 17 | }, 18 | }, 19 | }, 20 | optimizeDeps: { 21 | include: ['common-types', 'eas-shared'], 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/vite.main.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig, normalizePath } from 'vite'; 2 | import { viteStaticCopy } from 'vite-plugin-static-copy'; 3 | import path from 'node:path'; 4 | 5 | // normalizePath(path.resolve(__dirname, './foo')); // C:/project/foo 6 | 7 | // https://vitejs.dev/config 8 | export default defineConfig({ 9 | resolve: { 10 | mainFields: ['module', 'jsnext:main', 'jsnext'], 11 | }, 12 | optimizeDeps: { 13 | include: ['common-types'], 14 | }, 15 | build: { 16 | commonjsOptions: { 17 | include: [/common-types/, /node_modules/], 18 | }, 19 | }, 20 | plugins: [ 21 | viteStaticCopy({ 22 | targets: [ 23 | { 24 | src: '../modules/auto-updater/electron/**/*.(html|css|js)', 25 | dest: 'react-native-electron-modules', 26 | }, 27 | ], 28 | structured: true, 29 | }), 30 | viteStaticCopy({ 31 | targets: [ 32 | { 33 | src: './dist/**/*', 34 | dest: 'renderer/', 35 | }, 36 | ], 37 | structured: true, 38 | }), 39 | ], 40 | }); 41 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/vite.preload.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // https://vitejs.dev/config 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /apps/menu-bar/electron/vite.renderer.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // https://vitejs.dev/config 4 | export default defineConfig({ 5 | root: './dist', 6 | base: './dist', 7 | }); 8 | -------------------------------------------------------------------------------- /apps/menu-bar/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.jpg'; 2 | declare module '*.png'; 3 | declare module '*.svg' { 4 | import React from 'react'; 5 | import { SvgProps } from 'react-native-svg'; 6 | const content: React.FC; 7 | export default content; 8 | } 9 | -------------------------------------------------------------------------------- /apps/menu-bar/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import { registerRootComponent } from 'expo'; 6 | import 'react-native-url-polyfill/auto'; 7 | 8 | import App from './src/App'; 9 | import './src/windows'; 10 | 11 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 12 | // It also ensures that whether you load the app in the Expo client or in a native build, 13 | // the environment is set up appropriately 14 | registerRootComponent(App); 15 | -------------------------------------------------------------------------------- /apps/menu-bar/index.web.js: -------------------------------------------------------------------------------- 1 | import { registerRootComponent } from 'expo'; 2 | import { AppRegistry } from 'react-native'; 3 | 4 | import App from './src/App'; 5 | import './src/windows'; 6 | 7 | const params = new URL(document.location).searchParams; 8 | const module = params.get('moduleName'); 9 | 10 | if (module) { 11 | const rootTag = document.getElementById('root'); 12 | AppRegistry.runApplication(module, { 13 | rootTag, 14 | }); 15 | } else { 16 | registerRootComponent(App); 17 | } 18 | -------------------------------------------------------------------------------- /apps/menu-bar/ios/ExpoMenuBar.xcodeproj: -------------------------------------------------------------------------------- 1 | /Users/gabriel/Workspace/expo/ExpoMenuBar/apps/menu-bar/macos/ExpoMenuBar.xcodeproj -------------------------------------------------------------------------------- /apps/menu-bar/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # CocoaPods 2 | Pods/ 3 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/AutoLauncher/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AutoLauncher 4 | // 5 | // Created by Gabriel Donadel on 10/07/23. 6 | // 7 | 8 | import Cocoa 9 | 10 | class AutoLauncherAppDelegate: NSObject, NSApplicationDelegate { 11 | struct Constants { 12 | static let menuBarBundleID = "dev.expo.orbit" 13 | } 14 | 15 | func applicationDidFinishLaunching(_ aNotification: Notification) { 16 | let runningApps = NSWorkspace.shared.runningApplications 17 | let isRunning = runningApps.contains { 18 | $0.bundleIdentifier == Constants.menuBarBundleID 19 | } 20 | 21 | if !isRunning, let mainAppURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: Constants.menuBarBundleID) { 22 | NSWorkspace.shared.openApplication(at: mainAppURL, configuration: NSWorkspace.OpenConfiguration()) { (_, error) in 23 | if let error = error { 24 | print("Error opening Expo Orbit: \(error)") 25 | } 26 | 27 | DispatchQueue.main.async { 28 | NSApp.terminate(nil) 29 | } 30 | } 31 | } else { 32 | NSApp.terminate(nil) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/AutoLauncher/AutoLauncher.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/AutoLauncher/AutoLauncherRelease.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/AutoLauncher/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // AutoLauncher 4 | // 5 | // Created by Gabriel Donadel on 10/07/23. 6 | // 7 | 8 | import Cocoa 9 | 10 | let delegate = AutoLauncherAppDelegate() 11 | NSApplication.shared.delegate = delegate 12 | _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) 13 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import "Expo_Orbit-Swift.h" 5 | 6 | @interface AppDelegate : RCTAppDelegate 7 | { 8 | SwifterWrapper *httpServer; 9 | PopoverManager *popoverManager; 10 | } 11 | 12 | #if RCT_DEV 13 | @property (nonatomic, strong) NSWindowController *devWindowController; 14 | #endif 15 | - (IBAction)showHelp:(id)sender; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "mac-16.png", 5 | "idiom": "mac", 6 | "scale": "1x", 7 | "size": "16x16" 8 | }, 9 | { 10 | "filename": "mac-32.png", 11 | "idiom": "mac", 12 | "scale": "2x", 13 | "size": "16x16" 14 | }, 15 | { 16 | "filename": "mac-32.png", 17 | "idiom": "mac", 18 | "scale": "1x", 19 | "size": "32x32" 20 | }, 21 | { 22 | "filename": "mac-64.png", 23 | "idiom": "mac", 24 | "scale": "2x", 25 | "size": "32x32" 26 | }, 27 | { 28 | "filename": "mac-128.png", 29 | "idiom": "mac", 30 | "scale": "1x", 31 | "size": "128x128" 32 | }, 33 | { 34 | "filename": "mac-256.png", 35 | "idiom": "mac", 36 | "scale": "2x", 37 | "size": "128x128" 38 | }, 39 | { 40 | "filename": "mac-256.png", 41 | "idiom": "mac", 42 | "scale": "1x", 43 | "size": "256x256" 44 | }, 45 | { 46 | "filename": "mac-512.png", 47 | "idiom": "mac", 48 | "scale": "2x", 49 | "size": "256x256" 50 | }, 51 | { 52 | "filename": "mac-512.png", 53 | "idiom": "mac", 54 | "scale": "1x", 55 | "size": "512x512" 56 | }, 57 | { 58 | "filename": "mac-1024.png", 59 | "idiom": "mac", 60 | "scale": "2x", 61 | "size": "512x512" 62 | } 63 | ], 64 | "info": { 65 | "author": "xcode", 66 | "version": 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-1024.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-128.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-16.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-256.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-32.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-512.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIcon.appiconset/mac-64.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/1024-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/1024-mac.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/128-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/128-mac.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/16-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/16-mac.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/256-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/256-mac.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/32-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/32-mac.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/512-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/512-mac.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/64-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/AppIconDebug.appiconset/64-mac.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/menu-bar-icon-debug.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "menu-bar-icon-debug.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/menu-bar-icon-debug.imageset/menu-bar-icon-debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/menu-bar-icon-debug.imageset/menu-bar-icon-debug.png -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Assets.xcassets/menu-bar-icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "menu-bar-icon.pdf", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/AutoResizerRootView.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AutoResizerRootView : RCTView 4 | 5 | @property(nonatomic, assign) BOOL enabled; 6 | @property (nonatomic, assign) CGFloat maxRelativeHeight; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/AutoResizerRootView.m: -------------------------------------------------------------------------------- 1 | #import "AutoResizerRootView.h" 2 | #import "Expo_Orbit-Swift.h" 3 | 4 | const CGFloat minimumViewSize = 40.0; 5 | 6 | @implementation AutoResizerRootView 7 | 8 | 9 | - (void)setEnabled:(BOOL)enabled 10 | { 11 | if (_enabled != enabled) { 12 | _enabled = enabled; 13 | } 14 | } 15 | 16 | 17 | - (void)layout 18 | { 19 | if(!_enabled || (self.frame.size.height <= minimumViewSize || self.frame.size.width <= minimumViewSize)){ 20 | return; 21 | } 22 | 23 | CGFloat frameHeight = self.frame.size.height; 24 | 25 | NSRect mainScreenFrame = [[NSScreen mainScreen] frame]; 26 | CGFloat screenHeight = NSHeight(mainScreenFrame); 27 | CGFloat maxHeight = screenHeight * _maxRelativeHeight; 28 | 29 | CGFloat newHeight = frameHeight <= maxHeight ? frameHeight : maxHeight; 30 | 31 | dispatch_async(dispatch_get_main_queue(), ^{ 32 | [PopoverManager.shared setPopoverContentSize:CGSizeMake(self.frame.size.width, newHeight)]; 33 | }); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/AutoResizerRootViewManager.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AutoResizerRootViewManager : RCTViewManager 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/AutoResizerRootViewManager.m: -------------------------------------------------------------------------------- 1 | #import "AutoResizerRootViewManager.h" 2 | #import "AutoResizerRootView.h" 3 | 4 | #import 5 | 6 | 7 | @implementation AutoResizerRootViewManager 8 | 9 | RCT_EXPORT_MODULE() 10 | 11 | RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL) 12 | RCT_EXPORT_VIEW_PROPERTY(maxRelativeHeight, CGFloat) 13 | 14 | - (NSView *)view 15 | { 16 | return [[AutoResizerRootView alloc] init]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Checkbox.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface Checkbox : NSButton 5 | 6 | @property(nonatomic, assign) NSControlStateValue wasOn; 7 | @property(nonatomic, copy) RCTBubblingEventBlock onChange; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/Checkbox.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "Checkbox.h" 3 | 4 | 5 | @implementation Checkbox 6 | 7 | - (void)setState:(NSControlStateValue)state 8 | { 9 | _wasOn = state; 10 | [super setState:state]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/CheckboxManager.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface CheckboxManager : RCTViewManager 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/DevViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface DevViewController : NSViewController 4 | 5 | @end 6 | 7 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/DevViewController.m: -------------------------------------------------------------------------------- 1 | #import "DevViewController.h" 2 | #import "AppDelegate.h" 3 | 4 | #import 5 | 6 | @implementation DevViewController 7 | 8 | - (void)viewDidLoad { 9 | [super viewDidLoad]; 10 | 11 | RCTReactNativeFactory *reactNativeFactory = [((AppDelegate *)[NSApp delegate])reactNativeFactory]; 12 | RCTPlatformView *rootView = [reactNativeFactory.rootViewFactory viewWithModuleName:@"main" 13 | initialProperties:@{@"isDevWindow" : @YES}]; 14 | 15 | NSView *view = [self view]; 16 | view.wantsLayer = YES; 17 | view.layer.backgroundColor = [NSColor windowBackgroundColor].CGColor; 18 | 19 | [view addSubview:rootView]; 20 | [rootView setFrame:[view bounds]]; 21 | [rootView setAutoresizingMask:(NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin | NSViewMaxYMargin | NSViewWidthSizable | NSViewHeightSizable)]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/DragDropStatusItemView.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface DragDropStatusItemView : NSView 4 | 5 | @property (nonatomic, copy) void (^openPopoverAction)(void); 6 | - (instancetype)initWithFrame:(NSRect)frame; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/ExpoMenuBar-macOS-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "DragDropStatusItemView.h" 2 | #import "WindowNavigator.h" 3 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/ExpoMenuBar-macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/ExpoMenuBar-macOSRelease.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/ExpoMenuBar.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/RCTImageView+Private.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCTImageView (Private) 4 | 5 | - (void)updateWithImage:(UIImage *)image; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/SystemIconViewManager.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface SystemIconViewManager : RCTViewManager 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/SystemIconViewManager.m: -------------------------------------------------------------------------------- 1 | // SystemIconViewManager.m 2 | #import "SystemIconViewManager.h" 3 | 4 | 5 | @implementation SystemIconViewManager 6 | 7 | RCT_EXPORT_MODULE() 8 | 9 | RCT_CUSTOM_VIEW_PROPERTY(tintColor, NSColor, NSImageView) 10 | { 11 | view.contentTintColor = [RCTConvert NSColor:json]; 12 | } 13 | 14 | RCT_CUSTOM_VIEW_PROPERTY(systemIconName, NSString, NSImageView) 15 | { 16 | NSString *symbolName = [RCTConvert NSString:json]; 17 | NSImage *systemImage = [NSImage imageWithSystemSymbolName:symbolName accessibilityDescription:nil]; 18 | [systemImage setTemplate:YES]; 19 | 20 | if (systemImage) { 21 | view.image = systemImage; 22 | } else { 23 | NSLog(@"System symbol '%@' not found.", symbolName); 24 | } 25 | } 26 | 27 | 28 | - (NSView *)view 29 | { 30 | NSImageView *imageView = [[NSImageView alloc] init]; 31 | [imageView setImageScaling:NSImageScaleProportionallyUpOrDown]; 32 | return imageView; 33 | } 34 | 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/WindowManager.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface WindowsManager : NSObject 4 | @property (nonatomic, strong) NSMutableDictionary *windowsMap; 5 | @end 6 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/WindowNavigator.h: -------------------------------------------------------------------------------- 1 | 2 | @interface WindowNavigator : NSObject 3 | 4 | @property(nonatomic, strong) NSMutableDictionary *windowsMap; 5 | 6 | + (instancetype)shared; 7 | 8 | - (void)openWindow:(NSString *)moduleName options:(NSDictionary *)options; 9 | 10 | - (void)closeWindow:(NSString *)moduleName; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/WindowWithDeallocCallback.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef void (^DeallocCallback)(void); 4 | 5 | @interface WindowWithDeallocCallback : NSWindow 6 | 7 | @property (nonatomic, copy) DeallocCallback deallocCallback; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/WindowWithDeallocCallback.m: -------------------------------------------------------------------------------- 1 | #import "WindowWithDeallocCallback.h" 2 | 3 | @implementation WindowWithDeallocCallback 4 | 5 | - (void)dealloc { 6 | // Invoke the callback block if it exists 7 | if (self.deallocCallback) { 8 | self.deallocCallback(); 9 | } 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar-macOS/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | int main(int argc, const char *argv[]) { 4 | return NSApplicationMain(argc, argv); 5 | } 6 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExpoMenuBar.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExportLocalOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | destination 6 | export 7 | method 8 | developer-id 9 | signingCertificate 10 | Developer ID Application 11 | teamID 12 | C8D8QTF339 13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/ExportUploadOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | destination 6 | upload 7 | method 8 | developer-id 9 | signingCertificate 10 | Developer ID Application 11 | teamID 12 | C8D8QTF339 13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/Podfile.properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "showDevWindow": "false", 3 | "showDockIcon": "false" 4 | } 5 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | CA92.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryFileTimestamp 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | 0A2A.1 21 | 3B52.1 22 | C617.1 23 | 24 | 25 | 26 | NSPrivacyAccessedAPIType 27 | NSPrivacyAccessedAPICategoryDiskSpace 28 | NSPrivacyAccessedAPITypeReasons 29 | 30 | E174.1 31 | 85F4.1 32 | 33 | 34 | 35 | NSPrivacyAccessedAPIType 36 | NSPrivacyAccessedAPICategorySystemBootTime 37 | NSPrivacyAccessedAPITypeReasons 38 | 39 | 35F9.1 40 | 41 | 42 | 43 | NSPrivacyCollectedDataTypes 44 | 45 | NSPrivacyTracking 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/scripts/archive_cli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | source "${REACT_NATIVE_PATH}/scripts/find-node-for-xcode.sh" 4 | export PROJECT_ROOT="$PODS_ROOT/../../" 5 | export CLI_PROJECT="$PROJECT_ROOT/../cli/" 6 | 7 | cd $CLI_PROJECT 8 | yarn archive 9 | yarn codesign 10 | 11 | cd $PROJECT_ROOT 12 | yarn update-cli 13 | -------------------------------------------------------------------------------- /apps/menu-bar/macos/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | WORKSPACE_PATH='./macos/ExpoMenuBar.xcworkspace' 5 | CONFIGURATION='Debug' 6 | SCHEME='ExpoMenuBar-macOS' 7 | 8 | # Build 9 | xcodebuild -workspace "$WORKSPACE_PATH" -scheme "$SCHEME" -configuration "$CONFIGURATION" 10 | 11 | # Get build settings 12 | BUILD_SETTINGS_JSON=$(xcodebuild -showBuildSettings -workspace "$WORKSPACE_PATH" -scheme "$SCHEME" -configuration "$CONFIGURATION" -json) 13 | 14 | readBuildSetting() { 15 | echo "$BUILD_SETTINGS_JSON" | jq -r --arg key "$1" '.[0] | .buildSettings | .[$key]' 16 | } 17 | 18 | BUILT_PRODUCTS_DIR=$(readBuildSetting "BUILT_PRODUCTS_DIR") 19 | FULL_PRODUCT_NAME=$(readBuildSetting "FULL_PRODUCT_NAME") 20 | 21 | open -a "$BUILT_PRODUCTS_DIR/$FULL_PRODUCT_NAME" & -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/main.ts: -------------------------------------------------------------------------------- 1 | import Updater from './Updater'; 2 | import { AutoUpdaterType } from '../src/AutoUpdater.types'; 3 | 4 | const updater = new Updater(); 5 | updater.init({ 6 | url: 'https://raw.githubusercontent.com/expo/orbit/main/electron-updates.json', 7 | }); 8 | 9 | const AutoUpdaterModule: AutoUpdaterType & { name: string } = { 10 | name: 'AutoUpdater', 11 | checkForUpdates: () => { 12 | updater.checkForUpdates(); 13 | }, 14 | getAutomaticallyChecksForUpdates: async () => { 15 | return updater.getAutomaticallyChecksForUpdates(); 16 | }, 17 | setAutomaticallyChecksForUpdates(value: boolean) { 18 | updater.setAutomaticallyChecksForUpdates(value); 19 | }, 20 | }; 21 | 22 | export default AutoUpdaterModule; 23 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/platform/Platform.ts: -------------------------------------------------------------------------------- 1 | import { autoUpdater } from 'electron'; 2 | 3 | import HttpClient from '../utils/HttpClient'; 4 | import Logger from '../utils/Logger'; 5 | import { BuildInfo } from '../utils/meta'; 6 | import { Options } from '../utils/options'; 7 | 8 | export default class Platform { 9 | emit: any; 10 | options: Options; 11 | logger: Logger; 12 | httpClient: HttpClient; 13 | 14 | constructor(options: Options, logger: Logger, emit: Function, httpClient: HttpClient) { 15 | this.emit = emit; 16 | this.options = options; 17 | this.logger = logger; 18 | this.httpClient = httpClient; 19 | } 20 | 21 | init() { 22 | // Empty by default 23 | } 24 | 25 | downloadUpdate(buildInfo: BuildInfo) { 26 | autoUpdater.setFeedURL({ url: buildInfo.url }); 27 | autoUpdater.checkForUpdates(); 28 | } 29 | 30 | quitAndInstall() { 31 | autoUpdater.quitAndInstall(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/platform/index.ts: -------------------------------------------------------------------------------- 1 | import Linux from './Linux'; 2 | import Platform from './Platform'; 3 | import Windows from './Windows'; 4 | import HttpClient from '../utils/HttpClient'; 5 | import Logger from '../utils/Logger'; 6 | import { Options } from '../utils/options'; 7 | 8 | /** 9 | * @param {Options} options 10 | * @param {Logger} logger 11 | * @param {Function} emit 12 | * @param {HttpClient} httpClient 13 | * @param {string} platform 14 | * @return {Platform} 15 | */ 16 | export function createPlatform( 17 | options: Options, 18 | logger: Logger, 19 | emit: Function, 20 | httpClient: HttpClient, 21 | platform: NodeJS.Platform = process.platform 22 | ): Platform { 23 | switch (platform) { 24 | case 'darwin': 25 | return new Platform(options, logger, emit, httpClient); 26 | case 'win32': 27 | return new Windows(options, logger, emit, httpClient); 28 | default: 29 | return new Linux(options, logger, emit, httpClient); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/screens/downloading-update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Updating Expo Orbit 9 | 46 | 47 | 48 | 49 | 50 | 51 |
52 |

Downloading update

53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/screens/update-available/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron'); 2 | 3 | contextBridge.exposeInMainWorld('autoUpdater', { 4 | skipVersion: () => ipcRenderer.invoke('autoUpdater:skipVersion'), 5 | installUpdate: () => ipcRenderer.invoke('autoUpdater:installUpdate'), 6 | rememberLater: () => ipcRenderer.invoke('autoUpdater:rememberLater'), 7 | receiveInfo: (info) => { 8 | ipcRenderer.on('autoUpdater:sendInfo', info); 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/utils/HttpClient.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig } from 'axios'; 2 | import fs from 'fs'; 3 | import stream from 'stream'; 4 | import util from 'util'; 5 | 6 | import { Options } from './options'; 7 | 8 | const pipeline = util.promisify(stream.pipeline); 9 | 10 | export default class HttpClient { 11 | options: Options; 12 | 13 | constructor(options: Options) { 14 | this.options = options; 15 | } 16 | 17 | async getJson(url: string) { 18 | const { data } = await axios.get(url, this.getHttpOptions()); 19 | return data; 20 | } 21 | 22 | async downloadFile(url: string, savePath: fs.PathLike) { 23 | const { data: httpRequest } = await axios.get(url, { 24 | ...this.getHttpOptions(), 25 | responseType: 'stream', 26 | }); 27 | return pipeline(httpRequest, fs.createWriteStream(savePath)); 28 | } 29 | 30 | getHttpOptions(): AxiosRequestConfig { 31 | const options = this.options.http || {}; 32 | return { 33 | ...options, 34 | headers: { 35 | 'User-Agent': 'auto-updater 1.0', 36 | ...options.headers, 37 | }, 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/utils/Logger.ts: -------------------------------------------------------------------------------- 1 | import { Options } from './options'; 2 | 3 | const PREFIX = '[Updater]'; 4 | 5 | export default class Logger { 6 | options: Partial; 7 | error: (...args: any[]) => void; 8 | warn: (...args: any[]) => void; 9 | info: (...args: any[]) => void; 10 | debug: (...args: any[]) => void; 11 | 12 | constructor(options: Partial) { 13 | this.options = options; 14 | 15 | this.error = this.log.bind(this, 'error'); 16 | this.warn = this.log.bind(this, 'warn'); 17 | this.info = this.log.bind(this, 'info'); 18 | this.debug = this.log.bind(this, 'debug'); 19 | } 20 | 21 | log(level: Exclude, ...args: any[]) { 22 | const customLogger = this.options.logger; 23 | const logger = customLogger?.[level]; 24 | 25 | if (!logger || typeof logger !== 'function') { 26 | return; 27 | } 28 | 29 | logger(PREFIX, ...args); 30 | } 31 | 32 | static createEmpty() { 33 | return new Logger({}); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/utils/file.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | import { app } from 'electron'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | 6 | export function calcSha256Hash(filePath: string): Promise { 7 | const stream = fs.createReadStream(filePath); 8 | const shaSum = crypto.createHash('sha256'); 9 | 10 | return new Promise((resolve, reject) => { 11 | stream 12 | //@ts-ignore 13 | .on('data', (data) => shaSum.update(data)) 14 | .on('end', () => resolve(shaSum.digest('hex'))) 15 | .on('error', reject); 16 | }); 17 | } 18 | 19 | export function readPackageJson(appPath: string | undefined) { 20 | try { 21 | const packageFile = path.join(appPath || app.getAppPath(), 'package.json'); 22 | const content = fs.readFileSync(packageFile, 'utf-8'); 23 | return JSON.parse(content); 24 | } catch (e) { 25 | console.log('Error reading package.json', e); 26 | return {}; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/utils/meta.ts: -------------------------------------------------------------------------------- 1 | import semver from 'semver'; 2 | 3 | import HttpClient from './HttpClient'; 4 | 5 | type UpdatesJSON = { 6 | title: string; 7 | link: string; 8 | versions: VersionMeta[]; 9 | }; 10 | 11 | export type BuildInfo = { 12 | url: string; 13 | sha256?: string; 14 | }; 15 | 16 | export type VersionMeta = { 17 | version: string; 18 | release_notes: string; 19 | pub_date: string; 20 | builds: { 21 | [platform: string]: BuildInfo; 22 | }; 23 | }; 24 | 25 | /** 26 | * Return promise containing a JSON with information regarding 27 | * all available updates 28 | * 29 | * @param {HttpClient} httpClient 30 | * @param {string} updatesUrl 31 | * @returns {Promise} 32 | */ 33 | export async function getUpdatesMeta(httpClient: HttpClient, updatesUrl: string) { 34 | const json: UpdatesJSON = await httpClient.getJson(updatesUrl); 35 | 36 | return json; 37 | } 38 | 39 | export function getNewerVersion( 40 | updatesJSON: UpdatesJSON, 41 | currentVersion: string 42 | ): VersionMeta | undefined { 43 | const latestVersion = updatesJSON.versions.sort((a, b) => 44 | semver.compareLoose(b.version, a.version) 45 | )[0]; 46 | 47 | if (semver.gt(latestVersion.version, currentVersion)) { 48 | return latestVersion; 49 | } 50 | } 51 | 52 | export function extractBuildInfoFromMeta(updateMeta: VersionMeta, build: string) { 53 | return updateMeta.builds[build]; 54 | } 55 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/electron/utils/quit.ts: -------------------------------------------------------------------------------- 1 | import { app } from 'electron'; 2 | 3 | export function quit() { 4 | if (app) { 5 | app.quit(); 6 | } else { 7 | process.exit(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["apple", "web"], 3 | "ios": { 4 | "modules": ["AutoUpdaterModule"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/index.ts: -------------------------------------------------------------------------------- 1 | import AutoUpdater from './src/AutoUpdaterModule'; 2 | 3 | export default { 4 | ...AutoUpdater, 5 | checkForUpdates: () => AutoUpdater.checkForUpdates(), 6 | getAutomaticallyChecksForUpdates: () => AutoUpdater.getAutomaticallyChecksForUpdates(), 7 | }; 8 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/ios/AutoUpdater.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'AutoUpdater' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platform = :osx, '11.0' 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | s.dependency 'Sparkle', '~> 2.5.0' 14 | 15 | # Swift/Objective-C compatibility 16 | s.pod_target_xcconfig = { 17 | 'DEFINES_MODULE' => 'YES', 18 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 19 | } 20 | 21 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 22 | end 23 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/ios/AutoUpdaterModule.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | import Sparkle 3 | 4 | public class AutoUpdaterModule: Module { 5 | var updateController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) 6 | 7 | public func definition() -> ModuleDefinition { 8 | Name("AutoUpdater") 9 | 10 | AsyncFunction("checkForUpdates") { 11 | self.updateController.updater.checkForUpdates() 12 | }.runOnQueue(.main) 13 | 14 | AsyncFunction("getAutomaticallyChecksForUpdates") { (promise: Promise) in 15 | promise.resolve(updateController.updater.automaticallyChecksForUpdates) 16 | } 17 | 18 | AsyncFunction("setAutomaticallyChecksForUpdates") { (value: Bool) in 19 | updateController.updater.automaticallyChecksForUpdates = value 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/src/AutoUpdater.types.ts: -------------------------------------------------------------------------------- 1 | export type AutoUpdaterType = { 2 | checkForUpdates: () => void; 3 | getAutomaticallyChecksForUpdates: () => Promise; 4 | setAutomaticallyChecksForUpdates: (value: boolean) => void; 5 | }; 6 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/src/AutoUpdaterModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule } from 'expo-modules-core'; 2 | 3 | import { AutoUpdaterType } from './AutoUpdater.types'; 4 | 5 | // It loads the native module object from the JSI or falls back to 6 | // the bridge module (from NativeModulesProxy) if the remote debugger is on. 7 | export default requireNativeModule('AutoUpdater'); 8 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/auto-updater/src/AutoUpdaterModule.web.ts: -------------------------------------------------------------------------------- 1 | import { requireElectronModule } from 'react-native-electron-modules/build/requireElectronModule'; 2 | 3 | import { AutoUpdaterType } from './AutoUpdater.types'; 4 | 5 | export default requireElectronModule('AutoUpdater'); 6 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/electron/main.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from 'electron'; 2 | 3 | function sendOpenFile(url: string) { 4 | for (const window of BrowserWindow.getAllWindows()) { 5 | window.webContents.send('onOpenFile', { path: url }); 6 | } 7 | } 8 | 9 | function isLikelyFilePath(str: string) { 10 | // Check if the string contains slashes or backslashes (directory separators) 11 | const hasSlashes = str.includes('/') || str.includes('\\'); 12 | 13 | // Check if the string ends with a file extension 14 | const hasFileExtension = /\.\w+$/.test(str); 15 | 16 | return hasSlashes && hasFileExtension; 17 | } 18 | 19 | app.on('second-instance', (_, argv) => { 20 | const lastArg = argv[argv.length - 1]; 21 | if (typeof lastArg === 'string' && isLikelyFilePath(lastArg)) { 22 | sendOpenFile(lastArg); 23 | } 24 | }); 25 | 26 | const FileHandler: { 27 | name: string; 28 | } = { 29 | name: 'FileHandler', 30 | }; 31 | 32 | export default FileHandler; 33 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/electron/preload.ts: -------------------------------------------------------------------------------- 1 | import { IpcRendererEvent, ipcRenderer } from 'electron'; 2 | import type { EmitterSubscription } from 'react-native'; 3 | 4 | const FileHandler: { 5 | name: string; 6 | addListener(type: string, listener: (data: any) => void, context?: any): EmitterSubscription; 7 | } = { 8 | name: 'FileHandler', 9 | addListener: (event: string, callback: (...args: string[]) => void, context) => { 10 | const listener = (event: IpcRendererEvent, ...args: any[]) => { 11 | callback(...args); 12 | }; 13 | ipcRenderer.on(event, listener); 14 | 15 | return { 16 | remove: () => { 17 | ipcRenderer.removeListener(event, listener); 18 | }, 19 | } as EmitterSubscription; 20 | }, 21 | }; 22 | 23 | export default FileHandler; 24 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["apple", "web"], 3 | "apple": { 4 | "modules": ["FileHandlerModule"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './src/FileHandlerModule'; 2 | export * from './src/FileHandler.types'; 3 | export { useFileHandler } from './src/useFileHandler'; 4 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/ios/FileHandler.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'FileHandler' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platform = :osx, '11.0' 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | 14 | # Swift/Objective-C compatibility 15 | s.pod_target_xcconfig = { 16 | 'DEFINES_MODULE' => 'YES', 17 | } 18 | 19 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 20 | end 21 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/ios/FileHandlerModule.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | 3 | private let onOpenFileEvent = "onOpenFile" 4 | 5 | public class FileHandlerModule: Module { 6 | private var hasListeners = false 7 | 8 | public func definition() -> ModuleDefinition { 9 | Name("FileHandler") 10 | 11 | Events(onOpenFileEvent) 12 | 13 | OnStartObserving { 14 | hasListeners = true 15 | NotificationCenter.default.addObserver( 16 | self, 17 | selector: #selector(self.onOpenFile), 18 | name: NSNotification.Name("ExpoOrbit_OnOpenFile"), 19 | object: nil 20 | ) 21 | } 22 | 23 | OnStopObserving { 24 | hasListeners = false 25 | NotificationCenter.default.removeObserver( 26 | self, 27 | name: NSNotification.Name("ExpoOrbit_OnOpenFile"), 28 | object: nil 29 | ) 30 | } 31 | 32 | } 33 | 34 | @objc 35 | private func onOpenFile(_ notification: Notification) { 36 | if !hasListeners { 37 | return 38 | } 39 | 40 | if let filename = notification.object as? String { 41 | sendEvent(onOpenFileEvent, ["path": filename]) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/src/FileHandler.types.ts: -------------------------------------------------------------------------------- 1 | export type FileHandlerModuleEvents = { 2 | onOpenFile: (response: { path: string }) => void; 3 | }; 4 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/src/FileHandlerModule.ts: -------------------------------------------------------------------------------- 1 | import { NativeModule, requireNativeModule } from 'expo'; 2 | 3 | import { FileHandlerModuleEvents } from './FileHandler.types'; 4 | 5 | declare class FileHandlerModule extends NativeModule {} 6 | 7 | // This call loads the native module object from the JSI. 8 | export default requireNativeModule('FileHandler'); 9 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/src/FileHandlerModule.web.ts: -------------------------------------------------------------------------------- 1 | import { requireElectronModule } from 'react-native-electron-modules/build/requireElectronModule'; 2 | 3 | const FileHandler = requireElectronModule('FileHandler'); 4 | 5 | export default FileHandler; 6 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-handler/src/useFileHandler.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | import FileHandler from '../../../modules/file-handler'; 4 | 5 | export type UseFileHandlerParams = { 6 | onOpenFile: (path: string) => void; 7 | }; 8 | 9 | export const useFileHandler = ({ onOpenFile }: UseFileHandlerParams) => { 10 | useEffect(() => { 11 | const listener = FileHandler.addListener('onOpenFile', ({ path }) => { 12 | onOpenFile(path); 13 | }); 14 | 15 | return listener.remove; 16 | }, [onOpenFile]); 17 | }; 18 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-picker/electron/main.ts: -------------------------------------------------------------------------------- 1 | import { dialog } from 'electron'; 2 | 3 | import { NativeFilePickerModule } from '../src/types'; 4 | 5 | const FilePickerModule: Partial & { name: string } = { 6 | name: 'FilePicker', 7 | pickFileWithFilenameExtension: async (filenameExtensions: string[], prompt: string) => { 8 | const { filePaths, canceled } = await dialog.showOpenDialog({ 9 | properties: ['openFile'], 10 | buttonLabel: prompt, 11 | filters: [{ name: 'Apps', extensions: filenameExtensions }], 12 | }); 13 | 14 | if (canceled) { 15 | throw new Error('NSModalResponseCancel'); 16 | } 17 | 18 | return filePaths[0]; 19 | }, 20 | pickFolder: async () => { 21 | const { filePaths, canceled } = await dialog.showOpenDialog({ 22 | properties: ['openDirectory'], 23 | }); 24 | 25 | if (canceled) { 26 | throw new Error('NSModalResponseCancel'); 27 | } 28 | 29 | return filePaths[0]; 30 | }, 31 | }; 32 | 33 | export default FilePickerModule; 34 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-picker/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["apple", "web"], 3 | "apple": { 4 | "modules": ["FilePickerModule"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-picker/index.ts: -------------------------------------------------------------------------------- 1 | import FilePickerModule from './src/FilePickerModule'; 2 | 3 | export function pickFolder(): Promise { 4 | return FilePickerModule.pickFolder(); 5 | } 6 | 7 | export function getAppAsync(): Promise { 8 | return FilePickerModule.pickFileWithFilenameExtension( 9 | ['apk', 'app', 'gzip', 'ipa', 'tar'], 10 | 'Select' 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-picker/ios/FilePicker.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'FilePicker' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platform = :osx, '11.0' 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | 14 | # Swift/Objective-C compatibility 15 | s.pod_target_xcconfig = { 16 | 'DEFINES_MODULE' => 'YES', 17 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 18 | } 19 | 20 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 21 | end 22 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-picker/src/FilePickerModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule } from 'expo-modules-core'; 2 | 3 | import { NativeFilePickerModule } from './types'; 4 | 5 | // It loads the native module object from the JSI or falls back to 6 | // the bridge module (from NativeModulesProxy) if the remote debugger is on. 7 | export default requireNativeModule('FilePicker'); 8 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-picker/src/FilePickerModule.web.ts: -------------------------------------------------------------------------------- 1 | import { requireElectronModule } from 'react-native-electron-modules/build/requireElectronModule'; 2 | 3 | import { NativeFilePickerModule } from './types'; 4 | 5 | export default requireElectronModule('FilePicker'); 6 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/file-picker/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface NativeFilePickerModule { 2 | pickFolder(): Promise; 3 | pickFileWithFilenameExtension(filenameExtensions: string[], prompt: string): Promise; 4 | } 5 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/menu-bar/electron/preload.ts: -------------------------------------------------------------------------------- 1 | import { IpcRendererEvent, ipcRenderer } from 'electron'; 2 | import { EmitterSubscription } from 'react-native'; 3 | 4 | import { ElectronPreloadMenuBarModule } from '../src/types'; 5 | 6 | const MenuBarModule: ElectronPreloadMenuBarModule = { 7 | name: 'MenuBar', 8 | initialScreenSize: { 9 | height: globalThis.screen?.height || 0, 10 | width: globalThis.screen?.width || 0, 11 | }, 12 | openPopover: () => { 13 | ipcRenderer.invoke('open-popover'); 14 | }, 15 | closePopover: () => { 16 | ipcRenderer.invoke('close-popover'); 17 | }, 18 | addListener: (event: string, callback: (...args: string[]) => void) => { 19 | const listener = (event: IpcRendererEvent, ...args: any[]) => { 20 | callback(...args); 21 | }; 22 | ipcRenderer.on(event, listener); 23 | 24 | return { 25 | remove: () => { 26 | ipcRenderer.removeListener(event, listener); 27 | }, 28 | } as EmitterSubscription; 29 | }, 30 | }; 31 | 32 | export default MenuBarModule; 33 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/menu-bar/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["apple", "web"], 3 | "apple": { 4 | "modules": ["MenuBarModule"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/menu-bar/ios/MenuBar.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'MenuBar' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platform = :osx, '11.0' 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | 14 | # Swift/Objective-C compatibility 15 | s.pod_target_xcconfig = { 16 | 'DEFINES_MODULE' => 'YES', 17 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 18 | } 19 | 20 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 21 | end -------------------------------------------------------------------------------- /apps/menu-bar/modules/menu-bar/ios/MenuBarExceptions.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | 3 | internal class CLIOutputError: GenericException { 4 | override var reason: String { 5 | param 6 | } 7 | } 8 | 9 | internal class IntenalCLIError: GenericException { 10 | public override var code: String { 11 | "ERR_INTERNAL_CLI" 12 | } 13 | override var reason: String { 14 | "Unable to invoke internal CLI: \(param)" 15 | } 16 | } 17 | 18 | internal class LauncherError: Exception { 19 | override var reason: String { 20 | "Failed to update login item status." 21 | } 22 | } 23 | 24 | internal class MultiOptionError: Exception { 25 | override var reason: String { 26 | "Selection was canceled." 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/menu-bar/src/MenuBarModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule, LegacyEventEmitter } from 'expo-modules-core'; 2 | import { NativeModule } from 'react-native'; 3 | 4 | import { NativeMenuBarModule } from './types'; 5 | 6 | const MenuBarModule = requireNativeModule('MenuBar'); 7 | export const emitter = new LegacyEventEmitter(MenuBarModule); 8 | 9 | export default MenuBarModule; 10 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/menu-bar/src/MenuBarModule.web.ts: -------------------------------------------------------------------------------- 1 | import { requireElectronModule } from 'react-native-electron-modules/build/requireElectronModule'; 2 | 3 | import { ElectronMainMenuBarModule, ElectronPreloadMenuBarModule } from './types'; 4 | 5 | const MenuBar = requireElectronModule( 6 | 'MenuBar' 7 | ); 8 | 9 | class EventEmitter { 10 | addListener(eventName: string, listener: (event: T) => void) { 11 | return MenuBar.addListener(eventName, listener); 12 | } 13 | } 14 | export const emitter = new EventEmitter(); 15 | 16 | export default { 17 | ...MenuBar, 18 | async runCli(...args) { 19 | try { 20 | return await MenuBar.runCli(...args); 21 | } catch (error) { 22 | if (error instanceof Error) { 23 | /** 24 | * Electron adds a prefix to the error message, so we need to filter it out in order 25 | * to parse the JSON object. 26 | * 27 | * e.g. "Error occurred in handler for 'MenuBar:runCli': Error: {message:'Error: Command failed: expo start'}" 28 | */ 29 | const filteredErrorMessage = error.message.substring(error.message.indexOf('Error:') + 7); 30 | 31 | throw new Error(filteredErrorMessage); 32 | } 33 | throw error; 34 | } 35 | }, 36 | } as typeof MenuBar; 37 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/progress-indicator/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["apple", "web"], 3 | "apple": { 4 | "modules": ["ProgressIndicatorModule"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/progress-indicator/index.ts: -------------------------------------------------------------------------------- 1 | import ProgressIndicator from './src/ProgressIndicatorView'; 2 | 3 | export { ProgressIndicator }; 4 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/progress-indicator/ios/ProgressIndicator.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'ProgressIndicator' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platform = :osx, '11.0' 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | 14 | # Swift/Objective-C compatibility 15 | s.pod_target_xcconfig = { 16 | 'DEFINES_MODULE' => 'YES', 17 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 18 | } 19 | 20 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 21 | end 22 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/progress-indicator/ios/ProgressIndicatorModule.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | 3 | public class ProgressIndicatorModule: Module { 4 | public func definition() -> ModuleDefinition { 5 | Name("ProgressIndicator") 6 | 7 | View(ProgressIndicatorView.self) { 8 | Prop("indeterminate") { (view, isIndeterminate: Bool?) in 9 | if let isIndeterminate { 10 | view.setIndeterminate(isIndeterminate) 11 | } else { 12 | view.setIndeterminate(false) 13 | } 14 | } 15 | 16 | Prop("progress") { (view, progress: Double) in 17 | view.setProgress(progress) 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/progress-indicator/ios/ProgressIndicatorView.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | 3 | class ProgressIndicatorView: ExpoView { 4 | let progressIndicatorView = NSProgressIndicator() 5 | 6 | required init(appContext: AppContext? = nil) { 7 | super.init(appContext: appContext) 8 | clipsToBounds = true 9 | 10 | progressIndicatorView.style = .bar 11 | progressIndicatorView.minValue = 0.0 12 | progressIndicatorView.maxValue = 100.0 13 | addSubview(progressIndicatorView) 14 | } 15 | 16 | override func layoutSubviews() { 17 | progressIndicatorView.frame = bounds 18 | } 19 | 20 | func setIndeterminate(_ indeterminate: Bool) { 21 | progressIndicatorView.isIndeterminate = indeterminate 22 | if indeterminate { 23 | progressIndicatorView.startAnimation(nil) 24 | } else { 25 | progressIndicatorView.stopAnimation(self) 26 | } 27 | } 28 | 29 | func setProgress(_ progress: Double) { 30 | progressIndicatorView.doubleValue = progress 31 | 32 | if progressIndicatorView.isIndeterminate { 33 | setIndeterminate(false) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/progress-indicator/src/ProgressIndicator.types.ts: -------------------------------------------------------------------------------- 1 | import { ViewProps } from 'react-native'; 2 | 3 | export type ProgressIndicatorViewProps = ViewProps & { 4 | progress?: number; 5 | indeterminate?: boolean; 6 | size?: 'small' | 'large'; 7 | }; 8 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/progress-indicator/src/ProgressIndicatorView.tsx: -------------------------------------------------------------------------------- 1 | import { requireNativeViewManager } from 'expo-modules-core'; 2 | import * as React from 'react'; 3 | import { StyleSheet } from 'react-native'; 4 | 5 | import { ProgressIndicatorViewProps } from './ProgressIndicator.types'; 6 | 7 | const NativeView: React.ComponentType = 8 | requireNativeViewManager('ProgressIndicator'); 9 | 10 | export default function ProgressIndicatorView(props: ProgressIndicatorViewProps) { 11 | const [key, setKey] = React.useState(0); 12 | 13 | React.useEffect(() => { 14 | /** 15 | * There is a bug in NSProgressIndicator where the progress animation does not 16 | * work if we switch from a progress indicator (using doubleValue) to indeterminate. 17 | * To work around this, we need to force a re-render of the component. 18 | */ 19 | setKey((key) => key + 1); 20 | }, [props.indeterminate]); 21 | return ( 22 | 27 | ); 28 | } 29 | 30 | function getSizeStyle(size: ProgressIndicatorViewProps['size']) { 31 | return size === 'small' ? styles.sizeSmall : styles.sizeLarge; 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | container: { 36 | alignSelf: 'stretch', 37 | }, 38 | sizeSmall: { 39 | height: 20, 40 | }, 41 | sizeLarge: { 42 | height: 24, 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/progress-indicator/src/ProgressIndicatorView.web.tsx: -------------------------------------------------------------------------------- 1 | import { ProgressBar } from '@fluentui/react-progress'; 2 | import * as React from 'react'; 3 | 4 | import { ProgressIndicatorViewProps } from './ProgressIndicator.types'; 5 | 6 | export default function ProgressIndicatorView({ 7 | progress = 0, 8 | indeterminate, 9 | }: ProgressIndicatorViewProps) { 10 | return ( 11 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/rudder/electron/main.ts: -------------------------------------------------------------------------------- 1 | import { app } from 'electron'; 2 | import os from 'os'; 3 | 4 | import { type ElectronRudderModule } from '../src/Rudder.types'; 5 | 6 | const RudderModule: Omit = { 7 | name: 'Rudder', 8 | appVersion: app.getVersion(), 9 | osVersion: os.release(), 10 | osArch: os.arch(), 11 | }; 12 | 13 | export default RudderModule; 14 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/rudder/electron/preload.ts: -------------------------------------------------------------------------------- 1 | import { ElectronRudderModule } from '../src/Rudder.types'; 2 | 3 | function getAnalyticsPlatformFromPlatform(platform: string): string { 4 | switch (platform) { 5 | case 'darwin': 6 | return 'macos'; 7 | case 'win32': 8 | return 'windows'; 9 | default: 10 | return platform; 11 | } 12 | } 13 | 14 | const RudderModule: Pick = { 15 | name: 'Rudder', 16 | platform: getAnalyticsPlatformFromPlatform(process.platform), 17 | }; 18 | 19 | export default RudderModule; 20 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/rudder/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["apple", "web"], 3 | "ios": { 4 | "modules": ["RudderModule"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/rudder/index.ts: -------------------------------------------------------------------------------- 1 | import RudderClient from './src/RudderModule'; 2 | 3 | export { RudderClient }; 4 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/rudder/ios/RNRudder.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'RNRudder' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platform = :osx, '11.0' 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | s.dependency 'Rudder', '~> 2.4.3' 14 | 15 | # Swift/Objective-C compatibility 16 | s.pod_target_xcconfig = { 17 | 'DEFINES_MODULE' => 'YES', 18 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 19 | } 20 | 21 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 22 | end 23 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/rudder/ios/RudderModule.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | import Rudder 3 | 4 | public class RudderModule: Module { 5 | 6 | public func definition() -> ModuleDefinition { 7 | Name("Rudder") 8 | 9 | Constants([ 10 | "appVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, 11 | ]) 12 | 13 | AsyncFunction("load") { (writeKey: String, dataPlaneUrl:String) in 14 | let config = RSConfig(writeKey: writeKey) 15 | .dataPlaneURL(dataPlaneUrl) 16 | .trackLifecycleEvents(false) 17 | 18 | RSClient.sharedInstance().configure(with: config) 19 | }.runOnQueue(.main) 20 | 21 | AsyncFunction("track") { (event: String, properties: [String: Any], context: [String: [String: Any]]?) in 22 | let option = RSOption() 23 | 24 | if let context = context, !context.isEmpty { 25 | for (key, value) in context { 26 | option.putCustomContext(value, withKey: key) 27 | } 28 | } 29 | 30 | RSClient.sharedInstance().track(event, properties:properties, option: option) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/rudder/src/Rudder.types.ts: -------------------------------------------------------------------------------- 1 | export interface NativeRudderModule extends RudderClient { 2 | track( 3 | event: string, 4 | properties?: Record, 5 | context?: Record> 6 | ): Promise; 7 | appVersion: string; 8 | } 9 | 10 | export interface ElectronRudderModule { 11 | name: string; 12 | platform: string; 13 | appVersion: string; 14 | osVersion: string; 15 | osArch: string; 16 | } 17 | 18 | export interface RudderClient { 19 | load(writeKey: string, dataPlaneUrl: string): Promise; 20 | track(event: string, properties?: Record): Promise; 21 | } 22 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/rudder/src/RudderModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule } from 'expo-modules-core'; 2 | import { Platform } from 'react-native'; 3 | 4 | import { RudderClient, NativeRudderModule } from './Rudder.types'; 5 | 6 | const NativeRudder = requireNativeModule('Rudder'); 7 | 8 | const RudderModule: RudderClient = { 9 | async load(writeKey: string, dataPlaneUrl: string): Promise { 10 | NativeRudder.load(writeKey, dataPlaneUrl); 11 | }, 12 | async track(event: string, properties?: Record) { 13 | NativeRudder.track( 14 | event, 15 | { 16 | ...properties, 17 | }, 18 | { 19 | os: { 20 | name: Platform.OS, 21 | version: Platform.Version, 22 | }, 23 | app: { 24 | name: 'orbit', 25 | version: NativeRudder.appVersion, 26 | type: 'native', 27 | }, 28 | } 29 | ); 30 | }, 31 | }; 32 | 33 | export default RudderModule; 34 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/rudder/src/RudderModule.web.ts: -------------------------------------------------------------------------------- 1 | import { requireElectronModule } from 'react-native-electron-modules'; 2 | import * as rudderanalytics from 'rudder-sdk-js'; 3 | 4 | import { RudderClient, ElectronRudderModule } from './Rudder.types'; 5 | 6 | const RudderElectronModule = requireElectronModule('Rudder'); 7 | 8 | const RudderModule: RudderClient = { 9 | async load(writeKey: string, dataPlaneUrl: string): Promise { 10 | rudderanalytics.load(writeKey, dataPlaneUrl); 11 | }, 12 | async track(event: string, properties?: Record) { 13 | rudderanalytics?.track(event, properties, { 14 | os: { 15 | name: RudderElectronModule.platform, 16 | version: RudderElectronModule.osVersion, 17 | }, 18 | device: { 19 | model: RudderElectronModule.osArch, 20 | }, 21 | app: { 22 | name: 'orbit', 23 | version: RudderElectronModule.appVersion, 24 | type: 'electron', 25 | }, 26 | }); 27 | }, 28 | }; 29 | 30 | export default RudderModule; 31 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/web-authentication-session/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["apple", "web"], 3 | "ios": { 4 | "modules": ["WebAuthenticationSessionModule"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/web-authentication-session/index.ts: -------------------------------------------------------------------------------- 1 | import { WebBrowserResultType } from './src/WebAuthenticationSession.types'; 2 | import WebAuthenticationSessionModule from './src/WebAuthenticationSessionModule'; 3 | 4 | export async function openAuthSessionAsync(url: string) { 5 | return await WebAuthenticationSessionModule.openAuthSessionAsync(url); 6 | } 7 | 8 | export { WebBrowserResultType }; 9 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/web-authentication-session/ios/WebAuthContextProvider.swift: -------------------------------------------------------------------------------- 1 | import AuthenticationServices 2 | 3 | public class WebAuthContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding { 4 | public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { 5 | var anchor: ASPresentationAnchor? 6 | 7 | DispatchQueue.main.sync { 8 | anchor = NSApp.mainWindow 9 | } 10 | 11 | return anchor ?? ASPresentationAnchor() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/web-authentication-session/ios/WebAuthenticationSession.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'WebAuthenticationSession' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platform = :osx, '11.0' 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | 14 | # Swift/Objective-C compatibility 15 | s.pod_target_xcconfig = { 16 | 'DEFINES_MODULE' => 'YES', 17 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 18 | } 19 | 20 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 21 | end 22 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/web-authentication-session/ios/WebAuthenticationSessionModule.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | import AuthenticationServices 3 | 4 | public class WebAuthenticationSessionModule: Module { 5 | var authContextProvider = WebAuthContextProvider() 6 | var authSession: ASWebAuthenticationSession? 7 | 8 | public func definition() -> ModuleDefinition { 9 | Name("WebAuthenticationSession") 10 | 11 | AsyncFunction("openAuthSessionAsync") { (urlString: String, promise: Promise) in 12 | guard let url = URL(string: urlString) else { 13 | promise.reject("INVALID_URL", "Invalid URL provided") 14 | return 15 | } 16 | 17 | authSession = ASWebAuthenticationSession(url: url, callbackURLScheme: "expo-orbit") { callbackURL, error in 18 | if let error { 19 | promise.reject("AUTH_SESSION_ERROR", error.localizedDescription) 20 | } else { 21 | if let callbackURL { 22 | promise.resolve(["type": "success", "url": callbackURL.absoluteString]) 23 | } else { 24 | promise.resolve(["type": "cancel"]) 25 | } 26 | } 27 | } 28 | 29 | if #available(macOS 10.15, *) { 30 | authSession?.presentationContextProvider = authContextProvider 31 | } 32 | authSession?.start() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/web-authentication-session/src/WebAuthenticationSession.types.ts: -------------------------------------------------------------------------------- 1 | export enum WebBrowserResultType { 2 | CANCEL = 'cancel', 3 | SUCCESS = 'success', 4 | } 5 | 6 | export type WebBrowserResult = 7 | | { 8 | type: WebBrowserResultType.CANCEL; 9 | } 10 | | { 11 | type: WebBrowserResultType.SUCCESS; 12 | url: string; 13 | }; 14 | 15 | export type WebAuthenticationSessionModuleType = { 16 | openAuthSessionAsync: (url: string) => Promise; 17 | }; 18 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/web-authentication-session/src/WebAuthenticationSessionModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule } from 'expo-modules-core'; 2 | 3 | import { WebAuthenticationSessionModuleType } from './WebAuthenticationSession.types'; 4 | 5 | export default requireNativeModule('WebAuthenticationSession'); 6 | -------------------------------------------------------------------------------- /apps/menu-bar/modules/web-authentication-session/src/WebAuthenticationSessionModule.web.ts: -------------------------------------------------------------------------------- 1 | import { requireElectronModule } from 'react-native-electron-modules/build/requireElectronModule'; 2 | 3 | import { WebAuthenticationSessionModuleType } from './WebAuthenticationSession.types'; 4 | 5 | export default requireElectronModule( 6 | 'WebAuthenticationSession' 7 | ); 8 | -------------------------------------------------------------------------------- /apps/menu-bar/public/fonts/Inter/Inter-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/public/fonts/Inter/Inter-Bold.otf -------------------------------------------------------------------------------- /apps/menu-bar/public/fonts/Inter/Inter-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/public/fonts/Inter/Inter-Medium.otf -------------------------------------------------------------------------------- /apps/menu-bar/public/fonts/Inter/Inter-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/public/fonts/Inter/Inter-Regular.otf -------------------------------------------------------------------------------- /apps/menu-bar/public/fonts/Inter/Inter-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/public/fonts/Inter/Inter-SemiBold.otf -------------------------------------------------------------------------------- /apps/menu-bar/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %WEB_TITLE% 9 | 10 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /apps/menu-bar/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | 4 | import { Analytics, Event } from './analytics'; 5 | import AutoResizerRootView from './components/AutoResizerRootView'; 6 | import { SAFE_AREA_FACTOR } from './hooks/useSafeDisplayDimensions'; 7 | import Popover from './popover'; 8 | import { DevicesProvider } from './providers/DevicesProvider'; 9 | import { FluentProvider } from './providers/FluentProvider'; 10 | import { ThemeProvider } from './providers/ThemeProvider'; 11 | 12 | type Props = { 13 | isDevWindow: boolean; 14 | }; 15 | 16 | function App(props: Props = { isDevWindow: false }) { 17 | useEffect(() => { 18 | Analytics.track(Event.APP_OPENED); 19 | }, []); 20 | 21 | return ( 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | export default App; 38 | 39 | const styles = StyleSheet.create({ 40 | container: { 41 | minWidth: 380, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /apps/menu-bar/src/analytics/index.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | 3 | import { RudderClient } from '../../modules/rudder'; 4 | 5 | const WRITE_KEY = 6 | Platform.OS === 'macos' ? '2by14lpeuvkyu0SmuBwpnAYkeIS' : '2c8NqBkxQzReHTIzDueSCQ0zD8u'; 7 | const DATA_PLANE_URL = 'https://cdp.expo.dev'; 8 | 9 | const analyticsEnabled = !__DEV__; 10 | 11 | if (analyticsEnabled) { 12 | RudderClient.load(WRITE_KEY, DATA_PLANE_URL); 13 | } 14 | 15 | export const Analytics: { track: typeof RudderClient.track } = { 16 | track: async (...args) => { 17 | if (analyticsEnabled) { 18 | await RudderClient.track(...args); 19 | } 20 | }, 21 | }; 22 | 23 | export enum Event { 24 | APP_OPENED = 'APP_OPENED', 25 | LAUNCH_BUILD_FROM_LOCAL_FILE = 'LAUNCH_BUILD_FROM_LOCAL_FILE', 26 | LAUNCH_SNACK = 'LAUNCH_SNACK', 27 | LAUNCH_EXPO_GO = 'LAUNCH_EXPO_GO', 28 | LAUNCH_BUILD = 'LAUNCH_BUILD', 29 | LAUNCH_EXPO_UPDATE = 'LAUNCH_EXPO_UPDATE', 30 | LAUNCH_SIMULATOR = 'LAUNCH_SIMULATOR', 31 | } 32 | -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/icons/cable-connector.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/icons/check-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/icons/earth-02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/icons/file-05.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/icons/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/icons/iphone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/icons/project-background-icon.svg: -------------------------------------------------------------------------------- 1 | 7 | 14 | 21 | 28 | 35 | 42 | 49 | 50 | -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/icons/wifi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/images/android-studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/src/assets/images/android-studio.png -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/images/android-studio@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/src/assets/images/android-studio@2x.png -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/images/android-studio@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/src/assets/images/android-studio@3x.png -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/images/onboarding/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/src/assets/images/onboarding/background.png -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/images/onboarding/background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/src/assets/images/onboarding/background@2x.png -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/images/onboarding/background@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/src/assets/images/onboarding/background@3x.png -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/images/xcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/src/assets/images/xcode.png -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/images/xcode@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/src/assets/images/xcode@2x.png -------------------------------------------------------------------------------- /apps/menu-bar/src/assets/images/xcode@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expo/orbit/24874d3f963c6291c6511c4cacdce329dcd9ba74/apps/menu-bar/src/assets/images/xcode@3x.png -------------------------------------------------------------------------------- /apps/menu-bar/src/commands/bootDeviceAsync.ts: -------------------------------------------------------------------------------- 1 | import MenuBarModule from '../modules/MenuBarModule'; 2 | 3 | type BootDeviceAsyncOptions = { 4 | platform: 'android' | 'ios'; 5 | id: string; 6 | noAudio?: boolean; 7 | }; 8 | 9 | export const bootDeviceAsync = async ({ platform, id, noAudio }: BootDeviceAsyncOptions) => { 10 | const args = ['-p', platform, '--id', id]; 11 | if (noAudio) { 12 | args.push('--no-audio'); 13 | } 14 | await MenuBarModule.runCli('boot-device', args, console.log); 15 | }; 16 | -------------------------------------------------------------------------------- /apps/menu-bar/src/commands/detectIOSAppTypeAsync'.ts: -------------------------------------------------------------------------------- 1 | import { Device } from 'common-types/build/devices'; 2 | 3 | import MenuBarModule from '../modules/MenuBarModule'; 4 | 5 | export const detectIOSAppTypeAsync = async (appPath: string) => { 6 | return (await MenuBarModule.runCli( 7 | 'detect-ios-app-type', 8 | [appPath], 9 | console.log 10 | )) as Device['deviceType']; 11 | }; 12 | -------------------------------------------------------------------------------- /apps/menu-bar/src/commands/downloadBuildAsync.ts: -------------------------------------------------------------------------------- 1 | import MenuBarModule from '../modules/MenuBarModule'; 2 | import { extractDownloadProgress } from '../utils/helpers'; 3 | 4 | export async function downloadBuildAsync( 5 | url: string, 6 | progressCallback: (progress: number) => void 7 | ): Promise { 8 | return MenuBarModule.runCli('download-build', [url], (status) => { 9 | progressCallback(extractDownloadProgress(status)); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /apps/menu-bar/src/commands/installAndLaunchAppAsync.ts: -------------------------------------------------------------------------------- 1 | import MenuBarModule from '../modules/MenuBarModule'; 2 | 3 | type InstallAndLaunchAppAsyncOptions = { 4 | appPath: string; 5 | deviceId: string; 6 | }; 7 | 8 | export const installAndLaunchAppAsync = async ({ 9 | appPath, 10 | deviceId, 11 | }: InstallAndLaunchAppAsyncOptions) => { 12 | await MenuBarModule.runCli( 13 | 'install-and-launch', 14 | ['--app-path', appPath, '--device-id', deviceId], 15 | undefined 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /apps/menu-bar/src/commands/launchExpoGoAsync.ts: -------------------------------------------------------------------------------- 1 | import MenuBarModule from '../modules/MenuBarModule'; 2 | 3 | type LaunchExpoGoAsyncOptions = { 4 | platform: 'android' | 'ios'; 5 | deviceId: string; 6 | url: string; 7 | sdkVersion?: string | null; 8 | }; 9 | 10 | export const launchExpoGoAsync = async ({ 11 | url, 12 | platform, 13 | deviceId, 14 | sdkVersion, 15 | }: LaunchExpoGoAsyncOptions) => { 16 | const args = [url, '-p', platform, '--device-id', deviceId]; 17 | if (sdkVersion) { 18 | args.push('--sdk-version', sdkVersion); 19 | } 20 | 21 | await MenuBarModule.runCli('launch-expo-go', args, console.log); 22 | }; 23 | -------------------------------------------------------------------------------- /apps/menu-bar/src/commands/launchUpdateAsync.ts: -------------------------------------------------------------------------------- 1 | import MenuBarModule from '../modules/MenuBarModule'; 2 | import { MenuBarStatus, extractDownloadProgress } from '../utils/helpers'; 3 | 4 | type LaunchUpdateAsyncOptions = { 5 | platform: 'android' | 'ios'; 6 | deviceId: string; 7 | url: string; 8 | noInstall?: boolean; 9 | forceExpoGo?: boolean; 10 | }; 11 | 12 | type LaunchUpdateCallback = (status: MenuBarStatus, progress: number) => void; 13 | 14 | export async function launchUpdateAsync( 15 | { url, platform, deviceId, noInstall, forceExpoGo }: LaunchUpdateAsyncOptions, 16 | callback: LaunchUpdateCallback 17 | ) { 18 | const args = [url, '-p', platform, '--device-id', deviceId]; 19 | if (noInstall) { 20 | args.push('--skip-install'); 21 | } 22 | if (forceExpoGo) { 23 | args.push('--force-expo-go'); 24 | } 25 | 26 | await MenuBarModule.runCli('launch-update', args, (output) => { 27 | if (output.includes('Downloading app')) { 28 | callback(MenuBarStatus.DOWNLOADING, extractDownloadProgress(output)); 29 | } else if (output.includes('Installing your app')) { 30 | callback(MenuBarStatus.INSTALLING_APP, 0); 31 | } else if (output.includes('Opening url')) { 32 | callback(MenuBarStatus.OPENING_UPDATE, 0); 33 | } 34 | // Add other conditions for different status updates as needed 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /apps/menu-bar/src/commands/listDevicesAsync.ts: -------------------------------------------------------------------------------- 1 | import { DevicesPerPlatform } from 'common-types/build/cli-commands/listDevices'; 2 | 3 | import MenuBarModule from '../modules/MenuBarModule'; 4 | 5 | type ListDevicesAsyncOptions = { 6 | platform: 'android' | 'ios' | 'all'; 7 | }; 8 | 9 | export const listDevicesAsync = async ({ platform }: ListDevicesAsyncOptions) => { 10 | const args: string[] = []; 11 | if (platform) { 12 | args.push('-p', platform); 13 | } 14 | 15 | const stringResult = await MenuBarModule.runCli('list-devices', args, undefined); 16 | 17 | return JSON.parse(stringResult) as DevicesPerPlatform; 18 | }; 19 | -------------------------------------------------------------------------------- /apps/menu-bar/src/commands/setSessionAsync.ts: -------------------------------------------------------------------------------- 1 | import MenuBarModule from '../modules/MenuBarModule'; 2 | 3 | export const setSessionAsync = async (sessionSecret: string) => { 4 | await MenuBarModule.runCli('set-session', [sessionSecret], console.log); 5 | }; 6 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/AutoResizerRootView/index.tsx: -------------------------------------------------------------------------------- 1 | import { requireNativeComponent, ViewProps } from 'react-native'; 2 | 3 | const AutoResizerRootView = requireNativeComponent< 4 | ViewProps & { enabled: boolean; maxRelativeHeight: number } 5 | >('AutoResizerRootView'); 6 | 7 | export default AutoResizerRootView; 8 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/Checkbox/NativeCheckbox.web.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox as FluentCheckbox } from '@fluentui/react-checkbox'; 2 | import React from 'react'; 3 | 4 | import { CheckboxChangeEvent, NativeCheckboxProps } from './types'; 5 | 6 | const Checkbox = React.forwardRef(({ value, onChange }: NativeCheckboxProps, ref) => { 7 | return ( 8 | 14 | onChange?.({ 15 | nativeEvent: { value: event.target.checked }, 16 | } as CheckboxChangeEvent) 17 | } 18 | /> 19 | ); 20 | }); 21 | 22 | export default Checkbox; 23 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/Checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | import { Pressable, StyleSheet } from 'react-native'; 3 | 4 | import NativeCheckbox from './NativeCheckbox'; 5 | import { CheckboxChangeEvent, CheckboxProps } from './types'; 6 | import { Text } from '../Text'; 7 | import { Row } from '../View'; 8 | 9 | const Checkbox = ({ onChange, onValueChange, label, ...props }: CheckboxProps) => { 10 | const nativeCheckboxRef = useRef>(null); 11 | 12 | const handleChange = (event: CheckboxChangeEvent) => { 13 | onChange?.(event); 14 | onValueChange?.(event.nativeEvent.value); 15 | }; 16 | 17 | return ( 18 | 19 | 25 | {label && ( 26 | { 28 | onValueChange?.(!props.value); 29 | nativeCheckboxRef.current?.setNative({ value: !props.value }); 30 | }}> 31 | {label} 32 | 33 | )} 34 | 35 | ); 36 | }; 37 | 38 | export default Checkbox; 39 | 40 | const styles = StyleSheet.create({ 41 | checkbox: { height: 18, width: 18 }, 42 | }); 43 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/Checkbox/types.tsx: -------------------------------------------------------------------------------- 1 | import { TargetedEvent, NativeSyntheticEvent, ViewProps } from 'react-native'; 2 | 3 | interface CheckboxChangeEventData extends TargetedEvent { 4 | value: boolean; 5 | } 6 | 7 | export interface CheckboxChangeEvent extends NativeSyntheticEvent {} 8 | 9 | export type NativeCheckboxProps = ViewProps & { 10 | disabled?: boolean; 11 | onChange?: (event: CheckboxChangeEvent) => void; 12 | value?: boolean; 13 | }; 14 | 15 | export type CheckboxProps = Omit & { 16 | onValueChange?: (value: boolean) => void; 17 | label?: string; 18 | }; 19 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/DebugLogs/DebugLogRow.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { TouchableOpacity, StyleProp, ViewStyle } from 'react-native'; 3 | 4 | import { ObjectInspector } from './ObjectInspector'; 5 | import { Log } from '../../modules/Logs'; 6 | import { Text } from '../Text'; 7 | import { View, Row } from '../View'; 8 | 9 | interface Props { 10 | log: Log; 11 | style?: StyleProp; 12 | } 13 | 14 | const DebugLogRow = ({ log, style }: Props) => { 15 | const [isOpen, setIsOpen] = useState(false); 16 | 17 | return ( 18 | 19 | setIsOpen((prev) => !prev)} disabled={!log.info}> 20 | 25 | {log.command} 26 | 27 | {log.info} 28 | 29 | 30 | {isOpen ? : null} 31 | 32 | 33 | ); 34 | }; 35 | 36 | const ExtraInfo = ({ log }: Props) => { 37 | let extraInfo = log.info; 38 | const looksLikeJSON = extraInfo?.startsWith('{') || extraInfo?.startsWith('['); 39 | 40 | if (looksLikeJSON) { 41 | try { 42 | extraInfo = JSON.parse(log.info); 43 | } catch {} 44 | } 45 | 46 | return ; 47 | }; 48 | 49 | export default DebugLogRow; 50 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/DebugLogs/index.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollView } from 'react-native'; 2 | 3 | import DebugLogRow from './DebugLogRow'; 4 | import MenuBarModule from '../../modules/MenuBarModule'; 5 | import { useExpoPalette } from '../../utils/useExpoTheme'; 6 | import { Text } from '../Text'; 7 | import { Row } from '../View'; 8 | 9 | export const DebugLogs = () => { 10 | const palette = useExpoPalette(); 11 | 12 | return ( 13 | 18 | 24 | Command: 25 | Extra info: 26 | 27 | {MenuBarModule.logs.get().map((log, index) => { 28 | return ( 29 | 37 | ); 38 | })} 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/Image.tsx: -------------------------------------------------------------------------------- 1 | import { borderRadius, iconSize } from '@expo/styleguide-native'; 2 | import { Image as RNImage } from 'react-native'; 3 | 4 | import { create } from '../utils/create-component-primitive'; 5 | import { scale } from '../utils/theme'; 6 | 7 | export const Image = create(RNImage, { 8 | base: { 9 | resizeMode: 'cover', 10 | }, 11 | variants: { 12 | size: { 13 | tiny: { 14 | height: scale.small, 15 | width: scale.small, 16 | }, 17 | small: { 18 | height: iconSize.small, 19 | width: iconSize.small, 20 | }, 21 | 22 | large: { 23 | height: scale['10'], 24 | width: scale['10'], 25 | }, 26 | 27 | xl: { 28 | height: scale['20'], 29 | width: scale['20'], 30 | }, 31 | }, 32 | 33 | rounded: { 34 | small: { borderRadius: borderRadius.small }, 35 | medium: { borderRadius: borderRadius.medium }, 36 | large: { borderRadius: borderRadius.large }, 37 | huge: { borderRadius: borderRadius.huge }, 38 | full: { borderRadius: 99999 }, 39 | }, 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/Switch/index.tsx: -------------------------------------------------------------------------------- 1 | export { Switch } from 'react-native'; 2 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/Switch/index.web.tsx: -------------------------------------------------------------------------------- 1 | import { Switch as FluentSwitch } from '@fluentui/react-switch'; 2 | import { SwitchProps } from 'react-native'; 3 | 4 | export function Switch({ onValueChange, value, disabled }: SwitchProps) { 5 | return ( 6 | { 10 | if (onValueChange) { 11 | onValueChange(Boolean(ev.target.checked)); 12 | } 13 | }} 14 | /> 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/SystemIconView/index.tsx: -------------------------------------------------------------------------------- 1 | import { requireNativeComponent, StyleSheet, ImageProps } from 'react-native'; 2 | 3 | type Props = Omit & { systemIconName: string }; 4 | 5 | const SystemIconViewInternal = requireNativeComponent('SystemIconView'); 6 | 7 | const SystemIconView = (props: Props) => { 8 | return ; 9 | }; 10 | 11 | const styles = StyleSheet.create({ 12 | icon: { 13 | height: 18, 14 | width: 18, 15 | }, 16 | }); 17 | 18 | export default SystemIconView; 19 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/SystemIconView/index.web.tsx: -------------------------------------------------------------------------------- 1 | import { Bug16Filled, Question16Filled } from '@fluentui/react-icons'; 2 | 3 | function SystemIconView({ systemIconName }: { systemIconName: string }) { 4 | switch (systemIconName) { 5 | case 'ladybug': 6 | return ; 7 | default: 8 | return ; 9 | } 10 | } 11 | 12 | export default SystemIconView; 13 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/Text.tsx: -------------------------------------------------------------------------------- 1 | import { shadows } from '@expo/styleguide-native'; 2 | import { Platform, Text as RNText, TextInput as RNTextInput } from 'react-native'; 3 | 4 | import { PlatformColor } from '../modules/PlatformColor'; 5 | import { create } from '../utils/create-component-primitive'; 6 | import { text, textDark, padding, rounded } from '../utils/theme'; 7 | 8 | export const Text = create(RNText, { 9 | base: { 10 | fontSize: 14, 11 | lineHeight: 18, 12 | ...Platform.select({ 13 | web: { 14 | color: 'var(--text-color)', 15 | }, 16 | }), 17 | }, 18 | 19 | props: { 20 | accessibilityRole: 'text', 21 | }, 22 | 23 | variants: { 24 | ...text, 25 | }, 26 | 27 | selectors: { 28 | dark: textDark, 29 | }, 30 | }); 31 | 32 | export const TextInput = create(RNTextInput, { 33 | base: { 34 | fontSize: 16, 35 | }, 36 | 37 | variants: { 38 | ...text, 39 | 40 | border: { 41 | default: { 42 | borderWidth: 1, 43 | borderColor: PlatformColor('gridColor'), 44 | }, 45 | }, 46 | 47 | ...rounded, 48 | 49 | ...padding, 50 | 51 | shadow: { 52 | input: shadows.input, 53 | }, 54 | }, 55 | 56 | selectors: { 57 | dark: { 58 | ...textDark, 59 | 60 | border: { 61 | default: { 62 | borderColor: PlatformColor('gridColor'), 63 | }, 64 | }, 65 | }, 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /apps/menu-bar/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Checkbox } from './Checkbox'; 2 | export { Text, TextInput } from './Text'; 3 | export { Divider, Row, View, Spacer } from './View'; 4 | -------------------------------------------------------------------------------- /apps/menu-bar/src/generated/graphql.possibleTypes.json: -------------------------------------------------------------------------------- 1 | { 2 | "possibleTypes": { 3 | "AccountUsageMetadata": [ 4 | "AccountUsageEASBuildMetadata" 5 | ], 6 | "ActivityTimelineProjectActivity": [ 7 | "Build", 8 | "Submission", 9 | "Update", 10 | "WorkerDeployment", 11 | "WorkflowRun" 12 | ], 13 | "Actor": [ 14 | "Robot", 15 | "SSOUser", 16 | "User" 17 | ], 18 | "BuildOrBuildJob": [ 19 | "Build" 20 | ], 21 | "FcmSnippet": [ 22 | "FcmSnippetLegacy", 23 | "FcmSnippetV1" 24 | ], 25 | "NotificationMetadata": [ 26 | "BuildLimitThresholdExceededMetadata", 27 | "BuildPlanCreditThresholdExceededMetadata", 28 | "TestNotificationMetadata" 29 | ], 30 | "PlanEnablement": [ 31 | "Concurrencies", 32 | "EASTotalPlanEnablement" 33 | ], 34 | "Project": [ 35 | "App", 36 | "Snack" 37 | ], 38 | "UserActor": [ 39 | "SSOUser", 40 | "User" 41 | ] 42 | } 43 | } -------------------------------------------------------------------------------- /apps/menu-bar/src/graphql/apps.gql: -------------------------------------------------------------------------------- 1 | fragment AppForPinnedList on App { 2 | id 3 | name 4 | slug 5 | latestActivity 6 | icon { 7 | url 8 | primaryColor 9 | } 10 | profileImageUrl 11 | ownerAccount { 12 | name 13 | } 14 | } 15 | 16 | query GetAppsForPinnedList { 17 | meUserActor { 18 | id 19 | pinnedApps { 20 | ...AppForPinnedList 21 | } 22 | accounts { 23 | id 24 | appsPaginated(first: 10, filter: { sortByField: LATEST_ACTIVITY_TIME }) { 25 | edges { 26 | cursor 27 | node { 28 | ...AppForPinnedList 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/menu-bar/src/graphql/user.gql: -------------------------------------------------------------------------------- 1 | fragment CurrentUserData on UserActor { 2 | id 3 | username 4 | firstName 5 | lastName 6 | bestContactEmail 7 | profilePhoto 8 | } 9 | 10 | query GetCurrentUser { 11 | meUserActor { 12 | ...CurrentUserData 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/menu-bar/src/hooks/useDeepLinking.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import { Linking } from '../modules/Linking'; 4 | 5 | export type DeepLinkingCallback = (event: { url: string }) => void; 6 | 7 | export const useDeepLinking = (callback: DeepLinkingCallback) => { 8 | const [initialURL, setInitialURL] = useState(null); 9 | 10 | useEffect(() => { 11 | Linking.getInitialURL().then(setInitialURL); 12 | }, []); 13 | 14 | if (initialURL) { 15 | callback({ url: initialURL }); 16 | setInitialURL(null); 17 | } 18 | 19 | useEffect(() => { 20 | const listener = Linking.addEventListener('url', callback); 21 | 22 | return listener.remove; 23 | }, [callback]); 24 | }; 25 | -------------------------------------------------------------------------------- /apps/menu-bar/src/hooks/useDeviceAudioPreferences.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import { getUserPreferences, storage, userPreferencesStorageKey } from '../modules/Storage'; 4 | 5 | export const useDeviceAudioPreferences = () => { 6 | const [isEmulatorWithoutAudio, setEmulatorWithoutAudio] = useState( 7 | getUserPreferences().emulatorWithoutAudio 8 | ); 9 | 10 | useEffect(() => { 11 | const listener = storage.addOnValueChangedListener((key) => { 12 | if (key === userPreferencesStorageKey) { 13 | setEmulatorWithoutAudio(getUserPreferences().emulatorWithoutAudio); 14 | } 15 | }); 16 | 17 | return () => { 18 | listener.remove(); 19 | }; 20 | }, []); 21 | 22 | return { 23 | emulatorWithoutAudio: isEmulatorWithoutAudio, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /apps/menu-bar/src/hooks/usePopoverFocus.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | import { DeviceEventEmitter } from '../modules/DeviceEventEmitter'; 4 | 5 | type PopoverFocusedEvent = { 6 | screenSize: { height: number; width: number }; 7 | }; 8 | 9 | export function usePopoverFocusEffect(callback: (event: PopoverFocusedEvent) => void) { 10 | useEffect(() => { 11 | const listener = DeviceEventEmitter.addListener('popoverFocused', callback); 12 | 13 | return () => { 14 | listener.remove(); 15 | }; 16 | }, [callback]); 17 | } 18 | -------------------------------------------------------------------------------- /apps/menu-bar/src/hooks/useSafeDisplayDimensions.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | 3 | import { usePopoverFocusEffect } from './usePopoverFocus'; 4 | import MenuBarModule from '../modules/MenuBarModule'; 5 | 6 | export const SAFE_AREA_FACTOR = 0.85; 7 | 8 | const { initialScreenSize } = MenuBarModule; 9 | 10 | export const useSafeDisplayDimensions = () => { 11 | const [dimensions, setDimensions] = useState(initialScreenSize); 12 | 13 | usePopoverFocusEffect( 14 | useCallback(({ screenSize }) => { 15 | setDimensions(screenSize); 16 | }, []) 17 | ); 18 | 19 | return { 20 | ...dimensions, 21 | height: 22 | dimensions.height !== 0 23 | ? dimensions.height * SAFE_AREA_FACTOR 24 | : initialScreenSize.height * SAFE_AREA_FACTOR, 25 | width: 26 | dimensions.width !== 0 27 | ? dimensions.width * SAFE_AREA_FACTOR 28 | : initialScreenSize.width * SAFE_AREA_FACTOR, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/Alert/index.ts: -------------------------------------------------------------------------------- 1 | import { Alert as RNAlert, AlertOptions, AlertButton } from 'react-native'; 2 | 3 | type MacOSAlertOptions = AlertOptions & { 4 | modal?: boolean; 5 | critical?: boolean; 6 | }; 7 | 8 | /** 9 | * A wrapper around RNAlert to override the 10 | * default alert and set modal true, that way 11 | * users can dismiss it even without an active window 12 | **/ 13 | const Alert = { 14 | ...RNAlert, 15 | alert( 16 | title: string, 17 | message?: string, 18 | buttons?: AlertButton[], 19 | options: MacOSAlertOptions = {} 20 | ): void { 21 | RNAlert.alert(title, message, buttons, { modal: true, ...options }); 22 | }, 23 | }; 24 | 25 | export default Alert; 26 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/Alert/index.web.ts: -------------------------------------------------------------------------------- 1 | import { Alert as RNAlert, AlertButton } from 'react-native'; 2 | import { requireElectronModule } from 'react-native-electron-modules/build/requireElectronModule'; 3 | 4 | const ElectronAlert = requireElectronModule<{ 5 | alert(title: string, message?: string, buttons?: AlertButton[]): void; 6 | }>('Alert'); 7 | 8 | const Alert = { 9 | ...RNAlert, 10 | alert(title: string, message?: string, buttons?: AlertButton[]) { 11 | ElectronAlert.alert(title, message, buttons); 12 | }, 13 | }; 14 | 15 | export default Alert; 16 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/DeviceEventEmitter/index.ts: -------------------------------------------------------------------------------- 1 | export { DeviceEventEmitter } from 'react-native'; 2 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/DeviceEventEmitter/index.web.ts: -------------------------------------------------------------------------------- 1 | import { DeviceEventEmitter as NativeDeviceEventEmitter } from 'react-native'; 2 | import { requireElectronModule } from 'react-native-electron-modules/build/requireElectronModule'; 3 | 4 | export const DeviceEventEmitter = 5 | requireElectronModule('DeviceEventEmitter'); 6 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/FilePickerModule.ts: -------------------------------------------------------------------------------- 1 | import { NativeModule, NativeModules } from 'react-native'; 2 | 3 | type FilePickerModuleType = NativeModule & { 4 | pickFileWithFilenameExtension: (extensions: string[], prompt?: string) => Promise; 5 | pickFolder: () => Promise; 6 | }; 7 | 8 | const FilePickerModule: FilePickerModuleType = NativeModules.FilePicker; 9 | 10 | export default { 11 | ...FilePickerModule, 12 | getAppAsync: async () => { 13 | return await FilePickerModule.pickFileWithFilenameExtension( 14 | ['apk', 'app', 'gzip', 'ipa', 'tar'], 15 | 'Select' 16 | ); 17 | }, 18 | pickFolder: async () => FilePickerModule.pickFolder(), 19 | }; 20 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/Linking/index.ts: -------------------------------------------------------------------------------- 1 | export { Linking } from 'react-native'; 2 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/Linking/index.web.ts: -------------------------------------------------------------------------------- 1 | import { Linking as NativeLinking } from 'react-native'; 2 | import { requireElectronModule } from 'react-native-electron-modules/build/requireElectronModule'; 3 | 4 | export const Linking = requireElectronModule('Linking'); 5 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/Logs.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | 3 | export type Log = { command: string; info: string }; 4 | export class Logs { 5 | private logs: Log[] = []; 6 | 7 | push(log: Log) { 8 | if (Platform.OS === 'web') { 9 | const logs = this.get(); 10 | logs.push(log); 11 | localStorage.setItem('logs', JSON.stringify(logs)); 12 | } else { 13 | this.logs.push(log); 14 | } 15 | } 16 | 17 | get(): Log[] { 18 | if (Platform.OS === 'web') { 19 | return JSON.parse(localStorage.getItem('logs') ?? '[]'); 20 | } 21 | return this.logs ?? []; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/MenuBarModule.ts: -------------------------------------------------------------------------------- 1 | import MenuBarModule from '../../modules/menu-bar'; 2 | export default MenuBarModule; 3 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/PlatformColor/index.ts: -------------------------------------------------------------------------------- 1 | export { PlatformColor } from 'react-native'; 2 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/PlatformColor/index.web.ts: -------------------------------------------------------------------------------- 1 | const camelToSnakeCase = (str: string) => 2 | str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`); 3 | 4 | const bodyStyles = window.getComputedStyle(document.body); 5 | 6 | export const PlatformColor = (color: string) => { 7 | const colorValue = 8 | bodyStyles.getPropertyValue(`--${camelToSnakeCase(color)}`) || 9 | bodyStyles.getPropertyValue(`--${camelToSnakeCase(color)}-color`); 10 | 11 | return colorValue || '#000000'; 12 | }; 13 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/WindowManager/WindowProvider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | const WindowContext = createContext(''); 4 | export const useWindowId = () => useContext(WindowContext); 5 | 6 | type WindowProviderProps = { 7 | children: React.ReactNode; 8 | id: string; 9 | }; 10 | 11 | export function WindowProvider({ children, id }: WindowProviderProps) { 12 | return {children}; 13 | } 14 | 15 | export const withWindowProvider =

( 16 | WrappedComponent: React.ComponentType

, 17 | id: string 18 | ) => { 19 | const WithWindowProvider = (props: P) => { 20 | return ( 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | return WithWindowProvider; 28 | }; 29 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/WindowManager/index.web.ts: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import { requireElectronModule } from 'react-native-electron-modules/build/requireElectronModule'; 3 | 4 | import { withWindowProvider } from './WindowProvider'; 5 | import { WindowsConfig, WindowsManagerType } from './types'; 6 | import { withFluentProvider } from '../../providers/FluentProvider'; 7 | import { withThemeProvider } from '../../utils/useExpoTheme'; 8 | 9 | export { WindowStyleMask } from './types'; 10 | 11 | export const WindowManager = requireElectronModule('WindowManager'); 12 | 13 | export function createWindowsNavigator(config: T) { 14 | Object.entries(config).forEach(([key, value]) => { 15 | AppRegistry.registerComponent(key, () => 16 | withWindowProvider(withFluentProvider(withThemeProvider(value.component)), key) 17 | ); 18 | }); 19 | 20 | return { 21 | open: (windowName: keyof T) => { 22 | WindowManager.openWindow(String(windowName), config[windowName].options || {}); 23 | }, 24 | close: (window: keyof T) => { 25 | WindowManager.closeWindow(String(window)); 26 | }, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/WindowManager/types.ts: -------------------------------------------------------------------------------- 1 | export enum WindowStyleMask { 2 | Borderless, 3 | Titled, 4 | Closable, 5 | Miniaturizable, 6 | Resizable, 7 | UnifiedTitleAndToolbar, 8 | FullScreen, 9 | FullSizeContentView, 10 | UtilityWindow, 11 | DocModalWindow, 12 | NonactivatingPanel, 13 | } 14 | 15 | export type WindowOptions = { 16 | title?: string; 17 | windowStyle?: { 18 | mask?: WindowStyleMask[]; 19 | height?: number; 20 | width?: number; 21 | titlebarAppearsTransparent?: boolean; 22 | }; 23 | }; 24 | 25 | export type WindowsConfig = { 26 | [key: string]: { 27 | component: React.ComponentType; 28 | options?: WindowOptions; 29 | }; 30 | }; 31 | 32 | export type WindowsManagerType = { 33 | openWindow: (window: string, options: WindowOptions) => Promise; 34 | closeWindow(window: string): void; 35 | }; 36 | -------------------------------------------------------------------------------- /apps/menu-bar/src/modules/WindowManager/useWindowFocus.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | import { useWindowId } from './WindowProvider'; 4 | import { DeviceEventEmitter } from '../DeviceEventEmitter'; 5 | 6 | export function useWindowFocusEffect(callback: () => void) { 7 | const windowId = useWindowId(); 8 | 9 | useEffect(() => { 10 | const listener = DeviceEventEmitter.addListener('windowFocused', (focusedWindowId: string) => { 11 | if (focusedWindowId === windowId) { 12 | callback(); 13 | } 14 | }); 15 | 16 | return () => { 17 | listener.remove(); 18 | }; 19 | }, [callback, windowId]); 20 | } 21 | -------------------------------------------------------------------------------- /apps/menu-bar/src/popover/DeviceListSectionHeader.tsx: -------------------------------------------------------------------------------- 1 | import { TouchableOpacity } from 'react-native'; 2 | 3 | import SectionHeader from './SectionHeader'; 4 | import AlertIcon from '../assets/icons/AlertTriangle'; 5 | import { View } from '../components/View'; 6 | import Alert from '../modules/Alert'; 7 | import { PlatformColor } from '../modules/PlatformColor'; 8 | import { useCurrentTheme } from '../utils/useExpoTheme'; 9 | 10 | type Props = { 11 | label: string; 12 | errorMessage?: string; 13 | }; 14 | 15 | const DeviceListSectionHeader = ({ label, errorMessage }: Props) => { 16 | const theme = useCurrentTheme(); 17 | 18 | return ( 19 | 20 | Alert.alert('Something went wrong', errorMessage)}> 27 | 33 | 34 | ) : null 35 | } 36 | /> 37 | 38 | ); 39 | }; 40 | 41 | export default DeviceListSectionHeader; 42 | -------------------------------------------------------------------------------- /apps/menu-bar/src/popover/DevicesListError.tsx: -------------------------------------------------------------------------------- 1 | import { TouchableOpacity } from 'react-native'; 2 | 3 | import AlertIcon from '../assets/icons/AlertTriangle'; 4 | import { View, Text } from '../components'; 5 | import Alert from '../modules/Alert'; 6 | 7 | const DevicesListError = ({ error }: { error: Error }) => { 8 | return ( 9 | 10 | Alert.alert('Something went wrong', error.message)}> 13 | 14 | Something went wrong 15 | 16 | Unable to list devices, click here to see the full error 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default DevicesListError; 24 | -------------------------------------------------------------------------------- /apps/menu-bar/src/popover/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { ErrorInfo, FunctionComponent, PropsWithChildren, createElement } from 'react'; 2 | 3 | export type FallbackProps = { 4 | error?: Error; 5 | errorInfo?: string; 6 | }; 7 | 8 | type Props = PropsWithChildren<{ 9 | fallback: FunctionComponent; 10 | }>; 11 | type State = { 12 | hasError: boolean; 13 | error?: Error; 14 | errorInfo?: string; 15 | }; 16 | 17 | export class ErrorBoundary extends React.Component { 18 | constructor(props: Props) { 19 | super(props); 20 | this.state = { 21 | hasError: false, 22 | }; 23 | } 24 | 25 | static getDerivedStateFromError(_error: Error) { 26 | return { hasError: true }; 27 | } 28 | 29 | componentDidCatch(error: Error, errorInfo: ErrorInfo) { 30 | this.setState({ 31 | error, 32 | errorInfo: JSON.stringify(errorInfo), 33 | }); 34 | } 35 | 36 | render() { 37 | if (this.state.hasError) { 38 | // You can render any custom fallback UI 39 | return createElement(this.props.fallback, { 40 | error: this.state.error, 41 | errorInfo: this.state.errorInfo, 42 | }); 43 | } 44 | 45 | return this.props.children; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /apps/menu-bar/src/popover/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | 4 | import Item from './Item'; 5 | import { Divider, Text, View } from '../components'; 6 | import MenuBarModule from '../modules/MenuBarModule'; 7 | import { WindowsNavigator } from '../windows'; 8 | 9 | export const FOOTER_HEIGHT = 62; 10 | 11 | const Footer = () => { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | WindowsNavigator.open('Settings')}> 19 | Settings… 20 | 21 | 22 | Quit 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default memo(Footer); 30 | 31 | const styles = StyleSheet.create({ 32 | container: { 33 | height: FOOTER_HEIGHT, 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /apps/menu-bar/src/popover/Item.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, PropsWithChildren, useState } from 'react'; 2 | import { Pressable, PressableProps, StyleProp, StyleSheet, ViewStyle } from 'react-native'; 3 | 4 | import { Row, Text } from '../components'; 5 | import { useCurrentTheme } from '../utils/useExpoTheme'; 6 | 7 | type Props = PropsWithChildren & { 8 | shortcut?: string; 9 | style?: StyleProp; 10 | }; 11 | 12 | const Item = ({ children, onPress, shortcut, style }: Props) => { 13 | const [isHovered, setHovered] = useState(false); 14 | const theme = useCurrentTheme(); 15 | 16 | return ( 17 | setHovered(true)} 19 | onHoverOut={() => setHovered(false)} 20 | onPress={onPress} 21 | style={[ 22 | styles.itemContainer, 23 | style, 24 | isHovered && { 25 | backgroundColor: theme === 'dark' ? 'rgba(255,255,255,.12)' : 'rgba(0,0,0,.12)', 26 | }, 27 | ]}> 28 | 29 | {children} 30 | {shortcut && {shortcut}} 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default memo(Item); 37 | 38 | const styles = StyleSheet.create({ 39 | itemContainer: { 40 | borderRadius: 4, 41 | marginHorizontal: 6, 42 | }, 43 | shortcut: { 44 | marginLeft: 'auto', 45 | opacity: 0.4, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /apps/menu-bar/src/popover/SectionHeader.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | 4 | import { Row, Text } from '../components'; 5 | import { useTheme } from '../providers/ThemeProvider'; 6 | 7 | export const SECTION_HEADER_HEIGHT = 20; 8 | 9 | type Props = { 10 | label: string; 11 | accessoryRight?: React.ReactNode; 12 | }; 13 | 14 | const SectionHeader = ({ accessoryRight, label }: Props) => { 15 | const theme = useTheme(); 16 | return ( 17 | 18 | 23 | {label} 24 | 25 | {accessoryRight ? accessoryRight : null} 26 | 27 | ); 28 | }; 29 | 30 | export default memo(SectionHeader); 31 | 32 | const styles = StyleSheet.create({ 33 | row: { height: SECTION_HEADER_HEIGHT }, 34 | }); 35 | -------------------------------------------------------------------------------- /apps/menu-bar/src/providers/FluentProvider/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react'; 2 | 3 | export const withFluentProvider =

(WrappedComponent: React.ComponentType

) => { 4 | return (props: P) => { 5 | return ; 6 | }; 7 | }; 8 | 9 | export const FluentProvider = ({ children }: { children: ReactElement }) => { 10 | return children; 11 | }; 12 | -------------------------------------------------------------------------------- /apps/menu-bar/src/providers/FluentProvider/index.web.tsx: -------------------------------------------------------------------------------- 1 | import { FluentProvider as ReactFluentProvider } from '@fluentui/react-provider'; 2 | import { webLightTheme, webDarkTheme, Theme } from '@fluentui/react-theme'; 3 | import { CSSProperties, ComponentType, ReactElement } from 'react'; 4 | import { useColorScheme } from 'react-native'; 5 | 6 | const lightTheme: Theme = { 7 | ...webLightTheme, 8 | colorNeutralBackground1: 'var(--orbit-window-background)', 9 | }; 10 | 11 | const darkTheme: Theme = { 12 | ...webDarkTheme, 13 | colorNeutralBackground1: 'var(--orbit-window-background)', 14 | }; 15 | 16 | export const FluentProvider = ({ 17 | children, 18 | style, 19 | }: { 20 | children: ReactElement; 21 | style?: CSSProperties; 22 | }) => { 23 | const scheme = useColorScheme(); 24 | const theme = scheme === 'dark' ? darkTheme : lightTheme; 25 | 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | }; 32 | 33 | export const withFluentProvider =

(WrappedComponent: ComponentType

) => { 34 | const WithFluentProvider = (props: P) => { 35 | return ( 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | return WithFluentProvider; 43 | }; 44 | -------------------------------------------------------------------------------- /apps/menu-bar/src/providers/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useColorScheme } from 'react-native'; 3 | 4 | type ThemePreference = 'light' | 'dark' | 'no-preference'; 5 | type Theme = 'light' | 'dark'; 6 | 7 | const ThemeContext = React.createContext('light'); 8 | export const useTheme = () => React.useContext(ThemeContext); 9 | 10 | type ThemeProviderProps = { 11 | children: React.ReactNode; 12 | themePreference?: ThemePreference; 13 | }; 14 | 15 | export function ThemeProvider({ children, themePreference = 'no-preference' }: ThemeProviderProps) { 16 | const systemTheme = useColorScheme(); 17 | 18 | const theme = React.useMemo(() => { 19 | if (themePreference !== 'no-preference') { 20 | return themePreference; 21 | } 22 | 23 | return systemTheme ?? 'light'; 24 | }, [themePreference, systemTheme]); 25 | 26 | return {children}; 27 | } 28 | -------------------------------------------------------------------------------- /apps/menu-bar/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | import { Linking } from '../modules/Linking'; 2 | import MenuBarModule from '../modules/MenuBarModule'; 3 | 4 | export const openProjectsSelectorURL = () => { 5 | Linking.openURL('https://expo.dev/accounts/[account]/projects/[project]/builds'); 6 | MenuBarModule.closePopover(); 7 | }; 8 | -------------------------------------------------------------------------------- /apps/menu-bar/src/utils/useExpoTheme.tsx: -------------------------------------------------------------------------------- 1 | import { lightTheme, darkTheme, palette } from '@expo/styleguide-native'; 2 | import * as React from 'react'; 3 | 4 | import { ThemeProvider, useTheme } from '../providers/ThemeProvider'; 5 | 6 | export type ExpoTheme = typeof lightTheme; 7 | 8 | export function useCurrentTheme(): 'light' | 'dark' { 9 | const theme = useTheme(); 10 | return theme; 11 | } 12 | 13 | export function useExpoTheme(): ExpoTheme { 14 | const theme = useTheme(); 15 | 16 | if (theme === 'dark') { 17 | return darkTheme; 18 | } 19 | 20 | return lightTheme; 21 | } 22 | 23 | export function useExpoPalette() { 24 | const theme = useTheme(); 25 | return palette[theme]; 26 | } 27 | 28 | export const withThemeProvider =

(WrappedComponent: React.ComponentType

) => { 29 | const WithThemeProvider = (props: P) => { 30 | return ( 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | return WithThemeProvider; 38 | }; 39 | -------------------------------------------------------------------------------- /apps/menu-bar/src/windows/index.ts: -------------------------------------------------------------------------------- 1 | import DebugMenu from './DebugMenu'; 2 | import Onboarding from './Onboarding'; 3 | import Settings from './Settings'; 4 | import { WindowStyleMask, createWindowsNavigator } from '../modules/WindowManager'; 5 | 6 | export const WindowsNavigator = createWindowsNavigator({ 7 | Settings: { 8 | component: Settings, 9 | options: { 10 | title: 'Settings', 11 | windowStyle: { 12 | mask: [WindowStyleMask.Titled, WindowStyleMask.Closable], 13 | titlebarAppearsTransparent: true, 14 | height: 580, 15 | width: 500, 16 | }, 17 | }, 18 | }, 19 | Onboarding: { 20 | component: Onboarding, 21 | options: { 22 | title: '', 23 | windowStyle: { 24 | mask: [WindowStyleMask.Titled, WindowStyleMask.FullSizeContentView], 25 | titlebarAppearsTransparent: true, 26 | height: 618, 27 | width: 400, 28 | }, 29 | }, 30 | }, 31 | DebugMenu: { 32 | component: DebugMenu, 33 | options: { 34 | title: 'Debug Menu', 35 | windowStyle: { 36 | height: 600, 37 | width: 800, 38 | }, 39 | }, 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /apps/menu-bar/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@react-native/typescript-config/tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["es2019", "dom"] 5 | }, 6 | "exclude": ["node_modules", "electron"] 7 | } 8 | -------------------------------------------------------------------------------- /eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 5.4.0" 4 | }, 5 | "build": { 6 | "development": { 7 | "developmentClient": true, 8 | "distribution": "internal" 9 | }, 10 | "preview": { 11 | "distribution": "internal" 12 | }, 13 | "production": {} 14 | }, 15 | "submit": { 16 | "production": {} 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "version": "0.0.0", 4 | "npmClient": "yarn" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "monorepo", 4 | "version": "1.0.0", 5 | "workspaces": [ 6 | "apps/*", 7 | "packages/*" 8 | ], 9 | "scripts": { 10 | "build": "lerna run build", 11 | "lint": "lerna run lint", 12 | "watch": "lerna run watch --stream --parallel", 13 | "typecheck": "lerna run typecheck", 14 | "postinstall": "patch-package" 15 | }, 16 | "dependencies": { 17 | "@tsconfig/node12": "1.0.7" 18 | }, 19 | "devDependencies": { 20 | "lerna": "^7.3.0", 21 | "patch-package": "^8.0.0", 22 | "postinstall-postinstall": "^2.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/common-types/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: 'universe/node', 4 | ignorePatterns: ['build/**', 'node_modules/**'], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/common-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common-types", 3 | "version": "1.0.0", 4 | "main": "build/index.js", 5 | "scripts": { 6 | "build": "tsc", 7 | "watch": "yarn build --watch --preserveWatchOutput", 8 | "lint": "eslint .", 9 | "typecheck": "tsc" 10 | }, 11 | "devDependencies": { 12 | "eslint-config-universe": "^15.0.3", 13 | "typescript": "^5.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/common-types/src/InternalError.ts: -------------------------------------------------------------------------------- 1 | import { JSONObject } from '@expo/json-file'; 2 | 3 | const ERROR_PREFIX = 'Error: '; 4 | export default class InternalError extends Error { 5 | override readonly name = 'InternalError'; 6 | code: InternalErrorCode; 7 | details?: JSONObject; 8 | 9 | constructor(code: InternalErrorCode, message: string, details?: JSONObject) { 10 | super(''); 11 | 12 | // If e.toString() was called to get `message` we don't want it to look 13 | // like "Error: Error:". 14 | if (message.startsWith(ERROR_PREFIX)) { 15 | message = message.substring(ERROR_PREFIX.length); 16 | } 17 | 18 | this.message = message; 19 | this.code = code; 20 | this.details = details; 21 | } 22 | } 23 | 24 | export type InternalErrorCode = 25 | | 'APPLE_APP_VERIFICATION_FAILED' 26 | | 'APPLE_DEVICE_LOCKED' 27 | | 'EXPO_GO_NOT_INSTALLED_ON_DEVICE' 28 | | 'INVALID_VERSION' 29 | | 'MULTIPLE_APPS_IN_TARBALL' 30 | | 'TOOL_CHECK_FAILED' 31 | | 'XCODE_COMMAND_LINE_TOOLS_NOT_INSTALLED' 32 | | 'XCODE_LICENSE_NOT_ACCEPTED' 33 | | 'XCODE_NOT_INSTALLED' 34 | | 'SIMCTL_NOT_AVAILABLE' 35 | | 'NO_DEVELOPMENT_BUILDS_AVAILABLE' 36 | | 'UNAUTHORIZED_ACCOUNT'; 37 | 38 | export type MultipleAppsInTarballErrorDetails = { 39 | apps: { 40 | name: string; 41 | path: string; 42 | }[]; 43 | }; 44 | -------------------------------------------------------------------------------- /packages/common-types/src/cli-commands/checkTools.ts: -------------------------------------------------------------------------------- 1 | export type FailureReason = { 2 | message: string; 3 | command?: string; 4 | }; 5 | 6 | export type PlatformToolsCheck = { 7 | android?: { 8 | success: boolean; 9 | reason?: FailureReason; 10 | }; 11 | ios?: { 12 | success: boolean; 13 | reason?: FailureReason; 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/common-types/src/cli-commands/index.ts: -------------------------------------------------------------------------------- 1 | import * as CheckTools from './checkTools'; 2 | import * as ListDevices from './listDevices'; 3 | import { Platform } from './platform'; 4 | 5 | export { Platform, ListDevices, CheckTools }; 6 | -------------------------------------------------------------------------------- /packages/common-types/src/cli-commands/listDevices.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AndroidConnectedDevice, 3 | AndroidEmulator, 4 | AppleConnectedDevice, 5 | IosSimulator, 6 | } from '../devices'; 7 | import { Platform } from './platform'; 8 | 9 | export type Device

= P extends Platform.Ios 10 | ? IosSimulator | AppleConnectedDevice 11 | : P extends Platform.Android 12 | ? AndroidConnectedDevice | AndroidEmulator 13 | : never; 14 | 15 | export type DevicesPerPlatform = { 16 | [P in Exclude]: { 17 | devices: Device

[]; 18 | error?: { code: string; message: string }; 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/common-types/src/cli-commands/platform.ts: -------------------------------------------------------------------------------- 1 | export enum Platform { 2 | Android = 'android', 3 | Ios = 'ios', 4 | All = 'all', 5 | } 6 | -------------------------------------------------------------------------------- /packages/common-types/src/constants.ts: -------------------------------------------------------------------------------- 1 | const host = 'api.expo.dev'; 2 | const origin = `https://${host}`; 3 | const websiteOrigin = 'https://expo.dev'; 4 | 5 | export const Config = { 6 | api: { 7 | host, 8 | origin, 9 | }, 10 | website: { 11 | origin: websiteOrigin, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/common-types/src/devices.ts: -------------------------------------------------------------------------------- 1 | export interface AppleConnectedDevice { 2 | /** @example `00008101-001964A22629003A` */ 3 | udid: string; 4 | /** @example `Evan's phone` */ 5 | name: string; 6 | /** @example `iPhone13,4` */ 7 | model: string; 8 | /** @example `device` */ 9 | deviceType: 'device' | 'catalyst'; 10 | /** @example `USB` */ 11 | connectionType: 'USB' | 'Network'; 12 | /** @example `15.4.1` */ 13 | osVersion: string; 14 | osType: 'iOS'; 15 | developerModeStatus?: 'enabled' | 'disabled'; 16 | } 17 | 18 | export interface IosSimulator { 19 | runtime: string; 20 | osVersion: string; 21 | windowName: string; 22 | osType: 'iOS' | 'tvOS'; 23 | state: 'Booted' | 'Shutdown'; 24 | isAvailable: boolean; 25 | name: string; 26 | udid: string; 27 | lastBootedAt?: number; 28 | deviceType: 'simulator'; 29 | } 30 | 31 | export interface AndroidEmulator { 32 | pid?: string; 33 | name: string; 34 | osType: 'Android'; 35 | deviceType: 'emulator'; 36 | state: 'Booted' | 'Shutdown'; 37 | } 38 | 39 | export interface AndroidConnectedDevice { 40 | pid: string; 41 | model: string; 42 | name: string; 43 | osType: 'Android'; 44 | deviceType: 'device'; 45 | connectionType?: 'USB' | 'Network'; 46 | } 47 | 48 | export type Device = AppleConnectedDevice | IosSimulator | AndroidEmulator | AndroidConnectedDevice; 49 | -------------------------------------------------------------------------------- /packages/common-types/src/index.ts: -------------------------------------------------------------------------------- 1 | import InternalError, { InternalErrorCode } from './InternalError'; 2 | import * as CliCommands from './cli-commands'; 3 | import { Platform } from './cli-commands'; 4 | import { Config } from './constants'; 5 | import * as Devices from './devices'; 6 | import * as StorageUtils from './storage'; 7 | 8 | export { Devices, CliCommands, InternalError, InternalErrorCode, Platform, StorageUtils, Config }; 9 | -------------------------------------------------------------------------------- /packages/common-types/src/storage.ts: -------------------------------------------------------------------------------- 1 | export const MMKVInstanceId = 'mmkv.default'; 2 | const AUTH_FILE_NAME = 'auth.json'; 3 | 4 | export function getExpoOrbitDirectory(homedir: string) { 5 | return `${homedir}/.expo/orbit`; 6 | } 7 | 8 | export function userSettingsFile(homedir: string): string { 9 | return `${getExpoOrbitDirectory(homedir)}/${AUTH_FILE_NAME}`; 10 | } 11 | 12 | export type UserSettingsData = { 13 | sessionSecret?: string; 14 | envVars?: Record; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/common-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "outDir": "build", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/eas-shared/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: 'universe/node', 4 | ignorePatterns: ['build/**', 'node_modules/**'], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/eas-shared/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['@expo/babel-preset-cli'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/eas-shared/src/api/internal/Config.ts: -------------------------------------------------------------------------------- 1 | import getenv from 'getenv'; 2 | 3 | import * as Env from '../../env'; 4 | 5 | interface ApiConfig { 6 | scheme: string; 7 | host: string; 8 | port: number | null; 9 | } 10 | 11 | interface XDLConfig { 12 | api: ApiConfig; 13 | developerTool: string; 14 | } 15 | 16 | function getAPI(): ApiConfig { 17 | if (Env.isLocal()) { 18 | return { 19 | scheme: 'http', 20 | host: 'localhost', 21 | port: 3000, 22 | }; 23 | } else if (Env.isStaging()) { 24 | return { 25 | scheme: getenv.string('XDL_SCHEME', 'https'), 26 | host: 'staging.exp.host', 27 | port: getenv.int('XDL_PORT', 0) || null, 28 | }; 29 | } else { 30 | return { 31 | scheme: getenv.string('XDL_SCHEME', 'https'), 32 | host: getenv.string('XDL_HOST', 'exp.host'), 33 | port: getenv.int('XDL_PORT', 0) || null, 34 | }; 35 | } 36 | } 37 | 38 | const config: XDLConfig = { 39 | api: getAPI(), 40 | developerTool: 'expo-cli', 41 | }; 42 | 43 | export default config; 44 | -------------------------------------------------------------------------------- /packages/eas-shared/src/api/internal/ConnectionStatus.ts: -------------------------------------------------------------------------------- 1 | let offline: boolean = false; 2 | 3 | export function setIsOffline(bool: boolean): void { 4 | offline = bool; 5 | } 6 | 7 | export function isOffline(): boolean { 8 | return offline; 9 | } 10 | -------------------------------------------------------------------------------- /packages/eas-shared/src/api/internal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Config } from './Config'; 2 | export * as ConnectionStatus from './ConnectionStatus'; 3 | -------------------------------------------------------------------------------- /packages/eas-shared/src/downloadApkAsync.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | import { downloadAppAsync } from './downloadAppAsync'; 5 | import UserSettings from './userSettings'; 6 | import * as Versions from './versions'; 7 | 8 | function _apkCacheDirectory() { 9 | const dotExpoHomeDirectory = UserSettings.dotExpoHomeDirectory(); 10 | const dir = path.join(dotExpoHomeDirectory, 'android-apk-cache'); 11 | fs.mkdirpSync(dir); 12 | return dir; 13 | } 14 | 15 | export async function downloadApkAsync( 16 | url: string, 17 | downloadProgressCallback?: (roundedProgress: number) => void 18 | ) { 19 | if (!url) { 20 | const versions = await Versions.versionsAsync(); 21 | url = versions.androidUrl; 22 | } 23 | 24 | const filename = path.parse(url).name; 25 | const apkPath = path.join(_apkCacheDirectory(), `${filename}.apk`); 26 | 27 | if (await fs.pathExists(apkPath)) { 28 | return apkPath; 29 | } 30 | 31 | await downloadAppAsync(url, apkPath, undefined, downloadProgressCallback); 32 | return apkPath; 33 | } 34 | -------------------------------------------------------------------------------- /packages/eas-shared/src/env.ts: -------------------------------------------------------------------------------- 1 | import getenv from 'getenv'; 2 | 3 | export function isDebug(): boolean { 4 | return getenv.boolish('EXPO_DEBUG', false); 5 | } 6 | 7 | export function isStaging(): boolean { 8 | return getenv.boolish('EXPO_STAGING', false); 9 | } 10 | 11 | export function isLocal(): boolean { 12 | return getenv.boolish('EXPO_LOCAL', false); 13 | } 14 | 15 | export function isMenuBar(): boolean { 16 | return getenv.boolish('EXPO_MENU_BAR', false); 17 | } 18 | -------------------------------------------------------------------------------- /packages/eas-shared/src/fetch.ts: -------------------------------------------------------------------------------- 1 | import https from 'https'; 2 | import fetch, { RequestInfo, RequestInit, Response } from 'node-fetch'; 3 | import { systemCertsSync } from 'system-ca'; 4 | export { Response, RequestInit } from 'node-fetch'; 5 | 6 | let ca: string[] | undefined = undefined; 7 | try { 8 | ca = systemCertsSync({ includeNodeCertificates: true }); 9 | } catch {} 10 | const agent = new https.Agent({ 11 | ca, 12 | }); 13 | 14 | export class RequestError extends Error { 15 | constructor( 16 | message: string, 17 | public readonly response: Response 18 | ) { 19 | super(message); 20 | } 21 | } 22 | 23 | export default async function (url: RequestInfo, init?: RequestInit): Promise { 24 | const response = await fetch(url, { 25 | agent, 26 | ...init, 27 | }); 28 | if (response.status >= 400) { 29 | throw new RequestError(`Request failed: ${response.status} (${response.statusText})`, response); 30 | } 31 | return response; 32 | } 33 | -------------------------------------------------------------------------------- /packages/eas-shared/src/files.ts: -------------------------------------------------------------------------------- 1 | export function formatBytes(bytes: number): string { 2 | if (bytes === 0) { 3 | return `0`; 4 | } 5 | let multiplier = 1; 6 | if (bytes < 1024 * multiplier) { 7 | return `${Math.floor(bytes)} B`; 8 | } 9 | multiplier *= 1024; 10 | if (bytes < 102.4 * multiplier) { 11 | return `${(bytes / multiplier).toFixed(1)} KB`; 12 | } 13 | if (bytes < 1024 * multiplier) { 14 | return `${Math.floor(bytes / 1024)} KB`; 15 | } 16 | multiplier *= 1024; 17 | if (bytes < 102.4 * multiplier) { 18 | return `${(bytes / multiplier).toFixed(1)} MB`; 19 | } 20 | if (bytes < 1024 * multiplier) { 21 | return `${Math.floor(bytes / multiplier)} MB`; 22 | } 23 | multiplier *= 1024; 24 | if (bytes < 102.4 * multiplier) { 25 | return `${(bytes / multiplier).toFixed(1)} GB`; 26 | } 27 | return `${Math.floor(bytes / 1024)} GB`; 28 | } 29 | -------------------------------------------------------------------------------- /packages/eas-shared/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | downloadAndMaybeExtractAppAsync, 3 | AppPlatform, 4 | extractAppFromLocalArchiveAsync, 5 | } from './download'; 6 | import * as Env from './env'; 7 | import * as ManifestUtils from './manifest'; 8 | import { Manifest } from './manifest'; 9 | import { runAppOnIosSimulatorAsync, runAppOnAndroidEmulatorAsync, detectIOSAppType } from './run'; 10 | import * as Emulator from './run/android/emulator'; 11 | import { assertExecutablesExistAsync as validateAndroidSystemRequirementsAsync } from './run/android/systemRequirements'; 12 | import AppleDevice from './run/ios/device'; 13 | import * as Simulator from './run/ios/simulator'; 14 | import { validateSystemRequirementsAsync as validateIOSSystemRequirementsAsync } from './run/ios/systemRequirements'; 15 | 16 | export { 17 | AppPlatform, 18 | downloadAndMaybeExtractAppAsync, 19 | extractAppFromLocalArchiveAsync, 20 | runAppOnIosSimulatorAsync, 21 | runAppOnAndroidEmulatorAsync, 22 | validateAndroidSystemRequirementsAsync, 23 | validateIOSSystemRequirementsAsync, 24 | detectIOSAppType, 25 | Emulator, 26 | Simulator, 27 | AppleDevice, 28 | Env, 29 | ManifestUtils, 30 | Manifest, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/eas-shared/src/paths.ts: -------------------------------------------------------------------------------- 1 | import envPaths from 'env-paths'; 2 | 3 | // Paths for storing things like data, config, cache, etc. 4 | // Should use the correct OS-specific paths (e.g. XDG base directory on Linux) 5 | const { 6 | data: DATA_PATH, 7 | config: CONFIG_PATH, 8 | cache: CACHE_PATH, 9 | log: LOG_PATH, 10 | temp: TEMP_PATH, 11 | } = envPaths('expo-orbit'); 12 | 13 | export const getDataDirectory = (): string => DATA_PATH; 14 | export const getConfigDirectory = (): string => CONFIG_PATH; 15 | export const getCacheDirectory = (): string => CACHE_PATH; 16 | export const getLogDirectory = (): string => LOG_PATH; 17 | export const getTmpDirectory = (): string => TEMP_PATH; 18 | -------------------------------------------------------------------------------- /packages/eas-shared/src/run/android/sdk.ts: -------------------------------------------------------------------------------- 1 | import { pathExists } from 'fs-extra'; 2 | import os from 'os'; 3 | import path from 'path'; 4 | 5 | export const ANDROID_DEFAULT_LOCATION: Readonly>> = { 6 | darwin: path.join(os.homedir(), 'Library', 'Android', 'sdk'), 7 | linux: path.join(os.homedir(), 'Android', 'Sdk'), 8 | win32: path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk'), 9 | }; 10 | 11 | const ANDROID_DEFAULT_LOCATION_FOR_CURRENT_PLATFORM = ANDROID_DEFAULT_LOCATION[process.platform]; 12 | 13 | export async function getAndroidSdkRootAsync(): Promise { 14 | if (process.env.ANDROID_HOME && (await pathExists(process.env.ANDROID_HOME))) { 15 | return process.env.ANDROID_HOME; 16 | } else if (process.env.ANDROID_SDK_ROOT && (await pathExists(process.env.ANDROID_SDK_ROOT))) { 17 | return process.env.ANDROID_SDK_ROOT; 18 | } else if ( 19 | ANDROID_DEFAULT_LOCATION_FOR_CURRENT_PLATFORM && 20 | (await pathExists(ANDROID_DEFAULT_LOCATION_FOR_CURRENT_PLATFORM)) 21 | ) { 22 | return ANDROID_DEFAULT_LOCATION_FOR_CURRENT_PLATFORM; 23 | } else { 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/eas-shared/src/run/android/systemRequirements.ts: -------------------------------------------------------------------------------- 1 | import spawnAsync from '@expo/spawn-async'; 2 | import chalk from 'chalk'; 3 | import { InternalError } from 'common-types'; 4 | 5 | import { getAaptExecutableAsync } from './aapt'; 6 | import { getAdbExecutableAsync } from './adb'; 7 | import { getEmulatorExecutableAsync } from './emulator'; 8 | 9 | async function assertExecutableExistsAsync(executable: string, options?: string[]): Promise { 10 | try { 11 | await spawnAsync(executable, options); 12 | } catch (err: any) { 13 | throw new InternalError( 14 | 'TOOL_CHECK_FAILED', 15 | `${chalk.bold( 16 | executable 17 | )} executable doesn't seem to work. Please make sure Android Studio is installed on your device and ${chalk.bold( 18 | 'ANDROID_HOME' 19 | )} or ${chalk.bold('ANDROID_SDK_ROOT')} env variables are set. 20 | ${err.message}` 21 | ); 22 | } 23 | } 24 | 25 | export async function assertExecutablesExistAsync(): Promise { 26 | await assertExecutableExistsAsync(await getAdbExecutableAsync(), ['--version']); 27 | await assertExecutableExistsAsync(await getEmulatorExecutableAsync(), ['-list-avds']); 28 | await assertExecutableExistsAsync(await getAaptExecutableAsync(), ['version']); 29 | } 30 | -------------------------------------------------------------------------------- /packages/eas-shared/src/run/ios/SimControl.ts: -------------------------------------------------------------------------------- 1 | type DeviceState = 'Shutdown' | 'Booted'; 2 | 3 | export type SimulatorDevice = { 4 | availabilityError: 'runtime profile not found'; 5 | /** 6 | * '/Users/name/Library/Developer/CoreSimulator/Devices/00E55DC0-0364-49DF-9EC6-77BE587137D4/data' 7 | */ 8 | dataPath: string; 9 | /** 10 | * '/Users/name/Library/Logs/CoreSimulator/00E55DC0-0364-49DF-9EC6-77BE587137D4' 11 | */ 12 | logPath: string; 13 | /** 14 | * '00E55DC0-0364-49DF-9EC6-77BE587137D4' 15 | */ 16 | udid: string; 17 | /** 18 | * com.apple.CoreSimulator.SimRuntime.tvOS-13-4 19 | */ 20 | runtime: string; 21 | isAvailable: boolean; 22 | /** 23 | * 'com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p' 24 | */ 25 | deviceTypeIdentifier: string; 26 | state: DeviceState; 27 | /** 28 | * 'Apple TV' 29 | */ 30 | name: string; 31 | 32 | osType: OSType; 33 | /** 34 | * '13.4' 35 | */ 36 | osVersion: string; 37 | /** 38 | * 'iPhone 11 (13.6)' 39 | */ 40 | windowName: string; 41 | }; 42 | 43 | export type XCTraceDevice = { 44 | /** 45 | * '00E55DC0-0364-49DF-9EC6-77BE587137D4' 46 | */ 47 | udid: string; 48 | /** 49 | * 'Apple TV' 50 | */ 51 | name: string; 52 | 53 | deviceType: 'device' | 'catalyst'; 54 | /** 55 | * '13.4' 56 | */ 57 | osVersion: string; 58 | }; 59 | 60 | type OSType = 'iOS' | 'tvOS' | 'watchOS' | 'macOS'; 61 | -------------------------------------------------------------------------------- /packages/eas-shared/src/run/ios/appleDevice/client/ServiceClient.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2021 Expo, Inc. 3 | * Copyright (c) 2018 Drifty Co. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | import { Socket } from 'net'; 9 | 10 | import { CommandError } from '../../../../utils/errors'; 11 | import { ProtocolClient } from '../protocol/AbstractProtocol'; 12 | 13 | export abstract class ServiceClient { 14 | constructor( 15 | public socket: Socket, 16 | protected protocolClient: T 17 | ) {} 18 | } 19 | 20 | export class ResponseError extends CommandError { 21 | constructor( 22 | msg: string, 23 | public response: any 24 | ) { 25 | super(msg); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/eas-shared/src/run/ios/constants.ts: -------------------------------------------------------------------------------- 1 | export const EXPO_GO_BUNDLE_IDENTIFIER = 'host.exp.Exponent'; 2 | export const APP_STORE_BUNDLE_IDENTIFIER = 'com.apple.AppStore'; 3 | 4 | export const EXPO_GO_APP_STORE_URL = 'https://apps.apple.com/br/app/expo-go/id982107779'; 5 | -------------------------------------------------------------------------------- /packages/eas-shared/src/run/ios/device.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getConnectedDevicesAsync, 3 | getBundleIdentifierForBinaryAsync, 4 | openURLAsync, 5 | openExpoGoURLAsync, 6 | ensureExpoClientInstalledAsync, 7 | checkIfAppIsInstalled, 8 | } from './appleDevice/AppleDevice'; 9 | import { getAppDeltaDirectory, installOnDeviceAsync } from './appleDevice/installOnDeviceAsync'; 10 | 11 | const AppleDevice = { 12 | getConnectedDevicesAsync, 13 | getAppDeltaDirectory, 14 | installOnDeviceAsync, 15 | getBundleIdentifierForBinaryAsync, 16 | openURLAsync, 17 | openExpoGoURLAsync, 18 | ensureExpoClientInstalledAsync, 19 | checkIfAppIsInstalled, 20 | }; 21 | 22 | export default AppleDevice; 23 | -------------------------------------------------------------------------------- /packages/eas-shared/src/run/ios/inspectApp.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | import { parseBinaryPlistAsync } from '../../utils/parseBinaryPlistAsync'; 5 | 6 | export async function detectIOSAppType(appPath: string): Promise<'device' | 'simulator'> { 7 | const builtInfoPlistPath = path.join(appPath, 'Info.plist'); 8 | if (!fs.existsSync(builtInfoPlistPath)) { 9 | return 'device'; 10 | } 11 | 12 | const { DTPlatformName }: { DTPlatformName: string } = 13 | await parseBinaryPlistAsync(builtInfoPlistPath); 14 | 15 | return DTPlatformName.includes('simulator') ? 'simulator' : 'device'; 16 | } 17 | -------------------------------------------------------------------------------- /packages/eas-shared/src/run/ios/simctl.ts: -------------------------------------------------------------------------------- 1 | import { SpawnOptions, SpawnResult } from '@expo/spawn-async'; 2 | 3 | import { xcrunAsync } from './xcrun'; 4 | 5 | export async function simctlAsync(args: string[], options?: SpawnOptions): Promise { 6 | return await xcrunAsync(['simctl', ...args], options); 7 | } 8 | -------------------------------------------------------------------------------- /packages/eas-shared/src/run/ios/xcode.ts: -------------------------------------------------------------------------------- 1 | import spawnAsync from '@expo/spawn-async'; 2 | import chalk from 'chalk'; 3 | import semver from 'semver'; 4 | 5 | import Log from '../../log'; 6 | 7 | // Based on the RN docs (Aug 2020). 8 | export const MIN_XCODE_VERSION = '9.4.0'; 9 | export const APP_STORE_ID = '497799835'; 10 | 11 | export async function getXcodeVersionAsync(): Promise { 12 | try { 13 | const { stdout } = await spawnAsync('xcodebuild', ['-version']); 14 | 15 | const version = stdout.match(/Xcode (\d+\.\d+)/)?.[1]; 16 | 17 | const semverFormattedVersion = `${version}.0`; 18 | 19 | if (!semver.valid(semverFormattedVersion)) { 20 | Log.warn( 21 | `Xcode version ${chalk.bold(version)} is in unknown format. Expected format is ${chalk.bold( 22 | '12.0' 23 | )}.` 24 | ); 25 | return undefined; 26 | } 27 | 28 | return semverFormattedVersion; 29 | } catch { 30 | // not installed 31 | return undefined; 32 | } 33 | } 34 | 35 | export async function openAppStoreAsync(appId: string): Promise { 36 | const link = getAppStoreLink(appId); 37 | await spawnAsync(`open`, [link]); 38 | } 39 | 40 | function getAppStoreLink(appId: string): string { 41 | if (process.platform === 'darwin') { 42 | // TODO: Is there ever a case where the macappstore isn't available on mac? 43 | return `macappstore://itunes.apple.com/app/id${appId}`; 44 | } 45 | return `https://apps.apple.com/us/app/id${appId}`; 46 | } 47 | -------------------------------------------------------------------------------- /packages/eas-shared/src/timer.ts: -------------------------------------------------------------------------------- 1 | const LABEL = 'DEFAULT'; 2 | const startTimes: Record = {}; 3 | 4 | export function hasTimer(label: string): number | null { 5 | return startTimes[label] ?? null; 6 | } 7 | 8 | export function startTimer(label = LABEL): void { 9 | startTimes[label] = Date.now(); 10 | } 11 | 12 | export function endTimer(label = LABEL, clear: boolean = true): number { 13 | const endTime = Date.now(); 14 | const startTime = startTimes[label]; 15 | if (startTime) { 16 | const delta = endTime - startTime; 17 | if (clear) { 18 | delete startTimes[label]; 19 | } 20 | return delta; 21 | } 22 | throw new Error(`Timer '${label}' has not be started yet`); 23 | } 24 | 25 | /** 26 | * Optimally format milliseconds 27 | * 28 | * @example `1h 2m 3s` 29 | * @example `5m 18s` 30 | * @example `40s` 31 | * @param duration 32 | */ 33 | export function formatMilliseconds(duration: number): string { 34 | const portions: string[] = []; 35 | 36 | const msInHour = 1000 * 60 * 60; 37 | const hours = Math.trunc(duration / msInHour); 38 | if (hours > 0) { 39 | portions.push(hours + 'h'); 40 | duration = duration - hours * msInHour; 41 | } 42 | 43 | const msInMinute = 1000 * 60; 44 | const minutes = Math.trunc(duration / msInMinute); 45 | if (minutes > 0) { 46 | portions.push(minutes + 'm'); 47 | duration = duration - minutes * msInMinute; 48 | } 49 | 50 | const seconds = Math.trunc(duration / 1000); 51 | if (seconds > 0) { 52 | portions.push(seconds + 's'); 53 | } 54 | 55 | return portions.join(' '); 56 | } 57 | -------------------------------------------------------------------------------- /packages/eas-shared/src/utils/delayAsync.ts: -------------------------------------------------------------------------------- 1 | export function delayAsync(ms: number): Promise { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/eas-shared/src/utils/dir.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | 3 | export function directoryExistsSync(file: string): boolean { 4 | try { 5 | return fs.statSync(file)?.isDirectory() ?? false; 6 | } catch { 7 | return false; 8 | } 9 | } 10 | 11 | export async function directoryExistsAsync(file: string): Promise { 12 | return (await fs.promises.stat(file).catch(() => null))?.isDirectory() ?? false; 13 | } 14 | 15 | export async function fileExistsAsync(file: string): Promise { 16 | return (await fs.promises.stat(file).catch(() => null))?.isFile() ?? false; 17 | } 18 | 19 | export const ensureDirectoryAsync = (path: string) => fs.promises.mkdir(path, { recursive: true }); 20 | 21 | export const ensureDirectory = (path: string) => fs.mkdirSync(path, { recursive: true }); 22 | 23 | export const copySync = fs.copySync; 24 | 25 | export const copyAsync = fs.copy; 26 | 27 | export const removeAsync = fs.remove; 28 | -------------------------------------------------------------------------------- /packages/eas-shared/src/utils/fn.ts: -------------------------------------------------------------------------------- 1 | /** `lodash.memoize` */ 2 | export function memoize any>(fn: T): T { 3 | const cache: { [key: string]: any } = {}; 4 | return ((...args: any[]) => { 5 | const key = JSON.stringify(args); 6 | if (cache[key]) { 7 | return cache[key]; 8 | } 9 | const result = fn(...args); 10 | cache[key] = result; 11 | return result; 12 | }) as any; 13 | } 14 | 15 | /** memoizes an async function to prevent subsequent calls that might be invoked before the function has finished resolving. */ 16 | export function guardAsync Promise>(fn: T): T { 17 | let invoked = false; 18 | let returnValue: V; 19 | 20 | const guard: any = async (...args: any[]): Promise => { 21 | if (!invoked) { 22 | invoked = true; 23 | returnValue = await fn(...args); 24 | } 25 | 26 | return returnValue; 27 | }; 28 | 29 | return guard; 30 | } 31 | 32 | export function uniqBy(array: T[], key: (item: T) => string): T[] { 33 | const seen: { [key: string]: boolean } = {}; 34 | return array.filter((item) => { 35 | const k = key(item); 36 | if (seen[k]) { 37 | return false; 38 | } 39 | seen[k] = true; 40 | return true; 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /packages/eas-shared/src/utils/parseBinaryPlistAsync.ts: -------------------------------------------------------------------------------- 1 | import plist from '@expo/plist'; 2 | import binaryPlist from 'bplist-parser'; 3 | import fs from 'fs'; 4 | 5 | const CHAR_CHEVRON_OPEN = 60; 6 | const CHAR_B_LOWER = 98; 7 | // .mobileprovision 8 | // const CHAR_ZERO = 30; 9 | 10 | export async function parseBinaryPlistAsync(plistPath: string) { 11 | return parsePlistBuffer(await fs.promises.readFile(plistPath)); 12 | } 13 | 14 | export function parsePlistBuffer(contents: Buffer) { 15 | if (contents[0] === CHAR_CHEVRON_OPEN) { 16 | const info = plist.parse(contents.toString()); 17 | if (Array.isArray(info)) return info[0]; 18 | return info; 19 | } else if (contents[0] === CHAR_B_LOWER) { 20 | const info = binaryPlist.parseBuffer(contents); 21 | if (Array.isArray(info)) return info[0]; 22 | return info; 23 | } else { 24 | throw new Error(`Cannot parse plist of type byte (0x${contents[0].toString(16)})`); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/eas-shared/src/utils/promise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a promise that will be resolved after given ms milliseconds. 3 | * 4 | * @param ms A number of milliseconds to sleep. 5 | * @returns A promise that resolves after the provided number of milliseconds. 6 | */ 7 | export async function sleepAsync(ms: number): Promise { 8 | return new Promise((res) => setTimeout(res, ms)); 9 | } 10 | -------------------------------------------------------------------------------- /packages/eas-shared/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "outDir": "build/cjs" 6 | }, 7 | "exclude": ["**/__mocks__/*", "**/__tests__/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/eas-shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "importHelpers": true, 5 | "esModuleInterop": true, 6 | "noFallthroughCasesInSwitch": true, 7 | "noImplicitOverride": true, 8 | "noImplicitReturns": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "strict": true, 12 | "outDir": "build/esm", 13 | "rootDir": "src", 14 | "allowSyntheticDefaultImports": true, 15 | "lib": ["ES2022"], 16 | "module": "ES2022", 17 | "moduleResolution": "node", 18 | "target": "ES2022", 19 | "typeRoots": ["../../ts-declarations", "src/ts-declarations", "./node_modules/@types"] 20 | }, 21 | "include": ["src/**/*"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-native-electron-modules/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: 'universe/node', 4 | ignorePatterns: ['build/**', 'node_modules/**'], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/react-native-electron-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-electron-modules", 3 | "version": "1.0.0", 4 | "main": "build/index.js", 5 | "scripts": { 6 | "build": "tsc", 7 | "watch": "yarn build --watch --preserveWatchOutput", 8 | "lint": "eslint .", 9 | "typecheck": "tsc" 10 | }, 11 | "devDependencies": { 12 | "electron": "28.2.0", 13 | "eslint-config-universe": "^15.0.3", 14 | "typescript": "^5.8.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-native-electron-modules/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './registerElectronModules'; 2 | export * from './exposeElectronModules'; 3 | export * from './requireElectronModule'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/react-native-electron-modules/src/registerElectronModules.ts: -------------------------------------------------------------------------------- 1 | import { ipcMain } from 'electron'; 2 | 3 | import { ElectronModule, IpcMainModules, Registry } from './types'; 4 | 5 | const ipcMainModules: IpcMainModules = {}; 6 | 7 | export function registerMainModules(modules: Registry) { 8 | modules.forEach((module) => { 9 | registerMainModule(module); 10 | }); 11 | 12 | ipcMain.on('get-all-ipc-main-modules', (event) => { 13 | event.returnValue = ipcMainModules; 14 | }); 15 | } 16 | 17 | function registerMainModule(module: ElectronModule) { 18 | ipcMainModules[module.name] = { functions: [], values: [] }; 19 | 20 | Object.entries(module).forEach(([key, value]) => { 21 | const moduleFunctionKey = `${module.name}:${key}`; 22 | if (typeof value === 'function') { 23 | // Adds a handler for an invokeable IPC and send IpcMainInvokeEvent as the last argument 24 | ipcMain.handle(moduleFunctionKey, (event, ...args) => value(...args, event)); 25 | ipcMainModules[module.name].functions.push(key); 26 | } else { 27 | // No need to add a handler for the module name 28 | if (key === 'name') return; 29 | 30 | ipcMain.on(moduleFunctionKey, (event) => { 31 | event.returnValue = value; 32 | }); 33 | ipcMainModules[module.name].values.push(key); 34 | } 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /packages/react-native-electron-modules/src/requireElectronModule.ts: -------------------------------------------------------------------------------- 1 | import { ElectronModule } from './types'; 2 | 3 | type ReactNativeElectronModulesObject = { 4 | modules: { 5 | [moduleName: string]: ElectronModule; 6 | }; 7 | }; 8 | 9 | declare global { 10 | // eslint-disable-next-line no-var 11 | var __reactNativeElectronModules: ReactNativeElectronModulesObject | undefined; 12 | } 13 | 14 | /** 15 | * Imports a module registered with the given name. 16 | * 17 | * @param moduleName Name of the requested native module. 18 | * @returns Object representing the electron module. 19 | * @throws Error when there is no electron module with given name. 20 | */ 21 | export function requireElectronModule(moduleName: string): ModuleType { 22 | const nativeModule = 23 | (globalThis.__reactNativeElectronModules?.modules?.[moduleName] as ModuleType) ?? null; 24 | 25 | if (!nativeModule) { 26 | throw new Error(`Cannot find electron module '${moduleName}'`); 27 | } 28 | return nativeModule; 29 | } 30 | -------------------------------------------------------------------------------- /packages/react-native-electron-modules/src/types.ts: -------------------------------------------------------------------------------- 1 | export type ElectronModule = { 2 | name: string; 3 | [key: string]: 4 | | number 5 | | string 6 | | boolean 7 | | ((...args: any[]) => Promise | any) 8 | | { [key: string]: number | string | boolean }; 9 | }; 10 | 11 | export type Registry = ElectronModule[]; 12 | 13 | export type IpcMainModules = { 14 | [moduleName: string]: { functions: string[]; values: string[] }; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/react-native-electron-modules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "outDir": "build", 5 | "rootDir": "src", 6 | "module": "ESNext", 7 | "moduleResolution": "Bundler" 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /ts-declarations/exec-async/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'exec-async' { 2 | import { ExecFileOptions } from 'child_process'; 3 | 4 | export type ExecAsyncOptions = ExecFileOptions; 5 | 6 | export default function execAsync( 7 | command: string, 8 | args?: readonly string[] | object | undefined, 9 | options?: ExecAsyncOptions 10 | ): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node12/tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "composite": true, 6 | "inlineSources": true, 7 | "moduleResolution": "node", 8 | "noImplicitReturns": true, 9 | "resolveJsonModule": true, 10 | "sourceMap": true, 11 | "typeRoots": ["./ts-declarations", "./node_modules/@types", "../../node_modules/@types", "../../ts-declarations"] 12 | } 13 | } 14 | --------------------------------------------------------------------------------